TCP IP ネットワーク プログラミング (4) TCP ベースのサーバーとクライアント

TCP、UDPを理解する

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

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

画像の説明を追加してください

TCP/IPのプロトコルスタックは4層に分かれており、データの送受信が4階層の処理に分かれていることが分かります。

TCPプロトコルスタック

ここに画像の説明を挿入します

UDPプロトコルスタック

ここに画像の説明を挿入します

リンク層

リンク層は物理接続分野の標準化の結果であり、特に LAN、WAN、MAN などのネットワーク標準を定義する最も基本的な分野でもあります。2 つのホストがネットワークを介してデータを交換するには、次の図に示すように物理接続が必要で、リンク層がこれらの標準を担当します。

ここに画像の説明を挿入します

IP層

IP プロトコルはメッセージ指向で信頼性の低いプロトコルです。データを送信するたびにパスを選択するのに役立ちますが、一貫性はありません。伝送中に経路エラーが発生した場合は、別の経路が選択されます。ただし、データの損失やエラーが発生した場合、それを解決することはできません。IP プロトコルはデータ エラーに対処できません。

TCP/UDP層

IP 層は、データ送信におけるパス選択の問題を解決し、このパスに沿ってデータを送信するだけで済みます。TCP 層と UDP 層は、IP 層から提供される経路情報に基づいて実際のデータ伝送を完了するため、この層はトランスポート層とも呼ばれます。TCP は信頼性の高いデータ送信を保証しますが、データは IP 層に基づいて送信されます。

ここに画像の説明を挿入します

TCPとUDPはIP層の上位に存在し、ホスト間のデータ伝送方式を決定し、信頼性の低いIPプロトコルに確認を経て信頼性を与えます。

アプリケーション層

上記のクラスはソケット通信中に自動的に処理されます。データの送信経路の選択やデータの確認処理はソケット内に隠蔽されます。しかし、これらの理論を習得することによってのみ、ニーズを満たすネットワーク プログラムを作成できるようになります。

誰もが提供するツールはソケットであり、誰もがプログラムを書くためにソケットを使用するだけで済みます。ソフトウェアを作成する過程では、プログラムの特性に応じてサーバーとクライアント間のデータ送信ルールを決める必要がありますが、これがアプリケーション層プロトコルです。ネットワーク プログラミングの大部分は、アプリケーション層プロトコルの設計と実装です。

TCPベースのサーバーとクライアントを実装する

TCPサーバー側のデフォルトの関数呼び出しシーケンス

1、socket()    		创建套接字
2、bind()			分配套接字地址
3、listen()			等待连接请求状态
4、accept()			允许连接
5、read()/write()	数据交换
6、close()			断开连接

接続要求待ち状態に入る

bind関数を呼び出してソケットにアドレスを割り当てました。次にlisten関数を呼び出して接続要求待ちの状態になります。listen関数を呼び出すだけでクライアントは接続を発行できる状態になります。このとき、クライアントは connect. 関数を呼び出すことができます。

#include<sys/socket.h>

int listen(int sock, int backlog);
	成功返回0,失败返回-1
    sock	为希望进入等待连接请求状态的文件描述符,传递的描述符套接字参数为服务器端套接字
    backlog 连接请求等待队列的长度,若为5,则队列长度为5,表示最多使5个连接请求进入队列

接続要求待ち状態とは、クライアントが接続を要求したときに、接続を受け入れる前に接続が待機状態にあることを意味します。クライアントの接続要求自体もネットワークで受信されるデータの一種であり、受け入れるにはソケットが必要です。それ。

クライアント接続リクエストを受け入れる

listen 関数を呼び出した後、新しい接続要求がある場合は、順番に受け入れられる必要があります。リクエストを受け付けるとは、データを受け取れる状態になることを意味しますが、このときデータを受け付けるにはソケットが必要になりますが、サーバー側のソケットはゲートキーパーの役割を果たしており、データを受け付ける役割を果たせなくなります。したがって、別のソケットが必要です。このソケットを個人的に作成する必要はありません。accept 関数がソケットを作成し、リクエストを開始したクライアントに接続します。

#include <sys/socket.h>

int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);
	成功返回创建的套接字文件描述符,失败返回-1
    sock  	服务器套接字的文件描述符
	addr  	保存发起连接请求的客户端地址信息的变量的地址,调用函数后会向该变量填充客户端地址信息
	addrlen	第二个参数addr结构体的长度,调用函数后会向该变量填充客户端地址长度

accept 関数は接続要求を受け入れ、キュー内の保留中のクライアント接続要求を待ちます。関数呼び出しが成功すると、accept 関数は内部でデータ I/O 用のソケットを生成し、ファイル記述子を返します。ソケットは自動的に作成され、接続要求を開始したクライアントとの接続が自動的に確立されます。

TCP クライアントのデフォルトの関数呼び出し順序

TCPクライアント関数の呼び出しシーケンス

1、socket()			创建套接字
2、connect()			请求连接
3、read()/write()	交换数据
4、closr()			断开连接

サーバー側との違いは接続リクエストであり、クライアントソケットを作成した後にサーバー側に接続リクエストを開始し、サーバー側がlisten関数を呼び出してリクエスト待ちキューを作成し、クライアントがリクエストを行うことができます。接続。

#include<sys/socket.h>

int connect(int sock, struct sockaddr* servaddr, socklen_t addrlen);
	成功返回0,失败返回-1
     sock  		客户端套接字文件描述符
	 servaddr	保存目标服务器地址信息的变量地址值
	 addrlen	以字节为单位,传递第二个参数的地址变量的长度

クライアントが connect 関数を呼び出した後、次の状況が発生した場合にのみ戻ります。

  • サーバーは接続リクエストを受け入れます
  • ネットワーク切断などの異常事態により、接続要求が中断されました。

接続要求を受け付けることは、サーバーが accept 関数を呼び出すことを意味するのではなく、サーバーが接続要求の情報を待機キューに記録するため、connect 関数が戻った直後にデータ交換が行われるわけではありません。

TCP ベースのサーバー側とクライアント側の関数呼び出し関係

ここに画像の説明を挿入します

全体的なプロセスは次のとおりです。

サーバーはソケットを作成した後、bind 関数と listen 関数を継続的に呼び出して待機状態に入ります。クライアントは connect 関数を呼び出して接続要求を開始します。クライアントは、サーバーが listen 関数を呼び出すまで待機してから、connect を呼び出して接続を開始します。リクエスト。クライアントも独立している必要があります。connect 関数を呼び出す前に、サーバーは最初に accept 関数を呼び出すことができます。このとき、サーバーは accept 関数を呼び出し、クライアントが connect 関数を呼び出すまでブロック状態になります。

サーバー側とクライアント側で反復を実装する

サーバー側で反復を実装する

ここに画像の説明を挿入します

サーバー側で反復を実装する最も簡単な方法は、loop ステートメントを挿入し、accept 関数を繰り返し呼び出すことです。ループの最後の close(client) は、accept 関数を呼び出して作成されたソケットを閉じます。これは、特定のクライアントに対するサービスが終了したことを意味します。この時点でまだ他のクライアントにサービスを提供したい場合は、accept 関数を呼び出す必要があります。再び機能します。現在、同時にサービスを提供できるのは 1 つのクライアントのみですが、プロセスとスレッドを学習した後は、同時に複数のクライアントにサービスを提供するサーバーを作成できます。

反復エコーサーバー側、クライアント側

エコー サーバーとサポートされるエコー クライアントの基本的な動作モード:

  • サーバーは同時に 1 つのクライアントにのみ接続し、エコー サービスを提供します。
  • サーバーは 5 つのクライアントに順番にサービスを提供して終了します。
  • クライアントはユーザーの入力文字列を受け入れ、それをサーバーに送信します。
  • サーバーは受信した文字列データ、つまり「エコー」をクライアントに送信します。
  • 両端間の文字列エコーは、クライアントが Q に入るまで実行されます。

まずは上記の要件を満たすエコーサーバーを紹介します。

エコーサーバー.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);
}

演算結果

gcc echo_server.c -o eserver
./eserver 9190
输出:
Connecten client 1
Connecten client 2
Connecten client 3

エコークライアントコード

#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;
	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); 	//如果输入Q说明结束while循环
		fgets(message, BUF_SIZE, stdin);
		
		if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))  //检验message是否为Q/q
			break;

		write(sock, message, strlen(message));
		str_len=read(sock, message, BUF_SIZE-1);
		message[str_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);
}

演算結果

gcc echo_client.c -o eclient
./eclient 192.168.233.20 9190
输出:
Connected ....
Input message: hello
Message from server: hello
Input message : Q

「TCP/IP ネットワーク プログラミング」コラムの 4 回目の記事です。読者の皆様もぜひご購読ください。

詳細については、「 GitHub 」をクリックしてください。

⭐学術交流グループQ 754410389更新中~~~

おすすめ

転載: blog.csdn.net/m0_63743577/article/details/132707211