本文最后更新于:2023年4月15日 晚上
0 | 考点 四-六道手写代码大题, 非常硬核
由于得到的往年题资源较多, 而本课程知识比较分散, 故复习手段采用「以往年题为中心带出相关知识点」的方式
重点范围: tcp和udp服务器(包括基本模型,超时,地址可重用,字节顺序,僵尸,并发);高级套接字接收函数的几个选项;正向和反向代理原理;管道和有名管道;守护进程;udp广播;延迟创建子进程。
可省略部分
头文件不需要写
bind()
, connect()
, accepct()
, write()
, sendto()
等基本套接字函数不用写参数
不考虑EINTR
的错误和read()
返回0的情况
填写sockaddr
和sockaddr_in
时, 可以用SET_ADDR_PORT
单播和SET_ADDR_PORT_255
广播代替
char->int用CharToInt
代替, int->char用IntToChar
代替
重复代码块可以标上序号, 之后复用
1 | 2017年题目 题目1
考点 由于摄像机只有「一台」,故不需要多个子进程, 采用一个父进程+一个子进程的方式, 控制子进程的时间为2分钟(设置超时120s), 父进程等待子进程结束后再监听
技术要点 TCP: socket() - bind() - listen() - accept() - close() 模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int sock, connfd;if ((sock = socket (AF_INET, SOCK_STREAM, 0 )) < 0 ) { exit (1 ); }; SET_ADDR_PORT; if (bind () < 0 ) exit (1 ); if (listen (sock, 5 ) < 0 ) exit (1 ); for (;;) { connfd = accept (); if (connfd < 0 ) exit (1 ); ... wait (); close (connfd); }
如何设置超时 - 利用信号 模板:
1 2 3 4 5 6 7 8 9 10 11 12 void sigalrm_handler (int sig) { exit (1 ); }int main () { struct sigaction sigact; sigact.sa_handler = sigalrm_handler; sigact.sa_mask = 0 ; sigact.sa_flags = 0 ; sigaction (SIGALRM, &sigact, NULL ); }
老师说直接signal(SIGALRM, sigalrm_handler)
也可以
创建子进程 pid_t pid = fork()
pid == 0
为子进程
pid > 0
为父进程
完整代码 (老师的标程)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void sigalrm_handler (int sig) { exit (0 ); }int main () { int sock, connfd; struct sigaction sigact; sigact.sa_handler = sigalrm_handler; sigact.sa_flags = 0 ; sigact.sa_mask = 0 ; sigaction (SIGALRM, &sigact, NULL ); if ((sock == socket (AF_INET, SOCK_STREAM, 0 )) < 0 ) { exit (1 ); } SET_ADDR_PORT; if (bind () < 0 ) exit (1 ); if (listen (sock, 5 ) < 0 ) exit (1 ); for (;;) { connfd = accept (); if (connfd < 0 ) exit (1 ); if (fork() == 0 ) { close (sock); alarm (120 ); CONTROL_CAMERA; exit (0 ); } close (connfd); wait (NULL ); } close (sock); exit (0 ); }
题目2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 int main () { signal (SIG_CHLD, SIG_IGN); int listenfd = socket (AF_INET, SOCK_STREAM, 0 ); if (listenfd < 0 ) exit (1 ); SET_ADDR_PORT; bind (); listen (listenfd, 5 ); for (;;) { int connfd = accept (); if (fork() == 0 ) { close (listenfd); char buf[1024 ]; read (connfd, buf, 1024 ); string outbuf (buf) ; cout << outbuf << endl; close (connfd); exit (0 ); } string str; cin >> str; write (connfd, str, sizeof (str)); close (connfd); close (listenfd); exit (0 ); } }int sock;char buf[1024 ];void sigio_handler (int sig) { int n = recvfrom (sock, buf, 1024 , 0 , (struct sockaddr*) &addr, &addrlen); if (n < 0 ) exit (1 ); string outbuf (buf) ; cout << outbuf << endl; }int main () { signal (SIGIO, sigio_handler); sock = socket (AF_INET, SOCK_DGRAM, 0 ); SET_ADDR_PORT; bind (); if (sock < 0 ) exit (1 ); int on = 1 ; fcntl (sock, F_SETOWN, getpid ()); ioctl (sock, FIOASYNC, &on); string msg; cin >> msg; sendto (sockfd, msg.c_str (), sizeof (msg.c_str ()), 0 , (struct sockaddr*) &addr1, sizeof (addr1)); fcntl () }
考点 根据题目要求, 不允许读写阻塞的情况发生
选择TCP协议工作, 采用一个主进程 + 一个子进程, 主进程负责收, 子进程负责发
技术要点
前面提到过的不再写
僵尸进程 - 父进程捕获子进程的SIGCHLD信号 和上面处理超时的逻辑一致, 不过信号变为SIGCHLD
1 2 3 4 5 6 7 8 9 void sigchild_handler (int sig) { wait (NULL ); } struct sigactio sigact; sigact.sa_handler = sigchild_handler; sigact.sa_mask = 0 ; sigact.sa_flags = 0 ;sigaction (SIGCHLD, &sigact, NULL );
老师说直接signal(SIGCHLD, SIG_IGN)
也可以
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 void sigchild_handler (int sig) { wait (NULL ); } int main () { int sock, connfd, sock2; struct sockaddr_int addr; string str; struct sigaction sigact; sigact.sa_hadnler = sigchild_handler; sigact.sa_mask = 0 ; sigact.sa_flags = 0 ; sigaction (SIGCHLD, &sigact, NULL ); if (fork() == 0 ) { if ((sock2 = socket (AF_INET, SOCK_STREAM, 0 )) < 0 ) exit (1 ); SET_ADDR_PORT; if (connect () < 0 ) exit (1 ); cin >> str; write (sock2, str.c_str (), sizeof (str)); close (sock2); } if ((sock = socket (AF_INET, SOCK_STREAM, 0 )) < 0 ) exit (1 ); SET_ADDR_PORT; if (bind () < 0 ) exit (1 ); if (listen (sock, 5 ) < 0 ) exit (1 ); for (;;) { connfd = accept (); if (connfd < 0 ) exit (1 ); char ch[1024 ]; int n = read (connfd, ch, 1024 ); close (connfd); string str (ch) ; cout << str; } close (sock); }
题目3
考点 UDP协议的使用, 同时控制10w个灯, TCP难以满足实时性, 故采用UDP+位运算的方式, 用10w位的01组合代表每个灯的亮灭情况, 每个灯根据自己的编号取出状态位并将自己打开/关闭
将10w位以文件的方式存放在磁盘上
技术要点 UDP的连接建立 将tcp中的SOCK_STREAM
改为SOCK_DGRAM
即可
sock = socket(AF_INET, SOCK_DGRAM, 0)
注意本题中UDP采用广播的方式, 故使用SET_ADDR_PORT_255
完整代码 1 2 3 4 5 6 7 8 9 10 11 int main () { int sock; struct sockaddr_in addr; string str; if ((sock = socket (AF_INET, SOCK_DGRAM, 0 )) < 0 ) exit (1 ); SET_ADDR_PORT_255; int len = READ_FILE (char * buf); int n = send (sock, buf, len); close (sock); }
题目4 ICMP…?
题目5
考点 由于匿名管道只能在父子进程之间通信
若要实现子进程之间的管道通信, 只能采取子进程发送给父进程, 父进程转发给另一个子进程的方式
技术要点 管道的建立和读写 管道创建:
1 2 int pipe1[2 ]; if (pipe (pipe1) < 0 ) cout << "pipe error" ;
在读写时, 如果需要读, 就关闭写端, 如果需要写, 就关闭读端
如果不关闭写端, 由于管道是阻塞的, 在read时会一直阻塞住, 如果写端已经关闭, 在管道为空时read会返回0
1 2 3 4 5 close (pipe1[1 ]);close (pipe2[0 ]);close (pipe3[0 ]); close (pipe3[1 ]); close (pipe4[0 ]); close (pipe4[1 ]);
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 int main () { int pipe1[2 ], pipe2[2 ], pipe3[2 ], pipe4[2 ]; char psend1[] = "hello" ; char psend2[] = "welcome" ; char precv1[16 ]; char precv2[16 ]; pid_t pid; if (pipe (pipe1) < 0 || pipe (pipe2) < 0 || pipe (pipe3) < 0 || pipe (pipe4) < 0 ) { cout << "pipe error" << endl; exit (1 ); } pid = fork(); if (pid < 0 ) exit (1 ); else if (pid == 0 ) { cout << "child A" << endl; close (pipe1[1 ]); close (pipe2[0 ]); close (pipe3[0 ]); close (pipe3[1 ]); close (pipe4[0 ]); close (pipe4[1 ]); for (;;) { if (read (pipe1[0 ], precv1, 16 ) > 0 ) { cout << "recv : " << precv1 << endl; } write (pipe2[1 ], psend1, sizeof (psend1)); } exit (0 ); } pid = fork(); if (pid < 0 ) exit (1 ); else if (pid == 0 ) { cout << "child B" << endl; close (pipe3[1 ]); close (pipe4[0 ]); close (pipe1[0 ]); close (pipe1[1 ]); close (pipe2[0 ]); close (pipe2[1 ]); for (;;) { write (pipe4[1 ], psend2, sizeof (psend2)); if (read (pipe3[0 ], precv2, 16 ) > 0 ) { cout << "recv : " << precv2 << endl; } } exit (0 ); } close (pipe1[0 ]); close (pipe2[1 ]); close (pipe3[0 ]); close (pipe4[1 ]); for (;;) { read (pipe4[0 ], precv1, 16 ); write (pipe1[1 ],precv1, sizeof (precv1)); read (pipe2[0 ], precv2, 16 ); write (pipe3[1 ], precv2, sizeof (precv2)); } }
题目6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int main () { const int N = 20000 ; int sockfd; sockfd = socket (AF_INET, SOCK_STREAM, 0 ); if (sockfd < 0 ) exit (1 ); SET_ADDR_PORT; if (bind () < 0 ) exit (1 ); if (listen (sockfd, 5 ) < 0 ) exit (1 ); int connfd[N]; for (int i = 0 ; i < N; ++i) { connfd[i] = accept (); if (connfd[i] < 0 ) exit (1 ); int flag = fcntl (connfd[i], F_GETFL, 0 ); fcntl (connfd[i], F_SETFL, flag | O_NONBLOCK); } for (;;) { sleep (120 ); for (int i = 0 ; i < N; ++i) { if (write (connfd[i], msg, sizeof (msg)) < 0 ) exit (1 ); } } }
考点 「长连接」-> TCP
非阻塞IO模型
由于只能创建2000个子进程, 故不能使用并发服务, 这里采用非阻塞io的方式, 先接受20000个客户端的连接, 然后每隔2分钟(120s)向所有客户机发送一组数据
技术要点 设置socket为非阻塞
第一种方式: 使用fcntl
先拿到flag, 再加上非阻塞的条件
1 2 int flag = fcntl (sock, F_GETFL, 0 );fcntl (sock, F_SETFL, flag | O_NONBLOCK);
第二种方式: 使用ioctl
1 2 int on = 1 ;ioctl (sock, FIONBIO, &on);
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int main () { int sock, connfd[2000 ]; if ((sock = socket (AF_INET, SOCK_STREAM, 0 )) < 0 ) exit (1 ); SET_ADDR_PORT; if (bind () < 0 ) exit (1 ); if (listen (sock, 5 ) < 0 ) exit (1 ); int num = 0 ; while (num < 20000 ) { connfd[num] = accept (); if (connfd[num] < 0 ) exit (1 ); int flag = fcntl (connfd[num], F_GETFL, 0 ); fcntl (connfd[num], F_SETFL, flag | O_NONBLOCK); ++num; } while (1 ) { for (int i = 0 ; i < 20000 ; ++i) { int n = write (connfd[i], str.c_str (), 6 ); } sleep (120 ); } close (sock); exit (0 ); }
2 | 2020年题目 题目1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int main () { int sockfd; sockfd = socket (AF_INET, SOCK_STREAM, 0 ); if (sockfd < 0 ) exit (1 ); SET_PORT_ADDR; if (bind () < 0 ) exit (1 ); if (listen (sockfd, 5 ) < 0 ) exit (1 ); int connfd = accpet (); if (connfd < 0 ) exit (1 ); char buf[1024 ]; int insock = socket (AF_INET, SOCK_DGRAM, 0 ); if (insock < 0 ) exit (1 ); SET_PORT_ADDR_255; bind (); for (;;) { int n = read (connfd, buf, 1024 ); if (n < 0 ) exit (1 ); n = sendto (insock, buf, sizeof (buf), ...); if (n < 0 ) exit (1 ); } }
考点 该程序分为:
两个部分, 其中外网通信部分采用单播, 而内网通信采用广播, 故外网通信部分使用TCP通信而内网通信部分使用UDP通信
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 int main () { int sock, connfd; struct sockaddr_in addr, addrX; if ((sock = socket (AF_INET, SOCK_STREAM, 0 )) < 0 ) { exit (1 ); } SET_ADDR_PORT; if (bind (sock) < 0 ) exit (1 ); if (listen (sock, 5 ) < 0 ) exit (1 ); char buf[1024 ]; for (;;) { connfd = accpet (sock); if (connfd < 0 ) exit (1 ); int n = read (connfd, buf, 1024 ); if (n < 0 ) exit (1 ); int socku = socket (AF_INET, SOCK_DGRAM, 0 ); if (socku < 0 ) exit (1 ); SET_ADDR_PORT_255; n = sendto (); close (socku); close (connfd); exit (0 ); } }
题目2
考点 对于整数的乘除法, 需要考虑主机字节序和网络字节序 的转换问题, 设计实现为: 每接收一个客户机创建一个子进程服务客户机的计算
规定接收数据的格式如下:
int mType, int n1, int n2
其中mType表示计算方式(0-乘法, 1-除法)
n1, n2为两个操作数
规定发送数据的格式如下:
int mRet, int n
其中mRet = 0表示计算成功, 1表示发生错误, n为运算结果
(程序中省略了发送和接收报文后对数据提取的处理, 默认read后mType, n1, n2已被读取, write后mRet, n已被发送)
技术要点 僵尸进程的清除 楼上有说, 采用捕获SIGCHLD
信号的方式(标程采用)
1 2 3 4 5 6 7 8 9 10 11 void sigchild_handler (int sig) { wait (NULL ); } int main () { struct sigaction sigact; sigact.sa_handler = sigchild_handler; sigact.sa_flags = 0 ; sigact.sa_mask = 0 ; sigaction (SIGCHLD, &sigact, NULL ); }
另外, 我感觉也可以直接signal(SIGCHLD, SIG_IGN);
(未得到老师确定)
字节序转换问题 由于试卷上已经说明int->char可用IntToChar代替, char->int可用CharToInt代替, 故数据在网络中以字符串形式传输, 使用ntohl
和htonl
进行字节序的转换
发送时
1 2 3 int ansans = htonl(ans) IntToChar(ans)
接收时
1 2 read (connfd, buf, 1024 )int ans = ntohl (CharToInt (buf));
完整程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 void sigchild_handler (int sig) { wait (NULL ); }int main () { int sock, connfd; struct sockaddr_n addr; struct sigaction sigact; sigact.sa_handler = sigchild_handler; sigact.sa_flags = 0 ; sigact.sa_mask = 0 ; sigaction (SIGCHLD, &sigact, NULL ); int n, n1, n2; int mRet, mType; char buf[1024 ]; if ((sock = socket (AF_INET, SOCK_STREAM, 0 )) < 0 ) exit (1 ); SET_ADDR_PORT; if (bind () < 0 ) exit (1 ); if (listen (sock, 5 ) < 5 ) exit (1 ); for (;;) { connfd = accept (); if (connfd < 0 ) exit (1 ); if (fork == 0 ) { close (sock); int n = read (connfd, buf, 1024 ); if (n < 0 ) exit (1 ); mRet = 0 ; CharToInt; ntoh (); if (mType == 0 ) { n = hton (n1 * n2); } else if (mType == 1 && n2 != 0 ) { n = hton (n1 / n2); } else { mRet = hton (1 ); n = 0 ; } IntToChar; write (connfd, buf, 2 * sizeof (int )); close (connfd); exit (0 ); } close (connfd); } close (sock); exit (0 ); }
题目3
考点 客户机数据格式设计为最前面的一个字节 表示A或B的ID号, 标记为IDA和IDB, 其后是发送给对方的数据, AB都和C进行连接, 在C上使用非阻塞式的I/O模型的TCP服务器, C在与A,B都保持连接的情况下, 收到一组数据后就转发
非阻塞IO上面写过了这里就不写了, 感觉这题直接用select也可以…
完整程序 非阻塞IO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 int main () { int sock, sockA, sockB; int n, ida = 0 , idb = 0 ; struct sockaddr_in addr; char a; char buf[1024 ]; if ((sock = socket (AF_INET, SOCK_STREAM, 0 )) < 0 ) { exit (1 ); } SET_ADDR_PORT; if (bind () < 0 ) exit (1 ); if (listen (sock, 5 ) < 0 ) exit (1 ); while (ida == 0 || idb == 0 ) { connfd = accept (); if (connfd < 0 ) exit (1 ); n = read (connfd, buf, 1 ); a = buf[0 ]; if (a == IDA) { sockA = connfd; } else if (a == IDB) { sockB = connfd; } } int flag = fcntl (sockA, F_GETFL, 0 ); fcnt (sockA, F_SETFL, flag | O_NONBLOCK); int flag = fcntl (sockB, F_GETFL, 0 ); fcnt (sockB, F_SETFL, flag | O_NONBLOCK); for (;;) { n = read (sockA, buf, 1024 ); if (n > 0 ) { write (sockB, buf, n); } n = read (sockB, buf, 1024 ); if (n > 0 ) { write (sockA, buf, n); } } close (sockA); close (sockB); close (sock); }
题目4
考点 信号驱动I/O模式的编程, 如何实现数据同步.
数据同步即需要做到在数据采集的过程中, 不会被用户的请求所打扰, 这里采用一个OK标记和复制的方式保护数据的完整性
技术要点 信号驱动方式的设置:
1 2 3 4 5 6 7 8 9 10 void sigio_handler (int sig) { }int main () { signal (SIGIO, sigio_handler); fcntl (sockfd, F_SETOWN, getpid ()); int on = 1 ; ioctl (sockfd, FIOAYNC, &on); }
注意读写的主机-网络字节序转换
完整程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 char buf[256 ];int photo1[4096 ], photo2[4096 ];int nOK[2 ];struct sockaddr_in addr;int sock;void sigio_handler (int sig) { int tmp[4096 ]; int n = recvfrom (); if (n <= 0 ) exit (1 ); if (nOK[0 ] == 1 ) { for (int j = 0 ; j < 4096 ; ++j) { tmp[j] = htonl (photo1[j]); } } else if (nOK[1 ] == 1 ) { for (int j = 0 ; j < 4096 ; ++j) { tmp[j] = htonl (photo2[j]); } } else { for (int j = 0 ; j < 4096 ; ++j) { tmp[j] = htonl (0 ); } } sendto (); }int main () { struct sigaction sigact; sigact.sa_handler = sigio_handler; sigact.sa_mask = 0 ; sigact.sa_flags = 0 ; sigaction (SIGIO, &sigact, NULL ); nOK[0 ] = nOK[1 ] = 0 ; memset (photo1, 0 , sizeof (photo1)); memset (photo2, 0 , sizeof (photo2)); sock = socket (AF_INET, SOCK_DGRAM, 0 ); if (sock < 0 ) exit (1 ); SET_ADDR_PORT; if (bind () < 0 ) exit (1 ); if (listen (sock, 5 ) < 0 ) exit (1 ); fcntl (sock, F_SETOWN, getpid ()); int on = 1 ; ioctl (sock, FIOASYNC, &on); int tp[4096 ]; for (;;) { takePhoto (tp); nOK[0 ] = 0 ; for (int i = 0 ; i < 4096 ; ++i) { photo1[i] = tp[i]; } nOK[0 ] = 1 ; nOK[1 ] = 0 ; for (int i = 0 ; i < 4096 ; ++i) { photo2[i] = tp[i]; } nOK[1 ] = 1 ; } close (sock); }
其他 守护进程 守护进程的创建过程如下:
1 2 3 4 5 6 7 8 9 10 11 12 void daemon_init () { if (fork() != 0 ) exit (0 ); if (setsid () < 0 ) exit (0 ); signal (SIGHUP, SIG_IGN); if (fork() != 0 ) exit (0 ); chdir ("/" ); umask (0 ); for (int i = 0 ; i < NOFILE; ++i) { close (i); } signal (SIG_CHLD, SIG_IGN); return 0 ; }
有名管道 命名管道使用
写进程使用mkfifo
创建命名管道
写进程调用open
以写阻塞方式打开管道
读进程调用open
以读阻塞方式打开管道
写进程调用write
写入数据
读进程调用read
读出数据
有名管道的创建
1 2 #define FIFO_NAME "/xxx/xxx" mkfifo (FIFO_NAME, O_CREAT|O_EXCL)
有名管道阻塞写打开
1 int fd = open (FIFO_NAME, O_WRONLY, 0 );
有名管道阻塞读打开
1 int fd = open (FIFO_NAME, O_RDONLY, 0 );
地址可重用 1 2 3 4 int listenfd;int on = 1 ; listenfd = socket (AF_INET, SOCK_STREAM, 0 );setsockopt (listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int ));
IO多路复用 多路复用
1 2 3 4 5 6 7 int select (int maxfd, struct fd_set* rdset, struct fd_set* wrset, struct fd_set* exset, struct timeval* timeout) ;void FD_SET (int fd,fd_set *fdset) void FD_CLR (int fd,fd_set *fdset) void FD_ZERO (fd_set *fdset) int FD_ISSET (int fd,fd_set *fdset)
select( )可以设置超时,使长期没有文件描述符就绪时,进程可以跳出阻塞状态。select( )的第一个参数 maxfd 是集合中最大的文件描述符加1,如:一个包含3个套接字描述符的集合{12,23,30},那么 maxfd 就应该是30+1=31。
在我们调用select( )时,进程会一直阻塞到以下的一种情况发生:
有文件可以读,包括出现错误;
有文件可以写,包括出现错误;
超时所设置的时间到;
被信号中断。
下面例子中,客户机与多个服务器建立连接,然后设置读描述符集合,调用函数将进程阻塞。
当任一个服务器的数据到达时,进程就被唤醒,检查集合中哪个描述符就绪,然后作相应处理。
代码片段如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 int main (int argc,char *argv[]) { int sockfd[NUMBER]; struct sockaddr_in servaddr[NUMBER]; fd_set rfds; char buf[1024 ]; int i; for (i=0 ;i<NUMBER;i++) { sockfd[i]=socket (AF_INET,SOCK_STREAM,0 ); if (sockfd[i]<0 ) exit (1 ); } ...... ...... int nOK[NUMBER]; for (i=0 ;i<NUMBER;i++) {nOK[i]=0 ;} int nEnd=NUMBER; while (nEnd!=0 ) { for (i=0 ;i<NUMBER;i++) if (nOK[i]==0 ) FD_SET (sockfd[i],&rds); n=select (theMax (NUMBER,sockfd)+1 ,&rds,NULL ,,NULL ,,NULL ); if (n<0 && errno==EINTR) continue ; for (i=0 ;i<NUMBER;i++) { if ( FD_ISSET (sockfd[i],&rds) ) { n=read (sockfd[i],buf,1024 ); if (n<=0 && errno!=EINTR) { perror ("An Error." ); nOK[i]=1 ;nEnd--; } else if (n>0 ) { process (buf,...); nOK[i]=1 ;nEnd--; } } } } for (i=0 ;i<NUMBER;i++) {close (sockfd[i]);} }