Socket Programming: Error on listen() - visual-c++

I'm working on a server part of my app and I came across a problem I can't seem to solve. The server initialization function, which is a part of ConnectionManager class, is as follows:
int ConnectionManager::init_server() {
// Log
OutputDebugString(L"> Initializing server...\n");
// Initialize winsock
WSAData wsa;
int code = WSAStartup(MAKEWORD(2, 2), &wsa);
if (code != 0) {
// Error initializing winsock
OutputDebugString(L"> Log: WSAStartup()\n");
output_error(code);
return -1;
}
// Get server information
struct addrinfo hints, *serverinfo, *ptr;
SOCKET sockfd = INVALID_SOCKET;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_protocol = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
if (getaddrinfo(NULL, PORT, &hints, &serverinfo) != 0) {
// Error when getting server address information
OutputDebugString(L"> Log: getaddrinfo()\n");
output_error(WSAGetLastError()); // Call Cleanup?
return -1;
}
for (ptr = serverinfo; ptr != NULL; ptr = ptr->ai_next) {
// Create socket
if ((sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == INVALID_SOCKET) {
// Error when creating a socket
OutputDebugString(L"> Log: socket()\n");
output_error(WSAGetLastError()); // Call Cleanup?
continue;
}
// Set options
const char enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) == SOCKET_ERROR) {
// Error when setting options
OutputDebugString(L"> log: setsockopt()\n");
output_error(WSAGetLastError()); // call cleanup?
if (closesocket(sockfd) != 0) {
output_error(WSAGetLastError());
}
return -1;
}
// Bind socket
if (bind(sockfd, ptr->ai_addr, ptr->ai_addrlen) == SOCKET_ERROR) {
// Error on binding
OutputDebugString(L"> Log: bind()\n");
output_error(WSAGetLastError()); // Call Cleanup?
if (closesocket(sockfd) != 0) {
output_error(WSAGetLastError());
}
continue;
}
break;
}
freeaddrinfo(serverinfo);
if (ptr == NULL) {
OutputDebugString(L"Error: Failed to launch server.\n");
return -1;
}
// Listen
if (listen(sockfd, BACKLOG) == SOCKET_ERROR) {
OutputDebugString(L"> Log: listen()\n");
output_error(WSAGetLastError()); // Call Cleanup?;
return -1;
}
// Accept
struct sockaddr_storage clientinfo;
int size = sizeof(struct sockaddr_storage);
m_exchfd = accept(sockfd, (struct sockaddr *)&clientinfo, &size);
if (m_exchfd = INVALID_SOCKET) {
// Error when accepting
OutputDebugString(L"> Log: accept()\n");
output_error(WSAGetLastError()); // Call Cleanup?
if (closesocket(sockfd) != 0) {
output_error(WSAGetLastError());
}
return -1;
}
m_isConnected = true;
return 0;
}
The output_error function simply prints the message corresponding to the error using FormatMessage() function. However, I get the following output:
> Log: listen()
> ERROR: The attempted operation is not supported for the type of object referenced.
Thus, the error should be caused by the call to listen() which is confusing. Could anyone explain me what's the cause of the problem? I bet it should be something easy to fix but I just don't seem to see it.

The root of your problem is when calling getaddrinfo(), you are filling out the hints structure incorrectly.
You are assigning SOCK_STREAM to the ai_protocol field, and leaving the ai_socktype field set to 0. SOCK_STREAM is defined as 1, which is the same value as IPPROTO_ICMP, which is commonly used with SOCK_DGRAM sockets. So, getaddrinfo() is likely returning addrinfo entries whose ai_socktype field is set to SOCK_DGRAM. You can't use listen() on a datagram socket, thus the WSAEOPNOTSUPP error you are seeing:
If no error occurs, listen returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.
...
WSAEOPNOTSUPP
The referenced socket is not of a type that supports the listen operation.
You need to assign SOCK_STREAM to the hints.ai_socktype field instead, and set the hints.ai_protocol field to either 0 or IPPROTO_TCP (preferably the latter).
Also, getaddrinfo() returns an error code, just like WSAStartup() does. Don't use WSAGetLastError() to get its error code.
Aside from that, I see a number of other issues with your code.
SO_REUSEADDR requires a BOOL (4 byte integer), not a char (1 byte). You are passing in a pointer to a single char, but are telling setsockopt() that you are passing in a pointer to an int. setsockopt() will end up trying to read a value from stack memory you don't own.
When your loop calls closesocket(), you should reset sockfd to INVALID_SOCKET as well, and then after the loop you should check for that condition instead of checking ptr for NULL.
You should be calling listen() inside your loop instead of after it. Just because you bind() a socket successfully does not guarantee you can open its assigned listening port. You should keep looping until you actually open a listening port successfully. You might also consider adding an extra log message after the loop to know which local IP/Port pair is actually listening, so you know what clients are then able to connect() to.
When calling WSAGetLastError(), always call it immediately after a failed Winsock call. If you call anything else beforehand, you risk resetting the error code, since WSAGetLastError() is just an alias for GetLastError(), which many APIs use.
When calling accept(), you are using the = assignment operator instead of the == comparison operator when checking if m_exchfd is equal to INVALID_SOCKET. Even after you fix that, if accept() succeeds, you are leaking sockfd since you lose track of it and don't call closesocket() on it. If you are expecting only 1 client to connect, close the listening socket after the client is accepted. Otherwise, store the listening socket in your class and close it after you close the accepted client socket.
With all of that said, try something more like this:
void OutputWinsockError(LPCWSTR funcName, int errCode)
{
std::wostringstream msg;
msg << L"> Log: " << funcName << L"()\n";
OutputDebugStringW(msg.str().c_str());
output_error(errCode);
}
void OutputWinsockError(LPCWSTR funcName)
{
OutputWinsockError(funcName, WSAGetLastError());
}
int ConnectionManager::init_server() {
// Log
OutputDebugString(L"> Initializing server...\n");
// Initialize winsock
WSAData wsa;
int err = WSAStartup(MAKEWORD(2, 2), &wsa);
if (err != 0) {
// Error initializing winsock
OutputWinsockError(L"WSAStartup", err);
return -1;
}
// Get server information
struct addrinfo hints, *serverinfo, *ptr;
SOCKET sockfd = INVALID_SOCKET;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
err = getaddrinfo(NULL, PORT, &hints, &serverinfo);
if (err != 0) {
// Error when getting server address information
OutputWinsockError(L"getaddrinfo", err);
// Call Cleanup?
return -1;
}
for (ptr = serverinfo; ptr != NULL; ptr = ptr->ai_next) {
// Create socket
sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (sockfd == INVALID_SOCKET) {
// Error when creating a socket
OutputWinsockError(L"socket");
continue;
}
// Set options
const BOOL enable = TRUE;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(BOOL)) == SOCKET_ERROR) {
// Error when setting options
OutputWinsockError(L"setsockopt");
if (closesocket(sockfd) == SOCKET_ERROR) {
// Error when closing socket
OutputWinsockError(L"closesocket");
}
sockfd = INVALID_SOCKET;
continue;
}
// Bind socket
if (bind(sockfd, ptr->ai_addr, ptr->ai_addrlen) == SOCKET_ERROR) {
// Error on binding
OutputWinsockError(L"bind");
if (closesocket(sockfd) == SOCKET_ERROR) {
// Error when closing socket
OutputWinsockError(L"closesocket");
}
sockfd = INVALID_SOCKET;
continue;
}
// Listen on port
if (listen(sockfd, BACKLOG) == SOCKET_ERROR) {
// Error on listening
OutputWinsockError(L"listen");
if (closesocket(sockfd) == SOCKET_ERROR) {
// Error when closing socket
OutputWinsockError(L"closesocket");
}
sockfd = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(serverinfo);
if (sockfd == INVALID_SOCKET) {
OutputDebugString(L"Error: Failed to launch server.\n");
// Call Cleanup?
return -1;
}
// Accept
struct sockaddr_storage clientinfo;
int size = sizeof(clientinfo);
m_exchfd = accept(sockfd, (struct sockaddr *)&clientinfo, &size);
if (m_exchfd == INVALID_SOCKET) {
// Error when accepting
OutputWinsockError(L"accept");
if (closesocket(sockfd) == SOCKET_ERROR) {
OutputWinsockError(L"closesocket");
}
// Call Cleanup?
return -1;
}
m_isConnected = true;
// is not storing sockfd, close it
// m_listenfd = sockfd;
if (closesocket(sockfd) == SOCKET_ERROR) {
OutputWinsockError(L"closesocket");
}
return 0;
}

Related

Win32 multithreaded sockets

I have a server class that has a method called handle_client as follows:
void server::handle_client()
{
do {
// Accept a client socket
EnterCriticalSection(&listenSocketCriticalSection);
SOCKET clientSocket = accept(listenSocket, NULL, NULL);
LeaveCriticalSection(&listenSocketCriticalSection);
// ... rest of the client handling code that reads
// from the client socket and sends appropriate response
// ...
} while(true);
}
I have a run method as follows:
void server::run()
{
// create the threads
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
DWORD dwThreadId;
thread_pool_handle[i] = CreateThread(NULL, 0, thread_function, this, 0, &dwThreadId);
}
WaitForMultipleObjects(THREAD_POOL_SIZE, thread_pool_handle, true, INFINITE);
}
I have a thread_function as follows:
DWORD WINAPI thread_function(LPVOID lpParam)
{
server* pServer = (server*)lpParam;
pServer->handle_client();
}
I am creating a pool of threads that are all waiting for a client socket connection to be accepted. Since I have wrapped the accept within a critical section, only one thread will succeed at a time. Once a client socket is accepted by the server thread, that thread continues to go on to handle the request. The idea is that the thread will loop back indefinitely to the accept call after completing a request.
Questions:
Is the Critical Section necessary? (I think so, because otherwise the accept call from the multiple threads on the same listenSocket would clobber things. Is this correct?)
If handle_client loops indefinitely, what is the best way to cleanly terminate all the threads and exit the server? Should I use a special message from the client to trigger the thread terminations? Any other suggestions?
How should I handle the server process termination gracefully (as it pertains to the thread pool)?
It Is recommended to use Select model to store socket objects in multithreaded sockets. In Select model, you can use FD_CLR() to clear sockets when there are no network events.
I have the code for the server with the select socket, You can try to run and modify.
#include <iostream>
#include<WinSock2.h>
#include<windows.h>
#include<WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
#pragma warning(disable:4996)//inet_addr
int main()
{
//1.Obtain version info
WSADATA wsaData = { 0 };
SOCKET hServer = { 0 };
WSAStartup(MAKEWORD(2, 2), &wsaData);
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("version failed %d\n", GetLastError());
return -1;
}
else
{
printf("version succeed \n");
}
//2.create socket
hServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hServer == SOCKET_ERROR)
{
printf("create socket tcp failed %d\n", GetLastError());
return -1;
}
else
{
printf("create socket tcp succeed \n");
}
//3. Create a protocol address family
sockaddr_in ServerAddr = { 0 };
ServerAddr.sin_family = AF_INET6;
ServerAddr.sin_zero[8];
ServerAddr.sin_port = htons(8888);
ServerAddr.sin_addr.S_un.S_addr = inet_addr("192.168.2.50");;//modify your address
//4.bind
int nRet = bind(hServer, (sockaddr*)&ServerAddr, sizeof ServerAddr);
if (nRet == SOCKET_ERROR)
{
printf("bind failed %d\n", GetLastError());
closesocket(hServer);
WSACleanup();
return -1;
}
else
{
printf("bind succeed \n");
}
//5.listen
nRet = listen(hServer, 3);
if (nRet == SOCKET_ERROR)
{
printf("listen failed %d\n", GetLastError());
closesocket(hServer);
WSACleanup();
return -1;
}
else
{
printf("listen succeed \n");
}
sockaddr_in clientAddr = { 0 };// The protocol address family used to receive the client
int len = sizeof(clientAddr);// The size of the accepted client protocol address family information
// Create a select model to store socket objects
FD_SET fd_read;
FD_ZERO(&fd_read);
FD_SET(hServer, &fd_read);
//6. Accept client connections
while (1) {
FD_SET fd_tmp = fd_read;// Read backup can only be in
const timeval tv = { 1,0 };
int Ret = select(NULL, &fd_tmp, NULL, NULL, &tv);
if (Ret == 0) // No network events, TMP is automatically deleted
{
Sleep(1000);
continue;
}
for (int i = 0; i < fd_tmp.fd_count; i++)
{
// If there are network events for a listening socket, it proves that a client is connecting to the socket
if (fd_tmp.fd_array[i] == hServer)
{
SOCKET hclient;
hclient = accept(hServer, (sockaddr*)&clientAddr, &len);// If you do not want to store the protocol address family information of the client, you can pass a NULL address
if (hclient == SOCKET_ERROR)
{
printf("recieve information of client failed %d\n", GetLastError());
closesocket(hServer);
return -1;
}
printf("connecting: %s******** \n", inet_ntoa(clientAddr.sin_addr));
FD_SET(hclient, &fd_read);
}
else // The client socket has network events that prove that the client is sending data and the server is accepting the data
{
char buff[32] = { 0 };
int nRet = recv(fd_tmp.fd_array[i], (char*)buff, 32, NULL);
if (nRet > 0)
{
printf("message: %s\n", buff);
}
else// Removes the current socket fd_array from the fd_read
{
FD_CLR(fd_tmp.fd_array[i], &fd_read);
printf("Disconnect \n", GetLastError());
closesocket(hServer);
closesocket(fd_read.fd_array[i]);
break;
}
}
break;
}
}
//7.close socket
WSACleanup();
getchar();
return 0;
}

Linux to Windows PC Communication through Socket

I have used the linux part of code in windows to establish RDB communication. Though the socket opening is successful, I am unable to bind the socket with the IP Address defined. Is there any limitation between Linux and Windows over socket?
Here's my code,
SOCKET sock;
SOCKET sClient;
struct sockaddr_in server;
char szServer128; // Server to connect to
char szBuffer204800; // allocate on heap
sClient = udpsock_open(&server, szServer, DEFAULT_RX_PORT);
int udpsock_open(struct sockaddr_in *psa, char *ipv4, uint16_t port) {
WSADATA wsaData;
int opt;
int domain = AF_INET;
int type = SOCK_DGRAM;
int proto = IPPROTO_UDP;
sClient = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (sClient 0)
printf(" WSAStartup Successful %I64d\n", sClient);
else
printf("Failed. Error Code : %d\n", WSAGetLastError());
// Prepare a socket to listen for connections
if ((sock = WSASocket(domain, type, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) INVALID_SOCKET)
{
printf("WSASocket() failed with error %d\n", WSAGetLastError());
return 1;
}
else
{
printf("WSASocket() is OK!\n");
}
if (sock < 0)
{
//rtos_log ( LOG_ERR, "Failed to open socket %m" );
printf("Failed to open socket %I64d\n", sock);
return -1;
}
set_reuse(sock);
memset(psa, 0x00, sizeof(struct sockaddr_in));
psa->sin_family = domain;
psa->sin_port = htons(port);
psa->sin_addr.s_addr = inet_addr(ipv4);
if (bind(sock, ( struct sockaddr *)psa, sizeof(struct sockaddr_in)) < 0)
{
printf("Failed to bind socket Error: %d\n", WSAGetLastError());
closesocket(sock);
WSACleanup();
return -4;
}
opt = 1;
char buf[2048];
DWORD dwBytesRet;
//ioctlsocket(sock, FIONBIO, &opt)
if (WSAIoctl(sock, SIO_ADDRESS_LIST_QUERY,NULL,0, buf,2048, &dwBytesRet,NULL,NULL) == SOCKET_ERROR)
{
printf("ioctlsocket() failed with error %d\n", WSAGetLastError());
return 1;
}
else
{
printf("ioctlsocket() is OK!\n");
}
return sock;
}

Second time TFTP PUT opearation doesnt work after GET/PUT opearation is successfully finished first time on TFTP client side

Implementing the TFTP client over UDP socket.
First I create the UDP non blocking socket and perform the PUT/GET operation it works fine.
But when again I try to perform the GET/PUT it does not work. The RRQ/WRQ request itself is not reaching to the server but from the client side it has been sent successfully.
Below is my tftp client code.
=========================================================
int sockfd;
struct sockaddr_in serv_addr;
//called when we retrieve a file from the server
void getFile(int port, char *filename)
{
printf("enter to get file\n");
FILE * file;
if (strchr(filename,'/') != NULL )
{
printf("We do not support file transfer out of the current working directory\n");
return;
}
file = fopen(filename, "wb");
if(file == NULL)
{
perror(filename);
return;
}
if(sockfd < 0)
{
printf("Couldn't open socket\n");
return;
}
if(!send_RRQ(sockfd, (struct sockaddr *) &serv_addr, filename, TFTP_SUPORTED_MODE))
{
printf("Error: couldn't send RRQ\n");
return;
}
if(!recvFile(sockfd, (struct sockaddr *) &serv_addr, file,filename))
{
printf("Error: didn't receive file\n");
return;
}
fclose(file);
return;
}
//used to upload files to the server
void putFile(int port, char *filename)
{
printf("filenemae is: %s \t",filename);
PACKET packet;
int result;
FILE * fileh;
int timeout_counter = 0;
if (strchr(filename,'/') != NULL )
{
printf("We do not support file transfer out of the current working directory\n");
return;
}
fileh = fopen(filename, "rb");
if(fileh == NULL)
{
perror(filename);
return;
}
if(!send_WRQ(sockfd, (struct sockaddr *) &serv_addr, filename, TFTP_SUPORTED_MODE))
{
printf("Error: couldn't send WRQ to server\n");
return;
}
while (timeout_counter < MAX_TFTP_TIMEOUTS)
{
result = waitForPacket(sockfd, (struct sockaddr *) &serv_addr, TFTP_OPTCODE_ACK, &packet);
if (result < 0)
{
printf("Error: Timeout sending packet to server\n");
if(!send_WRQ(sockfd, (struct sockaddr *) &serv_addr, filename, TFTP_SUPORTED_MODE))
{
printf("Error: couldn't send WRQ to server\n");
return;
}
timeout_counter++;
}else
{
break;
}
}
if (result < 0)
{
//we still timed out
printf("Timed out after %d tries, is the server running\n",MAX_TFTP_TIMEOUTS);
fclose(fileh);
return;
}
if (packet.optcode == TFTP_OPTCODE_ERR)
{
//we recieved an error, print it
printError(&packet);
}else
{
if (!sendFile(sockfd, (struct sockaddr *) &serv_addr, fileh))
{
printf("Unable to send file to server\n");
}
}
fclose(fileh);
return;
}
int createUDPSocketAndBind(int port)
{
//create a socket
sockfd = socket(PF_INET, SOCK_DGRAM, 0);
//return -1 on error
if (sockfd == -1)
{
return -1;
}
//zero out the struct
bzero((char*) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(port);
return 0;
}
//main client, checks for args and starts an operation if no errors detected
int main(int argc, char *argv[])
{
int port = 59;
int argOffset = 1;
char* filename;
char fme[] = "test.txt";
createUDPSocketAndBind(port);
printf("for put file\n");
putFile(port,fme); //=====> this operation is successful.
//createUDPSocketAndBind(port); //=====> If I uncomment this function, next operation works.
printf("for getfile\n");
getFile(port,fme); //=======> It is failing.
printf("Usage: %s [-p port] (-w putfile || -r getFile)\n",argv[0]);
}
=========================================================================
I have investigated more and found the problem with my code and solution as well.
When you send the first RD/WR request to the Server it will create a separate process or thread to handle your request and at the same time it will create the new socket and will use the different port number as well to handle the RD/WR request.
Client side when you will receive the response from the Server using below UDP socket API.
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
In the "struct sockaddr *src_addr" instance port number will be updated as server is responding using different port number which server
has created to handle your request and your RD/WR operation will complete successfully using this port number.
Once RD/WR is done successfully server will close the socket which has been created to handle you request and start listening to your new request
on the Original port. But client side "struct sockaddr *src_addr" instance will still have the modified port number and when you try to send the new RD/WR request it will not reach to the server. And that is the reason second RD/WR request will not reach to the server and it will fail.
To fix this on the client side in the "struct sockaddr *src_addr" instance you have to reset the port to the initial value which you have used initially to configure the server instance of the "struct sockaddr *src_addr". So after each RD/WR operation you have to reset the port number
to the original value.
I am quite new to the UDP socket and it was good learning for me.
I think this will be helpful for the beginners like me.
Thanks

socket::accept continual to return EGAIN

I use nonblocking socket to receive new connection. But the code repeatedly fails to accept().
int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
::bind(sockfd, bind_addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
ret = ::listen(sockfd, SOMAXCONN);
while (True) {
::poll(&*pollfds_.begin(), pollfds_.size(), timeoutMs);
struct sockaddr_in6 addr;
bzero(&addr, sizeof addr);
socklen_t addrlen = static_cast<socklen_t>(sizeof *addr);
int connfd = ::accept4(sockfd, sockaddr_cast(addr),
&addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
}
errno is EAGAIN.
From the manpage to accept(2):
EAGAIN or EWOULDBLOCK
The socket is marked nonblocking and no connections are present to be accepted. POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possibilities.
This means that the call to accept is made before the client has connected.
Before calling accept, you must call listen and bind.
But as your socket is not blocking, you should wait for client to wait to connect. You can do that with select function:
int sockfd = ::socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
// addr is for accept call, sin for bind call
struct sockaddr_in6 addr, sin;
bzero(&addr, sizeof addr);
// prepare sin to tell bind to listen on any connection on given port
sin.sin6_family = family;
sin.sin6_addr = in6addr_any;
sin.sin6_port = htons(port); // choose port on which client could connect
sin.sin6_scope_id = 0;
// bind socket to interface
if (::bind(sock, (struct sockaddr*) &sin, sizeof(sin)) < 0)
{
perror("bind");
}
// listen for new connection
if (::listen(sock, SOMAXCONN) < 0)
{
perror("socket");
}
while (1)
{
fd_set conset;
FD_ZERO(&conset);
FD_SET(sockfd, &conset);
struct timeval timeout = {10, 0};
int maxfd = sockfd;
// wait for new client
select(maxfd + 1, &conset, NULL, NULL, &timeout);
if (FD_ISSET(sockfd, &conset))
{
// a new client is waiting
int connfd = ::accept(sockfd, &addr);
if (connfd < 0)
{
perror("accept");
}
else
{
// do thing with new client
}
}
else
{
printf("no new client in last 10 seconds")
}
}

Make new connections when blocked in a select() call

I have 2 threads:
Thread A:
It's the select() loop. Perform socket handling for reading operations, such as accepting new connections, receiving data.
while (1) {
FD_ZERO(&fdReadSet);
numActiveSockets = 0;
for (std::unordered_map<SOCKET, TcpSocket*>::iterator it = m_sock_table.begin(); it != m_sock_table.end(); it++)
{
numActiveSockets++;
FD_SET(it->first, &fdReadSet);
}
int ret;
bool hasListen = false;
if (( ret = select(numActiveSockets, &fdReadSet, NULL, NULL, NULL)) == SOCKET_ERROR) {
printf("Select Failed, Error code = %d\n", WSAGetLastError());
return -1;
}
for (std::unordered_map<SOCKET, TcpSocket*>::iterator it = m_sock_table.begin(); it != m_sock_table.end(); it++)
{
if (FD_ISSET(it->first, &fdReadSet))
{
if (it->first == TcpSocket::m_listen_sock)
{
if (!hasListen)
{
sockaddr_in sock_addr;
int sockLength = sizeof(sock_addr);
SOCKET sock = accept(it->first, (sockaddr *) &sock_addr, &sockLength);
TcpSocket * socket = new TcpSocket();
socket->m_sock = sock;
m_sock_table[sock] = socket;
it = m_sock_table.begin();
hasListen = true;
}
}
else
{
char * buffer = it->second->GetWriteBuffer();
int numRead = recv(it->first, buffer, SOCKET_BUFFER_SIZE, 0);
if (numRead == SOCKET_ERROR)
{
int err = WSAGetLastError();
if (err == WSAECONNRESET)
{
printf("Connection [%i]: RESET Received. Closing Socket\n", it->first);
closesocket(it->first);
it = socketVector.erase(it->first); // iterator invalidated after erase
}
else
{
printf("Recv Failed. Error code = %d\n", err);
return -1;
}
}
else if (numRead == 0)//connection close
{
printf("Connection [%i]: Graceful exit. Closing Socket\n", it->first);
closesocket(it->first);
it = socketVector.erase(it->first); // iterator invalidated after erase
}
else {
/* Process received data */
}
}
}
}
}
Thread B:
Allow the application to perform connect() to establish new connections. If a connect() is successful, it will the add the returned socket to m_sock_table.
I have a socket table called m_sock_table which holds all the sockets. I use this m_sock_table to initialize the fdReadSet to be used in select().
-----------Problem-----------------
If thread A is blocked by select(), and at the same time thread B establish a new connection through connect(), the application wouldn't be able to receive data from the new connection, because fdReadset has not been updated withed the new connected socket.
What would be a good way to solve this problem? Or the design is just wrong from the start?
You could use a signal that doesn't do anything other than interrupting the system call:
#include <signal.h>
void do_nothing() { }
struct sigaction sa;
sa.sa_handler = do_nothing;
sigemptyset(sa.sa_mask);
#ifdef SA_INTERRUPT
sa.sa_flags = SA_INTERRUPT;
#else
sa.sa_flags = 0;
#endif
sigaction(SIGUSR1, &sa, 0);
Then, in thread B, after starting a new connection, send the signal, after making sure thread A will handle it:
/* need only be done once, but needed in every thread other than A */
sigset_t sigs;
sigemptyset(&sigs);
sigaddset(&sigs, SIGUSR1)
pthread_sigmask(SIG_BLOCK, &sigs, 0);
/* each time we create a new connection */
kill(getpid, SIGUSR1);
With the above, select will return with an EINTR error -- so check for that and loop (adding the new connection to the set).

Resources