SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

仮想ネットワークの実装で学ぶTCP/IP

仮想ネットワーク実装でTCP/IPを学ぼう(4)
― トランスポート層の勘所

仮想ネットワーク実装でTCP/IPを学ぼう(4)


  • X ポスト
  • このエントリーをはてなブックマークに追加

コネクションの終了

 TCPはコネクションを終了する際にも手続きが必要です。データを送信し終わったクライアントは、サーバーへ終了したい旨を表すセグメントを送信します。この動作をアクティブ・クローズと呼びます。

(C#)
//TcpClientクラスから抜粋
/// <summary>
/// 接続を閉じます。
/// </summary>
/// <param name="data">送信するデータ</param>
/// <param name="portNumber">指定するポート番号</param>
/// <param name="sequenceNumber">シーケンス番号</param>
/// <param name="nic">使用するデバイスドライバ</param>
protected void Close ( Frame data, ushort portNumber, ref uint sequenceNumber, Nic nic )
{
    //接続を閉じるまで繰り返し処理をする
    while ( this.state != TcpConnectionState.PassiveCloseAsk ) {
        switch ( this.state ) {
            case TcpConnectionState.Open:
                TcpSegment segment = new TcpSegment ( );
                segment.Fin = true; //ここに注目
                segment.SequenceNumber = this.nextNumber;//ここに注目
                segment.AcknowledgementNumber = this.nextNumber; //ここに注目
                segment.SourcePortNumber = portNumber;
                segment.DestinationPortNumber = portNumber;
                data.Data.UserData = segment;
                this.state = TcpConnectionState.ActiveClose;
                nic.Send ( data );
                break;
        }
    }

    //関係のないコードは省略しています
}
(VB.NET)
'TcpClientクラスから抜粋
''' <summary>
''' 接続を閉じます。
''' </summary>
''' <param name="data">送信するデータ</param>
''' <param name="portNumber">指定するポート番号</param>
''' <param name="sequenceNumber">シーケンス番号</param>
''' <param name="nic">使用するデバイスドライバ</param>
Protected Sub Close(ByVal data As Frame, ByVal portNumber As UShort, ByRef sequenceNumber As UInteger, ByVal nic As Nic)
    '接続を閉じるまで繰り返し処理をする
    While Me.state <> TcpConnectionState.PassiveCloseAsk
        Select Case Me.state
            Case TcpConnectionState.Open
                Dim segment As TcpSegment = New TcpSegment()
                segment.Fin = True 'ここに注目
                segment.SequenceNumber = Me.nextNumber 'ここに注目
                segment.AcknowledgementNumber = Me.nextNumber 'ここに注目
                segment.SourcePortNumber = portNumber
                segment.DestinationPortNumber = portNumber
                data.Data.UserData = segment
                Me.state = TcpConnectionState.ActiveClose
                nic.Send(data)
        End Select
    End While

    '関係のないコードは省略しています
End Sub

 FINフラグを指定していることに注目してください。FINフラグはコネクションを終了する時に使用します。

 メッセージを受信したサーバーは、コネクションを終了する旨に対する肯定セグメントと、サーバー側からも終了させることを示すセグメントの計2つのセグメントをクライアントへ送信します。なぜ2つ必要なのかというと、TCPは全二重通信であり、双方向同時にデータが送受信されているからです。もし片方だけコネクションを終了してしまえば、もう片一方はデータを送信し続けることになってしまいます。この状態をハーフ・クローズと呼びます。通常は完全にコネクションを終了させますが、まれにハーフ・クローズを利用するUNIXコマンドrshなどが存在するそうです。

(C#)
//TcpServerクラスから抜粋
/// <summary>
/// クラアントとのやり取りを行います。
/// </summary>
/// <param name="data">受信したデータ</param>
/// <param name="nic">使用するドライバ</param>
public void Receive ( Frame data, Nic nic )
{
    //現在の接続状態に基づいて適切な処理をする
    SocketPair info = GetConnectionInfo ( packet.SourceAddress, segment.SourcePortNumber );
    if ( info == null ) {
    } else {
        switch ( info.State ) {
            case TcpConnectionState.ActiveOpen:
                //接続の最終段階へ移行する
                SetConnectionInfo ( packet.SourceAddress, segment.SourcePortNumber );
                break;
            case TcpConnectionState.Open:
                if ( segment.Fin == true ) {
                    //クローズ処理を行う
                    #region 返答をする

                    //状態遷移
                    SetConnectionInfo ( packet.SourceAddress, segment.SourcePortNumber );

                    //TCPセグメント
                    TcpSegment resultSegment = new TcpSegment ( );
                    resultSegment.Ack = true; //ここに注目
                    resultSegment.SourcePortNumber = segment.DestinationPortNumber;
                    resultSegment.DestinationPortNumber = segment.SourcePortNumber;
                    resultSegment.SequenceNumber = segment.SequenceNumber;
                    resultSegment.AcknowledgementNumber = segment.SequenceNumber + 1;

                    //IPパケットとMACフレームの生成は省略しています
                    MacFrame resultMac = new MacFrame ( );

                    //返答を送信
                    nic.Send ( ( Frame ) resultMac );
                    #endregion
                    #region 返答をもう1回する

                    //状態遷移
                    SetConnectionInfo ( packet.SourceAddress, segment.SourcePortNumber );

                    //TCPセグメント
                    resultSegment = new TcpSegment ( );
                    resultSegment.Ack = true; //ここに注目
                    resultSegment.Fin = true; //ここに注目
                    resultSegment.SourcePortNumber = segment.DestinationPortNumber;
                    resultSegment.DestinationPortNumber = segment.SourcePortNumber;
                    resultSegment.SequenceNumber = segment.SequenceNumber;
                    resultSegment.AcknowledgementNumber = segment.SequenceNumber + 1;
                    resultMac.Data.UserData = resultSegment;

                    //返答を送信
                    nic.Send ( ( Frame ) resultMac );
                    #endregion
                }
                break;
        }
    }
    //関係のないコードは省略しています
}
(VB.NET)
'TcpServerクラスから抜粋
''' <summary>
''' クラアントとのやり取りを行います。
''' </summary>
''' <param name="data">受信したデータ</param>
''' <param name="nic">使用するドライバ</param>
Public Sub Receive(ByVal data As Frame, ByVal nic As Nic)
    '現在の接続状態に基づいて適切な処理をする
    Dim info As SocketPair = GetConnectionInfo(packet.SourceAddress, segment.SourcePortNumber)
    If info Is Nothing Then
    Else
        Select Case info.State
            Case TcpConnectionState.Open
                If segment.Fin = True Then
                    'クローズ処理を行う

                    '状態遷移
                    SetConnectionInfo(packet.SourceAddress, segment.SourcePortNumber)

                    'TCPセグメント
                    Dim resultSegment As TcpSegment = New TcpSegment()
                    resultSegment.Ack = True 'ここに注目
                    resultSegment.SourcePortNumber = segment.DestinationPortNumber
                    resultSegment.DestinationPortNumber = segment.SourcePortNumber
                    resultSegment.SequenceNumber = segment.SequenceNumber
                    resultSegment.AcknowledgementNumber = CUInt(segment.SequenceNumber + 1)

                    'IPパケット
                    Dim resultIp As IPv4 = New IPv4()
                    resultIp.SourceAddress = packet.DestinationAddress
                    resultIp.DestinationAddress = packet.SourceAddress
                    resultIp.UserData = resultSegment
                    resultIp.Protocol = Protocol.TCP

                    'MACフレーム
                    Dim resultMac As MacFrame = New MacFrame()
                    resultMac.SourceAddress = mac.DestinationAddress
                    resultMac.DestinationAddress = mac.SourceAddress
                    resultMac.TypeOrLength = CUShort(EtherType.IP)
                    resultMac.UsertData = resultIp

                    '返答を送信
                    nic.Send(CType(resultMac, Frame))

                    '状態遷移
                    SetConnectionInfo(packet.SourceAddress, segment.SourcePortNumber)

                    'TCPセグメント
                    resultSegment = New TcpSegment()
                    resultSegment.Ack = True 'ここに注目
                    resultSegment.Fin = True 'ここに注目
                    resultSegment.SourcePortNumber = segment.DestinationPortNumber
                    resultSegment.DestinationPortNumber = segment.SourcePortNumber
                    resultSegment.SequenceNumber = segment.SequenceNumber
                    resultSegment.AcknowledgementNumber = CUInt(segment.SequenceNumber + 1)
                    resultMac.Data.UserData = resultSegment

                    '返答を送信
                    nic.Send(CType(resultMac, Frame))
                End If
        End Select
    End If
    '関係のないコードは省略しています
End Sub

 サーバーが送信する一つ目のセグメントは、ACKフラグをONにして、確認応答番号に次のシーケンス番号を設定したものです。このセグメントを受け取ったクライアントはサーバーに返答せず、必要な終了処理を行います。

 二つ目のセグメントは、ACKとFINフラグをNOにして、確認応答番号に次のシーケンス番号を設定したものです。このセグメントを受け取ったクライアントは、サーバー側がコネクションを終了させることに対する肯定応答のセグメントを送信せねばなりません。

(C#)
//TcpClientクラスから抜粋
/// <summary>
/// 接続を閉じます。
/// </summary>
/// <param name="data">送信するデータ</param>
/// <param name="portNumber">指定するポート番号</param>
/// <param name="sequenceNumber">シーケンス番号</param>
/// <param name="nic">使用するデバイスドライバ</param>
protected void Close ( Frame data, ushort portNumber, ref uint sequenceNumber, Nic nic )
{
    //接続を閉じるまで繰り返し処理をする
    while ( this.state != TcpConnectionState.PassiveCloseAsk ) {
        switch ( this.state ) {
            case TcpConnectionState.PassiveClose:
                segment = new TcpSegment ( );
                segment.Ack = true; //ここに注目
                segment.SequenceNumber = this.nextNumber; //ここに注目
                segment.AcknowledgementNumber = this.nextNumber; //ここに注目
                segment.SourcePortNumber = portNumber;
                segment.DestinationPortNumber = portNumber;
                data.Data.UserData = segment;
                this.state = TcpConnectionState.PassiveCloseAsk;
                nic.Send ( data );
                break;
        }
    }

    //関係のないコードは省略しています
}
(VB.NET)
'TcpClientクラスから抜粋
''' <summary>
''' 接続を閉じます。
''' </summary>
''' <param name="data">送信するデータ</param>
''' <param name="portNumber">指定するポート番号</param>
''' <param name="sequenceNumber">シーケンス番号</param>
''' <param name="nic">使用するデバイスドライバ</param>
Protected Sub Close(ByVal data As Frame, ByVal portNumber As UShort, ByRef sequenceNumber As UInteger, ByVal nic As Nic)    '接続を閉じるまで繰り返し処理をする
    While Me.state <> TcpConnectionState.PassiveCloseAsk
        Select Case Me.state
            Case TcpConnectionState.PassiveClose
                Dim segment As TcpSegment = New TcpSegment()
                segment.Ack = True 'ここに注目
                segment.SequenceNumber = Me.nextNumber 'ここに注目
                segment.AcknowledgementNumber = Me.nextNumber 'ここに注目
                segment.SourcePortNumber = portNumber
                segment.DestinationPortNumber = portNumber
                data.Data.UserData = segment
                Me.state = TcpConnectionState.PassiveCloseAsk
                nic.Send(data)
        End Select
    End While

    '関係のないコードは省略しています
End Sub

 クライアントが最後に送るセグメントはACKフラグをONにしたものです。これでコネクションの終了処理は完了です。

まとめ

 今回はトランスポート層のプロトコルであるUDPとTCPの特徴を解説しました。UDPはデータが消失しても関知しない信頼性の低いプロトコルですが、その分パフォーマンスがよく、複数の宛て先を指定できるプロトコルです。一方TCPはデータの消失を関知できる信頼性の高いプロトコルです。この2つのプロトコルの特性を覚えておくと、ネットワークの障害原因の把握・ネットワーク系のアプリケーション・Web系のシステムなどの幅広い局面に役立ちます。昨今インターネットに繋がないシステム開発はまれなので、ぜひとも習得することをお勧めします。次回はアプリケーション層に関する話題を解説します。お楽しみに。

参考資料

書籍

ウェブサイト

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
仮想ネットワークの実装で学ぶTCP/IP連載記事一覧

もっと読む

この記事の著者

インドリ(インドリ)

分析・設計・実装なんでもありのフリーエンジニア。ブログ「無差別に技術をついばむ鳥(http://indori.blog32.fc2.com/)」の作者です。アドバイザーをしたり、システム開発したり、情報処理技術を研究したりと色々しています。座右の銘は温故知新で、新旧関係なく必要だと考えたものは全て学...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/4209 2010/04/27 12:07

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング