C++ console chat

Started by
2 comments, last by Drew_Benton 13 years, 3 months ago
Hi, I have just started using SDL_net and I'm making a chat program between two clients that connect to a server.

When the server starts, it waits until two clients connect. The server has a a vector of the chat history, acctually it's a vector <C>. Here's C:

struct C{	C(const string &_name, const string &_str, Uint32 _time) : name(_name), str(_str), time(_time)	{	}	bool operator < (const C &c)	{		return time < c.time;	}	string str, name;	Uint32 time;};


So it holds the name of the client, the string he/she sent and the time he/she sent it.

The C struct holds the time because in my code I receive data from the 1st client and then from the 2nd. But if the 2nd send a string earlier, I have to know the time he sent it.

This is what I do:
char buffer[100]; // buffer to read the string to	int sz; // size of the vector	Uint32 currentTime; // the current time	while( true )	{		// read data from both clients                // read from 1st		SDLNet_TCP_Recv(clients[0], &currentTime, sizeof(Uint32));		SDLNet_TCP_Recv(clients[0], buffer, 100);		vec.push_back(C(names[0], string(buffer), currentTime));                // read from 2nd		SDLNet_TCP_Recv(clients[1], &currentTime, sizeof(Uint32));		SDLNet_TCP_Recv(clients[1], buffer, 100);		vec.push_back(C(names[1], string(buffer), currentTime));		// sort the vector so the strings with earlier times come first		sort(vec.begin(), vec.end());                /* send data to client to draw the strings */


The problem is that each client has to enter his string and then the strings are drawn. Is this because it's a console chat? Is there a way to fix this? (get input and if a string has been received draw it and continue reading the string)

Also how is this usually done? Is the time when something happens sent as well?
Advertisement
You have another problem: You use fixed-size 100-byte buffers for the chat message, but the message may be shorter than that. Because TCP is a stream, and recv() will return "whatever is available" (even if it's only a single byte), you will get out of sync pretty quickly on the real Internet. You should prefix each string with the number of bytes in the string, and then recv() until you get exactly that many bytes, or an error/disconnect.

On your original question: How would you do any game where the user provides control input while other things are going on? You use polling (with non-blocking sockets), and/or time-outs (with something like select), and/or threads, and/or the operating system event system, if there is one.
enum Bool { True, False, FileNotFound };
Hello, thanks for you reply!

Quote:Original post by hplus0603
You should prefix each string with the number of bytes in the string, and then recv() until you get exactly that many bytes, or an error/disconnect.


Do you mean something like this?

size_t strSZ;... // read from 1st                                // read the time client sends data		SDLNet_TCP_Recv(clients[0], &currentTime, sizeof(Uint32));                // read the size of the string the client will send (the client will send the size by calling strlen()                SDLNet_TCP_Recv(clients[0], &strSz, sizeof(size_t));                // now read the string		SDLNet_TCP_Recv(clients[0], buffer, strSZ);		vec.push_back(C(names[0], string(buffer), currentTime));


Quote:Original post by sheep19
Do you mean something like this?

*** Source Snippet Removed ***


The idea is right, but the implementation is still flawed. Expanding on what hplus0603 mentioned, you may not actually get all of the bytes you ask for in TCP since it's a stream protocol. More specifically:
Quote:SDLNet_TCP_Recv gets available data that has been received on the socket. Up to maxlen bytes of data is read and stored in the location referenced by data. If no data has been received on the socket, this routine waits (blocks) until some comes in. The actual number of bytes copied into the location may be less than maxlen bytes.


So it is very possible that you won't actually get the full fields worth of data for each of your SDLNet_TCP_Recv calls. This means the data is incomplete and your program state becomes corrupted. Worse of all, such bugs can exist and never show themselves until you get into high latency situations or data processing slowdowns and then you are left wondering what is wrong with your program!

So what you really need to do is implement different stream processing logic for your program. Simply call SDLNet_TCP_Recv with a generic char buffer and save it to the client object's stream. After each of those reads, check to see if you have enough bytes to process each field. Once you do, then you can process each field and know when you have a complete message. This type of logic requires that you use some other SDLNet_ API functions though. In your current design, if a client happened to not send all of the data, your program will simply hang while it's waiting for data (assuming you aren't using threads). This is also in expansion to the 2nd part of hplus0603's response.

One more note, if you use size_t, then your 32bit/64bit server/client programs will (most likely) be incompatible with each other. That is because size_t is (usually at least) 4 bytes on 32-bit and (usually at least) 8 bytes on 64-bit. The packet data will be of different sizes in each case, so you should use a data type that will be the same size on both 32-bit and 64-bit to ensure you don't suffer from such issues when doing network programming (specifically portable types).

Lastly, the clients should not really be time stamping their own messages. In such a design, someone could fake the time stamps to make their messages appear at any time they wish. It really serves no purpose because you can just time stamp on the server when it actually receives the chat message. I.e., imagine what would happen if the government let everyone use their own postmarks on mail!

So to wrap up, you need to expand your use of the SDL_Net API to get polled/non-blocking socket operations as well as change up your TCP stream processing. Before you construct a string from the data and save it though, you should also be doing data validation so you don't run into any issues. For example, what if a client were to send a string of invalid characters?

I'm not sure if you have to use SDL_net, but you might want to consider checking into boost::asio as well. It certainly has a steeper learning curve and might take some extra work to get setup if you are new to boost, but it is well worth it. It helps solve a lot of TCP nuances for you through their API command set (i.e., they have functions that will wait for all TCP data per read and write so you don't have to mess with it yourself). best of all, they even have a chat example you can look through to get an idea of how it'd work (their example is not perfect, but it's just an example).

It may seem kind of overkill for such a small program, but if you are going to be getting into more complicated TCP network programs or plan on spending more time network programming, the time is well spent with boost::asio, in my opinion. [wink] Good luck!

This topic is closed to new replies.

Advertisement