基本的なプログラミングプロセスの概要
クライアントとサーバー間の通信を実現するには、クライアントとサーバーを一緒に完了する必要があります。その中で、TCPサーバーとクライアントのプログラミングプロセスを図に示します。
最初にサーバーがソケットを作成し、次にバインドが通信ポートをバインドし、リッスンキューを作成します。その後、コミュニケーションのプロセスが始まりました。この時点で、サーバーは、tcpクライアントがイニシアチブを取り、接続を確立するための3ウェイハンドシェイクに接続するまで、受け入れ時にブロックされます。このとき、クライアントの書き込み機能がサーバーにリクエストを送信し、サーバーが読み取りを行ってリクエストを読み取り、処理します。処理が完了すると、サーバーの書き込み関数が呼び出されてクライアントに応答メッセージが送信され、クライアントは読み取りを通じて応答メッセージを受信します。この時点で、データ交換全体が終了し、クライアントはアクティブにシャットダウンできます。
クライアントとサーバーでのソケットプログラミング
ファミリは通常、IPV4プロトコルではAF_INETであり、IPv6プロトコルではAF_INET6です。タイプは、TCP伝送プロトコルではSOCK_STREAM、UDPプロトコルではSOCK_DGRAMです。通常、元のソケットで使用されていない限り、関数ソケットのパラメータプロトコルは0に設定されます。
クライアント接続機能は、サーバーとのリンクを確立します
TCPクライアントは、接続機能を使用してTCPサーバーとの接続を確立します。
sockfdは、ソケット関数の戻り値です。2番目のパラメーターはソケットアドレス構造体へのポインターであり、3番目のパラメーターは構造体のサイズです。
sin_portはポート番号、S_addrはサーバーネットワークのIPアドレスです。このプログラムを通じて、クライアントはクライアントのポートとIPに接続できます。つまり、リンクを確立できます。
バインドバインドソケットとIPポート
バインドは、ローカルプロトコルアドレスをソケットに割り当てます。ネットワークプロトコルの場合、プロトコルアドレスは32ビットIPv4アドレスまたは128ビットIPv6アドレスです。
最初のパラメーターはソケット関数の戻り値であり、2番目と3番目は接続関数で説明されているものと同じです。
サーバー用とクライアント用の2つのソケットオブジェクトを確立します。
サーバーリッスンリッスンキュー
listenはTCPサーバーによってのみ呼び出され、主に2つの機能を
実行します。バックログはリンクの最大数を指します。特定のリスニングソケットに対して、カーネルは2つのキューを維持します
。1。リンクキューを完了するため
。2。未完了の接続キューの
バックログの前に、2つのキューの合計の最大値として定義されています。通常、テスト環境では、バックログを5に設定します。しかし、最新のサーバーには十分とは言えません。バックログを0に設定することはできません。これにより、プログラムに欠陥が発生します。他の人に接続させたくない場合は、このポートを閉じる必要があります。
サーバー側の受け入れ
Acceptはサーバーによって呼び出され、クライアントから送信された接続要求をリンクします。これにより、3つの手の波を介して接続が確立されます。
受け入れの実行が成功した場合、戻り値はカーネルによって自動的に生成された新しい記述子であり、クライアントとのTCP接続を表すことに注意してください。この記述子は接続ソケットと呼ばれ、ソケット関数の戻り値はリスニングソケットと呼ばれます。リスニングソケットsockfdは、多くの場合、サーバープログラムで1つしか生成せず、常に存在します。接続されたソケットCは、クライアントとサーバーが3ウェイハンドシェイクを完了したことを意味します。クライアントが切断されると、Cソケットは閉じられます。
もちろん、C <0の場合、リンクの確立に失敗したことを意味し、クライアントが接続するのをブロックして待機します。
書く和読む
書き込みと読み取りは2つの状態であり、それぞれ処理データと受信データを表します。たとえば、プログラムが通信する場合、通常、書き込みは送信システムコールで表され、読み取りは通常、受信システムコールで表されます。
クライアント側での送信と受信:サーバー側での送信と受信
:
ソケットを閉じる閉じる
この時点で、TCPソケットのプログラミングプロセスは基本的に終了しており、ソケット記述子を閉じるだけで済みます。
実験結果
リンクを確立するためのスリーウェイハンドシェイク:
データの読み取りとクライアントからのデータの送信を続行します:
サーバーのソースコード:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(-1!=sockfd);
struct sockaddr_in saddr,caddr;
memset(&saddr,0,sizeof(saddr)); //注意memset是取地址
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);//主机转网络字节,网络是大段;
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
listen(sockfd,5); //以完成3次握手队列长度是5
while(1)
{
int len=sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
//这里是c也是套接字,类似与上面代码的sockfd;
if(c<0)
{
continue;
}
printf("accept c=%d\n",c); //链接套接子
while(1)
{
char buff[128]={
0};
int n=recv(c,buff,127,0);
//如果对方关闭了发送,recv返回0;-1是失败
if(n<=0)
{
break;
}
printf("buff=%s\n",buff);
send(c,"ok",2,0);
}
close(c);
}
}
クライアントのソースコード:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res =connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res!=-1);
while(1)
{
char buff[128]={
0};
printf("input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,128,0);
printf("buff=%s\n",buff);
}
exit(0);
}