1

I am working on a simple 1v1 (realtime) brick breaker game, in order to improve my programming skills. It is based on the server-client model and I am using <winsock2.h>, <thread> and <mutex> libraries for implementation. On the client side, I would like to call the send() and recv() functions from different threads (send() from the main() thread, while recv() from another one). I've realized, it would be the best if the recv() function was blocking, while send() non-blocking. I've heard that creating a socket like this in winsock2 is not possible. However, I came up with an idea and would like to know, if it is a bad practice: I connect to the server with a blocking socket. And when an important user event happens, I create a detached thread which calls the sender function (that hands the user's event to the server), instead of calling the sender method from the main thread.

With std::mutex, I would be able to guarantee that only one sender method is working at a time. Example:

#include <mutex>
#include <string>

class Connection
{
   std::mutex senderMutex
   // other member variables

   void sendMessage(std::string& msg)
   {
      std::lock_guard<std::mutex> lg(senderMutex);
      // some code     
   }

   void sendUserAction(Action& action)
   {
      std::lock_guard<std::mutex> lg(senderMutex);
      // some code
   }

   // other member functions
};

From the main thread:

if (userEventHappened)
{
   switch(userEvent)
   {
   case ChatMessage:
       std::thread(&Connection::sendMessage, &connection /* an instance of Connection*/, std::ref(message)).detach();
       break;
   case UserAction:
       std::thread(&Connection::sendUserAction, &connection, std::ref(action)).detach();
       break;
   }
}

Since it is a brick breaker game where the player have to move its own paddle, there are many user actions, thus too many threads can be created in a short time. I'm curious, if it is dangerous and/or a bad practice to call the send() methods from detached threads, or is there a better solution. It's also not clear to me, whether it causes performance issues that most of the threads are blocked by senderMutex.

  • Look into `select` and Overlapped IO. You can use either of them to eliminate threading, at least from the communication side of things, completely. – user4581301 Jul 24 '17 at 17:52
  • 1
    This doesn't make sense. If there is a thread doing blocking reads, the socket is in blocking mode, so it can't simultaneously be in non-blocking mode. – user207421 Jul 24 '17 at 18:03
  • "I've realized, it would be the best if the recv() function was blocking, while send() non-blocking. " no, it would be best if both were non-blocking. You shouldn't be using threads at all. – xaxxon Jul 24 '17 at 18:04
  • If you are starting from scratch, which it seems like you are then you really should look at using a library that wraps the complexity of winsock. Maybe something like http://www.boost.org/doc/libs/1_64_0/doc/html/boost_asio.html. Or look at this question for other libraries - https://stackoverflow.com/questions/118945/best-c-c-network-library – Rob Kreger Jul 24 '17 at 18:09
  • @user4581301 - Thank you, I will look it up. – Gergely Tomcsányi Jul 24 '17 at 18:22
  • @EJP - You're right, it cant be in both modes. I was just trying to imitate the behaviour which I need. – Gergely Tomcsányi Jul 24 '17 at 18:22
  • @xaxxon - You mean that I wouldnt even need a thread for a Connection::listenServer method which accepts and handles the user-independent events coming from the server? For example the actions of the opponent or messages sent by other players. – Gergely Tomcsányi Jul 24 '17 at 18:22
  • @RobKreger - Thanks, but I already have some code, and I think I've found the libraries which I can work with. – Gergely Tomcsányi Jul 24 '17 at 18:30
  • @GergelyTomcsányi no, you don't need additional threads for any of it. The only time you need additional threads is if you need to spawn off jobs that require a large amount of CPU time in order to complete - and even then it should still by async with a threadpool, not a 1:1 between connections and threads – xaxxon Jul 24 '17 at 18:46
  • @xaxxon - Sounds great. Well, I priorize eliminating threads, so I will learn more about non-blocking sockets. Thanks! – Gergely Tomcsányi Jul 24 '17 at 18:58
  • basically you call select https://msdn.microsoft.com/en-us/library/windows/desktop/ms740141%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 When it says you have data to read, you read it and concat the data into a read buffer and then see if that buffer contains enough data for you to operate on (remember, TCP is a byte stream, no guarantees on how much data you'll get at once). When you want to write, you append the data onto a write buffer then when select tells you you can write, you attempt to write the contents of that buffer and track how much was successfully written. – xaxxon Jul 24 '17 at 19:29
  • Things are a little simpler if you're using UDP (since you deal with entire blocks at once), but in general, the concepts are quite similar. – xaxxon Jul 24 '17 at 19:30
  • @xaxxon - Thanks again! I've found great tutorials about FD_SET and functions (like select) which work on it. I like how it saves the client (and also the server!) from multithreading. – Gergely Tomcsányi Jul 24 '17 at 21:10
  • @GergelyTomcsányi while you're learning, this is good stuff to do. Once you're ready to do "real" network programming, never do this stuff on your own. Too many edge cases to get wrong. Use ASIO for production code -- but learning what's going on by doing it yourself first is important. – xaxxon Jul 24 '17 at 21:22
  • Also, for a breakout game, you would want to be using UDP, not TCP for sending game position data (presumably the other player's position), since you have no interest in older data if newer data is available. Just throw in an incrementing packet iD at the beginning of each packet and toss it out if it's lower than a packet you've previously handled. – xaxxon Jul 24 '17 at 21:27

0 Answers0