在現今軟體開發中,網頁程式設計是非常重要的一部分,本文簡單介紹下網路程式設計的概念與實作,需要的朋友可以參考下
閱讀目錄:
基礎
Socket程式設計
多執行緒並發
阻塞式同步IO
基礎
在現今軟體開發中,網路程式設計是非常重要的一部分,本文簡要介紹下網路程式設計的概念和實踐。
Socket是一種網路編程接口,它是對傳輸層TCP、UDP通訊協定的一層封裝,透過友善的API暴露出去,方便在進程或多台機器間進行網路通訊。
在網路程式設計中分客戶端和服務端兩個角色,例如透過開啟瀏覽器存取到掛在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();
函數中有個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); }); }
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); }
至此处理成功,开始处理下一个连接请求。 调用发送函数同样会阻塞在当前,然后把用户缓冲区(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#網路程式設計的圖文代碼詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!