好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

浅谈C#网络编程详解篇

阅读目录:

基础
socket编程
多线程并发
阻塞式同步io

基础
在现今软件开发中,网络编程是非常重要的一部分,本文简要介绍下网络编程的概念和实践。
socket是一种网络编程接口,它是对传输层tcp、udp通信协议的一层封装,通过友好的api暴露出去,方便在进程或多台机器间进行网络通信。

 

socket编程

在网络编程中分客户端和服务端两种角色,比如通过打开浏览器访问到挂在web软件上的网页,从程序角度上来看,即客户端(浏览器)发起了一个socket请求到服务器端,服务器把网页内容返回到浏览器解析后展示。在客户端和服务端数据通信前,会进行三次确认才会正式建立连接,也即是三次握手。

客户端发送消息询问服务端是否准备好 服务端回应我准备好了,你呢准备好了吗 客户端回应服务端我也准备好了,可以通信了

tcp/ip协议是网络间通信的基础协议,在不同编程语言及不同操作系统下暴露的socket接口用法也大同小异,仅是其内部实现有所不同,比如linux下的epoll和windows下的iocp。

服务端 实例化socket 把公共地址端口绑定操作系统上 开始监听绑定的端口 等待客户端连接

?

ipendpoint ip = new ipendpoint(ipaddress.any, 6389);

       socket listensocket = new socket(ip.addressfamily, sockettype.stream, protocoltype.tcp);

       listensocket.bind(ip);

       listensocket.listen(100);

       listensocket.accept();

listen函数中有个int类型参数,它表示最大等待处理连接的数量,表示已建立连接但还未处理的数量,每调用accept函数一下即从这个等待队列中拿出一个连接。 通常服务端要服务多个客户端请求的连接,所以会循环从等待队列中拿出连接,进行接收发送。

?

while ( true )

       {

         var accept= listensocket.accept();

         accept.receive();

         accept.send();

       }

多线程并发
上面的服务端程序处理接收和发送消息都是在当前线程下完成的,这意味着要处理完一个客户端连接后才能去处理下一个连接,如果当前连接是进行数据库或者文件读取写入等io操作,那会极大浪费服务器的cpu资源,降低了服务器吞吐量。

?

while ( true )

       {

         var accept = listensocket.accept();

         threadpool.queueuserworkitem((obj) =>

         {

           byte [] receive = new byte [100];

           accept.receive(receive);

           byte [] send = new byte [100];

           accept.send(receive);

         });

       }

如例子中,当监听到有新连接请求过来时,调用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请求后,当前线程可以立即去做别的事情,当数据接收完毕通知线程处理即可。
其数据接收分2部分:

数据从别的机器发送内核缓冲区
内核缓冲区拷贝到用户缓冲区
第二部分示例代码:

?

byte [] msg = new byte [256]; socket.receive(msg);

介绍这2部分的目的是方便区分其他几种方式。 对于用户程序来说,同步io和异步io的区别在于第二部分是否需要等待。

非阻塞式同步io
非阻塞式同步io,由同步io延伸出来,把这个名词拆分成2部分描述:

非阻塞式,指的是上节"数据从别的机器发送内核缓冲区"部分是非阻塞的。 同步io,指的是上节"内核缓冲区拷贝到用户缓冲区"部分是等待的。

既然是第一部分是非阻塞的,那就需要一种方法得知什么时候内核缓冲区是ok的。 设置非阻塞模式后,在连接调用receive方法时,会立即返回一个标记,告知用户程序内核缓存区有没有数据,如果有数据开始进行第二部分操作,从内核缓冲区拷贝到用户程序缓冲区。 由于系统会返回个标记,那可以通过轮询方式来判断内核缓冲区是否ok。

设置非阻塞模式参考代码:

?

socketinformation sif= new socketinformation();

sif.options=socketinformationoptions.nonblocking;

sif.protocolinformation = new byte [24];

socket socket = new socket(sif);

轮询参考代码:

?

while ( true )

{

byte [] msg = new byte [256];

var temp = socket.receive(msg);

if (temp== "ok" ){

//do something

} else { continue }

}

这种方式近乎淘汰了,了解即可。

基于回调的异步io
上面介绍过:

异步io方式:连接receive请求->立即返回->事件或回调通知
当回调到执行时,数据已经在用户程序缓冲区已经准备好了,在回调代码中对这部分数据进行相应的逻辑即可。

发出接收请求:

?

static byte [] msg = new byte [256];

var temp = socket.beginreceive(msg, 0, msg.length, 0, new asynccallback(readcallback), socket);

回调函数中对数据做处理:

?

public static void readcallback(iasyncresult ar)

{

var socket = (socket)ar.asyncstate;

  int read = socket.endreceive(ar);

dosomething(msg);

socket.beginreceive(msg, 0, msg.length, 0, new asynccallback(read_callback), socket);

}

当回调函数执行时,表示数据已经准备好,需要先结束接收请求endreceive,以便第二次发出接收请求。 在服务端程序中要处理多个客户端的接收,再次发出beginreceive接收数据请求即可。

这里的回调函数是在另外一个线程的触发,必要时要对数据加锁防止数据竞争:

?

console.writeline(thread.currentthread.managedthreadid);

针对c#网络编程的介绍就到这了,具体的大家可以查看服务器之家之前发布的文章。

原文链接:http://HdhCmsTestcnblogs测试数据/mushroom/p/4845390.html

dy("nrwz");

查看更多关于浅谈C#网络编程详解篇的详细内容...

  阅读:46次