ソケットプログラミング実践ガイド

  1. TCP/IP階層モデル

ここでは重要な4層についてのみ説明します。

01,アプリケーション層: これは広範な概念で、いくつかの基本的なシステムレベルのTCP/IPアプリケーションやアプリケーションプロトコル、多くの企業アプリケーションやインターネットアプリケーションが含まれます。HTTPプロトコルはアプリケーション層で実行されます。

02,トランスポート層: UDPとTCPを含みます。UDPはほとんどメッセージを検証しませんが、TCPは伝送保証を提供します。

03,ネットワーク層: 一連のプロトコルで構成され、ICMP、IGMP、RIP、OSPF、IP(v4,v6)などが含まれます。

04,リンク層: 物理データネットワークインターフェース層とも呼ばれ、メッセージの伝送を担当します。

次にTCP階層モデル図を見てみましょう。

この図から、アプリケーションがアプリケーション層で実行され、トランスポート層でデータの前にTCPヘッダーが追加され、ネットワーク層でIPヘッダーが追加され、データリンク層でフレームが追加されていることがわかります。

2,ポート

ポート番号の範囲: 0-65535、合計で65536個の値を表現できます。

ポート番号による3つのカテゴリ

(1)ウェルナウンポート(Well Known Ports): 0から1023まで、これらはいくつかのサービスに厳密にバインドされています。通常、これらのポートの通信は特定のサービスプロトコルを明確に示しています。例えば、80ポートは常にHTTP通信に使用されます。

(2)登録ポート(Registered Ports): 1024から49151まで。これらはいくつかのサービスに緩やかにバインドされています。つまり、多くのサービスがこれらのポートにバインドされており、これらのポートは多くの他の目的でも使用されます。例えば、多くのシステムは1024あたりから動的ポートの処理を開始します。

(3)動的および/またはプライベートポート(Dynamic and/or Private Ports): 49152から65535まで。理論的には、これらのポートにはサービスを割り当てないべきです。実際には、マシンは通常1024から動的ポートを割り当てます。

3.TCPとUDPメッセージ

次にTCPとUDPのメッセージ図を見てみましょう。

この図から、TCPとUDPの両方にチェックサムがあることがわかりますが、UDPメッセージでは通常チェックサムを使用しないため、データ転送の速度が向上しますが、データの正確性が影響を受ける可能性があります。言い換えれば、TCPプロトコルには常にチェックサムがあり、転送データの正確性を保証するためです。

3.ソケット

ソケットにはIPアドレスとポート番号の2つの部分が含まれ、プログラムはソケットを介して通信します。ソケットはOSのコンポーネントと見なすことができます。ソケットはプロセス間通信メカニズムとして機能し、「ソケット」とも呼ばれ、IPアドレスとポート番号を記述し、通信チェーンのハンドルです。要するに、2つのプログラム間で通信するために使用されます。

生活での例での比較:

ソケット間の通信は、生活で電話をかける例に類似しています。ユーザーが通話を開始する前に、まず電話機を占有する必要があり、これはソケットを申請することに相当します。同時に相手の番号を知る必要があり、これは相手に固定されたソケットがあることに相当します。その後相手に電話をかけ、接続リクエストを送信します。相手が席にいて空いている場合、電話の受話器を取り上げれば、双方で通話ができます。通話の過程では、一方が電話機に信号を送信し、相手が電話機から信号を受信する過程であり、これはソケットにデータを送信し、ソケットからデータを受信することに相当します。通話が終了すると、一方が電話機を切り、これはソケットを閉じて接続を解除することに相当します。

注意:ソケットは2台のコンピュータ間だけでなく、同一コンピュータ上の2つのプログラム間でも通信できます。

4.ポートの高度な理解

IPアドレスによってネットワーク内のコンピュータが特定された後、そのコンピュータでは多くのサービスを提供するアプリケーションが実行されており、各アプリケーションはそれぞれポートに対応しています。

インターネットには多くのホストがあり、これらのホストは通常複数のサービスソフトウェアを実行し、同時に複数のサービスを提供しています。各サービスはソケットを開き、ポートにバインドし、異なるポートは異なるサービス(アプリケーション)に対応しています。

例えば、httpは80ポートを使用し、ftpは21ポートを使用し、smtpは25ポートを使用します

5.ソケットの分類

ソケットには主に2つのタイプがあります:

  1. ストリームソケット

接続指向のソケットで、接続指向のTCPサービスアプリケーション向けであり、安全ですが効率が低いです。

2,データグラムソケット

接続なしのソケットで、接続なしのUDPサービスアプリケーションに対応し、安全ではありませんが効率が高いです。

  1. ソケットの一般的な応用モデル(サーバー側とクライアント側)

サーバー側のソケット(少なくとも2つが必要)

01.クライアントの接続リクエストを受信する(クライアントとの通信は担当しない)

02.クライアントの接続が成功するたびに、サーバー側で対応する通信用ソケットが生成される

021.クライアント接続を受け取ったときに作成される

  1. 接続に成功した各クライアントリクエストに対して、サーバー側で対応するソケットを作成する(クライアントとの通信を担当)

クライアント側のソケット

  1. 接続するサーバーのアドレスとポートを指定する必要がある
  2. Socketオブジェクトを作成することで、サーバー側へのTCP接続を初期化する

上記の図から、まずサーバーはリスニング用のソケットを作成し、クライアントはソケットを介してサーバーの指定ポートに接続し、最後にサーバー側のリスニング用ソケットがクライアントの接続を検出すると、クライアントと通信するためのソケットを作成することがわかります。

次に、ソケットのより具体的な通信プロセスを見てみましょう:

ソケットの通信プロセス

サーバー側:

01.ソケットを申請する 02.IPアドレスとポートにバインドする 03.リスニングを開始し、接続の受信を待機する

クライアント側:

01.ソケットを申請する 02.サーバーに接続する(IPアドレスとポート番号を指定する)

サーバー側が接続リクエストを受信した後、新しいソケット(ポート番号が1024より大きい)を作成してクライアントとの接続を確立し、通信を行い、元のリスニングソケットは引き続きリスニングを続けます。

注意:通信を担当するソケットは無制限に作成できず、作成可能な数はOSに依存します。

7.ソケットのコンストラクタ

Public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)

AddressFamily: ソケットがアドレスを解釈するために使用するアドレス指定スキームを指定します。例:InterNetWorkは、ソケットがIPバージョン4アドレスを使用して接続するときを示します

SocketType: 開くソケットのタイプを定義します

Socketクラスは、ProtocolType列挙を使用してWindows Sockets APIに要求されたプロトコルを通知します

注意:

  1. ポート番号は1と65535の間である必要があり、1024以降であることが望ましいです。

  2. 接続するリモートホストは指定されたポートでリッスンしている必要があり、つまりリモートホストに任意に接続することはできません。

例:

IPAddress addr = IPAddress.Parse("127.0.0.1");

IPEndPoint endp = new IPEndPoint(addr, 9000);

サーバー側が最初にバインド:serverWelcomeSocket.Bind(endp)

クライアント側が次に接続:clientSocket.Connect(endp)

  1. 1つのソケットは一度に1台のホストにしか接続できません

  2. ソケットを閉じた後は再利用できません

  3. 各ソケットオブジェクトは1台のリモートホストのみに接続できます。複数のリモートホストに接続する場合は、複数のソケットオブジェクトを作成する必要があります。

8.ソケットの一般的なクラスとメソッド

関連クラス:

IPAddress: IPアドレスを含む

IPEndPoint: IPアドレスとポート番号のペアを含む

メソッド:

Socket(): ソケットを作成する

Bind(): ローカルのIPとポート番号(IPEndPoint)にバインドする

Listen(): ソケットが着信接続をリッスンし、リッスンキュー容量を指定するようにする

Connect(): 別のソケットとの接続を初期化する

Accept(): 接続を受け入れ、新しいソケットを返す

Send(): データをソケットに出力する

Receive(): ソケットからデータを読み取る

Close(): ソケットを閉じ、接続を破棄する

次に、簡単なサーバーとクライアントの通信の例を使って、ソケットの具体的な使用方法を見てみましょう。

サーバー側コード:

private void InitializeServer(object sender, EventArgs e)
{
    Control.CheckForIllegalCrossThreadCalls = false;
}

private void StartListening(object sender, EventArgs e)
{
    // IPアドレス
    IPAddress ip = IPAddress.Parse(ipInput.Text);
    // ポート番号
    IPEndPoint point = new IPEndPoint(ip, int.Parse(portInput.Text));
    
    // リスニング用のソケットを作成
    /*
     * AddressFamily.InterNetwork: IP4アドレスを使用します。
     * SocketType.Stream: 信頼できる、双方向、接続ベースのバイトストリームをサポートし、データを繰り返しません。
     * このタイプのソケットは単一の相手ホストと通信し、通信開始前にリモートホストの接続が必要です。
     * Streamはトランスポート制御プロトコル(Tcp)とInterNetworkAddressFamilyを使用します。
     * ProtocolType.Tcp: トランスポート制御プロトコルを使用します。
     */
    // IPv4アドレス、ストリームソケット方式、TCPプロトコルでデータを転送
    Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    
    try
    {
        // ソケットがどのポートをリスニングするか
        serverSocket.Bind(point);
        // 同じ時間に10個のクライアントが来たら、キューに入れる
        serverSocket.Listen(10);
        
        ShowMessage("サーバーがリスニングを開始しました");
        
        // クライアント接続を処理するスレッド
        Thread connectionThread = new Thread(HandleClientConnections);
        connectionThread.IsBackground = true;
        connectionThread.Start(serverSocket);
    }
    catch (Exception ex)
    {
        ShowMessage(ex.Message);
    }
}

// クライアント接続を管理する辞書
Dictionary<string, Socket> clientConnections = new Dictionary<string, Socket>();

void HandleClientConnections(object o)
{
    Socket listeningSocket = o as Socket;
    
    while (true)
    {
        try
        {
            // 通信用ソケットを作成
            Socket communicationSocket = listeningSocket.Accept();
            string clientEndPoint = communicationSocket.RemoteEndPoint.ToString();
            
            ShowMessage(clientEndPoint + " が接続しました!");
            clientSelector.Items.Add(clientEndPoint);
            clientConnections.Add(clientEndPoint, communicationSocket);
            
            // メッセージ受信用スレッドを開始
            Thread receiveThread = new Thread(ReceiveMessages);
            receiveThread.IsBackground = true;
            receiveThread.Start(communicationSocket);
        }
        catch (Exception ex)
        {
            ShowMessage(ex.Message);
            break;
        }
    }
}

// メッセージ受信
void ReceiveMessages(object o)
{
    Socket clientSocket = o as Socket;
    
    while (true)
    {
        try
        {
            // クライアントから送られてきたデータを受信するためのbyte配列を定義
            byte[] buffer = new byte[1024 * 1024];
            // 受信したデータをbufferに格納し、実際に受信したデータの長さを返す
            int bytesReceived = clientSocket.Receive(buffer);
            // バイトを文字列に変換
            string receivedMessage = Encoding.UTF8.GetString(buffer, 0, bytesReceived);
            
            ShowMessage(clientSocket.RemoteEndPoint.ToString() + ": " + receivedMessage);
        }
        catch (Exception ex)
        {
            ShowMessage(ex.Message);
            break;
        }
    }
}

void ShowMessage(string message)
{
    logTextBox.AppendText(message + "\r\n");
}

// クライアントにメッセージを送信
private void SendMessageToClient(object sender, EventArgs e)
{
    try
    {
        ShowMessage(messageInput.Text);
        string selectedClient = clientSelector.Text;
        byte[] messageBuffer = Encoding.UTF8.GetBytes(messageInput.Text);
        clientConnections[selectedClient].Send(messageBuffer);
    }
    catch (Exception ex)
    {
        ShowMessage(ex.Message);
    }
}

クライアント側コード

Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

private void ConnectToServer(object sender, EventArgs e)
{
    // 接続先のIP
    IPAddress serverIp = IPAddress.Parse(serverIpInput.Text);
    // 接続先IPのどのアプリケーションに接続するか(ポート番号!)
    IPEndPoint endPoint = new IPEndPoint(serverIp, int.Parse(serverPortInput.Text));
    
    try
    {
        // サーバーに接続
        clientSocket.Connect(endPoint);
        ShowMessage("接続成功");
        ShowMessage("サーバー " + clientSocket.RemoteEndPoint.ToString());
        ShowMessage("クライアント: " + clientSocket.LocalEndPoint.ToString());
        
        // 接続が成功したら、サーバーから送られてくる情報を受信できる
        Thread receiveThread = new Thread(ReceiveServerMessages);
        receiveThread.IsBackground = true;
        receiveThread.Start();
    }
    catch (Exception ex)
    {
        ShowMessage(ex.Message);
    }
}

// サーバーのメッセージを受信
void ReceiveServerMessages()
{
    while (true)
    {
        try
        {
            byte[] buffer = new byte[1024 * 1024];
            int bytesReceived = clientSocket.Receive(buffer);
            string message = Encoding.UTF8.GetString(buffer, 0, bytesReceived);
            ShowMessage(clientSocket.RemoteEndPoint.ToString() + ": " + message);
        }
        catch (Exception ex)
        {
            ShowMessage(ex.Message);
            break;
        }
    }
}

void ShowMessage(string message)
{
    infoTextBox.AppendText(message + "\r\n");
}

// サーバーにメッセージを送信
private void SendToServer(object sender, EventArgs e)
{
    if (clientSocket != null && clientSocket.Connected)
    {
        try
        {
            ShowMessage(messageInput.Text);
            byte[] buffer = Encoding.UTF8.GetBytes(messageInput.Text);
            clientSocket.Send(buffer);
        }
        catch (Exception ex)
        {
            ShowMessage(ex.Message);
        }
    }
    else
    {
        ShowMessage("サーバーに接続されていません");
    }
}

private void InitializeClient(object sender, EventArgs e)
{
    Control.CheckForIllegalCrossThreadCalls = false;
}

タグ: ネットワークプログラミング TCP/IP ソケット通信 C# クライアントサーバーモデル

6月5日 18:22 投稿