Jump to content
  • Advertisement


This topic is now archived and is closed to further replies.


Winsock Sending Data Back To Clients?

This topic is 5329 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi all, This is the server part of a networking tutorial from gametutorials.com was just wondering if anyone could help me sort it out so that the data gets sent back to the relevant clients. I would really appreciate the help. Heres the code... /**************************************************** Networking Tutorials #5 Multiple Hosts - ServerSide by redKlyde ****************************************************/ // Things that will be covered in this tutorial: // Handling connections from more than 1 host // using select() // using file descriptor sets // A quick note on threading: // In order to accomplish the goals of this tutorial, it will be necessary to do some threading. // However, since it is not the aim of this tutorial to give an in depth education on threading, // we will have to do a little magical hand-waving at some point in the threading discussion. // Even though we aren''t going to TEACH threading, we should still cover the basics // so we have a clear understanding of what is going on and why, so here we go ... // Why should we have threads in the first place? Well there are 2 basic types of functions // when we talk about networking. They are either blocking or non-blocking. A blocking function is // one that will halt the program until the function completes, or "blocking" the program from // continuing. A non-blocking function is one that will, obviously, NOT halt the program but // let it run while the function completes. This information is important to some of our functions, // in particular, the accept() function. When we call accept(), the program will wait inside the // accept() function until it receives a connection from a user. Obviously, if we want to send and // receive information from users, we can''t do this if our program is waiting to accept a connection. So // we have to make a thread to accept connections for us, while we run the program and communicate // with the users. We have a similar problem with the recv() function. The recv() function will ALSO // block until it receives data from the specified socket. This is bad because what if we read from // a socket that hasn''t sent any data? Well, we will have to wait until data is sent before we can // continue with the rest of the program. Of course, we don''t want to do this, because especially when // we are making a game, we need to send and receive data very quickly so we can process our game // objects. This was not a problem in our earlier examples because the system of sending and receiving // was very well defined. From the client we ALWAYS sent our message and then received a confirmation // from the server. On the server, we ALWAYS received a message from the client and then sent // our confirmation. Not only that, but we only had one client, so it didn''t matter when the client sent // the data, there wasn''t another client waiting for service. // How on earth are we going to fix these problems? Well, with a couple different solutions. Keep in // mind that again, this is not the ONLY way to get around these issues, but it is a simple, and effective // way to create a server application. The first thing we are going to do is create a thread to // accept connections. The second thing we will do is utilize the select() function to find out which // sockets already have data on them. This way we will not block while we wait to receive data // with recv(). Instead, the data will already be there. // Let us talk about threading first. // What is a thread anyway? In simple terms, a thread is a function that we can run at the same time // as another function. What we will do, is create a procedure (aka. a function) that will handle accepting // new connections, run this function as a thread (so it happens at the same time, or in other terms, so it // runs asynchronously), and our main program will handle sending and receiving to all the clients that // our thread accepts. // Now on to select(). // How does select() work to handle the blocking recv() function? What select() will do is create for us // a list of sockets that already have data to read. We will have to create a "socket set" that has // all our client sockets, and "select" from that set all the sockets with data ready. This way, when we // call recv(), we are guaranteed that we will receive data immediately, and we will not have to block // until data is sent. // That''s probably enough explanation for now, lets dig into the code. We start as usual with all the // proper setup. // winsock for our network communication #include <winsock2.h> // stdio for I/O #include <stdio.h> // ----------------------------------------------------------------------------------- // startupServerForListening() - will return us a socket that is bound to the // port we specify, and is listening for connections if all operations succeeded, // and a -1 if startup failed. int startupServerForListening(unsigned short port); // ----------------------------------------------------------------------------------- // shutdownServer() - will take the socket we specify and shutdown the // network utilities we started with startupServerForListening() // Note: In order to function properly, the socket passed in MUST be the // socket created by the startupServerForListening() function void shutdownServer(int socket); // Note: When programming multi-threaded applications, it is necessary to tell the compiler that we // are using more than one thread. This is so the compiler can chose certain functions that are said // to be "thread-safe". Some functions that we commonly use are not designed to work with more than // one thread. Calling these functions would cause some unexpected results, or even some serious // problems. So be certain to always set the libraries to be thread-safe. Do this by clicking Project/Settings. // Go to the C++ Tab. Change the category to "Code Generation". Change the "Use run-time library" // drop down box to "Debug Multithreaded", and you are ready to go. // Normally, after our definitions and includes we would jump right into the main() and start setting // up the network, but this time we will explore the acceptance thread first, since it is the most new // concept to our architecture. // First some definitions. // For threading, we will need a thread handle. We will receive this handle when we create the thread // and can use this handle any time we need to reference our thread. HANDLE threadHandle; // The beauty of threading is that both of our threads will have access to the same memory locations. // But this is dangerous because we could both potentially attempt to read or write to that memory at // the same time! So we need some way to protect that memory so that only one thread at a time // can control it. For this, we need a mutex. A mutex is a structure we can lock and unlock any time // we write to a shared section of memory. No other thread can lock our mutex while we have it locked, // and our thread will block until we gain access to the mutex. // a mutex for my shared data HANDLE mutexHandle; // Finally the file descriptor set (FD_SET). A FD_SET is simply our list of sockets. // my master socket set which will be protected by my mutex FD_SET masterSet; // A FD_SET has data members: // - fd_count - the number of file descriptors in the set // - fd_array - the array of file descriptors // There are a few convenient macros we can use to manipulate the set. // - FD_SET(a socket, FD_SET to manipulate) - This macro will add a socket to a set // - FD_ZERO(FD_SET to manipulate) - This macro will zero a set // - FD_CLR(a socket, FD_SET to manipulate) - This macro will remove a socket from a set // - FD_ISSET(a socket, FD_SET to manipulate) - This macro will check to see if a socket is in a set // A quit flag I will use to exit my program with if something goes wrong bool gQuitFlag = false; // Now that we have everything defined that we will need, lets look at the acceptance thread procedure. // The sole responsibility of this function is to accept connections from our clients, and add them to the // masterSet. This way our main thread can be free to send and receive data without blocking during // the accept call. // ----------------------------------------------------------------------------------- // acceptingThreadProcedure() - a procedure that will accept clients and add their // sockets to the master file descriptor set void acceptingThreadProcedure(int* serverSocket) { // copy my socket over to a local variable int mySocket = *serverSocket; // run forever for (; { // accept a client like normal unsigned int clientSocket = accept(mySocket, 0, 0); // make sure things are ok if (clientSocket == SOCKET_ERROR) { // just stop of we received any errors printf("Accept Failed!\n"); // signal to quit the application gQuitFlag = true; // and quit this thread return; } else { // Everything went well. We need to take the client socket we received, and add // it to our master socket set so the other thread can use it. Remember though, // since our masterSet can be accessed by both threads, we need to make sure // that we are not trying to write to it in this thread while at the same time we are trying // to read from it in the other. We need to lock our mutex. We do this with the function // WaitForSingleObject(). We need to give WaitForSingleObject() our mutex handle // and a time value to wait. We will use INFINITE for the time value, because there // really isn''t anything else our thread needs to do. // lock the mutex WaitForSingleObject(mutexHandle, INFINITE); // add this socket to the master set using our FD_SET() macro FD_SET(clientSocket, &masterSet); // Now we have to unlock our mutex so that our main thread can also access the data // To be absolutely clear on this, here is how it works. When we call WaitForSingleObject() // we have locked the data. We have control of the mutex until we call ReleaseMutex(). // If the main thread were to call WaitForSingleObject() before we call ReleaseMutex(), // WaitForSingleObject() will block until we unlock the mutex. At that point, WaitForSingleObject() // would immediately get a lock on the mutex, and the main thread would be able to continue. // So always lock the mutex, write/read the data, and then unlock the mutex. This will make // everyone trying to access the data very happy. // unlock the mutex ReleaseMutex(mutexHandle); // a quick message printf("client on %d connected\n", clientSocket); } } } // Well, that''s all our acceptance thread needs to do. Once we start the thread, we will begin accepting // clients and adding them to the masterSet. // Now to write our main(), we will need to startup the network like normal, start our acceptance thread // and then send and receive data from our clients. Since we are servicing multiple clients, we will have // to create a loop and continuously check our network. void main() { printf("Welcome to redKlyde''s Networking Tutorials!\n"); printf("Tutorial # 5 : Multiple Hosts - ServerSide\n\n"); // Startup our network as usual. // the socket my server will use for listening int serverSocket; // startup my network utilities with my handy functions serverSocket = startupServerForListening(7654); // check for errors if (serverSocket == -1) { printf("Network Startup Failed!\nProgram Terminating\n"); return; } // And now ... the magical hand waving act. It would be great if this were a REAL threading tutorial, // but unfortunately it isn''t. So, at this point we will just have to say that this stuff works. We will // create our mutex and our thread and receive handles for both. // create the mutex mutexHandle = CreateMutex(NULL, false, NULL); if (mutexHandle == NULL) { printf("Error creating mutex\n"); shutdownServer(serverSocket); return; } // create the thread int threadId; threadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)acceptingThreadProcedure, &serverSocket, 0, (LPDWORD)&threadId); if (threadHandle == NULL) { printf("Could not start acceptance thread\n"); shutdownServer(serverSocket); return; } // sleep the main() so the acceptance thread has time to start ... this is cheesy Sleep(100); // Now that that is all over with, it''s down to business. // It''s always a good idea to initialize our structures before we access them, so let''s zero our masterSet. FD_ZERO(&masterSet); // the main loop ... run forever for (; { if (gQuitFlag) { break; } // Now the fun part. At this point we have our masterSet that contains a list of all our client sockets. // We need to look in that set for clients that have data, and then read their data. This means we have // to access our masterSet that is, again, ALSO used by our acceptance thread. Which means we have // to use our mutex, so let''s get a lock on our mutex so we can properly access masterSet. // lock the mutex WaitForSingleObject(mutexHandle, INFINITE); // Instead of reading directly from masterSet we are going to make a copy of it instead. // This will keep us from having to lock the mutex for the entire duration of the loop. Remember, // as long as we are accessing masterSet, we need to keep the data locked. So we will need a temporary // set to use with the select call, we will call this the polling set. // make the polling set and copy everything from masterSet FD_SET pollingSet = masterSet; // unlock the mutex ReleaseMutex(mutexHandle); // Here is an interesting scenario. If we were to select() from a set with count 0, we would actually // generate an error. We could use WSAGetLastError() and handle the error properly, but it is easier // to avoid the situation entirely. We can do this by NOT calling select() if the set was empty. So check // the count, and just skip the select if our set is empty. // check if our set is empty if (pollingSet.fd_count == 0) { continue; } // Now it is time to use select(). We have a set that contains all of our socket, and that set is not empty. // Select() will actually alter the set we give it, and remove any sockets that do NOT have data ready. For // that reason, we always want to use the copy of the master set when we use select. If not, we will lose // all the sockets that did not have data read, which is never a good thing. // select() has 5 parameters. They are: // - the number of file descriptors // - a FD_SET to check for readability // - a FD_SET to check for writeability // - a FD_SET to check for errors // - a wait time // The wait time is a timeval structure. You can set the number of seconds and microseconds for select // to wait before it times out. This can be useful in specialized circumstances, but we will just make it 0 // so it times out immediately and returns. // the wait time timeval waitTime; waitTime.tv_sec = 0; waitTime.tv_usec = 0; // and select(). select from the polling set using fd_count as the number of sockets. We do not have a // write set nor an errors set, so just pass NULL for them. int result = select(pollingSet.fd_count, &pollingSet, NULL, NULL, &waitTime); // The return value for select() is the number of sockets that have data ready to read. Like most our // networking function, if an error occurred, this value will be SOCKET_ERROR, otherwise it will indicate // success. // But select() can return 0 if there was no data to be read. In this case, just continue on with the // rest of the program. // check for no sockets with data if (result == 0) { // no sockets have data continue; } // check for errors if (result == SOCKET_ERROR) { printf("Error in select()\n"); continue; } // Now that we have the polling set that contains just the sockets that have data, let''s step through // the list of sockets and read from them all. // for every socket in my polling set for (unsigned int i = 0; i < pollingSet.fd_count; i++) { // We can access the socket list directly using the fd_array member of the FD_SET unsigned int clientSocket = pollingSet.fd_array; // We will be using the same variable length data system that we implemented in tutorial #4. // So we need a few variables to facilitate the communication. // the number of bytes we received int nBytes; // a buffer to hold my data #define MAX_MESSAGE_SIZE 4096 char buffer[MAX_MESSAGE_SIZE]; // the size of the message that is being sent unsigned long messageSize; // receive the message size first nBytes = recv(clientSocket, (char*)&messageSize, sizeof(messageSize), 0); // check for errors if (nBytes == SOCKET_ERROR) { // Uh Oh! We have our first real use of error handling! Something can happen here that is // pretty significant. Let''s grab the error number from winsock. int error = WSAGetLastError(); // The error we got should be WSAECONNRESET. This error says that the connection was either // closed or somehow reset from the other end. That means that our client has shutdown his // application. We have 2 real ways of dealing with closing connections. One is to send a // disconnect message from the client to the server. The other is to handle the dropped // connection error. The disconnect message is something that we would have to write ourselves. // We would somehow make an identifier message that tells our server that we are disconnecting, // that way we can handle it properly. That will work assuming that all our connections are closed // cleanly (like they are supposed to). But what if someone just shuts down their application, or // looses power to their computer. They can''t send a message if their computer isn''t on! // So we still need to check for the error condition. // handle the dropped connection if (error == WSAECONNRESET) { // When we receive this error. Just get a lock on the master set, and remove this socket from // set using the FD_CLR() macro. // lock our mutex WaitForSingleObject(mutexHandle, INFINITE); // remove the socket from our master set FD_CLR(clientSocket, &masterSet); // unlock our mutex ReleaseMutex(mutexHandle); // close the socket on our side, so our computer cleans up properly closesocket(clientSocket); // a quick message printf("client on %d disconnected\n", clientSocket); // move on to the next client continue; } else { // we failed, but it wasn''t an error we were expecting ... so kill the server printf("Recv Failed!\n"); gQuitFlag = true; break; } } // We have already handled the unexpected disconnect from a client, but what about the first case we // talked about, the "clean disconnect"? Well in the case of the clean disconnect (the client called closesocket()), // we will NOT generate an error. Instead when we read from the socket, it will return us 0 bytes. // When this happens we should simply handle it just like we did the disconnect, remove the socket from our // master set, and continue. if (nBytes == 0) { // lock our mutex WaitForSingleObject(mutexHandle, INFINITE); // remove the socket from our master set FD_CLR(clientSocket, &masterSet); // unlock our mutex ReleaseMutex(mutexHandle); // close the socket on our side, so our computer cleans up properly closesocket(clientSocket); // a quick message printf("client on %d disconnected\n", clientSocket); // move on to the next client continue; } // At this point, the rest is just like tutorial #4. Just receive the rest of the message and print it out. // convert the message size to host ordering messageSize = ntohl(messageSize); // receive the reset of the message nBytes = recv(clientSocket, buffer, messageSize, 0); // check for error if (nBytes == SOCKET_ERROR) { printf("Recv Failed!\n"); gQuitFlag = true; break; } // remember this is a string, so terminate the buffer so we can print it buffer[messageSize] = ''\0''; // print the message printf("Message Received from client on %d : %s\n", clientSocket, buffer); } } // cleanup shutdownServer(serverSocket); printf("Press Enter to Exit ...\n"); getchar(); // That''s just about it. You now have all the tools you need to handle connections from multiple // hosts and a send and receive data between them and the server. This also concludes the basic // networking tutorials. Part 2 of this tutorial does not cover any new material. It has some mild // changes to allow clients to send information they type in, but no new material is covered. So, // browse through it if you like; otherwise, I hope these tutorials have been informative enough about // networking and helpful enough with code examples that you may have many joys in experimenting, // and a little less pains than the rest of us. Thanks for enduring my ramblings, and best of luck! // - redklyde } // ----------------------------------------------------------------------------------- // startupServerForListening() - a function to startup winsock, and open a socket for listening int startupServerForListening(unsigned short port) { // an error code we will use to get more information about our errors int error; // the winsock data structure WSAData wsaData; // startup winsock if ((error = WSAStartup(MAKEWORD(2, 2), &wsaData)) == SOCKET_ERROR) { printf("Could Not Start Up Winsock!\n"); return -1; } // create my socket int mySocket = socket(AF_INET, SOCK_STREAM, 0); // make sure nothing bad happened if (mySocket == SOCKET_ERROR) { printf("Error Opening Socket!\n"); return -1; } // the address structure struct sockaddr_in server; // fill the address structure with appropriate data server.sin_family = AF_INET; server.sin_port = htons(port); server.sin_addr.s_addr = INADDR_ANY; // and now bind my socket if (bind(mySocket, (sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) { printf("Bind Failed!\n"); closesocket(mySocket); return -1; } // mark my socket for listening if (listen(mySocket, 5) == SOCKET_ERROR) { printf("Listen Failed!\n"); closesocket(mySocket); return -1; } printf("Server Started\n"); return mySocket; } // ----------------------------------------------------------------------------------- // shutdownServer() - a function to shutdown a socket and clean up winsock void shutdownServer(int socket) { // kill my thread and my handle WaitForSingleObject(threadHandle, INFINITE); CloseHandle(threadHandle); CloseHandle(mutexHandle); // close our socket closesocket(socket); // shut down winsock WSACleanup(); printf("Server Shutdown\n"); }

Share this post

Link to post
Share on other sites

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!