I was writing a server that should serve more or less a thousand clients.
After writing the net part of the server code I had to stop writing because I saw that the code was chaotic and not very readable. The code work, but it seems all wrong.
Net.h:
#ifndef H_GUARD_NET
#define H_GUARD_NET
#define WIN32_LEAN_AND_MEAN
#include <WinSock2.h>
#include <Windows.h>
#include <WS2tcpip.h>
#include <iostream>
#include <WinBase.h>
#pragma comment(lib,"kernel32.lib");
#pragma comment(lib,"Ws2_32.lib");
#define INIT_PORT_NUMBER "64390"
/*Wrapper class around Server function */
class SERVER
{
public:
SERVER();
private:
int SetUpServer();
void gatherInformation(addrinfo** hints);
void * get_in_addr(sockaddr* sa);
~SERVER(){}
};
/*It's supposed to handle error */
class ERR
{
public:
ERR(DWORD& val) : ERR_NO(val) {}
int GetError() {return ERR_NO;}
~ERR() {}
private:
const int ERR_NO;
};
#endif
Net.cpp:
#include "Net.h"
/*Return the sin_addr of the sockaddr struct,
*probably not very windows-like because i took it
*from an old c++ UNIX code */
void * SERVER::get_in_addr(sockaddr* sa)
{
if(sa->sa_family == AF_INET)
{
return &(((sockaddr_in*)sa)->sin_addr);
}else if(sa->sa_family == AF_INET6)
{
return &(((sockaddr_in6*)sa)->sin6_addr);
}
}
SERVER::SERVER()
{
/*return the listener socket from the SetUpServer()
*function */
int listener = SERVER::SetUpServer();
sockaddr_storage remoteAddr;
socklen_t socketlength = sizeof(remoteAddr);
char buffer[4096];
memset(&buffer,0,sizeof(buffer));
int fdmax,nByte;
fd_set master,read_fd;
FD_ZERO(&master);
FD_ZERO(&read_fd);
char remoteIP[INET6_ADDRSTRLEN];
if(listen(listener,10) == SOCKET_ERROR)
{
std::cout << "listen() failed with error number " << WSAGetLastError() <<std::endl;
exit(-1);
}
fdmax = listener;
FD_SET(listener,&master);
/*endless loop to catch the connection and handle them with select() */
for(;;)
{
read_fd = master;
if(select(fdmax +1,&read_fd,NULL,NULL,NULL) == SOCKET_ERROR)
{
std::cout << "select() failed with error number " << WSAGetLastError() <<std::endl;
exit(-1);
}
for(int i = 0; i <= fdmax; i++)
{
if(FD_ISSET(i,&read_fd))
{
if(i == listener)
{
int newSock = accept(listener,(sockaddr *)&remoteAddr,&socketlength);
if(newSock == SOCKET_ERROR)
{
std::cout << "accept() failed with error number " << WSAGetLastError() << std::endl;
}
std::cout << "New connection from: " << inet_ntop(remoteAddr.ss_family,get_in_addr((sockaddr*)&remoteAddr),remoteIP,INET6_ADDRSTRLEN) <<std::endl;
FD_SET(newSock,&master);
if(newSock > fdmax)
fdmax = newSock;
}else
{
/*The server doesn't just recv(),but temporany i use it for
* debugging purpose */
if ((nByte = recv(i,buffer,sizeof(buffer),0)) == ERROR)
{
std::cout << "recv() failed with error number "<<WSAGetLastError() <<std::endl;
exit(-1);
}else if(nByte == 0)
{
std::cout << "Connection closed by host: " << i << std::endl;
closesocket(i);
FD_CLR(i,&master);
}else
{
std::cout << buffer << std::endl;
memset(&buffer,0,sizeof(buffer));
}
}
}
}
}
}
/*Do all the Networking stuff */
int SERVER::SetUpServer()
{
addrinfo *result = NULL;
SOCKET listener = 0;
const char yes = 1;
WSAData data;
WSAStartup(MAKEWORD(2,2),&data);
/*Gather info about the host machine
*and write them inside the result linked list */
SERVER::gatherInformation(&result);
for(;result != NULL; result = result->ai_next)
{
if((listener = socket(result->ai_family,result->ai_socktype,result->ai_protocol)) == INVALID_SOCKET)
{
std::cout << "Socket () failed with error number: " << WSAGetLastError() << std::endl;
}
if(setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,&yes,sizeof(int)) == ERROR)
{
std::cout << "Setsockopt() failed with error number:" << WSAGetLastError() <<std::endl;
freeaddrinfo(result);
exit(-1);
}
if(bind(listener,result->ai_addr,result->ai_addrlen) < 0)
{
continue;
}
return listener;
}
}
/*Wrapper around getaddrinfo, i think it should improve readability
*but i think that I just messed up more the code */
void SERVER::gatherInformation(addrinfo ** result)
{
addrinfo hints;
ZeroMemory(&hints,sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
retry:
try
{
if(getaddrinfo(NULL,INIT_PORT_NUMBER,&hints,result) < 0)
{
DWORD error = GetLastError();
throw new ERR(error);
}
freeaddrinfo(&hints);
}catch(ERR& e)
{
if(e.GetError() == WSATRY_AGAIN)
{
/* Handling this here because
* it's just a temporany error so i'll wait 10 second and then retry */
Sleep(10000);
goto retry;
}else
{
/*Using another function
* to parse non-handled error*/
std::cout << "getaddrinfo() failed with error number: " <<std::endl;
freeaddrinfo(*result);
}
}
}
I wonder how I can improve the code quality, also because this will be a long project (probably on the 10-20k LOC) so it's essential that every part is the most synthetic and neat as possible.
std::cout << "select() failed with error number "...;exit(-1)
. Look upEINTR
if you get this error code then you can just re-try on most system calls. \$\endgroup\$select()
is an old outdated API (that can be very slow with thousands of connections). You may want to look atpselect()
andepoll()
. All of these selection mechanisms can be extract by the librarylibevent
which usually picks the most efficient mechanism for your system. \$\endgroup\$