For those who have written a multi socket TCP/IP server, did you use multithreading (one thread per listen/accepting socket)?
For those who have written a multi socket TCP/IP server, did you use multithreading (one thread per listen/accepting socket)?
No, one thread per socket is a bad idea.
For networking beginners, in C++, the standard way to handle multiple connections is via the select() function. This tells you which of your sockets is ready right now, so you can iterate over those and send or receive without having to wait. An old but classic tutorial on this is in Beej's Guide to Network Programming: https://beej.us/guide/bgnet/html/multi/advanced.html
There are more efficient ways to handle large numbers of sockets, most of which are platform-dependent or involve a 3rd party library, but starting with select is probably wisest.
I was familiar with the use of the select() function when it comes to recv'ing data. So, do I also use select() to identify when a socket is to be accepted?
Is it wise to use ioctlsocket to set the socket(s) as non-blocking, and handling the WSAEWOULDBLOCK explicitly?
31 minutes ago, cowcow said:So, do I also use select() to identify when a socket is to be accepted?
Yes. select() works for accepting sockets too. There are a lot of basic select server examples that demonstrate this.
14 minutes ago, cowcow said:Is it wise to use ioctlsocket to set the socket(s) as non-blocking, and handling the WSAEWOULDBLOCK explicitly?
That's generally not necessary. Since select() tells you which sockets are ready to read, it's usually fine to perform blocking reads.
Moving to fully non-blocking I/O can help with certain performance/scale situations, but it also significantly increases the complexity of your server.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
49 minutes ago, swiftcoder said:Since select() tells you which sockets are ready to read, it's usually fine to perform blocking reads.
I feel that I should amend this statement slightly.
You also need to use select() to determine when it is safe to write if you are using blocking I/O. Otherwise a malicious client can denial-of-service you just by reading incredibly slowly with a very small TCP buffer.
While blocking I/O is simpler for the general cases, in a production service where you don't also control the client, it's conceptually easier to make a non-blocking service resilient to a variety of external factors. Up to you which route you want to go down.
Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]
I'm starting to consider rewriting my C# server in C++ and I'm thinking about using Boost.Asio for platform independence, since I'm developing on Windows but will probaby want to host the server on Linux.
I believe it uses iocp on windows and epoll on linux.
Developer journal: Multiplayer RPG dev diary
Those are correct assumptions; boost::asio is efficient and portable on both Windows and Linux.
It's not the greatest at everything, and sometimes annoying to use, but it's pretty solid overall.
When I was designing my server that inquiry also comes into mind, which implementation is better to handle multiple clients? I ended up implementing it all to test which one is better, you could have a separate scheme for accepting and receiving ^ _ ^y
m_ServerConfig.ReceiverMode = SReceiverMode.TcpThreadPollOnly;
TcpThreadPollIOCP
TcpThreadPollOVERLAPPED
TcpThreadSelectOnly
TcpThreadSelectIOCP
TcpThreadSelectOVERLAPPED
TcpThreadPerPlayer
TcpAsyncIOCPPerPlayer
TcpAsyncOVERLAPPEDPerPlayer
UdpThreadOnly
UdpThreadIOCP
UdpThreadOVERLAPPED
UdpAsyncIOCP
UdpAsyncOVERLLAPED
m_ServerConfig.AcceptorMode = SAcceptorMode.TcpAsyncIOCP;
TcpAsyncOverlapped;
TcpSynchronous;
UsingUDP;