TCP 10/24


TCP:転送制御プロトコル、制御データ転送プロセス

TCP/IPプロトコルスタック


インターネットベースの効率的なデータ伝送は大きな問題であり、大きなプロトコルとして設計されるのではなく、小さな階層化であり、TCP/IPプロトコルスタックを生成する.

各レイヤは、オペレーティングシステムやNICなどの物理デバイスです.

リンク層


リンク層は物理領域の標準化の結果である.これは最も基本的な領域であり,LAN,WAN,MANなどのネットワーク規格に関するプロトコルを定義する領域である.物理的な接続を担当します.

IP層


IP層は、データを宛先の中間経路に転送する問題を解決する.IP自体は非接続で信頼性の低いプロトコルである.データを転送するたびにパスが選択されますが、パスは固定されていません.特に、データ転送中にパスに問題が発生した場合、他のパスが見つかりますが、データ損失やエラーは解決されません.

TCPまたはUDP層


この層は、IP層が提供する経路情報に基づいて、データの実際の送受信を担当する.従って、この層を伝送層と呼ぶ.
TCPは信頼できるデータの転送を担当する.しかし、TCPがデータを送信する際のプロトコルはIPである.
IPは1つのパケットの伝送プロセスに対してのみ設計される.したがって、複数のパケットを転送しても、各パケットの転送のプロセスは信頼できない.TCPはここで信頼を高める役割を果たしている.
IPの上位層では、ホストからホストへのデータ伝送方式はTCPとUDPであり、TCPは検証プロセスを通じて信頼性の低いIPに信頼性を提供するプロトコルである.

アプリケーション層


ソケットを使用して物を作成する過程で、クライアントとサーバ間のデータの送信と受信の約束は、プログラムの性質によって決定されます.これがアプリケーションプロトコルです.
また、ほとんどのネットワークプログラミングは、アプリケーションプロトコルの設計と実装によって実現されます.

TCPベースのサーバ・クライアント実装


TCPサーバ上の関数呼び出し順序


socket() -> bind() -> listen() -> accept() -> read()/write() -> close()

接続待ち要求ステータスに入る


int listen(int sock, int backlog);
接続要求キューのサイズ情報を2番目のパラメータに渡します.5が転送されると、キューサイズは5になり、5つのクライアントの接続要求を待つことができる.
クライアントの接続要求もデータ伝送であり,ソケットが必要であり,これがサーバソケットの役割である.門番の役割を果たしたと言える.

クライアントの接続要求を受け入れる


listen関数を呼び出した後に接続要求を受信した場合は、受信した順序で受信する必要があります.サーバソケットは接続要求を受信する責任を負うため、ソケットをもう1つ作成する必要があります.
次の関数を呼び出してソケットを作成します.ソケットは、接続要求を発行したクライアントソケットに自動的に関連付けられます.
int accept(int sock, struct sockaddr addr, socklen_t addrlen);
accept関数は、呼び出しが成功したときに内部にデータI/O用のソケットを作成し、そのソケットのファイルディスクスクリプトを返します.重要な点は、ソケットが自動的に生成され、接続要求をクライアントソケットに接続することもできることです.

TCPクライアントの基本関数呼び出し順序


socket() -> connect() -> read()/write() -> close()
クライアントがconnect関数を呼び出すと、次のいずれかの場合にのみ関数が返されます.
  • サーバは接続要求を受信した.
  • ネットワーク割込みなどのエラーが発生したため、接続要求が中断された.
  • ここで、接続要求の受信はaccept関数を呼び出すのではなく、接続要求キューに登録する場合を指す.
    クライアントの場合、bind関数で直接アドレスを割り当てることなく、connect関数を呼び出すと自動的にソケットにIPとPORTが割り当てられます.IPの場合、コンピュータに割り当てられたIPはランダムに選択されたPORTである.

    Iterativeベースのサーバ、クライアント実装


    サーバはaccept関数を繰り返し呼び出して、絶えず送信されるクライアントの接続要求を受け入れる必要があります.
    echo_server.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    
    #define BUF_SIZE 1024
    void error_handling(char *message);
    
    int main(int argc, char *argv[])
    {
    	int serv_sock, clnt_sock;
    	char message[BUF_SIZE];
    	int str_len, i;
    	
    	struct sockaddr_in serv_adr;
    	struct sockaddr_in clnt_adr;
    	socklen_t clnt_adr_sz;
    	
    	if(argc!=2) {
    		printf("Usage : %s <port>\n", argv[0]);
    		exit(1);
    	}
    	
    	serv_sock=socket(PF_INET, SOCK_STREAM, 0);   
    	if(serv_sock==-1)
    		error_handling("socket() error");
    	
    	memset(&serv_adr, 0, sizeof(serv_adr));
    	serv_adr.sin_family=AF_INET;
    	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    	serv_adr.sin_port=htons(atoi(argv[1]));
    
    	if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
    		error_handling("bind() error");
    	
    	if(listen(serv_sock, 5)==-1)
    		error_handling("listen() error");
    	
    	clnt_adr_sz=sizeof(clnt_adr);
    
    	for(i=0; i<5; i++)
    	{
    		clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
    		if(clnt_sock==-1)
    			error_handling("accept() error");
    		else
    			printf("Connected client %d \n", i+1);
    	
    		while((str_len=read(clnt_sock, message, BUF_SIZE))!=0)
    			write(clnt_sock, message, str_len);
    
    		close(clnt_sock);
    	}
    
    	close(serv_sock);
    	return 0;
    }
    
    void error_handling(char *message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    
    echo_client.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    
    #define BUF_SIZE 1024
    void error_handling(char *message);
    
    int main(int argc, char *argv[])
    {
    	int sock;
    	char message[BUF_SIZE];
    	int str_len, recv_len, recv_cnt;
    	struct sockaddr_in serv_adr;
    
    	if(argc!=3) {
    		printf("Usage : %s <IP> <port>\n", argv[0]);
    		exit(1);
    	}
    	
    	sock=socket(PF_INET, SOCK_STREAM, 0);   
    	if(sock==-1)
    		error_handling("socket() error");
    	
    	memset(&serv_adr, 0, sizeof(serv_adr));
    	serv_adr.sin_family=AF_INET;
    	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    	serv_adr.sin_port=htons(atoi(argv[2]));
    	
    	if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
    		error_handling("connect() error!");
    	else
    		puts("Connected...........");
    	
    	while(1) 
    	{
    		fputs("Input message(Q to quit): ", stdout);
    		fgets(message, BUF_SIZE, stdin);
    		
    		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
    			break;
    
    		str_len=write(sock, message, strlen(message));
    		
    		recv_len=0;
    		while(recv_len<str_len)
    		{
    			recv_cnt=read(sock, &message[recv_len], BUF_SIZE-1);
    			if(recv_cnt==-1)
    				error_handling("read() error!");
    			recv_len+=recv_cnt;
    		}
    		
    		message[recv_len]=0;
    		printf("Message from server: %s", message);
    	}
    	
    	close(sock);
    	return 0;
    }
    
    void error_handling(char *message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }

    TCPの理論のストーリ


    TCPスロットに存在するI/Oバッファ


    サーバがwrite関数で40バイトを送信しても、クライアントはread関数を複数回呼び出すことで10バイトのデータを受信することができる.クライアントが先に10バイトを受信した場合、残りの30バイトはいったいどこにあるのでしょうか.
    write関数を呼び出すと、データは出力バッファに移動され、read関数を呼び出すと、データは入力バッファに取得されます.
  • I/Oバッファは、スロットごとに独立して存在します.
  • I/Oバッファはソケットの作成時に自動的に生成されます.
  • スロットをオフにしても、出力バッファのデータは転送され続けます.
  • スロットをオフにすると、入力バッファのデータは消えます.
  • TCPにはスライドウィンドウというプロトコルが存在する.これにより、入力バッファサイズを超えるデータ転送は発生しません.

    TCPの内部動作原理1:相対スロットへの接続


    3人で握手して3回会話する.

    TCPの内部操作原理2:相対スロットとのデータ受信


    転送バイトのサイズを増やします.
    データ転送中に転送に失敗した場合、タイマがタイムアウトしたときにパケットが再転送されます.

    TCPの内部動作原理3:相対スロットへの接続を終了する


    FINメッセージを交換して接続を終了します.Four way handshaking

    これらはすべてTCPフロー制御と呼ばれている.