ホームページ  >  記事  >  バックエンド開発  >  C#ネットワークプログラミングのグラフィックコードを詳しく解説

C#ネットワークプログラミングのグラフィックコードを詳しく解説

黄舟
黄舟オリジナル
2017-03-28 11:20:201440ブラウズ

今日のソフトウェア開発において、ネットワークプログラミングは非常に重要な部分です。この記事では、ネットワーク プログラミングの概念と実践について簡単に紹介します。必要な方は参考にしてください。

読書ディレクトリ:

基本
ソケット プログラミング
。同時実行性
同期 IO のブロック

基本
今日のソフトウェア開発において、ネットワーク プログラミングは非常に重要な部分です。この記事では、ネットワーク プログラミングの概念と実践について簡単に紹介します。
ソケットは、トランスポート層の TCP および UDP 通信プロトコルのカプセル化層であり、プロセスまたは複数のマシン間のネットワーク通信を容易にするために、フレンドリーな API を通じて公開されます。

ソケットプログラミング

ネットワークプログラミングには、クライアントとサーバーという 2 つの役割があります。たとえば、プログラムの観点、つまりクライアントから見ると、ブラウザを開いて Web ソフトウェア上にある Web ページにアクセスします。 (ブラウザ) ) はサーバーへのソケット リクエストを開始し、サーバーは解析および表示のために Web ページのコンテンツをブラウザに返します。クライアントとサーバー間のデータ通信の前に、接続が正式に確立される前に 3 つの確認が行われます。これが 3 ウェイ ハンドシェイクです。

  1. クライアントメッセージを送信サーバーに準備ができているか尋ねます

  2. サーバーは準備ができました、準備はできていますかに応答します

  3. クライアントはサーバーに応答私も準備ができています、わかりました通信しました

TCP/IP プロトコルは、さまざまな プログラミング言語 および異なるオペレーティング システムで公開されるソケット インターフェイスの使用法も同様ですが、内部実装が異なるだけです (例: epoll)。 Windows での Linux と 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();

リストen機能あります の int 型パラメータ。処理を待機している接続の最大数と、確立されているがまだ処理されていない接続の数を表します。Accept 関数が呼び出されるたびに、接続が取得されます。この待機列。 通常、サーバーは複数のクライアントから要求された接続に対応する必要があるため、待機キューから接続を取り出してループで送受信します。

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

マルチスレッド同時実行

上記のサーバー プログラムは、現在のスレッドでメッセージの送受信を処理します。つまり、現在の接続が次の接続を処理する前に、1 つのクライアント接続が処理される必要があります。データベースやファイルの読み取りと書き込みなどの 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() が呼び出されて現在の接続のソケットが取り出され、新しいスレッドを使用して情報の送受信が処理されるため、サーバーは次のことを実現できます。
同時処理

複数のクライアント。 上記のコードでは、実際には、同時実行性が高い場合に問題が発生します。数千のクライアント接続リクエストがある場合、各スレッドのスタック領域がメモリの一部を消費する必要があり、さらにスレッドのコンテキスト切り替えが発生する可能性があります。過剰なサーバー負荷、スループットの大幅な低下、深刻な場合にはダウンタイムが発生しやすくなります。 現在の例でシステム ThreadPool が使用されている場合、スレッドの数は一定の数に固定され、デフォルトは 1000 であり、スレッドの数を超えるリクエストは無期限にオープンされません。スレッドプール。

Unix には 2 つの同様の実装があります: クライアント接続を処理する新しいプロセスをフォークします:

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 関数が呼び出されてからクライアントからデータを受信するまで、関数は常にブロックして待機します。 このブロック期間中の処理の流れは次のとおりです。

クライアントはデータを送信します。
  1. WAN LAN経由でサーバーマシンのネットワークカードバッファに送信

  2. ネットワークカードドライバーはCPUに割り込みコマンドを送信

  3. CPUはデータをカーネルバッファにコピー

  4. その後CPUは上記の受信バイト配列と同様に、カーネル バッファ内のデータをユーザー バッファにコピーします。

至此处理成功,开始处理下一个连接请求。 调用发送函数同样会阻塞在当前,然后把用户缓冲区(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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。