浅谈C#网络编程详解篇
阅读目录: 基础 基础 Socket编程在网络编程中分客户端和服务端两种角色,比如通过打开浏览器访问到挂在Web软件上的网页,从程序角度上来看,即客户端(浏览器)发起了一个Socket请求到服务器端,服务器把网页内容返回到浏览器解析后展示。在客户端和服务端数据通信前,会进行三次确认才会正式建立连接,也即是三次握手。
TCP/IP协议是网络间通信的基础协议,在不同编程语言及不同操作系统下暴露的Socket接口用法也大同小异,仅是其内部实现有所不同,比如Linux下的epoll和windows下的IOCP。 服务端
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(); } 多线程并发 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,不会无限制开线程,会把处理超出线程数量的请求放到线程池中的队列上面。 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函数起到接受到客户端发过来的数据期间,该函数会一直阻塞等待着,这个阻塞期间处理流程如下:
至此处理成功,开始处理下一个连接请求。 调用发送函数同样会阻塞在当前,然后把用户缓冲区(send字节数组)数据拷贝到内核中TCP发送缓冲区中。 TCP的发送缓冲区也有一定的大小限制,如果发送的数据大于该限制,send函数会一直等待发送缓冲区有空闲时完全拷贝完才会返回,继续处理后续连接请求。 异步IO 同步IO方式:连接Receive请求->等待->等待->接收成功 数据从别的机器发送内核缓冲区 byte[] msg = new byte[256]; socket.Receive(msg); 介绍这2部分的目的是方便区分其他几种方式。 对于用户程序来说,同步IO和异步IO的区别在于第二部分是否需要等待。 非阻塞式同步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,msg.Length,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,new AsyncCallback(Read_Callback),socket); } 当回调函数执行时,表示数据已经准备好,需要先结束接收请求EndReceive,以便第二次发出接收请求。 在服务端程序中要处理多个客户端的接收,再次发出BeginReceive接收数据请求即可。 这里的回调函数是在另外一个线程的触发,必要时要对数据加锁防止数据竞争: Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 针对C#网络编程的介绍就到这了,具体的大家可以查看编程小技巧之前发布的文章。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |