Maison  >  Article  >  développement back-end  >  Explication détaillée du code graphique de la programmation réseau C#

Explication détaillée du code graphique de la programmation réseau C#

黄舟
黄舟original
2017-03-28 11:20:201406parcourir

Dans le développement logiciel d'aujourd'hui, la programmation réseau est une partie très importante. Cet article présente brièvement les concepts et les pratiques de la programmation réseau. Les amis qui en ont besoin peuvent se référer au répertoire de lecture

. :

Bases
Programmation de socket
Concurrence multithread
Blocage des E/S synchrones

Bases
Dans le développement logiciel actuel, la programmation réseau est très important Partie importante, cet article présente brièvement les concepts et les pratiques de la programmation réseau.
Socket est une interface de programmation réseau. Il s'agit d'une couche d'encapsulation des protocoles de communication TCP et UDP de la couche de transport. Elle est exposée via une API conviviale pour faciliter la communication réseau entre les processus ou plusieurs machines.

Programmation Socket

En programmation réseau, il y a deux rôles : client et serveur. Par exemple, en ouvrant un navigateur, vous pouvez accéder au programme qui. est accroché au logiciel Web. Du point de vue du programme, une page Web signifie que le client (navigateur) initie une requête Socket au serveur, et le serveur renvoie le contenu de la page Web au navigateur pour analyse et affichage. Avant la communication des données entre le client et le serveur, trois confirmations seront effectuées avant que la connexion ne soit officiellement établie, ce qui constitue une prise de contact à trois.

  1. ClientEnvoyer messageDemander au serveur s'il est prêt

  2. Le serveur répond je suis prêt, et vous ? Êtes-vous prêt ?

  3. Le client répond au serveur que je suis prêt et que je peux communiquer

TCP/IPLe protocole est le protocole de base pour la communication entre les réseaux. L'utilisation de l'interface Socket exposée sous différents langages de programmation et différents systèmes d'exploitation est également similaire, mais leur implémentation interne est différente, comme Linux. epoll sous Windows et IOCP sous Windows.

Serveur
  • Socket instantané

  • Lier le port d'adresse publique au système d'exploitation

  • Commencer à écouter le port lié

  • En attente de la connexion client

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();

fonction d'écoute, qui représente le nombre maximum de connexions en attente de traitement, et le nombre de connexions qui ont été établies mais qui n'ont pas encore été traitées. Chaque fois que la fonction Accept est appelée, une connexion sera supprimée. cette file d'attente . Habituellement, le serveur doit servir les connexions demandées par plusieurs clients, il effectuera donc une boucle pour retirer les connexions de la file d'attente pour la réception et l'envoi.

while (true) 
      { 
        var accept= listenSocket.Accept();
        accept.Receive(); 
        accept.Send(); 
      }

Concurrence multithreadLe programme serveur ci-dessus gère la réception et l'envoi de messages dans le fil de discussion actuel. La connexion client doit être traitée avant que la connexion suivante puisse être traitée. Si la connexion actuelle est destinée à des opérations d'E/S telles que la lecture et l'écriture de bases de données ou de fichiers, cela gaspillera considérablement les ressources CPU du serveur et réduira le débit du serveur.

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);
        });
      }
Comme dans l'exemple, lorsqu'une nouvelle demande de connexion est détectée, Accept() est appelé pour retirer le socket de la connexion actuelle, et un nouveau thread est utilisé pour traiter la réception et l'envoi des informations, afin que le serveur puisse implémenter un

Traitement simultanéPlusieurs clients. Dans le code ci-dessus, il y a en fait un problème en cas de concurrence élevée. S'il y a des milliers de demandes de connexion client, il y aura tellement de threads que l'espace de pile de chaque thread consommera une partie de la mémoire. peut facilement entraîner une charge excessive du serveur, une réduction considérable du débit et, dans les cas graves, des temps d'arrêt. Si le ThreadPool système est utilisé dans l'exemple actuel, le nombre de threads sera fixé à un nombre, la valeur par défaut est 1000, et les threads ne seront pas ouverts indéfiniment. Les requêtes qui dépassent le nombre de threads seront placées dans la file d'attente dans le. pool de threads. Il existe deux implémentations similaires sous Unix :

Fork un nouveau processus pour gérer les connexions client :

var connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len); 
var m = fork(); 
if(m == 0) 
{
 //do something 
}
Créez un nouveau thread pour gérer la limitation de courant :

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

Bloquer les E/S synchronesLe
modèle est utilisé dans l'exemple ci-dessus, et il est simple et pratique à utiliser.

while (true)
      {
        var accept = listenSocket.Accept();
        byte[] receive = new byte[100];
        accept.Receive(receive);
        byte[] send = new byte[100];
        accept.Send(receive);
      }
À partir du moment où la fonction de réception est appelée jusqu'au moment où elle reçoit les données du client, la fonction bloquera et attendra le flux de traitement pendant cette période de blocage :

  1. Le client envoie des données

  2. L'envoie au tampon de la carte réseau de la machine serveur via le réseau local WAN

  3. Le Le pilote de la carte réseau envoie une commande d'interruption au CPU

  4. Le CPU copie les données dans le tampon du noyau

  5. Le CPU copie ensuite les données de le tampon du noyau vers le tampon utilisateur, le tableau d'octets de réception ci-dessus.

至此处理成功,开始处理下一个连接请求。 调用发送函数同样会阻塞在当前,然后把用户缓冲区(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);

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn