c++ socket sending data after disconnection - c++

I'm writing a socket code with C++ and socket.h library and my server and clients are stream socket with TCP protocol.
I've got a class User that shows the clients connected.
class User
{
private:
int sockfd;
int n;
thread *Thread;
public:
User(int sockfd);
void Get_Message();
void Join();
~User();
};
User::User(int sockfd)
{
this->sockfd=sockfd;
Thread=new thread(&User::Get_Message,this);
}
void User::Get_Message()
{
while (1)
{
readmessage(sockfd);
}
}
void readmessage(int sock)
{
int n=0;
char buffer[256];
bzero(buffer, 256);
n = read(sock, buffer, 255);
if (n < 0)
{
error("ERROR reading from socket ");
}
cout<<"the message\t"<<buffer<<endl;
}
now this code works and several clients can join my server and send message;
However when one of those clients disconnects from the server, it keeps printing "the message:" and I don't know why and how to stop it...
I'll be grateful if anybody helps me with the why and how to fix it.

You aren't checking for read() (or recv()) returning zero, which is the normal case for a disconnected peer.

In your Get_Message() while loop, you might need to check if the sockfd is valid , hence sockfd !=0 before reading the message.

The read or write function return zero while the other peer disconnected, and the buffer is null, so you should deal with this case.
void readmessage(int sock)
{
int n=0;
char buffer[256];
bzero(buffer, 256);
n = read(sock, buffer, 255);
if (n < 0)
{
error("ERROR reading from socket ");
}
else if (n == 0)
{
cout << sock <<" client disconnected" << endl;
}
else {
cout<<"the message\t"<<buffer<<endl;
}
}

Related

UDP client socket read less data then sent

I am facing a very strange problem. I have a server application that runs UDP socket and wait for incoming data. As soon as it gets the command it begins to send back a stream. Just for testing, I limit the server to sending only one piece of data 8000 bytes long. I don't provide the server code since it work as expected. It receives the command and sends data back, I can see it with Wireshark. My problem is the client size.
The issue: I instantiate a client non-blocking UDP socket and send "Hello" to the server that responses with 8000 bytes of data. I'm trying to read data in a loop in chunks of 1024 bytes. But the problem that only one chunk of data has read. the next loop returns -1 infinitely. If I try to read 8000 bytes in recv I read it successfully, If I try to read 8100 bytes in recv I read 8000 bytes that sent. I mean that only one call to recv succeed. All subsequent calls return an error although not all data has read yet.
Here is a simplified code:
class ClienSocket
{
public:
void Init()
{
pollfd m_poll = {};
m_poll.fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(m_poll.fd == -1)
{
throw std::runtime_error(GetLastError());
}
int optval = 1;
setsockopt(m_poll.fd, SOL_SOCKET, SO_REUSEADDR, static_cast<const void *>(&optval), sizeof(int));
int on = 1;
if(ioctl(m_poll.fd, FIONBIO, &on) < 0)
{
throw std::runtime_error(std::string("failed to set the client socket non-blocking: ") + strerror(errno));
}
}
void Run()
{
struct sockaddr_in serv_addr;
m_servaddr.sin_family = AF_INET;
m_servaddr.sin_addr.s_addr = inet_addr(m_address.c_str());
m_servaddr.sin_port = htons(static_cast<uint16_t>(m_port));
m_poll.events = POLLIN;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(m_port);
m_running = true;
if(pthread_create(&m_readThread, nullptr, &ClienSocket::ReadThreadWrapper, this) != 0)
{
m_running = false;
throw std::runtime_error(std::string("thread creating error");
}
}
void ClienSocket::Write(const char *data, size_t size)
{
sendto(m_poll.fd, data, size, MSG_NOSIGNAL, reinterpret_cast<const struct sockaddr *>(&(m_servaddr)), sizeof(sockaddr_in));
}
static void *ClienSocket::ReadThreadWrapper(void *ptr)
{
ClienSocket *instance = static_cast<ClienSocket *>(ptr);
if(instance != nullptr)
{
return instance->ReadThreadFunc();
}
return nullptr;
}
void *ClienSocket::ReadThreadFunc()
{
while(m_running)
{
retval = poll(&m_poll, 1, 1000);
if(retval > 0)
{
if(m_poll.revents == POLLIN)
{
bool readMore = true;
do
{
ssize_t readBytes = recv(m_poll.fd, m_readBuffer, READ_BUFFER_SIZE, 0);
std::cout << readBytes << ", " << errno << std::endl;
if (readBytes < 0)
{
if (errno != EWOULDBLOCK)
{
throw std::runtime_error(std::string("socket error");
}
}
else if(readBytes == 0)
{
readMore = false;
}
else
{
ProcessData(m_readBuffer, readBytes);
}
}
while(readMore == true);
}
}
}
return nullptr;
}
void ClienSocket::Wait()
{
if(m_running)
{
pthread_join(m_readThread, nullptr);
}
}
void ProcessData(const char *data, size_t length)
{
std::cout << length << std::endl;
}
private:
bool m_running = false;
int m_port = 3335;
std::string m_address = "192.168.5.1";
struct sockaddr_in m_servaddr;
pollfd m_poll = {};
pthread_t m_readThread;
static constexpr size_t READ_BUFFER_SIZE = 1024;
char m_readBuffer[READ_BUFFER_SIZE];
}
The testcase:
ClienSocket client;
client.Init();
client.Run();
client.Write("hello", 5);
clientWait();
According to Wireshard 8000 bytes has sent:
Target system: Ubuntu 22.04
The output:
1024, 0
-1, 11
-1, 11
-1, 11
-1, 11
-1, 11
...
I'm trying to read data in a loop in chunks of 1024 bytes.
That will not work with UDP, as it is message-oriented rather than stream-oriented, like TCP is.
In UDP, there is a 1:1 relationship between sends and reads. If the UDP server sends a single message of 8000 bytes, the client must receive the entire message in a single read, it cannot receive it across multiple reads, like you are attempting to do.
If the buffer you are reading into is too small to receive the entire message, the read will fail with an EMSGSIZE error code and the unread bytes will be discarded, you can't recover them.
That is why your subsequent reads are failing (withan EWOULDBLOCK/EAGAIN error code), as there is no data available to read until the server sends a new message.

A good way to manage client abruptly disconnecting in C sockets

In a project I am currently doing in group, we have to build a card game from scratch that uses sockets (Linux). We also have to build a chat room that every player can use.
So far so good. The chat is implemented using three separate threads, one that receives incoming connections (up to 50) and stores them in a client list, one that constantly waits for messages from all connected clients, and one that is created each time a client sends a message, sending that message to all clients in the client list. All of this works, except when a single client disconnects.
I managed to keep the server alive (with a sig handler for SIGPIPE) when a client disconnects, but now, when a client disconnects, I keep getting the error Bad file descriptor. But that's not the only problem, since the server keeps receiving empty messages and sends them to the remaining clients, effectively flooding the whole chat in a matter of milliseconds with empty messages.
I believe that if I can fix the problem on the server side, there won't be any problems on the client side.
So my question is: What is the right way (or any way) to manage a Bad file descriptor in my case. I've already tried closing the socket FD and setting the value to -1 in the client list, but that created even more problems and didn't fix the initial ones.
Here is the code, if necessary. The most important function (for the chat) are reception_thread, chat_thread, receive_string, send_string and connect_to_chat on the client side.
Here is the client:
//includes
const int PORT = 2477;
const int CHAT_PORT = 2478;
#define DEBUG
//error()
// Sets up the connection to the server.
//connect_to_server()
int connect_to_chat(char * hostname)
{
#ifdef DEBUG
printf("[DEBUG] Initiating connection to chat server.\n");
#endif
struct sockaddr_in serv_addr;
struct hostent *server;
// Get a socket.
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("Error opening socket for server.");
// Get the address of the server.
server = gethostbyname(hostname);
if (server == NULL) {
fprintf(stderr, "ERROR, no such host\n");
exit(0);
}
// Zero out memory for server info.
memset(&serv_addr, 0, sizeof (serv_addr));
// Set up the server info.
serv_addr.sin_family = AF_INET;
memmove(server->h_addr, &serv_addr.sin_addr.s_addr, server->h_length);
serv_addr.sin_port = htons(CHAT_PORT);
// Make the connection.
if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0)
error("Error connecting to chat server");
#ifdef DEBUG
printf("[DEBUG] Connected to server.\n");
#endif
return sockfd;
}
//-------------------------------- Messages ------------------------------------
// Bunch of send/recv functions that are not important to chat
int send_string(int sockfd, std::string myString)
{
#ifdef DEBUG
printf("[DEBUG] Sending string: %s.\n", myString.c_str());
#endif
//send size
uint32_t stringLen = myString.size();
uint32_t sendLen = htonl(stringLen);
int n = send(sockfd, &sendLen, sizeof (uint32_t), 0);
if (n < 0) {
error("Error sending message (string size). Removing client from list.");
return -1;
}
//send string
n = send(sockfd, myString.c_str(), stringLen, 0);
if (n < 0) {
error("Error sending message (string). Removing client from list.");
return -1;
}
return 0;
}
std::string receive_string(int sockfd)
{
//get string length
uint32_t stringLen;
int n = recv(sockfd, &stringLen, sizeof (uint32_t), 0);
if (n < 0) {
perror("Error receiving message(string size).");
}
stringLen = ntohl(stringLen);
std::vector<uint8_t> buffer;
buffer.resize(stringLen, 0x00);
//get string
n = recv(sockfd, &(buffer[0]), stringLen, 0);
if (n < 0) {
perror("Error receiving message(string).");
}
std::string returnString;
returnString.assign(reinterpret_cast<const char*> (&(buffer[0])), buffer.size()); //might be a bad idea, but it works
#ifdef DEBUG
printf("[DEBUG] Received message: %s\n", returnString.c_str());
#endif
return returnString;
}
//----------------------------- Printing functions------------------------------
void print_menu_guest()
{
// some visual function
}
void print_menu_user()
{
// some visual function
}
void print_info()
{
std::cout << " No information available on the game yet." << std::endl;
}
//---------------------------- Account functions -------------------------------
// Not necessary for chat functions
//--------------------------- Chat thread functions ----------------------------
void reception_thread(int sockfd)
{
#ifdef DEBUG
printf("[DEBUG] Reception thread started.\n");
#endif
std::string stringToPrint;
while (1) {
stringToPrint = receive_string(sockfd);
std::cout << stringToPrint << std::endl;
}
}
void chat_thread(int sockfd, char* host)
{
#ifdef DEBUG
printf("[DEBUG] Chat thread started.\n");
#endif
std::string myString, myUsername, blank;
std::cout << "Enter your username (NO SPACES): ";
std::cin >> myUsername;
myUsername += ": ";
int chat_sockfd = connect_to_chat(host);
std::thread reception_thr(reception_thread, chat_sockfd);
reception_thr.detach();
while (1) {
getline(std::cin, myString);
if (!myString.empty()) {
if (myString != "/quit") {
send_string(chat_sockfd, (myUsername + myString));
}
else {
printf("On peut pas encore quitter :( ");
}
}
}
}
//---------------------- Menu management functions -----------------------------
// Main menu function
//---------------------------- Main function -----------------------------------
int main(int argc, char** argv)
{
/* Make sure host and port are specified. */
if (true) {
char* hostname = "localhost";
/* Connect to the server. */
int sockfd = connect_to_server(hostname);
#ifdef DEBUG
printf("[DEBUG] Client ID: Not yet implemented. ");
#endif
login_prompt(sockfd);
user_menu_loop(sockfd);
}
return 0;
}
And here is the server: Its most important functions (for the chat) are setup_user_fetcher, message_receiver, send_string_to_all, receive_string, send_string, get_chat_user, setup_chat_listener.
// Bunch of includes
const int PORT = 2477;
const int CHAT_PORT = 2478;
const int BACKLOG = 10;
const int MAX_CLIENTS = 20;
int clients_list[50] = {-1};
#define DEBUG
void error(const char *msg)
{
perror(msg);
}
/* Catch Signal Handler functio */
void signal_callback_handler(int signum){
printf("Caught signal SIGPIPE %d\n",signum);
}
//-------------------------- Server set-up functions ---------------------------
// Not necessary for chat
//--------------------------- Chat server functions ---------------------------
int setup_chat_listener()
{
int sockfd;
struct sockaddr_in serv_addr;
// Get a socket to listen on
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening listener socket.");
// Zero out the memory for the server information
memset(&serv_addr, 0, sizeof (serv_addr));
// set up the server info
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(CHAT_PORT);
// Bind the server info to the listener socket.
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof (serv_addr)) < 0)
error("Error binding listener socket.");
#ifdef DEBUG
printf("[DEBUG] Chat listener set.\n");
#endif
// Return the socket number.
return sockfd;
}
int get_chat_user(int sockfd)
{
#ifdef DEBUG
printf("[DEBUG] Getting chat user.\n");
#endif
struct sockaddr_in their_addr;
socklen_t sin_size;
if (listen(sockfd, BACKLOG) < 0) {
perror("Error while listening.");
exit(EXIT_FAILURE);
}
sin_size = sizeof (struct sockaddr_in);
// Mise a zero de la memoire pour le client.
memset(&their_addr, 0, sin_size);
int new_fd = accept(sockfd, (struct sockaddr *) &their_addr, &sin_size);
if (new_fd < 0)
error("Error while accepting.");
printf("Chat server: Connection received from: %s\n",
inet_ntoa(their_addr.sin_addr));
return new_fd;
}
int send_string(int sockfd, std::string myString)
{
#ifdef DEBUG
printf("[DEBUG] Sending string to client %d.\n", sockfd);
#endif
uint32_t stringLen = myString.size();
uint32_t sendLen = htonl(stringLen);
int n = send(sockfd, &sendLen, sizeof (uint32_t), 0);
if (n < 0) {
error("Error sending message (string size). Removing client from list.");
return -1;
}
//send string
n = send(sockfd, myString.c_str(), stringLen, 0);
if (n < 0) {
error("Error sending message (string). Removing client from list.");
return -1;
}
return 0;
}
std::string receive_string(int sockfd)
{
#ifdef DEBUG
printf("[DEBUG] Receiving string.\n");
printf("Current chat user sockfd: %d\n", sockfd);
#endif
uint32_t stringLen;
int n = recv(sockfd, &stringLen, sizeof (uint32_t), 0);
#ifdef DEBUG
printf("[DEBUG] String size received: %d.\n", stringLen);
#endif
if (n < 0) {
perror("Error receiving message(string size).");
}
stringLen = ntohl(stringLen);
std::vector<uint8_t> buffer;
buffer.resize(stringLen, 0x00);
//get string
n = recv(sockfd, &(buffer[0]), stringLen, 0);
if (n < 0) {
perror("Error receiving message(string).");
close(sockfd);
}
std::string returnString;
returnString.assign(reinterpret_cast<const char*> (&(buffer[0])), buffer.size()); //might be a bad idea, but it works
#ifdef DEBUG
printf("[DEBUG] Received message: %s\n", returnString.c_str());
#endif
return returnString;
}
void send_string_to_all(std::string myString)
{
#ifdef DEBUG
printf("[DEBUG] Sending string to all clients.\n");
#endif
int n;
for (int i = 0; i < 50; ++i) {
if (clients_list[i] != -1) {
n = send_string(clients_list[i], myString);
if (n < 0) {
close(clients_list[i]);
clients_list[i] = -1;
}
}
}
}
void message_receiver(int sockfd)
{
#ifdef DEBUG
printf("[DEBUG] Setting up message receiver.\n");
printf("Current chat user sockfd: %d", sockfd);
#endif
std::string message;
int n;
while (1) {
message = receive_string(sockfd);
std::thread t1(send_string_to_all, message);
t1.detach();
}
}
//------------------------------------------------------------------------------
// Bunch of send/recv functions, not necessary to chat
//----------------------------Account Functions---------------------------------
// Not necessary to chat
//------------------------------------------------------------------------------
// Main menu function
void setup_user_fetcher(int lis_chat_sockfd)
{
#ifdef DEBUG
printf("[DEBUG] Gotta catch'em all.\n");
#endif
while (1) {
int chat_user_sockfd = get_chat_user(lis_chat_sockfd);
for (int i = 0; i < 50; ++i)
if (clients_list[i] == -1) {
clients_list[i] = chat_user_sockfd;
break;
}
std::thread message_receiver_thread(message_receiver, chat_user_sockfd);
message_receiver_thread.detach();
}
}
int main(int argc, char** argv)
{
signal(SIGPIPE, signal_callback_handler);
int lis_sockfd = setup_listener();
int lis_chat_sockfd = setup_chat_listener();
std::thread chat_thread(setup_user_fetcher, lis_chat_sockfd);
chat_thread.detach();
while (1) {
int user_sockfd = get_user(lis_sockfd);
int* user_sockfd_ptr = (int*) malloc(sizeof (int));
memset(user_sockfd_ptr, 0, sizeof (int));
user_sockfd_ptr[0] = user_sockfd;
#ifdef DEBUG
printf("[DEBUG] Starting main menu...\n");
#endif
pthread_t thread;
int result = pthread_create(&thread, NULL, main_menu,
(void *) user_sockfd_ptr);
if (result) {
printf("Thread creation failed with return code %d\n", result);
exit(-1);
}
#ifdef DEBUG
printf("[DEBUG] New main menu thread started.\n");
#endif
}
close(lis_sockfd);
pthread_exit(NULL);
return 0;
}
If you wish to reproduce the error, you could compile the code using the following lines
g++ client.cpp -o client -std=c++14 -pthread
g++ server.cpp -o server -std=c++14 -pthread
and run both without any arguments. The client is set to connect on "localhost".
I would be really glad if anyone could help me out with this.
I recomment getting rid of the SIGPIPE signal itself.
signal(SIGPIPE, SIG_IGN);
Now, write()s on killed sockets will simply return -1. It should be easier to deal with that, instead of an asynchronous signal.
If you need SIGPIPE for other reasons, replace write()s with sendto()s with the MSG_NOSIGNAL option. See the sendto(2) manual page for more information.
You have UB. &(buffer[0]) will fail if the number of bytes read is 0 (which I believe will happen if client disconnects). You should test for 0 and return early before building your string.
Also you do not return after finding errors so you build your string from bad data in case of errors.
Maybe something more like:
std::string receive_string(int sockfd)
{
uint32_t stringLen;
int n = recv(sockfd, &stringLen, sizeof (uint32_t), 0);
if (n < 0) {
close(sockfd);
// exit early
throw std::runtime_error("Error receiving message(string size): "
+ std::string(std::strerror(errno)));
}
// test for zero
if(!n)
return {}; // empty string
stringLen = ntohl(stringLen);
std::vector<uint8_t> buffer(stringLen);
// buffer.resize(stringLen, 0x00);
//get string
n = recv(sockfd, &(buffer[0]), stringLen, 0);
if (n < 0) {
close(sockfd);
// exit early
throw std::runtime_error("Error receiving message(string): "
+ std::string(std::strerror(errno)));
}
// only build string if no errors
return {buffer.begin(), buffer.begin() + n};
}

Sockets workflow in FTP imitation

I am trying to implement client-server communication via sockets. The main tasks are:
Sending commands from clients to server
Sending data from server to clients
Sending data from clients to server
Commands should come via port1, data via port2.
I got it working without without multi-threading but I have some problems with understanding how do I need to handle sockets.
Current scenario:
1.Server starts (socket, bind, listen for both command and info sockets) and goes for infinite loop with this function:
void FTPServer::waitForConnection()
{
sockaddr_in client;
int clientsize = sizeof(client);
SOCKET clientSocket = accept(_infoSocket, (struct sockaddr*)&client, &clientsize);
if (clientSocket == INVALID_SOCKET)
{
cout << " Accept Info Error" << endl;
}
else
{
cout << " Accept Info OK" << endl;
char* buff = new char[CHUNK_SIZE];
string fullRequest;
int rc = recv(clientSocket, buff, CHUNK_SIZE, 0);
if (rc == SOCKET_ERROR)
{
cout << " Recieve Info Error" << endl;
}
else
{
buff[rc] = NULL;
fullRequest.append(buff);
cout << " Recieve Info OK" <<endl;
if (executeCommand(fullRequest, clientSocket))
{
logOperation(client, fullRequest.c_str());
}
}
delete buff;
}
}
2.Client starts (socket, connect), creates 2 sockets on same ports, waits for user input.
3.User types "LIST", clients checks that it's a valid command and sends it.
bool FTPClient::sendToServer(string data, const bool verbose)
{
int n = 0;
while (data.size() > CHUNK_SIZE)
{
string s = data.substr(CHUNK_SIZE).c_str();
n += send(_infoSocket, data.substr(CHUNK_SIZE).c_str(), CHUNK_SIZE, 0);
data = data.substr(CHUNK_SIZE+1);
}
n+=send(_infoSocket, data.c_str(), data.size(), 0);
cout<<n<<endl;
if(n<0)
{
cout<<"Error: sending"<<endl;
return 0;
}
if (verbose)
cout<<"Send "<<n<<" bytes"<<endl;
return true;
}
4.Servers receives it, accepts on _dataSocket and sends the list of available files.
5.Client receives the list:
string FTPClient::getDataFromServer(const bool verbose)
{
char data[CHUNK_SIZE];
int size = recv(_dataSocket, data, strlen(data), 0);
if (size > 0)
{
int n = 0;
string res;
while (size > CHUNK_SIZE)
{
int buff = recv(_dataSocket, data, CHUNK_SIZE, 0);
res.append(data);
size -= buff;
n += buff;
}
n+= recv(_dataSocket, data, CHUNK_SIZE, 0);
res.append(data);
if (verbose)
cout<<"Recevied "<<n<<" bytes"<<endl;
res.resize(n);
return res;
}
else
{
return "";
}
}
Till this, it works. But if try to execute same command again, I got nothing.
I think, problem is that for each connect we need an accept on server side.
In main loop server gets only one connect from client. Is closing client command socket and reconnecting it on every request only option here? Any other advices (except for "Google it") are highly appreciated.

Nonblocking sockets even if not explicitly setting them as nonblocking

I have a TCP application written in C++, where a client and a server exchange data. I've istantiated a socket, believing that it would have been blocking by default; on the contrary, after server waits for a client, I have that client calls the recv function without waiting for data. This is the code in which I inizialize the socket fr the client.
int TCPreceiver::initialize(char* address, int port)
{
sock = socket (AF_INET, SOCK_STREAM, 0);
cout << "Socket: " << sock << endl;
sockaddr_in target;
target.sin_family = AF_INET;
target.sin_port = htons (port);
target.sin_addr.s_addr = inet_addr(address);
int fails=0;
while (connect(sock, (sockaddr*) &target, sizeof(target)) == -1)
{
fails++;
if (fails==10)
{
close(sock);
cout << "Error with connection to the server, try again"<< endl;
exit(-1);
}
}
cout << "Client connected (control channel)" << endl;
unsigned char text[10]; //Request message
//fill text[]
if(send(sock, (char*)text, 10, 0)==-1)
{
printf("send() failed with error code : %d" , -1);
exit(EXIT_FAILURE);
}
return 0;
}
I've tried adding this code:
int opts;
opts = fcntl(sock,F_GETFL);
if (opts < 0) {
perror("fcntl(F_GETFL)");
exit(0);
}
opts = (opts & (~O_NONBLOCK));
if (fcntl(sock,F_SETFL,opts) < 0) {
perror("fcntl(F_SETFL)");
exit(0);
}
but it still doesn't work, and if I call the recv(), the application doesn't block (and recv() always returns 0). Here is the function where I call the recv():
void TCPreceiver::receive(char* text, int& dim)
{
int ret;
ret = recv(sock, text, dim, 0);
dim=ret;
if(ret == -1){
printf("recv() failed with error (%d)\n", ret);
//system("PAUSE");
exit(1);
}
}
Where am I wrong?
recv() returning zero indicates either (1) you passed a zero length, which is just a programming error which I won't discuss further here, or (2) end of stream. The peer has close the connection. This isn't a non-blocking situation, this is the end of the connection. You must close the socket and stop using it. It will never return anything. It zero ever again.
See the man pages.

Server & client socket connection issue re. send(), accept() and multi-threading

I'm designing a server program in C++ to receive multiple client connections and pass them into threads, however I've reached an impasse.
The socket connections all work fine, as does the multi-threading - almost. Please see my code below (it compiles and runs fine).
I've tried to pare it down to the essentials for you to make it easy to follow and take up the least of your time. I've commented the code to help you see where the problem is, then I describe the problem in detail at the bottom. If you can help me then I would be very grateful!
#include <vector>
#include <boost/thread.hpp>
#include "unix_serverSocket.h"
#include "server.h"
extern const string socketAddress;
void do_stuff(ServerSocket *client)
{
string in;
string out;
try
{
/* Gets input until the client closes the connection, then throws an exception, breaking out of the loop */
while (true)
{
*client >> in; /* Receives data from client socket connection */
/* Assume the input is processed fine and returns the result into 'out' */
sleep(3); /* I've put sleep() here to test it's multithreading properly - it isn't */
*client << out; /* Returns result to client - send() is called here */
/* If I put sleep() here instead it multithreads fine, so the server is waiting for send() before it accepts a new client */
}
}
catch (SocketException &)
{
delete client;
return;
}
}
int main()
{
try
{
ServerSocket server(socketAddress);
while (true)
{
ServerSocket *client = new ServerSocket();
/* See below */
server.accept(*client);
boost::thread newThread(do_stuff, client);
}
}
catch (SocketException &e)
{
cout << "Error: " << e.description() << endl;
}
return 0;
}
After a client socket connection has been passed to a thread, main() gets back to
the line:
server.accept(*client);
but then waits for the previous connection to send its result back to the
client via send() before it will accept a new connection - i.e. the server is waiting
for something to happen in the thread before it will accept a new client! I don't
want it to do this - I want it to send the client connection to a thread then accept
more client connections straight away and pass them into more threads!
In case you're wondering why I created a pointer to the socket here...
ServerSocket *client = new ServerSocket();
... if I don't create a pointer then the recv() function called by the thread fails to receive data from the client, which seems to be due to the thread shallow copying the client socket connection and the garbage collector not understanding threads and thinking the client connection is no longer going to be used after it has been passed to the thread and so destroying it before recv() is called in the thread. Hence using a pointer created on the heap, which worked. Anyway, when I reworked the code using fork() instead of threads (which meant I didn't need to create the socket on the heap), I still had the same problem with the server not being able to accept new clients.
I guess I need to change the server settings somehow so that it doesn't wait for a client to send() before accepting a new one, however despite much Googling I'm still at a loss!
Here's the relevant socket connection code in case it helps (the server and clients are all on the same box and thus connecting via local UNIX sockets):
class Socket
{
private:
int sockfd;
struct sockaddr_un local;
public:
Socket();
virtual ~Socket();
bool create();
bool bind(const string &);
bool listen() const;
bool accept(Socket &) const;
bool send(const string &) const;
int recv(string &) const;
void close();
bool is_valid() const
{
return sockfd != -1;
}
};
bool Socket::create()
{
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (!is_valid())
{
return false;
}
int reuseAddress = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*) &reuseAddress, sizeof(reuseAddress)) == -1)
{
return false;
}
return true;
}
bool Socket::bind(const string &socketAddress)
{
if (!is_valid())
{
return false;
}
local.sun_family = AF_UNIX;
strcpy(local.sun_path, socketAddress.c_str());
unlink(local.sun_path);
int len = strlen(local.sun_path) + sizeof(local.sun_family);
int bind_return = ::bind(sockfd, (struct sockaddr *) &local, len);
if (bind_return == -1)
{
return false;
}
return true;
}
bool Socket::listen() const
{
if (!is_valid())
{
return false;
}
int listen_return = ::listen(sockfd, MAXCLIENTCONNECTIONS);
if (listen_return == -1)
{
return false;
}
return true;
}
bool Socket::accept(Socket &socket) const
{
int addr_length = sizeof(local);
socket.sockfd = ::accept(sockfd, (sockaddr *) &local, (socklen_t *) &addr_length);
if (socket.sockfd <= 0)
{
return false;
}
else
{
return true;
}
}
int Socket::recv(string &str) const
{
char buf[MAXRECV + 1];
str = "";
memset(buf, 0, MAXRECV + 1);
int status = ::recv(sockfd, buf, MAXRECV, 0);
if (status == -1)
{
cout << "status == -1 errno == " << errno << " in Socket::recv" << endl;
return 0;
}
else if (status == 0)
{
return 0;
}
else
{
str = buf;
return status;
}
}
bool Socket::send(const string &str) const
{
int status = ::send(sockfd, str.c_str(), str.size(), MSG_NOSIGNAL);
if (status == -1)
{
return false;
}
else
{
return true;
}
}
class ServerSocket : private Socket
{
public:
ServerSocket(const string &);
ServerSocket() {};
virtual ~ServerSocket();
void accept(ServerSocket &);
const ServerSocket & operator << (const string &) const;
const ServerSocket & operator >> (string &) const;
};
ServerSocket::ServerSocket(const string &socketAddress)
{
if (!Socket::create())
{
throw SocketException("Could not create server socket");
}
if (!Socket::bind(socketAddress))
{
throw SocketException("Could not bind to port");
}
if (!Socket::listen())
{
throw SocketException("Could not listen to socket");
}
}
void ServerSocket::accept(ServerSocket &socket)
{
if (!Socket::accept(socket))
{
throw SocketException("Could not accept socket");
}
}
const ServerSocket & ServerSocket::operator << (const string &str) const
{
if (!Socket::send(str))
{
throw SocketException("Could not write to socket");
}
return *this;
}
const ServerSocket & ServerSocket::operator >> (string &str) const
{
if (!Socket::recv(str))
{
throw SocketException("Could not read from socket");
}
return *this;
}
I've figured it out! The reason the clients weren't multithreading was that the program creating the client connections was doing so within a mutex - hence it wouldn't create a new connection until the old one had received a reply from the server, and thus the server appeared to be only single-threading! So in short my server program above was fine and it was a problem at the client end - sorry for wasting your time - I didn't even consider the possibility until I completely reworked the program structure by putting the threading at the client end instead, which then revealed the issue.
Thanks for all your help!
Your sockets are blocking! This means that they will wait for the operation to finish before returning.
This is how you make a socket non-blocking:
bool nonblock(int sock)
{
int flags;
flags = fcntl(sock, F_GETFL, 0);
flags |= O_NONBLOCK;
return (fcntl(sock, F_SETFL, flags) == 0);
}
Now the functions accept, read and write will all return an error if the socket would block, setting the errno variable to EWOULDBLOCK or possibly EAGAIN.
If you want to wait for a socket to be ready for reading or writing, you can use the function select. For listening sockets (the one you do accept on) it will be ready to read when a new connection can be accepted.