I'm new to WinSock, and I'm trying something out. I have client and server programs that are communicating with each other. If the client types something, the server will just echo it back. I want them to receive and send at the same time, so I put the client in non-blocking mode, and it works kind-of OK. But when I try to put the server in non-blocking, it crashes saying that recv() == SOCKET_ERROR
.
So the question is : why can the client work in non-blocking, but the server can't? How can I solve this?
TCP_SERVER:
#include <iostream>
#include <WS2tcpip.h>
#include <Windows.h>
#pragma comment (lib,"ws2_32.lib")
using namespace std;
string receive(SOCKET clientSocket, char* buf)
{
ZeroMemory(buf, 4096);
int bytesReceived = recv(clientSocket, buf, 4096, 0);
string bufStr = buf;
cout << "bytes received: " << bytesReceived << endl;
if (bytesReceived == SOCKET_ERROR)
{
cerr << "error met recv() in de reciev() functie" << endl;
exit(EXIT_FAILURE);
}
if (bytesReceived == 0)
{
cout << "client disconnected" << endl;
exit(EXIT_FAILURE);
}
return bufStr;
}
void main()
{
//initialize winsock
WSADATA wsData;
WORD ver = MAKEWORD(2, 2);
int wsOk = WSAStartup(ver, &wsData);
if (wsOk != 0) {
cerr << "can't initialize winsock ABORT";
return;
}
//create socket
SOCKET listening = socket(AF_INET, SOCK_STREAM, 0);
if (listening == INVALID_SOCKET) {
cerr << "cant create socket ABORT" << std::endl;
}
//bind IP adress and port to socket
sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(54000);
hint.sin_addr.S_un.S_addr = INADDR_ANY; //could also inet_pton
bind(listening, (sockaddr*)&hint, sizeof(hint));
//tell winsock the socket is for listening
listen(listening, SOMAXCONN);
//wait for connection
sockaddr_in client;
int clientSize = sizeof(client);
SOCKET clientSocket = accept(listening, (sockaddr*)&client, &clientSize);
if (clientSocket == INVALID_SOCKET) {
cerr << "somthing went wrong with client socket accept ABORT";
exit(EXIT_FAILURE);
}
char host[NI_MAXHOST]; //client remote name
char service[NI_MAXSERV]; //service (i.e port) the client is connected on
ZeroMemory(host, NI_MAXHOST);
ZeroMemory(service, NI_MAXSERV);
if (getnameinfo((sockaddr*)&client, sizeof(client), host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0)
{
cout << host << " connected on port " << service << endl;
}
else {
inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
cout << host << " connected on port " << ntohs(client.sin_port) << endl;
}
//close listening socket
closesocket(listening);
//non blocking socket leads to error
u_long mode = 1; // 1 to enable non-blocking socket
ioctlsocket(clientSocket, FIONBIO, &mode);
//non blocking socket
//while loop: accept and echo message to client
char buf[4096];
string inputTxt;
while (true)
{
inputTxt = receive(clientSocket,buf);
send(clientSocket, buf, inputTxt.size() + 1, 0);
}
closesocket(clientSocket);
WSACleanup();
}
TCP_CLIENT:
#include <iostream>
#include <WS2tcpip.h>
#include <Windows.h>
#pragma comment (lib,"ws2_32.lib")
using namespace std;
string receive(SOCKET clientSocket, char* buf)
{
ZeroMemory(buf, 4096);
int bytesReceived = recv(clientSocket, buf, 4096, 0);
string bufStr = buf;
cout << "bytes received: " << bytesReceived << endl;
if (bytesReceived == SOCKET_ERROR)
{
cerr << "error met recv() in de reciev() functie" << endl;
exit(EXIT_FAILURE);
}
if (bytesReceived == 0)
{
cout << "client disconnected" << endl;
exit(EXIT_FAILURE);
}
return bufStr;
}
void main()
{
//initialize winsock
WSADATA wsData;
WORD ver = MAKEWORD(2, 2);
int wsOk = WSAStartup(ver, &wsData);
if (wsOk != 0) {
cerr << "can't initialize winsock ABORT";
return;
}
//create socket
SOCKET listening = socket(AF_INET, SOCK_STREAM, 0);
if (listening == INVALID_SOCKET) {
cerr << "cant create socket ABORT" << std::endl;
}
//bind IP adress and port to socket
sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(54000);
hint.sin_addr.S_un.S_addr = INADDR_ANY; //could also inet_pton
bind(listening, (sockaddr*)&hint, sizeof(hint));
//tell winsock the socket is for listening
listen(listening, SOMAXCONN);
//wait for connection
sockaddr_in client;
int clientSize = sizeof(client);
SOCKET clientSocket = accept(listening, (sockaddr*)&client, &clientSize);
if (clientSocket == INVALID_SOCKET) {
cerr << "somthing went wrong with client socket accept ABORT";
exit(EXIT_FAILURE);
}
char host[NI_MAXHOST]; //client remote name
char service[NI_MAXSERV]; //service (i.e port) the client is connected on
ZeroMemory(host, NI_MAXHOST);
ZeroMemory(service, NI_MAXSERV);
if (getnameinfo((sockaddr*)&client, sizeof(client), host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0) {
cout << host << " connected on port " << service << endl;
}
else {
inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
cout << host << " connected on port " << ntohs(client.sin_port) << endl;
}
//close listening socket
closesocket(listening);
/*
//non blocking socket leads to error
u_long mode = 1; // 1 to enable non-blocking socket
ioctlsocket(clientSocket, FIONBIO, &mode);
//non blocking socket
*/
//while loop: accept and echo message to client
char buf[4096];
string inputTxt;
while (true)
{
inputTxt = receive(clientSocket,buf);
send(clientSocket, buf, inputTxt.size() + 1, 0);
}
closesocket(clientSocket);
WSACleanup();
}
You are not handling the case where send()
/recv()
are failing due to a WSAEWOULDBLOCK
error, which is NOT a fatal error. It just means there is no work to be done at that moment, try again later.
For recv()
, that means there are no bytes available to read from the socket's receive buffer. The socket will be in a readable state when there are bytes available to read from it, or the peer has performed a graceful disconnect.
For send()
, it means the peer's receive buffer is full and can't receive new bytes until the peer reads some bytes to clear up buffer space. Any unsent bytes will have to be passed to send()
again at a later time. The socket will be in a writable state when new bytes can be sent to the peer, and not in a writable state when the peer's buffer is full.
When your server accepts a client and tries to receive()
from it, recv()
is going to keep failing with WSAEWOULDBLOCK
until the client actually sends something.
So, you need to handle WSAEWOULDBLOCK
properly and retry as needed. Or better, use select()
(or WSAAsyncSelect()
, or WSAEventSelect()
, or Overlapped I/O) to detect the socket's actual state to know when send()
/recv()
can be safely called without causing an WSAEWOULDBLOCK
error.