Async Chat server/client suggestions and hints.

Started by
13 comments, last by netpumber 12 years, 3 months ago
Hello.

So a month now im searching in net different methods to create a chat client/server app.
Clients will connect to the server and the server will send the message of each client to the rest.
I use C and Visual studio 10 and finally i decided to use the async method.
My app will be a simple console application so i will use WSAEventSelect instead of WSAAsyncSelect.

I have found only this tutorial on net talking about this.

Also i read and for IOCP but my knowledge is not enough for something like this.

I ask you to tell me if you find my decision correct and if you have any tutorial or post on mind that can help me. Please let me know.

Also any hint or suggestion is welcomed.

Thanks in advance.
Advertisement
It seems like several entries in the FAQ would cover this. Have you read the FAQ thoroughly and read the documents it links to?


Chat is no different than any other message; it happens to have text that you display, but that kind of network message is no different than a message about updating units or updating player stats or transmitting a VoIP message. It is just a block of data meant for direct display to the human.
Yeah ok i ve read the FAQ but it talks mostly for select() method as i saw.
The truth is that i have try the select method but there was a problem with the message send time.

Here is my code for my select() try.

Server:

....
// Listening
//iMode = 1; // If iMode!=0, non-blocking mode is enabled.
listen(ListeningSocket,5);
//ioctlsocket(ListeningSocket,FIONBIO,&iMode); // Enable the non-locking mode.

// Clear the two fd sets
FD_ZERO(&fdread);
FD_ZERO(&BackUpfdread);
FD_ZERO(&fdwrite);
FD_ZERO(&BackUpfdwrite);

// Asign the ListeningSocket with fdread.
FD_SET(ListeningSocket,&fdread);
FD_SET(ListeningSocket,&fdwrite);

// Set as maxDescriptor ListeningSocket.
maxDescriptor = ListeningSocket;

// Setup timer.
timeout.tv_sec = 2;
timeout.tv_usec = 5000;

// Main loop starts here.

for(; ; )
{
// Keep a backup of FD set cause select() erases it, if there's nothing to do.
BackUpfdread = fdread;
BackUpfdwrite = fdwrite;
selectResults = select(maxDescriptor+1,&BackUpfdread,&BackUpfdwrite,NULL,&timeout);

if(selectResults == -1)
{
printf("Select() error");
WSACleanup();
return 0;
}


// Check the fdread set for alive connections
for(i=0;i<=maxDescriptor;i++)
{
if(FD_ISSET(i,&BackUpfdread))
{
if(i == ListeningSocket) // That means we have a NEW CONNECTION and must hadle it.
{
ClientAddrSize = sizeof(ClientAddr);
AcceptSocket = accept(ListeningSocket,(SOCKADDR *)&ClientAddr,&ClientAddrSize);

if(AcceptSocket == -1)
{
printf("Winsock error - Unable to accept socket");
WSACleanup();
return 0;
}

// Add the newest socket in the fdread set.
FD_SET(AcceptSocket,&fdread);
FD_SET(AcceptSocket,&fdwrite);
// Keep track of the maxDescriptor.
if(AcceptSocket > maxDescriptor)
{
maxDescriptor = AcceptSocket;
}

// Receiving the username from the new client.
memset(username,0,sizeof(username));
recv(AcceptSocket,username,sizeof(username),0);

printf("New connection (username: %s) from %s on socket %d\n", username, inet_ntoa(ClientAddr.sin_addr), AcceptSocket);

// Send Welcome Message.
send(AcceptSocket,msg,sizeof(msg),0);

}else{ // That means that the socket (i) isn't from a new connection and has something sent.

RecvBytes = recv(i, RecvBuff, sizeof(RecvBuff)-1, 0);

if(RecvBytes == -1)
{
printf("Winsock error - Cannot receive data from client");
WSACleanup();
return 0;
}

if(RecvBytes == 0)
{
printf("Socket %d hung up\n", i);
// Shutdown Socket
shutdown(i,SD_RECEIVE);
// Clear fdread.
FD_CLR(i, &fdread);
}

if(RecvBytes > 0)
{
printf("Message Send.\n");
printf("Message was: %s\n",RecvBuff);

for(k=0;k<=maxDescriptor;k++)
{
if(FD_ISSET(k,&BackUpfdwrite))
{
memset(SentBuff,0,sizeof(SentBuff));
strcpy(SentBuff,RecvBuff);
SentBytes = send(k,SentBuff,sizeof(SentBuff),0);
}

}

}
}
}
}

} // Main loop ends here.
...



Client:

....
// Initialize Winshock 2.2
WSAStartup(MAKEWORD(2,2),&wsaData);

// Create a new socket to make a client connection.
ConnectSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

// Initialize Servers SOCKADDR_IN
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(port);
ServerAddr.sin_addr.s_addr = inet_addr(server_address);

// Clear the fd sets
FD_ZERO(&fdread);
FD_ZERO(&BackUpfdread);
FD_ZERO(&fdwrite);
FD_ZERO(&BackUpfdwrite);

// Asign the ListeningSocket with fdread and fdwrite.
FD_SET(ConnectSocket,&fdread);
FD_SET(ConnectSocket,&fdwrite);

// Set as maxDescriptor ListeningSocket.
maxDescriptor = ConnectSocket;

// Setup timer.
timer.tv_sec = 2;
timer.tv_usec = 5000;

//iMode = 1; // If iMode!=0, non-blocking mode is enabled.
//ioctlsocket(ConnectSocket,FIONBIO,&iMode); // Enable the non-locking mode.

// Make a connection to the server with socket s.
connect(ConnectSocket, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr));

// Send the username to server.
send(ConnectSocket,username,strlen(username),0);

// Receive Welcome Message From Server (If any..)
RecvBytes = recv(ConnectSocket,RecvBuff,sizeof(RecvBuff),0);

if(RecvBytes > 0)
{
printf("%s",RecvBuff);
// Cleaning the Receive Buffer
memset(RecvBuff,0,sizeof(RecvBuff));
}

// Main loop starts here
for(; ; )
{

// Copy the fdread into BackUpfdread and fdwrite to BackUpfdwrite.
BackUpfdread = fdread;
BackUpfdwrite = fdwrite;

SelectResults = select(maxDescriptor+1,&BackUpfdread,&BackUpfdwrite,NULL,&timer);

if(SelectResults == -1)
{
perror("Client-select() error!\n");
exit(1);
}

//printf("Client-select is OK\n");

//Check if we have some data to read.
if (FD_ISSET(ConnectSocket, &BackUpfdread))
{
RecvBytes = recv(ConnectSocket, RecvBuff, sizeof(RecvBuff), 0);
if(RecvBytes > 0)
{
printf("%s\n",RecvBuff);
// Cleaning the Receive Buffer
memset(RecvBuff,0,sizeof(RecvBuff));
}
}

memset(SentBuff, 0, sizeof(SentBuff));
printf("Write: ");
gets_s(SentBuff, sizeof(SentBuff));

//Check if we have some data to write.
if (FD_ISSET(ConnectSocket, &BackUpfdwrite))
{
SentBytes = send(ConnectSocket, SentBuff,sizeof(SentBuff),0);
// Cleaning the Sent Buffer
memset(SentBuff,0,sizeof(SentBuff));
}



} // Main loop ends here
....




and here is the image that shows the message transfer delay.

chatbq.png

After these problems i decided to use the WSAEventSelect with event creation and blah blah as the tutorial posted before says..


// Setup timer.
timeout.tv_sec = 2;
timeout.tv_usec = 5000;

[/quote]

This timeout specifies 2.005 seconds -- two seconds, and five thousand microseconds. Perhaps this is what you intend, but I think it's not.
Generally, when polling the network together with a game or event loop, you want to set tv_sec and tv_usec both to 0.



for(i=0;i<=maxDescriptor;i++)
{
if(FD_ISSET(i,&BackUpfdread))


[/quote]

This doesn't work well at all in Windows. sockets are handles, which can have very high values. "maxDescriptor" is ignored on input. You should test the values you know you have (the listening socket, and each client socket) rather than "all integers." You're basically testing ALMOST ALL INTEGERS to see if one of them happens to match a handle value stored in the FD_SET. That is one cause of performance problems.



// Receiving the username from the new client.
memset(username,0,sizeof(username));
recv(AcceptSocket,username,sizeof(username),0);

[/quote]

Someone can block your game forever by doing telnet to your server, and then not sending a username. Never recv from a socket unless select() tells you it won't block.



RecvBytes = recv(i, RecvBuff, sizeof(RecvBuff)-1, 0);

[/quote]

See above -- "i" is generally not a valid socket handle value.


After these problems i decided to use the WSAEventSelect with event creation and blah blah as the tutorial posted before says..
[/quote]

WSAEventSelect is only slightly less bad than WSAAsyncSelect. Use select(), or use OVERLAPPED I/O on sockets as handles.
Note that, apart from scanning all integers, other performance problems with your test may be that there is not enough CPU to handle all the clients+server you're running, so Windows will time slice, and also you're not using TCP_NODELAY, so sends will be delayed by several hundred milliseconds.
enum Bool { True, False, FileNotFound };
Thanks a lot for your answer hplus0603 .

First of all i think that the second problem you referred to, will be solved using a struct that will save the socket number of each client and in the for loop i will use these numbers.

As for the username sending i will find another way to do this.

Finally i want you to tell me why to use select() and not WSAEventSelect method.
As i read on net the first one uses more CPU than the other.

Finally i want you to tell me why to use select() and not WSAEventSelect method.
As i read on net the first one uses more CPU than the other.


WASEventSelect() has an inherent race condition that is very hard to work around. If you read about it on MSDN, they talk about it, but don't actually explain the systems programming aspects. And yet, it's not very scalable -- the NT kernel can wait on at most 64 events at the same time (using MsgWaitForMultipleObjectsEx()).

If you want simple to code, use select() -- it will work fine for dozens of connections.

If you want high performance, use OVERLAPPED I/O on sockets with I/O completion ports -- or use boost::asio, which puts a slightly nicer interface on top of that, plus is portable across operating systems.
enum Bool { True, False, FileNotFound };



for(i=0;i<=maxDescriptor;i++)
{
if(FD_ISSET(i,&BackUpfdread))




This doesn't work well at all in Windows. sockets are handles, which can have very high values. "maxDescriptor" is ignored on input. You should test the values you know you have (the listening socket, and each client socket) rather than "all integers." You're basically testing ALMOST ALL INTEGERS to see if one of them happens to match a handle value stored in the FD_SET. That is one cause of performance problems.
[/quote]

I had something like that and changed it to as follows. I didn't do any measurements, but thought there was a noticeable improvement in the performance from the change. I count down rather than starting at zero and counting up. Also the code with the selrc is used to shorten this potentially long loop. selrc is the return code from select. After addressing all of the events, we can skip the rest of the loop.


for (int32_t sock = fdmax; sock > 0; --sock) {
if (FD_ISSET(sock, &read_fds)) {
if (mediateRequest(sock)) {
FD_SET(cmwSendbuf.sock_, &masterwrite);
} else {
closeSocket(sock);
FD_CLR(sock, &master);
}
if (0 == --selrc) break;
}
}



Brian Wood
Ebenezer Enterprises
http://webEbenezer.net

[quote name='netpumber' timestamp='1324641936' post='4896797']
Finally i want you to tell me why to use select() and not WSAEventSelect method.
As i read on net the first one uses more CPU than the other.


WASEventSelect() has an inherent race condition that is very hard to work around. If you read about it on MSDN, they talk about it, but don't actually explain the systems programming aspects. And yet, it's not very scalable -- the NT kernel can wait on at most 64 events at the same time (using MsgWaitForMultipleObjectsEx()).

If you want simple to code, use select() -- it will work fine for dozens of connections.
[/quote]

I like select's portability and not having to depend on a large library which itself depends on a number of other libraries. (I don't use select in the C++ Middleware Writer, but portability isn't an issue there.)

Brian Wood
Ebenezer Enterprises
http://webEbenezer.net
[quote name='hplus0603' timestamp='1324602493' post='4896692']


for(i=0;i<=maxDescriptor;i++)
{
if(FD_ISSET(i,&BackUpfdread))


This doesn't work well at all in Windows. sockets are handles, which can have very high values. "maxDescriptor" is ignored on input. You should test the values you know you have (the listening socket, and each client socket) rather than "all integers." You're basically testing ALMOST ALL INTEGERS to see if one of them happens to match a handle value stored in the FD_SET. That is one cause of performance problems.
[/quote]I had something like that and changed it to as follows. I didn't do any measurements, but thought there was a noticeable improvement in the performance from the change.

for (int32_t sock = fdmax; sock > 0; --sock) {
if (FD_ISSET(sock, &read_fds)) {

[/quote]
That suffers from exactly the same problem.

You are not iterating over your handles in either examples.

Instead you are iterating over potentially billions of integers where by brute force it will eventually stumble over your handle.


Keep a separate list of handles and only iterate over those. For example, you may have:


typedef std::vector<int32_t> HandleCollection;
...
HandleCollection openHandles;
...

// add an incoming connection
AcceptSocket = accept(ListeningSocket,(SOCKADDR *)&ClientAddr,&ClientAddrSize);
openHandles.push_back(AcceptSocket);
...
for(HandleCollection::iterator it = openHandles.begin(); it!=openhandles.end(); ++it;)
{
if (FD_ISSET(*it, &read_fds)) {
...

This will reduce it from attempting to check against potentially billions of non-handles to instead just checking against the small number of actual handles you know to be open.

In practice you will likely want to store more than just an integer. Eventually you will want a structure or class with additional information about each client, but that is a future exercise.
Hello again.

After our conversation here i decide to give a try once again with select method.
Here is the new code.
I put 0 in timeout and also i use an array with sockets to avoid looping between all these integers.

Server:


#include <WinSock2.h>
#include <stdio.h>
#include <time.h>



main()
{

SOCKET ListeningSocket;
SOCKET AcceptSocket;

SOCKADDR_IN ServerAddr;
SOCKADDR_IN ClientAddr;

WSADATA wsaData;

const unsigned short PORT = 4444;

FD_SET fdread;
FD_SET BackUpfdread;
FD_SET fdwrite;
FD_SET BackUpfdwrite;

int maxDescriptor;
SOCKET SocketArray[20];
int index = 0;
int selectResults;
int i,k;
int clientAddrSize;
int RecvBytes;
int SentBytes;

char SentBuff[500];
char RecvBuff[500];

struct timeval timeout;

// Initialize Winsock2.2
WSAStartup(MAKEWORD(2,2),&wsaData);

// Initialize Listening Socket
ListeningSocket = socket(AF_INET,SOCK_STREAM,0);

// Initialize ServerAddr
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
ServerAddr.sin_port = htons(PORT);

// Bind the ServerAddr with ListeningSocket
bind(ListeningSocket,(SOCKADDR *)&ServerAddr,sizeof(ServerAddr));

// Listening Socket
listen(ListeningSocket,5);

FD_ZERO(&fdread);
FD_ZERO(&BackUpfdread);
FD_ZERO(&fdwrite);
FD_ZERO(&BackUpfdwrite);

// Asign ListeningSocket at fdread
FD_SET(ListeningSocket,&fdread);

maxDescriptor = ListeningSocket;

SocketArray[index] = ListeningSocket;
index++;

timeout.tv_sec = 0;
timeout.tv_usec = 0;

// Main loop starts here
for(; ;)
{

BackUpfdread = fdread;
BackUpfdwrite = fdwrite;
selectResults = select(maxDescriptor+1,&BackUpfdread,&BackUpfdwrite,NULL,&timeout);
//printf("server select() OK\n");

if(selectResults == -1)
{
printf("Select() error");
WSACleanup();
return 0;
}


for(i=0;i<=index-1;i++)
{
//printf("%d\n",SocketArray);
if(FD_ISSET(SocketArray,&BackUpfdread))
{
if(SocketArray == ListeningSocket) // we have a new connection
{
clientAddrSize = sizeof(ClientAddr);
AcceptSocket = accept(ListeningSocket,(SOCKADDR *)&ClientAddr,&clientAddrSize);

// Add the newest accepted socket to the fdread and fdwrite sets.
FD_SET(AcceptSocket,&fdread);
FD_SET(AcceptSocket,&fdwrite);

// Add the newest accepted socket into SocketArray
SocketArray[index] = AcceptSocket;
index++;

// keep track of the maxDescriptor.
if(AcceptSocket > maxDescriptor)
{
maxDescriptor = AcceptSocket;
}

printf("New connection from %s on socket %d\n", inet_ntoa(ClientAddr.sin_addr), AcceptSocket);

}else{ // That means that the socket is not from a new conection and has something sent.

memset(RecvBuff,0,sizeof(RecvBuff));
RecvBytes = recv(SocketArray, RecvBuff, sizeof(RecvBuff)-1, 0);

if(RecvBytes > 0) // Some data received.
{

printf("Message Send.\n");
printf("Message was: %s\n",RecvBuff);

for(k=0;k<=index-1;k++)
{
if(FD_ISSET(SocketArray[k],&BackUpfdwrite))
{
if(SocketArray[k] != ListeningSocket && SocketArray[k] != SocketArray)
{
memset(SentBuff,0,sizeof(SentBuff));
strcpy(SentBuff,RecvBuff);
SentBytes = send(SocketArray[k],SentBuff,sizeof(SentBuff),0);

}

}
}

}

}

}
}


}// Main loop ends here


}



Client:

#include <WinSock2.h>
#include <stdio.h>
#include <time.h>



main()
{

SOCKET ConnectSocket;
SOCKET SocketArray[20];

SOCKADDR_IN ServerAddr;

WSADATA wsaData;

FD_SET fdwrite;
FD_SET fdread;
FD_SET BackUpfdread;
FD_SET BackUpfdwrite;

char server_address[20] = "192.168.1.4";
char SentBuff[500];
char RecvBuff[500];

const unsigned short PORT = 4444;

int maxDescriptor;
int index = 0;
int SelectResults;
int i;
int RecvBytes;
int SentBytes;

struct timeval timeout;


// Initialize Winsock 2.2
WSAStartup(MAKEWORD(2,2),&wsaData);

// Initialize ServerAddr
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_addr.s_addr = inet_addr(server_address);
ServerAddr.sin_port = htons(PORT);

// Create a new socket to make a client connection.
ConnectSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

// Clear the fd sets
FD_ZERO(&fdread);
FD_ZERO(&BackUpfdread);
FD_ZERO(&fdwrite);
FD_ZERO(&BackUpfdwrite);

// Asign ConnectSocket into fdread and fdwrite.
FD_SET(ConnectSocket,&fdread);
FD_SET(ConnectSocket,&fdwrite);

// Set timer
timeout.tv_sec = 0;
timeout.tv_usec = 0;

maxDescriptor = ConnectSocket;
SocketArray[index] = ConnectSocket;
index++;

// Make a connection to the server with socket s.
if(connect(ConnectSocket, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
{
printf("Couldn't connect to the server\n");
}

// Main loop starts here
for(; ;)
{
BackUpfdread = fdread;
BackUpfdwrite = fdwrite;

memset(SentBuff, 0, sizeof(SentBuff));
printf("Write: ");
gets_s(SentBuff, sizeof(SentBuff));

SelectResults = select(maxDescriptor+1,&BackUpfdread,&BackUpfdwrite,NULL,&timeout);

for(i=0;i<=index-1;i++)
{
// Something to read from server.
if(FD_ISSET(SocketArray,&BackUpfdread) && SocketArray == ConnectSocket)
{
RecvBytes = recv(SocketArray, RecvBuff, sizeof(RecvBuff), 0);

if(RecvBytes > 0)
{
printf("%s\n",RecvBuff);
// Cleaning the Receive Buffer
memset(RecvBuff,0,sizeof(RecvBuff));
}

}

// Something to write.
if(FD_ISSET(SocketArray,&BackUpfdwrite) && SocketArray == ConnectSocket)
{
SentBytes = send(SocketArray, SentBuff,sizeof(SentBuff),0);
// Cleaning the Sent Buffer
memset(SentBuff,0,sizeof(SentBuff));

}


}

} // Main menu ends here

}



The problem is that i ve noticed that still there is a delay on message transfer. It looks like there are blocking sockets.
Here is a pic.

64047117.png

This topic is closed to new replies.

Advertisement