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 upEINTRif 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 librarylibeventwhich usually picks the most efficient mechanism for your system. \$\endgroup\$