Heim > Artikel > Backend-Entwicklung > Detaillierte Erläuterung des grafischen Codes der C#-Netzwerkprogrammierung
In der heutigen Softwareentwicklung ist die Netzwerkprogrammierung ein sehr wichtiger Teil. In diesem Artikel werden die Konzepte und Praktiken der Netzwerkprogrammierung kurz vorgestellt. Freunde, die sie benötigen, können sich auf das
-Leseverzeichnis beziehen : GrundlagenSocket-Programmierung
Multithread-Parallelität
Blockierung synchroner E/A
GrundlagenIn der heutigen Softwareentwicklung ist Netzwerkprogrammierung wichtig sehr wichtig In diesem Artikel werden als wichtiger Teil kurz die Konzepte und Praktiken der Netzwerkprogrammierung vorgestellt.
Socket ist eine Netzwerkprogrammierschnittstelle. Es handelt sich um eine Kapselungsschicht der Transportschicht-TCP- und UDP-Kommunikationsprotokolle. Es wird über eine benutzerfreundliche API bereitgestellt, um die Netzwerkkommunikation zwischen Prozessen oder mehreren Maschinen zu erleichtern.
Nachricht sendenFragen Sie den Server, ob er bereit ist
TCP/IPProtokoll ist das Basisprotokoll für die Kommunikation zwischen Netzwerken. Die Verwendung der Socket-Schnittstelle, die unter verschiedenen Programmiersprachen und verschiedenen Betriebssystemen verfügbar gemacht wird, ist ebenfalls ähnlich, aber ihre interne Implementierung ist unterschiedlich, wie z. B. Linux epoll unter Windows und IOCP unter Windows.
ServerIPEndPoint ip = new IPEndPoint(IPAddress.Any, 6389); Socket listenSocket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listenSocket.Bind(ip); listenSocket.Listen(100); listenSocket.Accept();Abhören
Funktion, der die maximale Anzahl der Verbindungen darstellt, die auf die Verarbeitung warten, und die Anzahl der Verbindungen, die hergestellt, aber noch nicht verarbeitet wurden. Bei jedem Aufruf der Accept-Funktion wird eine Verbindung herausgenommen diese Warteschlange . Normalerweise muss der Server die von mehreren Clients angeforderten Verbindungen bedienen, daher führt er eine Schleife durch, um die Verbindungen aus der Warteschlange zum Empfangen und Senden herauszunehmen.
while (true) { var accept= listenSocket.Accept(); accept.Receive(); accept.Send(); }
Multi-Thread-Parallelität
Das obige Serverprogramm übernimmt das Empfangen und Senden von Nachrichten im aktuellen Thread Die Clientverbindung muss verarbeitet werden, bevor die nächste Verbindung verarbeitet werden kann. Wenn die aktuelle Verbindung für E/A-Vorgänge wie das Lesen und Schreiben von Datenbanken oder Dateien bestimmt ist, werden die CPU-Ressourcen des Servers stark verschwendet und der Serverdurchsatz verringert.
Wenn wie im Beispiel eine neue Verbindungsanforderung erkannt wird, wird Accept() aufgerufen, um den Socket der aktuellen Verbindung zu entfernen, und ein neuer Thread wird zum Verarbeiten von Empfangs- und Sendeinformationen verwendet. damit der Server
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); }); }Mehrere Clients implementieren kann. Im obigen Code gibt es tatsächlich ein Problem bei hoher Parallelität. Wenn Tausende von Threads vorhanden sind, verbraucht jeder Thread einen Teil des Speichers kann leicht zu einer übermäßigen Serverlast, einem stark verringerten Durchsatz und in schweren Fällen zu Ausfallzeiten führen. Wenn im aktuellen Beispiel der System-ThreadPool verwendet wird, wird die Anzahl der Threads auf eine bestimmte Zahl festgelegt, der Standardwert ist 1000 und Threads werden nicht auf unbestimmte Zeit geöffnet. Anforderungen, die die Anzahl der Threads überschreiten, werden in die Warteschlange gestellt Thread-Pool.
Es gibt zwei ähnliche Implementierungen unter Unix: Forken Sie einen neuen Prozess, um Client-Verbindungen zu verwalten:
Erstellen Sie einen neuen Thread, um die Strombegrenzung zu verwalten:
var connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len); var m = fork(); if(m == 0) { //do something }Blockieren synchroner E/A
var *clientsockfd = accept(serversockfd,(struct sockaddr *)&clientaddress, (socklent *)&clientlen); if(pthreadcreate(&thread, NULL, recdata, clientsockfd)!=0) { //do something }
Das Modell wird im obigen Beispiel verwendet und ist einfach und bequem zu verwenden.
Vom Aufruf der Empfangsfunktion bis zum Empfang der vom Client gesendeten Daten blockiert und wartet die Funktion während dieser Blockierungsperiode wie folgt:
while (true) { var accept = listenSocket.Accept(); byte[] receive = new byte[100]; accept.Receive(receive); byte[] send = new byte[100]; accept.Send(receive); }Der Client sendet Daten
至此处理成功,开始处理下一个连接请求。 调用发送函数同样会阻塞在当前,然后把用户缓冲区(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);
Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des grafischen Codes der C#-Netzwerkprogrammierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!