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.
Related
I'm coding a simple client-server application using TCP. I'm using Winsock, but I'm limiting myself to the BSD socket API (since I want the code to run on Linux as well).
The application should run a server and accept incoming client connections, receive and send messages, and gracefully handle client disconnections. The approach I use is this:
(disclaimer: I know it might not be the best one, and that there are a bunch of more efficient techniques, but I'm just starting with network programming)
A connection object (that represents the actual endpoint of the connection and contains a socket object) has a queue of outgoing messages, and a reference to a queue of incoming messages (actual incoming messages queues are inside the clients and the server object, and are passed on construction):
#ifndef CONNECTION_H
#define CONNECTION_H
#include <memory>
#include <thread>
#include "ThreadsafeQueue.h"
#include "Message.h"
#include "debug.h"
template <typename T>
class Connection : public std::enable_shared_from_this<Connection<T>>
{
public:
enum class Owner { CLIENT, SERVER };
private:
using std::enable_shared_from_this<Connection>::shared_from_this;
public:
Connection(Owner owner, uint32_t id, SOCKET socket, ThreadsafeQueue<OwnedMessage<T>> &inMessageQueue);
~Connection();
void Send(const Message<T> &message);
void Close();
bool IsOpen() const { return mIsOpen; }
uint32_t GetId() const { return mId; }
private:
const Owner mOwner;
uint32_t mId;
SOCKET mSocket;
bool mIsOpen;
ThreadsafeQueue<Message<T>> mOutMessageQueue;
ThreadsafeQueue<OwnedMessage<T>> &mInMessageQueue;
std::thread mSendThread;
std::thread mReceiveThread;
void Send_();
void Receive_();
};
template <typename T>
Connection<T>::Connection(Owner owner, uint32_t id, SOCKET socket, ThreadsafeQueue<OwnedMessage<T>> &inMessageQueue) : mOwner(owner), mId(id), mSocket(socket), mInMessageQueue(inMessageQueue)
{
unsigned long socketMode = 1U;
if (ioctlsocket(socket, FIONBIO, &socketMode) != 0) // set non blocking socket
Error("error setting socket i/o mode");
mIsOpen = true;
mSendThread = std::thread(&Connection<T>::Send_, this);
mReceiveThread = std::thread(&Connection<T>::Receive_, this);
}
template <typename T>
Connection<T>::~Connection()
{
Close();
}
template <typename T>
void Connection<T>::Send(const Message<T> &message)
{
mOutMessageQueue.EnQueue(message);
}
template <typename T>
void Connection<T>::Close()
{
if (mIsOpen)
mIsOpen = false;
if (mSendThread.joinable())
mSendThread.join();
if (mReceiveThread.joinable())
mReceiveThread.join();
closesocket(mSocket);
}
template <typename T>
void Connection<T>::Send_()
{
//DbgPrint("send thread start");
while (mIsOpen)
{
// get from outgoing queue and send
if (!mOutMessageQueue.Empty())
{
Message<T> outMessage = mOutMessageQueue.Front();
mOutMessageQueue.DeQueue();
int sendFlags = 0;
int totalBytesSent = 0;
while (totalBytesSent < sizeof(Message<T>::Header))
{
int bytesSent = send(mSocket, reinterpret_cast<char*>(&outMessage.mHeader) + totalBytesSent, sizeof(Message<T>::Header) - totalBytesSent, sendFlags);
totalBytesSent += bytesSent;
if (bytesSent == SOCKET_ERROR)
Error("error sending message");
}
totalBytesSent = 0;
while (totalBytesSent < outMessage.mBody.Size())
{
int bytesSent = send(mSocket, reinterpret_cast<char*>(outMessage.mBody.Data()) + totalBytesSent, outMessage.mBody.Size() - totalBytesSent, sendFlags);
totalBytesSent += bytesSent;
if (bytesSent == SOCKET_ERROR)
Error("error sending message");
}
}
}
//DbgPrint("send thread end");
}
template <typename T>
void Connection<T>::Receive_()
{
//DbgPrint("receive thread start");
static bool skip = false; // used in non blocking mode
while (mIsOpen)
{
skip = false;
Message<T> inMessage;
int recvFlags = 0;
int totalBytesReceived = 0;
while (totalBytesReceived < sizeof(Message<T>::Header)) // receive message header
{
int bytesReceived = recv(mSocket, reinterpret_cast<char*>(&inMessage.mHeader) + totalBytesReceived, sizeof(Message<T>::Header) - totalBytesReceived, recvFlags);
if (bytesReceived == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
{
skip = true;
break;
}
if (bytesReceived == SOCKET_ERROR)
Error("error receiving message");
else if (bytesReceived == 0) // other side closed connection
{
mIsOpen = false;
skip = true;
break;
}
totalBytesReceived += bytesReceived;
}
if (skip)
continue;
inMessage.mBody.Resize(inMessage.mHeader.mSize);
totalBytesReceived = 0;
while (totalBytesReceived < inMessage.mBody.Size()) // receive message body
{
int bytesReceived = recv(mSocket, reinterpret_cast<char*>(inMessage.mBody.Data()) + totalBytesReceived, inMessage.mBody.Size() - totalBytesReceived, recvFlags);
if (bytesReceived == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
continue;
totalBytesReceived += bytesReceived;
if (bytesReceived == SOCKET_ERROR)
Error("error receiving message");
else if (bytesReceived == 0) // other side closed connection
{
mIsOpen = false;
skip = true;
break;
}
}
if (skip)
continue;
if (mOwner == Owner::SERVER)
mInMessageQueue.EnQueue(OwnedMessage<T>(shared_from_this(), inMessage)); // put received message into incoming queue
else
mInMessageQueue.EnQueue(OwnedMessage<T>(nullptr, inMessage));
}
//DbgPrint("receive thread start");
}
#endif // CONNECTION_H
A client has one Connection objects, the server has many of them in a container. Server runs and accept clients, spawns a Connection and put it into a container. Each connection runs in two threads (one to send and the other to receive, since my sockets are non-blocking it is redundant, I'll address the issue in the future). The connected client stores its own connection as well. The Send_ () and Receive_() threads take care of transferring messages in and out of the message queues (which are brutally thread-safe by synchronizing every operation on a std::mutex, not the best design I know).
The server runs two threads: one to keep listening to incoming clients, the other to "garbage-collect" the closed connections: a server-side Connection can detect if a client closed its connection by reading 0 from recv(), and signals that to the server by setting mIsOpen to false. The CloseSocket thread runs and cleans up the connections (calling the OnClientDisconnect hook):
#ifndef SERVER_H
#define SERVER_H
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <thread>
#include <string>
#include "Vector.h"
#include "Connection.h"
#include "ThreadsafeQueue.h"
#include "Message.h"
#include "debug.h"
template <typename T>
class Server
{
protected:
using ConnectionPtr = std::shared_ptr<Connection<T>>;
public:
Server(uint16_t port);
~Server();
void Start();
void Stop();
void Send(ConnectionPtr connection, const Message<T> &message) const;
void SendAll(const Message<T> &message, ConnectionPtr ignore = nullptr) const;
void Disconnect(ConnectionPtr connection);
bool Available() const { return !mInMessageQueue.Empty(); }
void ProcessMessage();
protected:
virtual void OnClientConnect(ConnectionPtr connection) = 0;
virtual void OnClientDisconnect(ConnectionPtr connection) = 0;
virtual void OnMessage(ConnectionPtr sender, Message<T> &message) = 0;
private:
Vector<ConnectionPtr> mConnections;
ThreadsafeQueue<OwnedMessage<T>> mInMessageQueue;
SOCKET mListenSocket;
std::thread mListenThread;
void Listen();
std::thread mSocketRemoveThread;
void RemoveConnections();
std::string mHost;
uint16_t mPort;
static const uint8_t sMaxNumConnections = 10;
bool mIsRunning;
};
template <typename T>
Server<T>::Server(uint16_t port) :mListenSocket(INVALID_SOCKET), mIsRunning(false)
{
WSAData wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
addrinfo hints, *address;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
//hints.ai_flags = AI_PASSIVE;
if (getaddrinfo(nullptr, std::to_string(port).c_str(), &hints, &address) != 0)
Error("cannot get server address");
char stringBuf[INET6_ADDRSTRLEN];
if (address->ai_family == AF_INET)
inet_ntop(AF_INET, &reinterpret_cast<sockaddr_in *>(address->ai_addr)->sin_addr, stringBuf, sizeof stringBuf);
else // address->ai_family == AF_INET6
inet_ntop(AF_INET6, &reinterpret_cast<sockaddr_in6 *>(address->ai_addr)->sin6_addr, stringBuf, sizeof stringBuf);
mHost = stringBuf;
mPort = ntohs(address->ai_family == AF_INET ? reinterpret_cast<sockaddr_in *>(address->ai_addr)->sin_port : reinterpret_cast<sockaddr_in6 *>(address->ai_addr)->sin6_port);
if ((mListenSocket = socket(address->ai_family, address->ai_socktype, address->ai_protocol)) == INVALID_SOCKET)
Error("cannot create server socket");
if (bind(mListenSocket, address->ai_addr, address->ai_addrlen) != 0)
Error("cannot bind server socket");
}
template <typename T>
Server<T>::~Server()
{
if (mIsRunning)
closesocket(mListenSocket);
WSACleanup();
}
template <typename T>
void Server<T>::Start()
{
if (mIsRunning) // TODO: error check
return;
mIsRunning = true;
PRINTLN("server running");
mListenThread = std::thread(&Server::Listen, this); // start thread that accepts new connections
mSocketRemoveThread = std::thread(&Server::RemoveConnections, this);
}
template <typename T>
void Server<T>::Stop()
{
for (std::unique_ptr<Connection<T>> &connection : mConnections)
connection->Close();
}
template <typename T>
void Server<T>::Listen()
{
if (listen(mListenSocket, sMaxNumConnections) != 0)
Error("listen error");
PRINT("server listening # ");
PRINT(mHost);
PRINT(" on port ");
PRINTLN(std::to_string(mPort));
static uint32_t connectionId = 0;
while (true)
{
sockaddr_storage clientAddress;
int clientAddressLength = sizeof(sockaddr_storage);
SOCKET clientSocket = accept(mListenSocket, reinterpret_cast<sockaddr*>(&clientAddress), &clientAddressLength); // accept connections (blocking)
if (clientSocket == INVALID_SOCKET)
Error("cannot create client socket");
mConnections.EmplaceLast(new Connection<T>(Connection<T>::Owner::SERVER, connectionId++, clientSocket, mInMessageQueue)); // create new connection to client (receive/send threads)
OnClientConnect(mConnections.Last()); // callback called on new connections
char stringBuf[INET6_ADDRSTRLEN];
if (clientAddress.ss_family == AF_INET)
inet_ntop(AF_INET, &reinterpret_cast<sockaddr_in *>(&clientAddress)->sin_addr, stringBuf, sizeof stringBuf);
else // clientAddress.ss_family == AF_INET6
inet_ntop(AF_INET6, &reinterpret_cast<sockaddr_in6 *>(&clientAddress)->sin6_addr, stringBuf, sizeof stringBuf);
PRINT("accepted connection from ");
PRINT(stringBuf);
PRINT(" on port ");
PRINTLN(clientAddress.ss_family == AF_INET ? std::to_string(ntohs(reinterpret_cast<sockaddr_in *>(&clientAddress)->sin_port)) : std::to_string(ntohs(reinterpret_cast<sockaddr_in6 *>(&clientAddress)->sin6_port)));
}
}
template <typename T>
void Server<T>::Send(ConnectionPtr connection, const Message<T> &message) const
{
if (connection->IsOpen())
connection->Send(message);
}
template <typename T>
void Server<T>::SendAll(const Message<T> &message, ConnectionPtr ignore) const
{
for (const ConnectionPtr &connection : mConnections)
{
if (connection != ignore && connection->IsOpen())
connection->Send(message);
}
}
template <typename T>
void Server<T>::ProcessMessage()
{
OwnedMessage<T> message(mInMessageQueue.Front());
mInMessageQueue.DeQueue();
OnMessage(message.GetSender(), message);
}
template <typename T>
void Server<T>::RemoveConnections()
{
while (true)
{
for (typename Vector<ConnectionPtr>::Iterator it = mConnections.Begin(), end = mConnections.End(); it != end; ++it)
if (!(*it)->IsOpen())
{
(*it)->Close();
OnClientDisconnect(*it);
it = mConnections.Remove(it);
}
}
}
#endif // SERVER_H
Everything works fine, but I have problems with synchronizing the container of Connections in the server: it is accessed by both the listening thread (which inserts new connections) and the garbage collector thread (which scans the array for dead connection to remove).
How can I address this issue? A vector is obviously the worst choice (dynamic reallocation), being inherently unsafe for concurrent access, but how can I store connections that needs to be manipulated by different threads? Please I need basic/bare-bones/not fancy solutions, since this application is for learning purposes only.
As an aside, how/where can I start learning about how to structure network code more efficiently? How can I improve the structure of my framework?
Thanks
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;
}
}
I am building a server using Sockets on Linux, and I'm having a weird problem: if I try to send a request via my browser (Chrome), my code will create two sockets for communication using accept. Here's the code that accepts a connection:
server->poll();
if (server->canRead(server))
{
cout << "Connection!" << endl;
newSocket = server->accept(poolSize);
}
And here are the functions used (which are part of a wrapper I wrote for the C socket library).
int ServerSocket::poll(int timeout)
{
int rc = ::poll(this->descriptors, this->maxDescriptors, timeout);
if(rc == -1)
throw std::runtime_error(std::string("Error: ") + strerror(errno));
return rc;
}
bool ServerSocket::canRead(Socket *socket)
{
if(socket == NULL)
throw std::invalid_argument(std::string("Error: null socket!"));
for (int i = 0; i < this->maxDescriptors; i++)
{
if (socket->socketDescriptor == this->descriptors[i].fd)
{
if (this->descriptors[i].revents & POLLIN)
return true;
break;
}
}
return false;
}
ClientSocket* ServerSocket::accept(const int poolSize)
{
socklen_t endpointSize = sizeof(this->endpoint);
int newDescriptor = ::accept(this->socketDescriptor,
(struct sockaddr *)&endpoint, &endpointSize);
if(newDescriptor == -1)
throw std::runtime_error(std::string("Error in accept: ") + strerror(errno));
ClientSocket *newSocket = NULL;
try
{
newSocket = new ClientSocket(newDescriptor, this->port, poolSize);
}
catch (std::exception e)
{
std::cout << "Allocation error!" << endl;
throw e;
}
return newSocket;
}
If I compile and run this code, my output is:
Connection!
Connection!
I've used both telnet and netcat to analyse the requisition made by the browser, and it shows no anomaly. I've also sent requests manually via telnet and netcat, and the server works just fine, no duplicate connections. What could be causing this behaviour?
I'm trying to understand this Libevent c++ code I got from this page.
I'm a bit confused - am I correct to think that this code might have memory leaks?
It seems like ConnectionData pointer is created in on_connect() callback, but delete() is only called on bad read or after write is complete.
What if connection was accept()ed - but there were no reads or writes? so is that pointer just stays in daemon memory?
#include <event.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
// Read/write buffer max length
static const size_t MAX_BUF = 512;
typedef struct {
struct event ev;
char buf[MAX_BUF];
size_t offset;
size_t size;
} ConnectionData;
void on_connect(int fd, short event, void *arg);
void client_read(int fd, short event, void *arg);
void client_write(int fd, short event, void *arg);
int main(int argc, char **argv)
{
// Check arguments
if (argc < 3) {
std::cout << "Run with options: <ip address> <port>" << std::endl;
return 1;
}
// Create server socket
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock == -1) {
std::cerr << "Failed to create socket" << std::endl;
return 1;
}
sockaddr_in sa;
int on = 1;
char * ip_addr = argv[1];
short port = atoi(argv[2]);
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = inet_addr(ip_addr);
// Set option SO_REUSEADDR to reuse same host:port in a short time
if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
std::cerr << "Failed to set option SO_REUSEADDR" << std::endl;
return 1;
}
// Bind server socket to ip:port
if (bind(server_sock, reinterpret_cast<const sockaddr*>(&sa), sizeof(sa)) == -1) {
std::cerr << "Failed to bind server socket" << std::endl;
return 1;
}
// Make server to listen
if (listen(server_sock, 10) == -1) {
std::cerr << "Failed to make server listen" << std::endl;
return 1;
}
// Init events
struct event evserver_sock;
// Initialize
event_init();
// Set connection callback (on_connect()) to read event on server socket
event_set(&evserver_sock, server_sock, EV_READ, on_connect, &evserver_sock);
// Add server event without timeout
event_add(&evserver_sock, NULL);
// Dispatch events
event_dispatch();
return 0;
}
// Handle new connection {{{
void on_connect(int fd, short event, void *arg)
{
sockaddr_in client_addr;
socklen_t len = 0;
// Accept incoming connection
int sock = accept(fd, reinterpret_cast<sockaddr*>(&client_addr), &len);
if (sock < 1) {
return;
}
// Set read callback to client socket
ConnectionData * data = new ConnectionData;
event_set(&data->ev, sock, EV_READ, client_read, data);
// Reschedule server event
event_add(reinterpret_cast<struct event*>(arg), NULL);
// Schedule client event
event_add(&data->ev, NULL);
}
//}}}
// Handle client request {{{
void client_read(int fd, short event, void *arg)
{
ConnectionData * data = reinterpret_cast<ConnectionData*>(arg);
if (!data) {
close(fd);
return;
}
int len = read(fd, data->buf, MAX_BUF - 1);
if (len < 1) {
close(fd);
delete data;
return;
}
data->buf[len] = 0;
data->size = len;
data->offset = 0;
// Set write callback to client socket
event_set(&data->ev, fd, EV_WRITE, client_write, data);
// Schedule client event
event_add(&data->ev, NULL);
}
//}}}
// Handle client responce {{{
void client_write(int fd, short event, void *arg)
{
ConnectionData * data = reinterpret_cast<ConnectionData*>(arg);
if (!data) {
close(fd);
return;
}
// Send data to client
int len = write(fd, data->buf + data->offset, data->size - data->offset);
if (len < data->size - data->offset) {
// Failed to send rest data, need to reschedule
data->offset += len;
event_set(&data->ev, fd, EV_WRITE, client_write, data);
// Schedule client event
event_add(&data->ev, NULL);
}
close(fd);
delete data;
}
//}}}
The documentation for event_set says that the only valid event types are EV_READ or EV_WRITE, but the callback will be invoked with EV_TIMEOUT, EV_SIGNAL, EV_READ, or EV_WRITE. The documentation is not clear, but I expect the read callback will be invoked when the socket is closed by the client. I expect the delete in the failure branch in client_read will handle this situation.
Note that that is only the case if the client sends a FIN or RST packet. A client could establish a connection and leave it open forever. For this reason, this code should be modified to have a timeout (perhaps via event_once) and require the client send a message within that timeout.
I'm having some problems with my Winsock application.
As it currently stands, when my client first connects to the server, the welcome message is sent fine but thats when it screws up. After that initial message its like the program goes into turbo mode.
Each client has a vector that stores messages that need to be sent, to debug I have it output how many messages are left to send and heres what the latest one says:
Successfully sent message. 1 messages left.
Successfully sent message. 1574803 messages left
................... (Counts down)
Successfully sent message. 1574647 messages left
Client connection closed or broken
EDIT: Managed to place some output code in the right place, for some reason when it starts sending update messages to clients, it starts sending the same message over and over.
Heres some code, am only posting the cpp's, 'Network::Update' is run by a thread in 'Game'
-- Server --
Main.cpp
while(true)
{
m_Game -> Update();
Sleep(500);
}
Network.cpp
#include "Network.h"
Network::Network()
{
}
Network::~Network()
{
closesocket(m_Socket);
}
Network::Network(char* ServerIP, int ServerPort)
{
Initialise(ServerIP, ServerPort);
}
void Network::Initialise(char* ServerIP, int ServerPort)
{
//
// Initialise the winsock library to 2.2
//
WSADATA w;
int error = WSAStartup(0x0202, &w);
if ((error != 0) || (w.wVersion != 0x0202))
{
MessageBox(NULL, L"Winsock error. Shutting down.", L"Notification", MB_OK);
exit(1);
}
//
// Create the TCP socket
//
m_Socket = socket(AF_INET, SOCK_STREAM, 0);
if (m_Socket == SOCKET_ERROR)
{
MessageBox(NULL, L"Failed to create socket.", L"Notification", MB_OK);
exit(1);
}
//
// Fill out the address structure
//
m_Address.sin_family = AF_INET;
m_Address.sin_addr.s_addr = inet_addr(ServerIP);
m_Address.sin_port = htons(ServerPort);
//
// Bind the server socket to that address.
//
if (bind(m_Socket, (const sockaddr *) &m_Address, sizeof(m_Address)) != 0)
{
MessageBox(NULL, L"Failed to bind the socket to the address.", L"Notification", MB_OK);
exit(1);
}
//
// Make the socket listen for connections.
//
if (listen(m_Socket, 1) != 0)
{
MessageBox(NULL, L"Failed to listen on the socket.", L"Notification", MB_OK);
exit(1);
}
m_ID = 1;
}
void Network::Update()
{
//
// Structures for the sockets
//
fd_set readable, writeable;
FD_ZERO(&readable);
FD_ZERO(&writeable);
//
// Let the socket accept connections
//
FD_SET(m_Socket, &readable);
//
// Cycle through clients allowing them to read and write
//
for (std::list<Client *>::iterator it = m_Clients.begin(); it != m_Clients.end(); ++it)
{
Client *client = *it;
if (client->wantRead())
{
FD_SET(client->sock(), &readable);
}
if (client->wantWrite())
{
FD_SET(client->sock(), &writeable);
}
}
//
// Structure defining the connection time out
//
timeval timeout;
timeout.tv_sec = 2;
timeout.tv_usec = 500000;
//
// Check if a socket is readable
//
int count = select(0, &readable, &writeable, NULL, &timeout);
if (count == SOCKET_ERROR)
{
ExitProgram(L"Unable to check if the socket can be read.\nREASON: Select failed");
}
//
// Check if a theres an incoming connection
//
if (FD_ISSET(m_Socket, &readable))
{
//
// Accept the incoming connection
//
sockaddr_in clientAddr;
int addrSize = sizeof(clientAddr);
SOCKET clientSocket = accept(m_Socket, (sockaddr *) &clientAddr, &addrSize);
if (clientSocket > 0)
{
// Create a new Client object, and add it to the collection.
Client *client = new Client(clientSocket);
m_Clients.push_back(client);
// Send the new client a welcome message.
NetworkMessage message;
message.m_Type = MT_Welcome;
client->sendMessage(message);
m_ID++;
}
}
//
// Loop through clients
//
for (std::list<Client *>::iterator it = m_Clients.begin(); it != m_Clients.end(); ) // note no ++it here
{
Client *client = *it;
bool dead = false;
//
// Read data
//
if (FD_ISSET(client->sock(), &readable))
{
dead = client->doRead();
}
//
// Write data
//
if (FD_ISSET(client->sock(), &writeable))
{
dead = client->doWrite();
}
//
// Check if the client is dead (Was unable to write/read packets)
//
if (dead)
{
delete client;
it = m_Clients.erase(it);
}
else
{
++it;
}
}
}
void Network::sendMessage(NetworkMessage message)
{
//Loop through clients and send them the message
for (std::list<Client *>::iterator it = m_Clients.begin(); it != m_Clients.end(); ) // note no ++it here
{
Client *client = *it;
client->sendMessage(message);
}
}
Client.cpp
#include "Client.h"
Client::Client(SOCKET sock)
{
m_Socket = sock;
send_count_ = 0;
}
// Destructor.
Client::~Client()
{
closesocket(m_Socket);
}
// Process an incoming message.
void Client::processMessage(NetworkMessage message)
{
// PROCESSING NEEDS TO GO HERE
}
// Return the client's socket.
SOCKET Client::sock()
{
return m_Socket;
}
// Return whether this connection is in a state where we want to try
// reading from the socket.
bool Client::wantRead()
{
// At present, we always do.
return true;
}
// Return whether this connection is in a state where we want to try-
// writing to the socket.
bool Client::wantWrite()
{
// Only if we've got data to send.
//return send_count_ > 0;
return Messages.size() > 0;
}
// Call this when the socket is ready to read.
// Returns true if the socket should be closed.
bool Client::doRead()
{
return false;
}
// Call this when the socket is ready to write.
// Returns true if the socket should be closed.
bool Client::doWrite()
{
/*int count = send(m_Socket, send_buf_, send_count_, 0);
if (count <= 0)
{
printf("Client connection closed or broken\n");
return true;
}
send_count_ -= count;
// Remove the sent data from the start of the buffer.
memmove(send_buf_, &send_buf_[count], send_count_);
return false;*/
int count = send(m_Socket, (char*)&Messages[0], sizeof(NetworkMessage), 0);
if( count <= 0 )
{
printf("Client connection closed or broken\n");
return true;
}
Messages.pop_back();
printf(" Successfully sent message. %d messages left.\n", Messages.size() );
return false;
}
void Client::sendMessage(NetworkMessage message)
{
/*if (send_count_ + sizeof(NetworkMessage) > sizeof(send_buf_))
{
ExitProgram(L"Unable to send message.\nREASON: send_buf_ full");
}
else
{
memcpy(&send_buf_[send_count_], message, sizeof(NetworkMessage));
send_count_ += sizeof(NetworkMessage);
printf(" Size of send_count_ : %d \n", send_count_);
}*/
Messages.push_back(message);
}
Game.cpp
void Game::Update()
{
tempPlayer -> ChangePosition(1, 1); //Increase X and Y pos by 1
Dispatch();
printf("Update\n");
}
void Game::Dispatch()
{
NetworkMessage temp;
temp.m_Type = MT_Update;
temp.m_ID = tempPlayer->getID();
temp.m_positionX = tempPlayer->getX();
temp.m_positionY = tempPlayer->getY();
m_Network->sendMessage(temp);
}
There are a few problems with your code.
The main problem is that Network::sendMessage() runs an infinite loop. Your it iterator is never incremented (there is a comment saying as much - the comment is doing the wrong thing!), so the loop sends the same NetworkMessage to the same Client over and over without ever stopping. Do this instead:
void Network::sendMessage(NetworkMessage message)
{
//Loop through clients and send them the message
for (std::list<Client *>::iterator it = m_Clients.begin(); it != m_Clients.end(); ++it)
{
...
}
}
Client::doWrite() is using pop_back() when it should be using pop_front() instead. If there are multiple pending messages in the vector, you will lose messages. Rather than using the [] operator to pass a NetworkMessage directly to send(), you should pop_front() the first pending message and then send() it:
bool Client::doWrite()
{
NetworkMessage message = Messages.pop_front();
int count = send(m_Socket, (char*)&message, sizeof(NetworkMessage), 0);
if( count <= 0 )
{
printf("Client connection closed or broken\n");
return true;
}
printf(" Successfully sent message. %d messages left.\n", Messages.size() );
return false;
}
Your dead check in Network::Update() has a small logic hole in it that can prevent you from deleting dead clients correctly if doRead() returns true and then doWrite() return false. Do this instead:
bool dead = false;
//
// Read data
//
if (FD_ISSET(client->sock(), &readable))
{
if (client->doRead())
dead = true;
}
//
// Write data
//
if (FD_ISSET(client->sock(), &writeable))
{
if (client->doWrite())
dead = true;
}