I am learning Socket Programming to make a chat room.
I know that I could use async socket such as
listenFd.BeginAccept(AcceptCallback, listenFd);
Also I could use
Socket.Select(checkRead,null,null,1000);
I know the basic meaning of what async
and select
does.
However, I don't know in what scenario one should be better than the other.
Edit:
Actually I was following a tutorial. It said using select is better than async because the logic is more clear.
Here are two examples:
The one use select:
namespace Server
{
class App
{
static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
static string ipAddr="127.0.0.1";
static int port=8888;
static void Main(string[] args)
{
Socket listenFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse(ipAddr);
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, port);
listenFd.Bind(iPEndPoint);
listenFd.Listen(0);
Console.WriteLine("Server start!");
List<Socket>checkRead=new List<Socket>();
while(true)
{
checkRead.Clear();
checkRead.Add(listenFd);
foreach(var clientState in clients.Values)
{
checkRead.Add(clientState.socket);
}
Socket.Select(checkRead,null,null,1000);
foreach(var socket in checkRead)
{
if(socket==listenFd)
{
ReadListenfd(socket);
}
else
{
ReadClientfd(socket);
}
}
}
}
public static void ReadListenfd(Socket listenfd)
{
Console.WriteLine("Accept");
Socket clientfd=listenfd.Accept();
ClientState state=new ClientState();
state.socket=clientfd;
clients.Add(clientfd,state);
}
public static bool ReadClientfd(Socket clientfd)
{
ClientState state=clients[clientfd];
int count=0;
try
{
count=clientfd.Receive(state.readBuff);
}
catch(SocketException ex)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine($"Receive Socket Exception {ex.ToString()}");
return false;
}
if(count==0)
{
clientfd.Close();
clients.Remove(clientfd);
Console.WriteLine("Socket close");
return false;
}
string recvStr=System.Text.Encoding.Default.GetString(state.readBuff,0,count);
Console.WriteLine($"Rec {recvStr}");
string strFromClientWithTime= DateTime.Now.ToString("hh:mm")+recvStr;
byte[]sendBytes=System.Text.Encoding.Default.GetBytes(strFromClientWithTime);
foreach(ClientState cs in clients.Values)
{
cs.socket.Send(sendBytes);
}
return true;
}
}
}
The one use Async:
namespace Server
{
class App
{
static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
static void Main(string[] args)
{
Socket listenFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse("127.0.0.1");
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 8888);
listenFd.Bind(iPEndPoint);
listenFd.Listen(0);
Console.WriteLine("Server start!");
listenFd.BeginAccept(AcceptCallback, listenFd);
while(true)
{
Thread.Sleep(1000);
}
}
static void AcceptCallback(IAsyncResult result)
{
var listenfd = result.AsyncState as Socket;
var connfd = listenfd.EndAccept(result);
var clientState = new ClientState { socket = connfd };
clients.Add(connfd, clientState);
connfd.BeginReceive(clientState.readBuff, 0, 1024, 0, EndReceiveCallback, connfd);
Console.WriteLine($" Client connected!");
listenfd.BeginAccept(AcceptCallback, listenfd);
}
static void EndReceiveCallback(IAsyncResult result)
{
var connfd = result.AsyncState as Socket;
var count = connfd.EndReceive(result);
if (count <= 0)
{
Console.WriteLine("Client disconnected!");
connfd.Close();
return;
}
connfd.BeginReceive(clients[connfd].readBuff, 0, 1024, 0, EndReceiveCallback, connfd);
string strFromClient=System.Text.Encoding.Default.GetString(clients[connfd].readBuff,0,count);
Console.WriteLine($"string from client:{strFromClient}");
string strFromClientWithTime= DateTime.Now.ToString("hh:mm")+strFromClient;
byte[] sendBuff= System.Text.Encoding.Default.GetBytes(strFromClientWithTime,0,strFromClientWithTime.Length);
foreach(var conn in clients.Keys)
{
conn.BeginSend(sendBuff, 0, sendBuff.Length, 0, EndSendCallback, conn);
}
}
static void EndSendCallback(IAsyncResult result)
{
var connfd = result.AsyncState as Socket;
connfd.EndSend(result);
}
}
}
In both examples, the Class ClientState
is
class ClientState
{
public Socket socket;
public byte[] readBuff=new byte[1024];
}
Both examples should work well. But I thought async should be better as Damien_The_Unbeliever said.
However, the author of the tutorial in the second edition prefer using select only telling that the logic is more clear.
I have done hours of research but still confused. Is it just a preference or is there sth I am missing here.
You could compare 1.synchronous and 2.asynchronous behaviour in your chat room with this:
Imagine you have an auction, a room with 1000 persons, potential buyers.
Synchronous: The auctioneer walks through all seats using a fixed pattern/order [your poll loop], and asks [polls] each one if he/she wants to place a higher bid [checks incoming connection / incoming data]. If yes, the auctioneer registers it [accepts the connection or processes the typed chat sentence], then walks to the next one and asks, and so on. Once all 1000 are done, he repeats. Note that no talking is allowed, ie. nobody can register a bid until being asked, altough the auctioneer immediately announces new bids as he gets them [send whatever new from the chat server to all clients].
Asynchronous: Talking is allowed. Anyone may shout out his/her bid at any time [callback to your callback fn], even simultaneously [the OS creates multiple, simultaneous and parallel threads] - the auctioneer is very alert, fast and skilled, so he hears each single bid [the OS manages incoming] and announces it in the best way he's ever able to do [all threads place data in your common, shared data repository, in the order the threads happen to raise, and you send out that common data asap] AND he is able to hear even 200 simultaneous bids [multiple threads]. No walking needed, and no fixed order for bid placing.
In the case 1, you'd probably get a compaint from random user John: "Why is Lisa always commenting before me? Unfair!" [your fixed poll order, from 1 to max, Lisa sits before John]. And from any user named "X" (given that you feed out all chat entries from a common server, ie. X's entry makes a server roundtrip): "Why does my chat entry sometimes appear immediately [the auctioneer asked the X-1 person, he'll ask X in a nanosec] but sometimes it takes 2 secs? [the auctioneer asked the X+1 person, takes time before he's here again, the OS stack keeps X on hold]"
In heavy load conditions, the sync alternative would be a slowly and badly rotating gearbox. Kind of... :-)
Small addendum re. async coding (not too scientific)
Async is harder to code, test and debug. It requires a totally different style of coding, although the async part may exist in(side) an otherwise synchronous application (one that is still event-driven by user actions, like in Windows).
The async part could be considered a well-isolated motor inside your code; it works on global and persistent housekeeping data that you maintain around it, but inside the box it is free to "go wild", depending on what is coming from the outer world = the network connections and data, that bombard the innermost of the box directly, independently of your own actions. This is the OS raising events, originating from clients out there.
There are two important features to understand:
The box consists of only one single set of code (a few functions), but this set can be triggered multiple times, simultaneously. Each time it is triggered, a new, isolated instance of it will run. The only thing that isolates it from other similar threads is the thread id, which is unique per instance. The instances work independently of each other, as speedy as the CPU/OS can perform. However, each one will finish the work at some point, and probably want to deliver the result (each in turn, in any, unknown-in-advance, order) into your global data (eg. a list of clients, the total chat of all clients, etc). This means there must be a locking mechanism, because only one instance (thread) may access the global data at time, otherwise it would be a mess. Your coding language has tools for this (hold, lock).
As this single set of code is event-driven, it cannot know in advance how much work there will be. Hence it must have the ability to a)start doing the work b)continue doing the work and c)finish in a clean way when the OS says "that was it". But in case of failure, if the OS of some reason never says "that was it", there must be a timeout that finishes things anyway.
Needless to mention that all this is tricky to get right. If there is a bug that halts one thread, will the other threads continue? How do you access the internals of the failed thread? How do you walk backwards to see why/how that failed thread was spawned in the first place? Etc.
When coding async, one should do it very step-by-step; start with the very simplest implementations; and have a proper outer environment that feeds rich test data, from a step-by-step rate (eg. a key press) to a very high, automated rate, even simultaneously (eg. other other computers in the LAN, that feed data on a start feeding/stop feeding basis).