r3dux.org

A number-pimping side project from the valleys in *NEW* upside-down flavour.

  • Home
  • ABOUT
  • OLD SITE
  • SEARCH
  • FEEDBACK

A Simple C++/SDL_Net Chat Server & Client

r3dux | January 14, 2011

Update – Nov 2011: I’ve refactored this code into something significantly easier to work with, modify and extend – so you should probably try this instead: http://r3dux.org/2011/11/a-simple-csdl_net-chat-server-client-rewritten/.


I was due to be teaching some network programming in the new term, so I thought I’d try to find the simplest library for cross-platform socket programming and knock up some examples that I could teach from. The trouble is, there are lots of different socket libraries… Lots and lots. So I did a bit of investigating and came up with the following:

Library Good Points Bad Points
Boost ASIO
  • The real deal – a world-class, well documented socket library
  • Comprehensive – you can do absolutely anything with it
  • More complicated than I’d like for the level I’m teaching at – I’m not convinced they’ll get it
  • You need to include the boost library in each project, which makes each project big (like 60MB+ big)
C++ Sockets Library
  • Lots of documentation
  • Appears to be very solid from all accounts
  • I couldn’t get it to play ball!
SDL_net
  • Pretty small
  • Works without you having to jump through too many hoops
  • Decent level of abstraction without the need to mess about with too much C (as opposed to C++) stuff
  • Does not require SDL – SDL_net can be happily used standalone
  • Good documentation available at:
    http://www.libsdl.org/projects/SDL_net/docs/index.html
  • You do still need to transfer strings to char array pointers
  • Resolving hosts and IPs puts the details in Network Byte Order (i.e. Big Endian) numbers, which you then have to jump through hoops to retrieve as human-readable values, so you end up doing stuff like hacking a unsigned 32-bit number into an array of four 8-bit numbers to get the dot-quad IP address etc.
NetLink Socket Library
  • Small!
  • No documentation at all (that I could find), which could be because…
  • …netlink is already the name of a *nix socket mechanism which transfers data between kernel-space and user-space – why the hell would you name your C++ socket library with a name which is already used for a different type of socket communication? Fail.
SimpleSockets (now defunct)
  • ?!?
  • Surprisingly for a library called SimpleSockets, not that simple – lots of memset and memcpy stuff needed

In the end I chose SDL_net as teh winnah, as I’d already done some SDL stuff with the class previously (before switching to GLFW to minimise code-bloat), and I managed to get SDL_net up and communicating pretty easily. So, for the next couple of days I put together some simple client/server examples, culminating in the code you’ll find below, which is a (very) simple IRC-esque chat server.

Have a look – you can tell what it’s doing from the output in the windows:

SDL_net Client-Server Example

The server is in the middle, the clients connect in, and chat commences (click for larger, more legible version)

Although I’ve written this code in Linux, as SDL_net is cross-platform, this code should be cross platform – only there are two tweaks you’ll need to make for it to work in Windows: I’ve used some custom kbhit and getch functions to check for a keypress and read what key was pressed, if one was. These functions come natively with Windows, so you should use the native versions and strip the custom ones out.

With those small changes made this should work fine in Windows – but if it doesn’t please feel free to fix it yourself and send me the changes you made :)

As the code for all this is a couple of hundred lines for each for the client and server I’ve put all the source after the jump. Part of the reason it’s so large is that I’ve commented it to the hilt (no really, I’ve gone to town on it – even by my exceptionally verbose standards) as it was originally meant to be a teaching aid. Also, I’ve also left a lot of commented-out debug code in there, so you can uncomment it if you’d like to see exactly what’s going on behind the scenes.

Anyways, I hope this is of use to someone starting off socket programming with SDL_net – and if you have any issues or such please feel free to sling a comment in the article and I’ll do my best to help out.

Cheers!

SDL_net Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// SDL_net Server | r3dux.org | 14/01/2011
 
#include <iostream>
#include <cstdlib>
#include <string>
 
#include <SDL_net.h>
 
using namespace std;
 
const unsigned short PORT        = 1234;            // The port our server will listen for incoming connecions on
const unsigned short BUFFER_SIZE = 512;             // Size of our message buffer
const unsigned short MAX_SOCKETS = 4;               // Max number of sockets
const unsigned short MAX_CLIENTS = MAX_SOCKETS - 1; // Max number of clients in our socket set (-1 because server's listening socket takes the 1st socket in the set)
 
// Messages to send back to any connecting client to let them know if we can accept the connection or not
const string SERVER_NOT_FULL = "OK";
const string SERVER_FULL     = "FULL";
 
int main(int argc, char **argv)
{
    IPaddress serverIP;                  // The IP of the server (this will end up being 0.0.0.0 - which means roughly "any IP address")
    TCPsocket serverSocket;              // The server socket that clients will use to connect to us
    TCPsocket clientSocket[MAX_CLIENTS]; // An array of sockets for the clients, we don't include the server socket (it's specified separately in the line above)
    bool      socketIsFree[MAX_CLIENTS]; // An array of flags to keep track of which client sockets are free (so we know whether we can use the socket for a new client connection or not)
 
    char buffer[BUFFER_SIZE];            // Array of characters used to store the messages we receive
    int receivedByteCount = 0;           // A variable to keep track of how many bytes (i.e. characters) we need to read for any given incoming message i.e. the size of the incoming data
 
    int clientCount = 0;                 // Count of how many clients are currently connected to the server
 
    bool shutdownServer = false;         // Flag to control when to shut down the server
 
    // Initialise SDL_net (Note: We don't initialise or use normal SDL at all - only the SDL_net library!)
    if (SDLNet_Init() == -1)
    {
        cout << "Failed to intialise SDL_net: " << SDLNet_GetError() << endl;
        exit(-1); // Quit!
    }
 
    // Create the socket set with enough space to store our desired number of connections (i.e. sockets)
    SDLNet_SocketSet socketSet = SDLNet_AllocSocketSet(MAX_SOCKETS);
    if (socketSet == NULL)
    {
        cout << "Failed to allocate the socket set: " << SDLNet_GetError() << "\n";
        exit(-1); // Quit!
    }
    else
    {
        cout << "Allocated socket set with size:  " << MAX_SOCKETS << ", of which " << MAX_CLIENTS << " are availble for use by clients." <<  endl;
    }
 
    // Initialize all the client sockets (i.e. blank them ready for use!)
    for (int loop = 0; loop < MAX_CLIENTS; loop++)
    {
        clientSocket[loop] = NULL;
        socketIsFree[loop] = true; // Set all our sockets to be free (i.e. available for use for new client connections)
    }
 
    // Try to resolve the provided server hostname. If successful, this places the connection details in the serverIP object and creates a listening port on the provided port number
    // Note: Passing the second parameter as "NULL" means "make a listening port". SDLNet_ResolveHost returns one of two values: -1 if resolving failed, and 0 if resolving was successful
    int hostResolved = SDLNet_ResolveHost(&serverIP, NULL, PORT);
 
    if (hostResolved == -1)
    {
        cout << "Failed to resolve the server host: " << SDLNet_GetError() << endl;
    }
    else // If we resolved the host successfully, output the details
    {
        // Get our IP address in proper dot-quad format by breaking up the 32-bit unsigned host address and splitting it into an array of four 8-bit unsigned numbers...
        Uint8 * dotQuad = (Uint8*)&serverIP.host;
 
        //... and then outputting them cast to integers. Then read the last 16 bits of the serverIP object to get the port number
        cout << "Successfully resolved server host to IP: " << (unsigned short)dotQuad[0] << "." << (unsigned short)dotQuad[1] << "." << (unsigned short)dotQuad[2] << "." << (unsigned short)dotQuad[3];
        cout << " port " << SDLNet_Read16(&serverIP.port) << endl << endl;
    }
 
    // Try to open the server socket
    serverSocket = SDLNet_TCP_Open(&serverIP);
 
    if (!serverSocket)
    {
        cout << "Failed to open the server socket: " << SDLNet_GetError() << "\n";
        exit(-1);
    }
    else
    {
        cout << "Sucessfully created server socket." << endl;
    }
 
    // Add our server socket to the socket set
    SDLNet_TCP_AddSocket(socketSet, serverSocket);
 
    cout << "Awaiting clients..." << endl;
 
    // Main loop...
    do
    {
        // Check for activity on the entire socket set. The second parameter is the number of milliseconds to wait for.
        // For the wait-time, 0 means do not wait (high CPU!), -1 means wait for up to 49 days (no, really), and any other number is a number of milliseconds, i.e. 5000 means wait for 5 seconds
        int numActiveSockets = SDLNet_CheckSockets(socketSet, 0);
 
        if (numActiveSockets != 0)
        {
            cout << "There are currently " << numActiveSockets << " socket(s) with data to be processed." << endl;
        }
 
        // Check if our server socket has received any data
        // Note: SocketReady can only be called on a socket which is part of a set and that has CheckSockets called on it (the set, that is)
        // SDLNet_SocketRead returns non-zero for activity, and zero is returned for no activity. Which is a bit bass-ackwards IMHO, but there you go.
        int serverSocketActivity = SDLNet_SocketReady(serverSocket);
 
        // If there is activity on our server socket (i.e. a client has trasmitted data to us) then...
        if (serverSocketActivity != 0)
        {
            // If we have room for more clients...
            if (clientCount < MAX_CLIENTS)
            {
 
                // Find the first free socket in our array of client sockets
                int freeSpot = -99;
                for (int loop = 0; loop < MAX_CLIENTS; loop++)
                {
                    if (socketIsFree[loop] == true)
                    {
                        //cout << "Found a free spot at element: " << loop << endl;
                        socketIsFree[loop] = false; // Set the socket to be taken
                        freeSpot = loop;            // Keep the location to add our connection at that index in the array of client sockets
                        break;                      // Break out of the loop straight away
                    }
                }
 
                // ...accept the client connection and then...
                clientSocket[freeSpot] = SDLNet_TCP_Accept(serverSocket);
 
                // ...add the new client socket to the socket set (i.e. the list of sockets we check for activity)
                SDLNet_TCP_AddSocket(socketSet, clientSocket[freeSpot]);
 
                // Increase our client count
                clientCount++;
 
                // Send a message to the client saying "OK" to indicate the incoming connection has been accepted
                strcpy( buffer, SERVER_NOT_FULL.c_str() );
                int msgLength = strlen(buffer) + 1;
                SDLNet_TCP_Send(clientSocket[freeSpot], (void *)buffer, msgLength);
 
                cout << "Client connected. There are now " << clientCount << " client(s) connected." << endl << endl;
            }
            else // If we don't have room for new clients...
            {
                cout << "*** Maximum client count reached - rejecting client connection ***" << endl;
 
                // Accept the client connection to clear it from the incoming connections list
                TCPsocket tempSock = SDLNet_TCP_Accept(serverSocket);
 
                // Send a message to the client saying "FULL" to tell the client to go away
                strcpy( buffer, SERVER_FULL.c_str() );
                int msgLength = strlen(buffer) + 1;
                SDLNet_TCP_Send(tempSock, (void *)buffer, msgLength);
 
                // Shutdown, disconnect, and close the socket to the client
                SDLNet_TCP_Close(tempSock);
            }
 
        } // End of if server socket is has activity check
 
        // Loop to check all possible client sockets for activity
        for (int clientNumber = 0; clientNumber < MAX_CLIENTS; clientNumber++)
        {
            // If the socket is ready (i.e. it has data we can read)... (SDLNet_SocketReady returns non-zero if there is activity on the socket, and zero if there is no activity)
            int clientSocketActivity = SDLNet_SocketReady(clientSocket[clientNumber]);
 
            //cout << "Just checked client number " << clientNumber << " and received activity status is: " << clientSocketActivity << endl;
 
            // If there is any activity on the client socket...
            if (clientSocketActivity != 0)
            {
                // Check if the client socket has transmitted any data by reading from the socket and placing it in the buffer character array
                receivedByteCount = SDLNet_TCP_Recv(clientSocket[clientNumber], buffer, BUFFER_SIZE);
 
                // If there's activity, but we didn't read anything from the client socket, then the client has disconnected...
                if (receivedByteCount <= 0)
                {
                    //...so output a suitable message and then...
                    cout << "Client " << clientNumber << " disconnected." << endl << endl;
 
                    //... remove the socket from the socket set, then close and reset the socket ready for re-use and finally...
                    SDLNet_TCP_DelSocket(socketSet, clientSocket[clientNumber]);
                    SDLNet_TCP_Close(clientSocket[clientNumber]);
                    clientSocket[clientNumber] = NULL;
 
                    // ...free up their slot so it can be reused...
                    socketIsFree[clientNumber] = true;
 
                    // ...and decrement the count of connected clients.
                    clientCount--;
 
                    cout << "Server is now connected to: " << clientCount << " client(s)." << endl << endl;
                }
                else // If we read some data from the client socket...
                {
                    // Output the message the server received to the screen
                    cout << "Received: >>>> " << buffer << " from client number: " << clientNumber << endl;
 
                    // Send message to all other connected clients
                    int originatingClient = clientNumber;
 
                    for (int loop = 0; loop < MAX_CLIENTS; loop++)
                    {
                        // Send a message to the client saying "OK" to indicate the incoming connection has been accepted
                        //strcpy( buffer, SERVER_NOT_FULL.c_str() );
                        int msgLength = strlen(buffer) + 1;
 
                        // If the message length is more than 1 (i.e. client pressed enter without entering any other text), then
                        // send the message to all connected clients except the client who originated the message in the first place
                        if (msgLength > 1 && loop != originatingClient && socketIsFree[loop] == false)
                        {
                            cout << "Retransmitting message: " << buffer << " (" << msgLength << " bytes) to client number: " << loop << endl;
                            SDLNet_TCP_Send(clientSocket[loop], (void *)buffer, msgLength);
                        }
 
                    }
 
                    // If the client told us to shut down the server, then set the flag to get us out of the main loop and shut down
                    if ( strcmp(buffer, "shutdown") == 0 )
                    {
                        shutdownServer = true;
 
                        cout << "Disconnecting all clients and shutting down the server..." << endl << endl;
                    }
 
                }
 
            } // End of if client socket is active check
 
        } // End of server socket check sockets loop
 
    }
    while (shutdownServer == false); // End of main loop
 
    // Free our socket set (i.e. all the clients in our socket set)
    SDLNet_FreeSocketSet(socketSet);
 
    // Close our server socket, cleanup SDL_net and finish!
    SDLNet_TCP_Close(serverSocket);
 
    SDLNet_Quit();
 
    return 0;
}

Client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
// SDL_net Client | r3dux.org | 14/01/2011
 
// Includes for non-blocking keyboard input
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <termios.h> // If we do not include termios.h the client WILL compile but it WILL NOT WORK!
#include <unistd.h>
#include <fcntl.h>
 
#include <iostream>
#include <string>
 
#include <SDL_net.h>
 
using namespace std;
 
const unsigned short PORT        = 1234; // The port we are connecting to
const unsigned short BUFFER_SIZE = 512;  // Size of our message buffer (i.e. maximum length of characters in a message)
 
struct termios orig_termios;
 
// Function to reset the terminal to blocking mode
void reset_terminal_mode()
{
    tcsetattr(0, TCSANOW, &orig_termios);
}
 
// Sets the terminal mode to conio mode
void set_conio_terminal_mode()
{
    struct termios new_termios;
 
    // Take two copies - one for now, one for later
    tcgetattr(0, &orig_termios);
    memcpy(&new_termios, &orig_termios, sizeof(new_termios));
 
    // register cleanup handler, and set the new terminal mode
    //atexit(reset_terminal_mode); // Commented out because I switch and swap terminal modes a lot - just remember to call reset_terminal_mode() when we finish up
    cfmakeraw(&new_termios);
    tcsetattr(0, TCSANOW, &new_termios);
}
 
// Fuction to check if a key has been pressed
int kbHit()
{
    // How long to wait for input
    // Note: As soon as we get input the wait is immediately over - so it's not like our typing rate is limited in any way!
    long waitSeconds      = 1L;
    long waitMicroSeconds = 0L;
    struct timeval tv = { waitSeconds, waitMicroSeconds };
 
    // Create a file descriptor set
    fd_set fds;
 
    FD_SET(0, &fds);
    return select(1, &fds, NULL, NULL, &tv);
}
 
// Function to read the contents of the keypress
int getch()
{
    int r;
    unsigned char c;
    if ((r = read(0, &c, sizeof(c))) < 0)
    {
        //cout << "About to return a number..." << endl;
        return r;
    }
    else
    {
        //cout << "About to return a character..." << endl;
        return c;
    }
}
 
int main(int argc, char **argv)
{
    const char *host;         // Where we store the host name
 
    IPaddress serverIP;       // The IP we will connect to
    TCPsocket clientSocket;   // The socket to use
    string    serverName;     // The server name
 
    string userInput = "";    // A string to hold our user input
    int inputLength  = 0;     // The length of our string in characters
    char buffer[BUFFER_SIZE]; // Array of character's we'll use to transmit our message. We get input into the userInput string for ease of use, then just copy it to this character array and send it.
 
    // Initialise SDL_net
    if (SDLNet_Init() < 0)
    {
        cout << "Failed to intialise SDN_net: " << SDLNet_GetError() << "\n";
        exit(-1); // Quit!
    }
 
    // Ask the user for a server to connect to - can be entered as a hostname (i.e. localhost etc.) or an IP address (i.e. 127.0.0.1 etc.)
    cout << "Server Name: ";
    //getline(cin, serverName); // Uncomment this and remove the below line to change the server we're connecting to...
    serverName = "localhost";
 
    // Create the socket set with enough space to store our desired number of connections (i.e. sockets)
    SDLNet_SocketSet socketSet = SDLNet_AllocSocketSet(1);
    if (socketSet == NULL)
    {
        cout << "Failed to allocate the socket set: " << SDLNet_GetError() << "\n";
        exit(-1); // Quit!
    }
    else
    {
        cout << "Successfully allocated socket set." << endl;
    }
 
    // Try to resolve the host. If successful, this places the connection details in the serverIP object
    int hostResolved = SDLNet_ResolveHost(&serverIP, serverName.c_str(), PORT);
 
    if (hostResolved == -1)
    {
        cout << "Failed to resolve the server hostname: " << SDLNet_GetError() << "\nContinuing...\n";
    }
    else // If we successfully resolved the host then output the details
    {
        // Get our IP address in proper dot-quad format by breaking up the 32-bit unsigned host address and splitting it into an array of four 8-bit unsigned numbers...
        Uint8 * dotQuad = (Uint8*)&serverIP.host;
 
        //... and then outputting them cast to integers. Then read the last 16 bits of the serverIP object to get the port number
        cout << "Successfully resolved host to IP: " << (unsigned short)dotQuad[0] << "." << (unsigned short)dotQuad[1] << "." << (unsigned short)dotQuad[2] << "." << (unsigned short)dotQuad[3];
        cout << " port " << SDLNet_Read16(&serverIP.port) << endl << endl;
    }
 
    // Try to resolve the IP of the server, just for kicks
    if ((host = SDLNet_ResolveIP(&serverIP)) == NULL)
    {
        cout << "Failed to resolve the server IP address: " << SDLNet_GetError() << endl;
    }
    else
    {
        cout << "Successfully resolved IP to host: " << host << endl;
    }
 
    // Flag to keep track of when to disconnect and finish up. We initially set it so that we CANNOT connect, and only change this to false when we got an "OK" response from the server
    bool shutdownClient = true;
 
    // Try to open a connection to the server and quit out if we can't connect
    clientSocket = SDLNet_TCP_Open(&serverIP);
    if (!clientSocket)
    {
        cout << "Failed to open socket to server: " << SDLNet_GetError() << "\n";
        exit(-1);
    }
    else // If we successfully opened a connection then check for the server response to our connection
    {
        cout << "Connection okay, about to read connection status from the server..." << endl;
 
        // Add our socket to the socket set for polling
        SDLNet_TCP_AddSocket(socketSet, clientSocket);
 
        // Wait for up to five seconds for a response from the server
        // Note: If we don't check the socket set and WAIT for the response, we'll be checking before the server can respond, and it'll look as if the server sent us nothing back
        int activeSockets = SDLNet_CheckSockets(socketSet, 5000);
 
        cout << "There are " << activeSockets << " socket(s) with data on them at the moment." << endl;
 
        // Check if we got a response from the server
        int gotServerResponse = SDLNet_SocketReady(clientSocket);
 
        if (gotServerResponse != 0)
        {
            cout << "Got a response from the server... " << endl;
            int serverResponseByteCount = SDLNet_TCP_Recv(clientSocket, buffer, BUFFER_SIZE);
 
            cout << "Got the following from server: " << buffer << "(" << serverResponseByteCount << " bytes)" << endl;
 
            // We got an okay from the server, so we can join!
            if ( strcmp(buffer, "OK") == 0 )
            {
                // So set the flag to say we're not quitting out just yet
                shutdownClient = false;
 
                cout << "Joining server now..." << endl << endl;
            }
            else
            {
                cout << "Server is full... Terminating connection." << endl;
            }
        }
        else
        {
            cout << "No response from server..." << endl;
        }
 
    } // End of if we managed to open a connection to the server condition
 
    bool wrotePrompt = false; // Whether or not we've already written the prompt
    bool sendMessage = false; // Whether or not it's time to send the message (flips to true when the user presses return)
 
    // While it's not time to shutdown the client...
    while (shutdownClient == false)
    {
        // Write the prompt only once per line of input. This gets reset so that it's displayed again after a message is sent
        if (wrotePrompt == false)
        {
            cout << "Write something:" << endl;
            wrotePrompt = true;
        }
 
        // If we've detected that the user has pressed a key..
        set_conio_terminal_mode();
        int status = kbHit();
        reset_terminal_mode();
 
        //cout << "status is: " << status << endl;
 
        if (status != 0)
        {
            //cout << "key was pressed and status is" << status << endl;
 
            // Get the keypress
            set_conio_terminal_mode();
            char theChar = getch();
            reset_terminal_mode();
 
            // Output the character to stdout
            cout << theChar;
 
            // Flush the character to the screen
            fflush(stdout);
 
            // If the keypressed wasn't return then add the character to our message string
            if ((int)theChar != 13)
            {
                //cout << "Got the character: " << theChar << " (which is number: " << int(theChar) << ")" << endl;
 
                // Add the character to our input string
                userInput += theChar;
            }
            else // Otherwise (if the user pressed enter) then send the message
            {
                //cout << "user pressed return" << endl;
 
                // Copy our user's string into our char array called "buffer"
                strcpy( buffer, userInput.c_str() );
 
                // Calculate the length of our input and then add 1 (for the terminating character) to get the total number of characters we need to send
                inputLength = strlen(buffer) + 1;
 
                // Send the message to the server
                if (SDLNet_TCP_Send(clientSocket, (void *)buffer, inputLength) < inputLength)
                {
                    cout << "Failed to send message: " << SDLNet_GetError() << endl;
                    exit(-1);
                }
                else
                {
                    //cout << "Message sent successfully." << endl;
 
                    // If we've asked the server to shutdown or we want out then set the flag appropriately
                    if (sendMessage == true && (userInput == "quit" || userInput == "exit" || userInput == "shutdown"))
                    {
                        shutdownClient = true;
                    }
 
                    // Reset for the next message
                    cout << endl;
                    wrotePrompt = false;
                    sendMessage = false;
                    userInput = "";
                }
 
            } // End of message sending section
 
        } // End of if the user pressed a key test
 
        // Check our socket set for activity. Don't wait if there's nothing on the socket just continue
        int socketActive = SDLNet_CheckSockets(socketSet, 0);
 
        //cout << "Sockets with data on them at the moment: " << activeSockets << endl;
 
        if (socketActive != 0)
        {
            // Check if we got a response from the server
            int messageFromServer = SDLNet_SocketReady(clientSocket);
 
            if (messageFromServer != 0)
            {
                //cout << "Got a response from the server... " << endl;
                int serverResponseByteCount = SDLNet_TCP_Recv(clientSocket, buffer, BUFFER_SIZE);
 
                cout << "Received: " << buffer << endl;// "(" << serverResponseByteCount << " bytes)" << endl;
 
                if (strcmp(buffer, "shutdown") == 0)
                {
                    cout << "Server is going down. Disconnecting..." << endl;
                    shutdownClient = true;
                }
            }
            else
            {
                //cout << "No response from server..." << endl;
            }
 
        } // End of if socket has activity check
 
    } // End of main while loop
 
    // Close our socket, cleanup SDL_net, reset the terminal mode and finish!
    SDLNet_TCP_Close(clientSocket);
 
    SDLNet_Quit();
 
    reset_terminal_mode();
 
    return 0;
}

The Kicker: Soon after doing all this research and spending a few days (of my own time, unpaid) writing client/server code I found out that I’m not teaching any programming this year, and have instead been assigned Systems Analysis & Design and MS/Cisco Networking. Pah! I’m not going to call this time wasted though – I learnt a lot, and I hope that you might find this code of use too.. =D


Update – Nov 2011: I’ve refactored this code into something significantly easier to work with, modify and extend – so you should probably try this instead: http://r3dux.org/2011/11/a-simple-csdl_net-chat-server-client-rewritten/.

Related posts:

  1. How To: Use MySQL Connector/C++ to Connect to a MySQL Database in Windows
  2. How To: Create a Simple Fireworks Effect in OpenGL and SDL
  3. How To: Set up a FTP Server in Linux
  4. Mystify 2.0
  5. How To: Get Sound Working in Windows 7 64-Bit running in VirtualBox
Categories
Coding
Tags
ASIO, Boost, C++, network, Programming, SDL_net, Socket, Sockets
Comments rss
Comments rss
Trackback
Trackback
Print This Post Print This Post

« Four Tet – Smile Around The Face NASA Suck at PR – But Reid Gower Doesn’t »

46 Responses to “A Simple C++/SDL_Net Chat Server & Client”

  1. rsanchez says:
    January 25, 2011 at 5:46 am

    The fact that this was written to teach network programming makes this code a great resource for anyone learning SLD_net. Thanks!

    Reply
  2. assaf says:
    April 28, 2011 at 7:48 pm

    This is absolutely terrific and helpful. I’ve been looking for exactly that kind of messenger server example and this is by far the simplest.
    Thanks alot!

    Reply
  3. assaf says:
    April 28, 2011 at 11:40 pm

    Just one thing which bothers me and would be great if you can help with: you state that SDL_net doesn’t require SDL, but it does so at both Compile (“Where’s SDL.h?”) & Link (“Where’s SDL.dll?) time.
    They also mention it at . So what did I not get here? Am I using a different version or what? My version of SDL_Net is 1.2.7.

    Thanks.

    Reply
    • r3dux says:
      April 29, 2011 at 8:39 pm

      Hi Assaf,

      I’m not sure your comment came through the filters correctly (“they also mention it at .”??) – but at no point am I ever including SDL.h or linking to SDL.dll. SDL_Net is completely standalone from SDL as far as I’m aware. Where are you getting these “Where’s SDL.blah” statements from?

      I have no idea what version of SDL_Net I used – whichever was current at the time I wrote this post or a week or so earlier.

      Reply
  4. assaf says:
    May 3, 2011 at 6:55 pm

    Hi Al,

    Thanks for your reply.

    Sorry about the mess. Okay, “they also mention it at ” had the download page’s link after it, same as the one you gave in the libraries table above.
    In the 1.2.7 version (that’s where it’s currently pointing), SDL_net.h includes SDL.h.

    So, SDL_net is indeed coupled with SDL (in this version at least) , but only to utilize the SDL_Get/SetError() functions. The author also mentions that he wishes to disentangle the libraries in the future.

    So all one needs to do is to find a different solution for getting/setting errors and he can be SDL-free.

    Assaf.

    Reply
    • r3dux says:
      June 25, 2011 at 11:10 am

      Ah, I get ya. I’m not using SDL_Get/SetError functions, so that’s probably why I’m not seeing the dependency crop up.

      Cheers for the clarification :D

      Yup – you’re right. SDL_net requires the SDL library. I must’ve got my head in a twist about it.

      Reply
  5. Chaquito says:
    July 11, 2011 at 1:42 am

    Im having trouble understaindg where the types
    IPaddress and TCPsocket came from

    Reply
    • r3dux says:
      July 11, 2011 at 10:08 am

      IPaddress and TCPsocket are built in types provided by the SDL_net package.

      To compile the code you’ll need to link in the SDL and SDL_net libs, as well as include SDL_net.h – which is where these new types are defined.

      For example, inside SDL_net.h, you’ll find the following code (amongst other things):

      typedef struct {
      	Uint32 host;			/* 32-bit IPv4 host address */
      	Uint16 port;			/* 16-bit protocol port */
      } IPaddress;
      Reply
  6. Su says:
    October 25, 2011 at 11:57 pm

    im trying to compile this file in UBUNTU.. since im beginner. can u teach me how to compile this file.. thanks for helping..

    Reply
    • r3dux says:
      October 26, 2011 at 10:21 am

      Hi Su,

      I’m really short on time at the moment but will try to provide some instructions for you this evening or tomorrow.

      Regards,
      r3dux

      Reply
  7. Su says:
    October 26, 2011 at 2:24 pm

    thank u..never mind i will wait.. take your time.. :)

    Reply
  8. Su says:
    October 26, 2011 at 2:30 pm

    if u dont mind i have another question to ask.. how can this program be multiple client chat, do we need to define the port address on client’s computer. what about the server… need to do some change or not.. hehe.. sorry. so many question to ask..i really want to know how this program work..

    Reply
    • r3dux says:
      October 26, 2011 at 2:51 pm

      The program comes in two parts:
      - A chat server which can link multiple clients so they can chat, and
      - A client application which can connect to the server.

      The chat flow goes like this:
      1 – Run the server, which sits there waiting for incoming connections,
      2 – Run a client, which connects to the server. The client can send messages to the server, and the server then sends those messages to all connected clients.

      So, if you first run the server and leave it running, and then run one instance of the client, then the client connects to the server and can talk to it – but there’s no-one to talk to as there are no other clients!

      If you then run ANOTHER copy of the client (while the first client is still running), then there are now two clients connected to the server, and any message entered into any client will be broadcast (by the server) to all other clients.

      You can configure the number of clients that can connect to the server before the server will refuse any more connections, I think I set that to be a low number like 4 clients connected at once, but you can change it to be any number you like.

      When the server starts it opens up port 1234 to listen on, and it can listen to multiple connections on that port. When a client starts, it connects to port 1234 on (if I remember correctly) any available, valid socket i.e. the client picks a random port to listen/transmit on – which is connected to the server port 1234, and the server keeps track of all the ports of the connected clients.

      Will post up build instructions soon – hope this helps for now.

      -r3dux

      Reply
  9. Su says:
    October 26, 2011 at 4:44 pm

    now i understand.so the server can be the client at the same time right.. correct me if im wrong..

    Reply
    • r3dux says:
      October 26, 2011 at 5:20 pm

      Not quite…

      The server just sits in the background and listens for incoming connections. When it gets an incoming connection from a client, it adds the client to the list of active clients. The server then takes any messages that it receives from any client, and sends the message on to all the other connected clients in the list (if there are any).

      A client just connects to the server, and allows you to type some text which gets sent to the server. When the server receives that text, as mentioned above, it sends on the message to any other connected clients.

      That’s it! =D

      Also, I might as well give you the build instructions now I have a moment. To compile both the client and the server projects you need to link in the following libraries:

      - libSDL (on my system this means the file libSDL.a, which is in the package libsdl1.2-dev)

      - libSDL_net (on my system this means the file libSDL_net.a, which is in the package libsdl1.2-net-dev)

      - pthread (on my system this means the file libpthread.so.0 which should exist as a core part of your Ubuntu system – I’d imagine it’s in /usr/lib/ but I don’t run Ubuntu anymore so can’t guarantee it).

      With that done, the project should compile without issue.

      Let me know how you get on.

      Reply
  10. Su says:
    October 26, 2011 at 7:44 pm

    i will try as recommended and i will inform u later.. TQ very much

    Reply
  11. Su says:
    October 26, 2011 at 8:56 pm

    i already install the SDL and SDL_net lib in my UBUNTU but i dont know how to compile it.. i have try compile using compilation script on the internet but got error..

    Reply
    • r3dux says:
      October 27, 2011 at 8:47 am

      You’re making life difficult for yourself by compiling from the command line – it’s far easier to set libraries and paths in an IDE like Code::Blocks or such…

      Anyways, I looked into it and (assuming you have the server and clients called SDL-Net-Server.cpp and SDL-Net-Client.cpp), you can build them with the following commands:

      g++ -Wall `sdl-config --cflags --libs` -lpthread -lSDL -lSDL_net -o SDL-Net-Server ./SDL-Net-Server.cpp
      g++ -Wall `sdl-config --cflags --libs` -lpthread -lSDL -lSDL_net -o SDL-Net-Client ./SDL-Net-Client.cpp

      Where the generated executables will be called SDL-Net-Server and SDL-Net-Client respectively. Using the -l switch uses the shared object (.so) versions of the libraries, so the final executables won’t be standalone i.e. the system running them will need to have those same libraries available on the system to execute successfully, but this is good enough for our purposes.

      I’ve even put the files together into a zip for you with “build-server.sh” and “build-client.sh” scripts to do the commands for you – all you should have to do is make the build*.sh scripts executable with chmod +x build-server.sh and chmod +x build-client.sh – or, of course, you could just copy/paste/run the above commands one at a time (making sure that the .cpp files are in the same directory where you’re running the commands!).

      You can find the archive here

      I reckon you’ll be able to do it this time!

      Final note – when compiling the client it’ll moan about an unused variable: don’t worry about it, it’s just a warning (as opposed to an error), and I think the variable it’s complaining about IS used in the program when some of the debug comments are uncommented!).

      Cheers!

      Reply
  12. Su says:
    October 27, 2011 at 2:48 pm

    thank u very much… i able to execute the file and compile.. now i have 3 file in the folder client and folder sever.. which 2 file i got from u and another 1 file exist after i follow your instruction.. is this correct..?? :) im not sure… so how to run this file..

    Reply
  13. Su says:
    October 27, 2011 at 3:12 pm

    i can run it .. im so happy.. thanks for ur help.. i really appreciate it.. :)

    Reply
    • r3dux says:
      October 27, 2011 at 3:52 pm

      No worries, man – enjoy! =D

      Reply
  14. Su says:
    October 27, 2011 at 6:00 pm

    thanks again.. i don’t know how to say thank to u.. :) .. can i ask something.. if i want to increase the no of client.. then how.. do i need to change the code only or need to change something in build*.sh also

    Reply
  15. Su says:
    October 27, 2011 at 6:13 pm

    and how this application terminate…

    Reply
    • r3dux says:
      October 27, 2011 at 6:21 pm

      To increase the number of clients that can connect at once, you have to change the number of sockets available in the server at line 13:

      const unsigned short MAX_SOCKETS = 4;               // Max number of sockets

      And then you obviously have to recompile the server.

      The server itself takes up 1 socket, so if MAX_SOCKETS is 4, you can connect 3 clients – if MAX_SOCKETS is 10, you can connect 9 clients, etc.

      You can see this at the line 14 where it says:

      const unsigned short MAX_CLIENTS = MAX_SOCKETS - 1; // Max number of clients in our socket set...

      To shutdown the server, you have to enter “shutdown” into one of the clients connected to the server – on receiving the “shutdown” message, the server will… shut down.

      The shutdown flag is set on line 224 to 230, finally exiting the main loop at line 239.

      It’s all there in the source code – you just need to look.

      Reply
  16. Su says:
    October 27, 2011 at 6:56 pm

    then on client computer, do we need to compile the file again or we need to copy the application that already execute in my computer

    Reply
    • r3dux says:
      October 27, 2011 at 7:44 pm

      Any changes you make to ANY code have to be compiled/recompiled to produce the executable file (incorporating the changes) that you ‘run’.

      So if you change the server code, you have to recompile and run the new server. And if you change the client code, then you need to recompile and run the new client.

      If I understand you correctly, you just want to allow the server to accept more clients. To do this, you simply have to change the number of clients the server will accept on line 13 of the server code (as I’ve already shown you), and then recompile the server code to produce a new server executable which you can run, and then allow clients to connect to it.

      So you don’t compile the client again, but you will have to compile the server again.

      That is, UNLESS you want to run the server on one computer, and the clients on other (different) computers, in which case you’ll need to change the client source code so that lines 96 to 99 change FROM:

      // Ask the user for a server to connect to - can be entered as a hostname (i.e. localhost etc.) or an IP address (i.e. 127.0.0.1 etc.)
      cout << "Server Name: ";
      //getline(cin, serverName); // Uncomment this and remove the below line to change the server we're connecting to...
      serverName = "localhost";

      TO:

      // Ask the user for a server to connect to - can be entered as a hostname (i.e. localhost etc.) or an IP address (i.e. 127.0.0.1 etc.)
      cout << "Server Name: ";
      getline(cin, serverName);

      THEN:
      Recompile the client with those changes made.

      At which point, when you run a client, you’ll be asked for the server address (i.e. IP address) of the computer which is running the server software, instead of the client assuming localhost/127.0.0.1 as the address of the server.

      Reply
  17. Su says:
    October 27, 2011 at 10:45 pm

    correct.. that was what i want to do with your program.. brilliant. ..so now let me try first.. later i will ask u again if i have some problem. i need to find my friend to compile this program in other computer..

    Reply
  18. Su says:
    October 28, 2011 at 12:38 am

    can i run client in the same computer with server??

    Reply
  19. Su says:
    October 28, 2011 at 1:20 am

    i run the client prog on other computer but got this error
    Failed to open socket to server: Couldn’t connect to remote host
    i compile this file in my comp.. so is that problem

    Reply
    • r3dux says:
      October 28, 2011 at 8:44 am

      Is that after changing the client so that you have to specify the server address, and then specifying the correct IP address of server?

      There are many issues that could be going on here:
      - If the other computer and the server are not on the same network it won’t work by providing internal IPs, you’ll have to provide external IPs of the gateways to the server, and then port-forward the server port (1234) to the correct internal IP from the router…
      - Firewalls and proxies could be in the way,
      - You might not even have recompiled the client to allow for specifying an IP address for all I know..
      - Many, many other factors.

      I’m sorry, but you don’t have the technical skills to do this. I’m not even sure WHY you want to use this client/server code across networks – it’s not a fully functional chat client, it’s just an example of how to do the most basic possible client/server socket communication which I put together to teach teenagers some basic socket code.

      If you really want to run a peer-to-peer chat client (i.e. a chat client that doesn’t use a centralised server, as chat services like MS Live, YahooChat, ICQ, IRC etc DO use centralised servers), then might I suggest searching for “p2p chat client” – it’ll come up with things like:
      - PiChat,
      - Universal,
      - JabberD14 jabber (protocol) server with any jabber client of your choice.

      If you’re going to persist with using the client/server code provided, then you’ll have to find someone who can see your network and IP setup and advise from the ground – I cannot do that from here.

      Also, as mentioned, this is just example code I put together to teach from, and NOT an official, supported chat system.

      I’ve done as much as I can, and I think I’ve been very patient and answered all of your questions to the best of my ability – but now you’re on your own.

      Please do not contact me with any further questions regarding the source code – I am not a limitless fountain of free technical support. If you want to learn programming and networking, then go and learn programming and networking instead of relying on other people to solve each and every issue that you have.

      Best of luck.

      Reply
  20. Su says:
    October 28, 2011 at 10:41 am

    :) . orite.. thanks for ur reply.. i have learn a lot from u.. i will try my best to fix this thing.. thank u very much.. may god bless u..

    Reply
  21. Su says:
    October 28, 2011 at 11:07 am

    can i have your name to put in my reference together with your website address. if u don’t want to reveal your name here may be u can email me..anis152003@yahoo.com. thanks a lot ..sir/madam. actually i’m doing this for my Laboratory.. I’m university Student. I take electric telecommunication and this is my final year. Since i have only 3 week to complete this and i know nothing about socket programming and UBUNTU, so i just take your example. Hope u don’t mind

    Reply
  22. loh says:
    November 1, 2011 at 6:55 pm

    I’m quite interesting to the server- multiple client program..may I know if all client simultaneously click the Enter after type the message then the server cant detect which client sent, correct??

    May I know isn’t need to allocate different port to different client?? I’m new to ubuntu and chat programming..may I know which part of the above program need to modify in order using different port for each client??

    Thanks a lot for the help…
    Just wonder if using different port the program can work @@

    Reply
    • r3dux says:
      November 1, 2011 at 8:46 pm

      1 – The server CAN tell which client sent which message – it needs to know so that it can broadcast the message to all the other clients. You can even see in the server output that it says “Received [some message] from client [some client number]“.

      2 – Each client already has its own (unique) randomly assigned port. To see which ports the client(s) are using, try using the command:

      netstat --tcp | grep 1234

      This will find all tcp sockets which are currently open on the machine, and the “1234″ bit is the default port number of the server.

      If you want to assign specific ports to clients, then you’ll have to look up how to do that in the SDL_net documentation.

      Best of luck.

      Reply
  23. Su says:
    November 3, 2011 at 12:20 pm

    Sir, i have try this program using LAN and the program work great.. We can communicate with each other. but i have something to adjust. for this program, at client side we wouldn’t know the message is sent by which client. If we have multiple client, we are going to have a little bit problem to detect which client sent the message.Only the server know who is sending the message. But never mind. I will configure it out and will let you know the progress. Thanks a lot Sir/Teacher

    Reply
    • r3dux says:
      November 3, 2011 at 5:00 pm

      Easiest way to do this is to modify the client to ask for a username (i.e. who is using the client) and then just prepend that username to each message sent from the client.

      Reply
  24. Su says:
    November 14, 2011 at 2:29 am

    hi Sir, after looking at some example of C++ and socket programming, i already successful modify the client code to ask for user name and broadcast it together with the message.. The received message will be like this

    Received: My message :User Name /Client Name

    That is the best that i can do.. :)

    Thanks for the clue.. i really appreciate it..
    Cheers!

    Reply
  25. ComputerCowboy says:
    November 17, 2011 at 7:45 am

    this code was so well designed that i added sdl to it and made it a regular, non terminal code with minimal effort. I hope you do get teaching gigs cuz you are a good example.

    Reply
    • r3dux says:
      November 17, 2011 at 6:25 pm

      Thanks – and glad you’ve found it useful.

      I’d love to take a look sometime if you do anything cool with it! =D

      Reply
  26. A simple C++/SDL_net chat server & client rewritten | r3dux.org says:
    November 25, 2011 at 5:28 pm

    [...] deal with network programming with sockets and stuff, so I duly did my research and put together a simple chat server and client in SDL_net. And then my classes changed and I got moved on to teach other stuff. I wasn’t too [...]

    Reply
  27. Exiled says:
    June 13, 2012 at 3:15 pm

    Thank you so much for this tutorial! I am in the process of creating a multiplayer game. This tutorial was exactly what I needed to get used to SDL_net and get chat working. Once again, thanks!

    -Ex

    Reply
    • r3dux says:
      June 13, 2012 at 6:29 pm

      You’re very welcome – glad you found it useful! =D

      Reply
  28. YD says:
    December 21, 2012 at 4:32 pm

    This is a synchronous socket system right?
    Is there any way to create an asynchronous socket with SDL_net?
    Or at least a non-blocking send and receive function?

    Nice tut by the way.

    Reply
    • r3dux says:
      December 21, 2012 at 4:45 pm

      It’s all asynchronous and uses non-blocking input already.

      Reply
  29. Slider says:
    April 28, 2013 at 12:33 am

    Just a stupid question… How do you link SDL_Net?

    Reply
    • r3dux says:
      April 28, 2013 at 11:27 am

      In what operating system and IDE?

      Reply

Leave a Reply

Click here to cancel reply.

Translate

Categories

Archives

Tags

3D ActionScript ActionScript 3.0 Adobe AI Ballarat Bash C++ Class Cover CS4 Effect Error Film Flash FPS GLFW Glitch GLSL Hack How-To install Java Kinect Linux Live Mash-Up Microsoft Motion mount OpenGL Particle Problem PS3 Remix Retro script Slides Sound Ubuntu Video VirtualBox Wii Windows XBox

Gamercard

OpenR3dux

Misc.

Flattr this

RSS Feed

r3dux twitter feed



“Only a life lived for others is worth living.”

 - Albert Einstein

rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox