コネクションの終了
TCPはコネクションを終了する際にも手続きが必要です。データを送信し終わったクライアントは、サーバーへ終了したい旨を表すセグメントを送信します。この動作をアクティブ・クローズと呼びます。
//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; } } //関係のないコードは省略しています }
'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などが存在するそうです。
//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; } } //関係のないコードは省略しています }
'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にして、確認応答番号に次のシーケンス番号を設定したものです。このセグメントを受け取ったクライアントは、サーバー側がコネクションを終了させることに対する肯定応答のセグメントを送信せねばなりません。
//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; } } //関係のないコードは省略しています }
'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系のシステムなどの幅広い局面に役立ちます。昨今インターネットに繋がないシステム開発はまれなので、ぜひとも習得することをお勧めします。次回はアプリケーション層に関する話題を解説します。お楽しみに。
参考資料
書籍
- 『詳解TCP/IP Vol.1 プロトコル』 W.Richard Stevens著、橘康雄・井上尚司 訳、ピアソンエデュケーション、2000年12月
- 『マスタリングTCP/IP 入門編 第4版』 竹下隆史・村山公保・荒井透・苅田幸雄 著、オーム社、2007年2月
- 『インターネットRFC事典』 笠野英松 著、マルチメディア通信研究会 編、アスキー、1998年10月
ウェブサイト
- @IT 『Master of IP Network』