I.はじめに
Linuxシステムによって提供される従来のプロセス間通信メカニズム(IPC):パイプ、共有メモリ、メッセージキュー、およびセマフォ。これらのメカニズムにより、同じコンピューターで実行されているプロセスが相互に通信したり、異なるコンピューター(ネットワーク)プロセス間通信は、新しいメカニズムを導入します。ネットワークプロセス間通信、プロセスはソケットネットワークプロセス間通信インターフェイスを介して相互に通信でき、さまざまなネットワークプロトコルを使用してソケットインターフェイスと通信できます。
1. 5層ネットワークプロトコルスタック:
アプリケーション層プロトコル:FTP、HTTP、SMTP
トランスポート層プロトコル:TCPプロトコル、UDPプロトコル
ネットワーク層プロトコル:IPプロトコル
この章で説明するTCPプロトコルとUDPプロトコルは、トランスポート層に属します。5層構造の特定の機能については、https://blog.csdn.net/weixin_37719279/article/details/82846226を参照してください。
2.TCPプロトコルとUDPプロトコルの違い
TCPはコネクション型の信頼できるサービスを上位層に提供し、UDPはコネクションレス型で信頼性の低いサービスを上位層に提供します。UDPはTCP送信ほど正確ではありませんが、リアルタイム要件の高いシナリオでよく使用されます。
3.TCPのスリーウェイハンドシェイクと4つのウェーブハンド
(1)3回のハンドシェイク
最初のハンドシェイク:接続を確立すると、クライアントはsynパケット(syn = j)をサーバーに送信し、サーバーが確認するのを待ってSYN_SENT状態に入ります。SYN:シーケンス番号を同期します。
2番目のハンドシェイク:サーバーはsynパケットを受信し、クライアントのSYN(ack = j + 1)を確認すると同時に、SYNパケット(syn = k)、つまりSYN + ACKパケットを送信する必要があります。サーバーはSYN_RECV状態になります。
3番目のハンドシェイク:クライアントはサーバーからSYN + ACKパケットを受信し、確認応答パケットACK(ack = k + 1)をサーバーに送信します。パケットが送信された後、クライアントとサーバーはESTABLISHED(TCP接続に成功)に入ります。 )3回完了した状態で握手します。
(2)4回振る
a。クライアントプロセスは接続解放メッセージを送信し、データの送信を停止します。データメッセージのヘッダーFIN = 1を解放し、そのシーケンス番号はseq = u(プラス1の前に送信されたデータの最後のバイトのシーケンス番号に等しい)、この時点で、クライアントはFIN-に入ります。 WAIT-1(待機を終了1)ステータス。TCPは、FINセグメントがデータを伝送しない場合でも、シーケンス番号を消費することを規定しています。
b。サーバーは接続解放メッセージを受信し、確認メッセージACK = 1、ack = u + 1を送信し、独自のシリアル番号seq = vをもたらします。このとき、サーバーはCLOSE-WAIT(クローズ待機)ステータスになります。 。TCPサーバーは、クライアントがサーバーの方向に解放されたことを高レベルのアプリケーションプロセスに通知します。この時点では、クライアントはハーフクローズ状態です。つまり、クライアントには送信するデータがありませんが、サーバーがデータを送信しても、クライアントはそれを受け入れる必要があります。この状態はしばらく続きます。つまり、CLOSE-WAIT状態全体が持続します。
c。クライアントがサーバーから確認要求を受信した後、この時点で、クライアントはFIN-WAIT-2(終了待機2)状態に入り、サーバーが接続解放メッセージを送信するのを待機します(その前に、最後のデータを受け入れます)。
d。サーバーは最終データを送信した後、接続解放メッセージFIN = 1、ack = u + 1をクライアントに送信します。これは、サーバーがセミクローズ状態であるため、サーバーがさらにデータを送信する可能性が高いためです。今回の想定シーケンス番号はseq = wです。このとき、サーバーはLAST-ACK(最終確認)状態になり、クライアントの確認を待ちます。
e。クライアントがサーバーから接続解放メッセージを受信した後、確認応答ACK = 1、ack = w + 1を送信する必要があり、シリアル番号はseq = u + 1です。このとき、クライアントはTIME-に入ります。 WAIT(待機時間)ステータス。この時点ではTCP接続は解放されていないことに注意してください。2∗∗ MSL(メッセージセグメントの最長寿命)時間を経過する必要があり、クライアントが対応するTCBをキャンセルすると、CLOSED状態になります。
f。サーバーがクライアントから確認を受信している限り、サーバーはすぐにCLOSED状態になります。同様に、TCBが取り消された後、TCP接続は終了します。ご覧のとおり、サーバーはクライアントよりも早くTCP接続を終了します。
詳細なリファレンス:https://www.cnblogs.com/bj-mr-li/p/11106390.html
2:ネットワークプログラミング
1.アドレス指定
(1)バイトオーダー
アプリケーションでは、プロセッサのバイト順序とネットワークのバイト順序を切り替える必要がある場合があります。アプリケーションの場合、プロセッサのバイト順序とネットワークのバイト順序を変換するために使用される4つの関数があります。
「H」は「ホスト」バイトオーダー、「n」は「ネットワーク」バイトオーダー、「l」は「ロング」(つまり4バイト)整数、「s」は「ショート」(つまり2バイト)整数を意味します。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32); 返回值:以网络字节序表示的32位整数
uint16_t htons(uint16_t hostint16); 返回值:以网络字节序表示的16位整数
uint32_t ntohl(uint32_t netint32); 返回值:以主机字节序表示的32位整数
uint16_t ntohs(uint16_t netint16); 返回值:以主机字节序表示的16位整数
(2)アドレス形式
アドレスは、特定の通信ドメインのソケットエンドポイントを識別します。アドレス形式は、この特定の通信ドメインに関連しています。さまざまな形式のアドレスをソケット関数に渡すことができるようにするために、アドレスは一般的なアドレス構造に強制されます。
struct sockaddr{
unisgned short sa_family; /*地址族*/
char sa_data[14]; /*14字节的协议地址,包含该socket的IP地址和端口号。*/
};
システムの互換性の問題により、新しいアドレス構造は次のことを示すために使用されます。
struct sockaddr_in{
unsigned short sin_family; /*地址族*/
unsigned short int sin_port; /*端口号*/
struct in_addr sin_addr; /*IP 地址*/
unsigned char sin_zero[8]; /*填充0 以保持与struct sockaddr 同样大小*/
}
(3)アドレス形式の変換
通常、ユーザーはアドレスを表現するときにドット付き10進表記(またはコロンで区切られたIPv6アドレス)を使用しますが、バイナリ値は一般的に使用されるソケットプログラミングで使用され、これら2つを組み合わせる必要があります。値は変換されます。ここでIPv4で使用される関数はinet_aton()、inet_addr()、inet_ntoa()であり、IPv4とIPv6と互換性のある関数はinet_pton()とinet_ntop()です。
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr)
int inet_ntop(int family, void *addrptr, char *strptr, size_t len)
family:{
AF_INET:IPv4 协议
AF_INET6:IPv6 协议
}
strptr:要转化的值
addrptr:转化后的地址
len:转化后值的大小
返回值:成功0、出错-1
inet_atonは、文字列IPアドレスを32ビットネットワークシリアルIPアドレスに変換するための改善された方法です。
1入力パラメータ文字列にはASCIIIPアドレスが含まれています。
2出力パラメータaddrは、新しいIPアドレスで更新される構造体です。
この関数が成功した場合、関数の戻り値はゼロ以外です。入力アドレスが正しくない場合、ゼロが返されます。この関数を使用する場合、errnoにエラーコードが格納されないため、その値は無視されます。
例:
struct sockaddr_in socket_server_addr;
socket_server_addr.sin_family = AF_INET;
/*主机字节序转换为网络字节序*/
socket_server_addr.sin_port = htons(SERVER_PORT);
if (inet_aton(“192.168.1.100”, &socket_server_addr.sin_addr) == 0)
{
printf("invalid server ip\n");
return -1;
}
(4)名前アドレス変換
一般に、人々は使用中に長いIPアドレスを記憶することに消極的であり、特にIPv6では、アドレスの長さは最大128ビットであり、そのような長いIPアドレスを何度も記憶することはさらに不可能です。したがって、ホスト名を使用することをお勧めします。Linuxには、ホスト名とアドレスの変換を実現できる関数もいくつかあります。最も一般的な関数は、gethostbyname()、gethostbyaddr()、getaddrinfo()などで、これらはすべてIPv4とIPv6の間の変換を実現できます。アドレスとホスト名。その中で、gethostbyname()はホスト名をIPアドレスに変換し、gethostbyaddr()はIPアドレスをホスト名に変換する逆演算です。さらに、getaddrinfo()はIPv4アドレスとIPv6アドレスを自動的に識別できます。
struct hostent
{
char *h_name;/*正式主机名*/
char **h_aliases;/*主机别名*/
int h_addrtype;/*地址类型*/
int h_length;/*地址字节长度*/
char **h_addr_list;/*指向 IPv4 或 IPv6 的地址指针数组*/
}
struct addrinfo
{
int ai_flags;/*AI_PASSIVE, AI_CANONNAME;*/
int ai_family;/*地址族*/
int ai_socktype;/*socket 类型*/
int ai_protocol;/*协议类型*/
size_t ai_addrlen;/*地址字节长度*/
char *ai_canonname;/*主机名*/
struct sockaddr *ai_addr;/*socket 结构体*/
struct addrinfo *ai_next;/*下一个指针链表*/
}
ai_flags{
AI_PASSIVE:该套接口是用作被动地打开
AI_CANONNAME:通知 getaddrinfo 函数返回主机的名字
}
ai_family{
AF_INET:IPv4 协议
AF_INET6:IPv6 协议
AF_UNSPEC:IPv4 或 IPv6 均可
}
ai_socktype {
SOCK_STREAM:字节流套接字 socket(TCP)
SOCK_DGRAM:数据报套接字 socket(UDP)
}
ai_protocol{
IPPROTO_IP:IP 协议
IPPROTO_IPV4:IPv4 协议 4 IPv4
IPPROTO_IPV6:IPv6 协议
IPPROTO_UDP:UDP
IPPROTO_TCP:TCP
}
#include <netdb.h>
struct hostent *gethostbyname(const char *hostname)
hostname:主机名
返回值:成功返回hostent类型指针、错误-1
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints,struct addrinfo **result)
node:网络地址或者网络主机名
service:服务名或十进制的端口号字符串
hints:服务线索
result:返回结果
返回值:成功0、错误-1
(1)通常、サーバー側でgetaddrinfo()を呼び出す前に、ai_flagsはbind()関数(後で説明するポートとアドレスのバインドに使用)にAI_PASSIVEを設定し、ホスト名nodenameは通常NULLに設定されます。
(2)クライアントがgetaddrinfo()を呼び出す場合、ai_flagsは通常AI_PASSIVEを設定しませんが、ホスト名nodenameおよびサービス名servname(ポート)は空であってはなりません。
(3)ai_flagsがAI_PASSIVEに設定されていなくても、取得したアドレスをバインドできます。多くのプログラムでは、ai_flagsが直接0に設定されている、つまり3つのフラグビットが設定されていません。この場合、ホスト名である限りとservnameは問題なく設定されています。正しくバインドできます。
例:
/* getaddrinfo.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
struct addrinfo hints, *res = NULL;
int rc;
memset(&hints, 0, sizeof(hints));
/*设置 addrinfo 结构体中各参数 */
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
/*调用 getaddinfo 函数*/
rc = getaddrinfo("localhost", NULL, &hints, &res);
if (rc != 0)
{
perror("getaddrinfo");
exit(1);
}
else
{
printf("Host name is %s\n", res->ai_canonname);
}
exit(0);
}
2.ソケットの基本的なプログラミング
(1)ソケット記述子
ソケットを作成します。
#include <sys/socket.h>
int socket(int domain, int type,int protocol);
domain:确定通信的特性,包括地址格式
AF_INET:IPv4因特网域
AF_INET6:IPv6因特网域
AF_UNIX:UNIX域
AF_UPSPEC:未指定
type:确定套接字类型
SOCK_DGRAM:固定长度的,无连接的,不可靠的报文传递(UDP)
SOCK_RAW:IP协议的数据报接口
SOCK_SEQPACKET:固定长度的,有序的,可靠的,面向连接的报文传递
SOCK_STREAM:有序的,可靠的,双向的,面向连接的字节流(TCP)
protocol:通常为0,表示为给定的域和套接字类型选择默认协议
ソケットを閉じます。
#include <sys/socket.h>
int shutdown(int sockfd, int how);
how:SHUT_RD(关闭读端),无法从套接字读取数据
SHUT_WR(关闭写端),无法从套接字发送数据
SHUT_RDWR,无法读取和发送数据
(2)サーバーアソシエートソケットとアドレス
int bind(int sockfd, struct sockaddr *addr, int addrlen);
示例:
struct sockaddr_in socket_server_addr;
duty_socket = socket(AF_INET, SOCK_STREAM, 0);
/* 服务器端填充 sockaddr_in结构 */
socket_server_addr.sin_family = AF_INET;
/*端口号转换为网络字节序*/
socket_server_addr.sin_port = htons(SERVER_PORT);
/*接收本机所有网口的数据*/
socket_server_addr.sin_addr.s_addr = INADDR_ANY;
memset(socket_server_addr.sin_zero, 0, 8);
/* 捆绑sockfd描述符 */
ret = bind(duty_socket, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));
(3)サーバーはlisten関数を呼び出して接続要求を受け入れます
int listen(int sockfd,int backlog);
ret = listen(duty_socket, C_QUEUE);
sockfd是bind后的文件描述符。
backlog设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
listen函数将bind的文件描述符变为监听套接字,返回的情况和bind一样。
(4)サーバーは接続要求を取得し、接続を確立します
int accept(int sockfd, struct sockaddr *addr,int *addrlen);
int customer_socket;
customer_socket = accept(duty_socket, (struct sockaddr *)&socket_client_addr, &addr_len);
sockfd是listen后的文件描述符。
addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen和accept是服务器端用的函数。
accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。 accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。
(5)クライアントが接続を確立する
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);
ret = connect(client_socket, (const struct sockaddr *)&socket_server_addr, sizeof(struct sockaddr));
可以用connect建立一个连接,在connect中所指定的地址是想与之通信的服务器的地址。
sockfd是socket函数返回的文件描述符。
serv_addr储存了服务器端的连接信息,其中sin_add是服务端的地址。
addrlen是serv_addr的长度
connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符,失败时返回-1。
(6)データを送信する
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd 指定发送端套接字描述符;
buf 指明一个存放应用程序要发送数据的缓冲区;
len 指明实际要发送的数据的字节数;
flags 一般置0。
客户或者服务器应用程序都用send函数来向TCP连接的另一端发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sendto和send相似,区别在于sendto允许在无连接的套接字上指定一个目标地址。
dest_addr 表示目地机的IP地址和端口号信息,
addrlen 常常被赋值为sizeof (struct sockaddr)。
sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。
(7)データを受け入れる
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd 指定接收端套接字描述符;
buf 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
len 指明buf的长度;
flags 一般置0。
客户或者服务器应用程序都用recv函数从TCP连接的另一端接收数据。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
recvfrom通常用于无连接套接字,因为此函数可以获得发送者的地址。
src_addr 是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。
addrlen 常置为sizeof (struct sockaddr)。
TCPの例:
インスタンスは、クライアントとサーバーの2つの部分に分かれています。サーバーは、最初にソケットを確立し、次にローカルポートにバインドし、次にクライアントからの接続要求の受信とクライアントとの接続の確立を開始し、次にclient送信されたメッセージ。ソケットが確立された後、クライアントはconnect()関数を呼び出して接続を確立します。
サーバ:
/*server.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321
#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5
int main()
{
struct sockaddr_in server_sockaddr,client_sockaddr;
int sin_size,recvbytes;
int sockfd, client_fd;
char buf[BUFFER_SIZE];
/*建立 socket 连接*/
if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
{
perror("socket");
exit(1);
}
printf("Socket id = %d\n",sockfd);
/*设置 sockaddr_in 结构体中相关参数*/
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_sockaddr.sin_zero), 8);
int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
/*绑定函数 bind()*/
if (bind(sockfd, (struct sockaddr *)&server_sockaddr,
sizeof(struct sockaddr)) == -1)
{
perror("bind");
exit(1);
}
printf("Bind success!\n");
/*调用 listen()函数,创建未处理请求的队列*/
if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
{
perror("listen");
exit(1);
}
printf("Listening....\n");
/*调用 accept()函数,等待客户端的连接*/
if ((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr, &sin_size)) == -1)
{
perror("accept");
exit(1);
}
/*调用 recv()函数接收客户端的请求*/
memset(buf , 0, sizeof(buf));
if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) == -1)
{
perror("recv");
exit(1);
}
printf("Received a message: %s\n", buf);
close(sockfd);
exit(0);
}
クライアント:
/*client.c*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 4321
#define BUFFER_SIZE 1024
int main(int argc, char *argv[])
{
int sockfd,sendbytes;
char buf[BUFFER_SIZE];
struct hostent *host;
struct sockaddr_in serv_addr;
if(argc < 3)
{
fprintf(stderr,"USAGE: ./client Hostname(or ip address) Text\n");
exit(1);
}
/*地址解析函数*/
if ((host = gethostbyname(argv[1])) == NULL)
{
perror("gethostbyname");
exit(1);
}
memset(buf, 0, sizeof(buf));
sprintf(buf, "%s", argv[2]);
/*创建 socket*/
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
/*设置 sockaddr_in 结构体中相关参数*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero), 8);
/*调用 connect 函数主动发起对服务器端的连接*/
if(connect(sockfd,(struct sockaddr *)&serv_addr,
sizeof(struct sockaddr))== -1)
{
perror("connect");
exit(1);
}
/*发送消息给服务器端*/
if ((sendbytes = send(sockfd, buf, strlen(buf), 0)) == -1)
{
perror("send");
exit(1);
}
close(sockfd);
exit(0);
}
結果:
$ ./server
Socket id = 3
Bind success!
Listening....
Received a message: Hello,Server!
$ ./client localhost(或者输入 IP 地址) Hello,Server!
3.高度なネットワークプログラミング
実際の状況では、複数のクライアントがサーバーに接続する状況に遭遇することがよくあります。以前に導入されたconnet()、recv()、send()はすべてブロッキング関数であるため、リソースの準備ができていない場合、この関数を呼び出すプロセスはスリープ状態になり、I / O多重化を処理できなくなります。使用されている。このセクションでは、I / O多重化を解決するための2つのソリューションを示します。これらの2つの関数は、以前に学習したfcntl()とselect()です。
(1)fcntl()
関数fcntl()は、ソケットプログラミング用に次のプログラミング機能を提供します。
- ノンブロッキングI / O:cmdをF_SETFLに設定し、ロックをO_NONBLOCKに設定できます。
- 非同期I / O:cmdをF_SETFLに設定し、ロックをO_ASYNCに設定できます。
/* net_fcntl.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#define PORT 1234
#define MAX_QUE_CONN_NM 5
#define BUFFER_SIZE 1024
int main()
{
struct sockaddr_in server_sockaddr, client_sockaddr;
int sin_size, recvbytes, flags;
int sockfd, client_fd;
char buf[BUFFER_SIZE];
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_sockaddr.sin_zero), 8);
int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
if (bind(sockfd, (struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)
{
perror("bind");
exit(1);
}
if(listen(sockfd,MAX_QUE_CONN_NM) == -1)
{
perror("listen");
exit(1);
}
printf("Listening....\n");
/* 调用 fcntl()函数给套接字设置非阻塞属性 */
flags = fcntl(sockfd, F_GETFL);
if (flags < 0 || fcntl(sockfd, F_SETFL, flags|O_NONBLOCK) < 0)
{
perror("fcntl");
exit(1);
}
while(1)
{
sin_size = sizeof(struct sockaddr_in);
if ((client_fd = accept(sockfd,(struct sockaddr*)&client_sockaddr, &sin_size)) < 0)
{
perror("accept");
exit(1);
}
if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) < 0)
{
perror("recv");
exit(1);
}
printf("Received a message: %s\n", buf);
} /*while*/
close(client_fd);
exit(1);
}
$ ./net_fcntl
Listening....
accept: Resource temporarily unavailable
可以看到,当 accept()的资源不可用(没有任何未处理的等待连接的请求)时,程序就会自动返回。
(2)select()
fcntl()関数は、ノンブロッキングI / Oまたは信号駆動型I / Oを実現するために使用できますが、実際の使用では、リソースが循環できるかどうかをテストすることが多く、不要なCPUリソースの消費が大幅に増加します。ここでは、select()関数を使用してこの問題を解決できます。同時に、select()関数を使用して待機時間を設定します。これは、より強力であると言えます。
/* net_select.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#define PORT 4321
#define MAX_QUE_CONN_NM 5
#define MAX_SOCK_FD FD_SETSIZE
#define BUFFER_SIZE 1024
int main()
{
struct sockaddr_in server_sockaddr, client_sockaddr;
int sin_size, count;
fd_set inset, tmp_inset;
int sockfd, client_fd, fd;
char buf[BUFFER_SIZE];
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_sockaddr.sin_zero), 8);
int i = 1;/* 允许重复使用本地地址与套接字进行绑定 */
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
if (bind(sockfd, (struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)
{
perror("bind");
exit(1);
}
if(listen(sockfd, MAX_QUE_CONN_NM) == -1)
{
perror("listen");
exit(1);
}
printf("listening....\n");
/*将调用 socket()函数的描述符作为文件描述符*/
FD_ZERO(&inset);
FD_SET(sockfd, &inset);
while(1)
{
tmp_inset = inset;
sin_size=sizeof(struct sockaddr_in);
memset(buf, 0, sizeof(buf));
/*调用 select()函数*/
if (!(select(MAX_SOCK_FD, &tmp_inset, NULL, NULL, NULL) > 0))
{
perror("select");
}
for (fd = 0; fd < MAX_SOCK_FD; fd++)
{
if (FD_ISSET(fd, &tmp_inset) > 0)
{
if (fd == sockfd)
{ /* 服务端接收客户端的连接请求 */
if ((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr, &sin_size))== -1)
{
perror("accept");
exit(1);
}
FD_SET(client_fd, &inset);
printf("New connection from %d(socket)\n", client_fd);
}
else /* 处理从客户端发来的消息 */
{
if ((count = recv(client_fd, buf, BUFFER_SIZE, 0)) > 0)
{
printf("Received a message from %d: %s\n",
client_fd, buf);
}
else
{
close(fd);
FD_CLR(fd, &inset);
printf("Client %d(socket) has left\n", fd);
}
}
} /* end of if FD_ISSET*/
} /* end of for fd*/
} /* end if while while*/
close(sockfd);
exit(0);
}
$ ./server
listening....
New connection from 4(socket) /* 接受第一个客户端的连接请求*/
Received a message from 4: Hello,First! /* 接收第一个客户端发送的数据*/
New connection from 5(socket) /* 接受第二个客户端的连接请求*/
Received a message from 5: Hello,Second! /* 接收第二个客户端发送的数据*/
Client 4(socket) has left /* 检测到第一个客户端离线了*/
Client 5(socket) has left /* 检测到第二个客户端离线了*/
$ ./client localhost Hello,First! & ./client localhost Hello,Second