浅谈C#网络编程详解篇

2019-12-30 15:56:08刘景俊

如例子中,当监听到有新连接请求过来时,调用Accept()取出当前连接的socket,使用新的线程去处理接收和发送信息,这样服务端就能实现并发处理多个客户端了。 上述代码中,在高并发下其实是有问题的,如果客户端连接请求成千上万个,那线程数量也会有这么多,每个线程的栈空间都需要消耗部分内存,再加上线程上下文切换,容易导致服务器负载过高,吞吐量大大下降,严重时会引起宕机。 当前例子中使用系统ThreadPool的话,线程数量会固定在一个数量上,默认是1000,不会无限制开线程,会把处理超出线程数量的请求放到线程池中的队列上面。
在unix下类似的实现有2种:

fork一个新进程去处理客户端的连接:


var connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len); 
var m = fork(); 
if(m == 0) 
{
 //do something 
}

创建一个新的线程处理限流:


var *clientsockfd = accept(serversockfd,(struct sockaddr *)&clientaddress, (socklent *)&clientlen);
 if(pthreadcreate(&thread, NULL, recdata, clientsockfd)!=0) 
{ //do something 
}

阻塞式同步IO
上述例子中使用的即是该模型,使用起来简单方便。


while (true)
      {
        var accept = listenSocket.Accept();
        byte[] receive = new byte[100];
        accept.Receive(receive);
        byte[] send = new byte[100];
        accept.Send(receive);
      }

从调用Receive函数起到接受到客户端发过来的数据期间,该函数会一直阻塞等待着,这个阻塞期间处理流程如下:

    客户端发送数据 通过广域网局域网发送到服务端机器网卡缓冲区上 网卡驱动对CPU发送中断指令 CPU把数据拷贝到内核缓冲区 CPU再把内核缓冲区的数据拷贝用户缓冲区,上面的receive字节数组。

至此处理成功,开始处理下一个连接请求。 调用发送函数同样会阻塞在当前,然后把用户缓冲区(send字节数组)数据拷贝到内核中TCP发送缓冲区中。 TCP的发送缓冲区也有一定的大小限制,如果发送的数据大于该限制,send函数会一直等待发送缓冲区有空闲时完全拷贝完才会返回,继续处理后续连接请求。

异步IO
上篇提到用多线程处理多个阻塞同步IO而实现并发服务端,这种模式在连接数量比较小的时候非常适合,一旦连接过多,性能会急速下降。 在大多数服务端网络软件中会采用一种异步IO的方式来提高性能。

同步IO方式:连接Receive请求->等待->等待->接收成功
异步IO方式:连接Receive请求->立即返回->事件或回调通知
采用异步IO方式,意味着单线程可以处理多个请求了,连接发起一个Receive请求后,当前线程可以立即去做别的事情,当数据接收完毕通知线程处理即可。