UNIX网络编程卷1 回射服务器程序 TCP服务器程序设计范式 四个版本
来源:互联网 发布:linux下源码安装mysql 编辑:程序博客网 时间:2024/05/17 02:37
本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie
这是一个简单的回射服务器程序。它将客户发送的数据读入缓冲区并回射其中内容
下面我会介绍同一个使用 TCP 协议的回射服务器程序的几个不同版本,分别是 fork 版本、select 版本、poll 版本、多线程版本
fork 版本:为每一个客户连接派生(fork) 一个子进程用来处理客户请求
/** * TCP/IPv4 协议相关 * **/#include"unp.h"intmain(int argc, char **argv){intlistenfd, connfd;pid_tchildpid;socklen_tclilen;struct sockaddr_incliaddr, servaddr; //1.创建套接字,捆绑服务器的众所周知端口listenfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));Listen(listenfd, LISTENQ); //2.等待完成客户连接for ( ; ; ) {clilen = sizeof(cliaddr);connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); //3.并发服务器if ( (childpid = Fork()) == 0) {/* 子进程 */Close(listenfd);/* 关闭监听套接字*/str_echo(connfd);/* 处理客户请求*/exit(0);}Close(connfd);/* 父进程关闭连接套接字 */}}#include"unp.h"voidstr_echo(int sockfd){ssize_tn;charbuf[MAXLINE];again:// 读入缓冲区并回射其中内容while ( (n = read(sockfd, buf, MAXLINE)) > 0)Writen(sockfd, buf, n);if (n < 0 && errno == EINTR)goto again;else if (n < 0)err_sys("str_echo: read error");}
问题:僵死子进程
改善:通过捕获 SIGCHLD 信号并在信号的调用 waitpid 函数来回收子进程的资源。
之所以用 waitpid 而不用 wait 是因为 Unix 信号是不排队的,而 waitpid 因为可以设置不阻塞,所以可以在循环中
被调用,这样可以处理同时到达的多个 SIGCHLD 信号。
/*** TCP/IPv4 协议相关 收拾终止了的子进程**/#include"unp.h"intmain(int argc, char **argv){intlistenfd, connfd;pid_tchildpid;socklen_tclilen;struct sockaddr_incliaddr, servaddr;voidsig_chld(int);//1.创建套接字,绑定众所周知的端口listenfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));Listen(listenfd, LISTENQ);//2.当fork 子进程时,必须捕获 SIGCHLD 信号, sig_chld 为信号处理函数Signal(SIGCHLD, sig_chld);/* must call waitpid() *///3.等待客户连接for ( ; ; ) {clilen = sizeof(cliaddr);//4.当捕获信号时,必须处理被中断的系统调用if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {if (errno == EINTR)continue;/* back to for() */elseerr_sys("accept error");}//5.并发服务器if ( (childpid = Fork()) == 0) {/* child process */Close(listenfd);/* close listening socket */str_echo(connfd);/* process the request */exit(0);}Close(connfd);/* parent closes connected socket */}}#include"unp.h"voidsig_chld(int signo){pid_tpid;intstat;while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)printf("child %d terminated\n", pid);return;}
select 版本:使用 select 来处理任意个客户的单进程程序,而不是为每个客户派生一个子进程
/*** TCP 使用 select **/#include"unp.h"intmain(int argc, char **argv){inti, maxi, maxfd, listenfd, connfd, sockfd;intnready, client[FD_SETSIZE];ssize_tn;fd_setrset, allset;charbuf[MAXLINE];socklen_tclilen;struct sockaddr_incliaddr, servaddr;//1.创建套接字,绑定众所周知的端口listenfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));Listen(listenfd, LISTENQ);//2.初始化maxfd = listenfd;/* initialize */maxi = -1;/* index into client[] array */for (i = 0; i < FD_SETSIZE; i++)client[i] = -1;/* -1 indicates available entry */FD_ZERO(&allset);FD_SET(listenfd, &allset);/* end fig01 *//* include fig02 */for ( ; ; ) {//3.阻塞于 select//select 等待某个事件发生:或是新客户连接的建立,或是数据、FIN或 RST的到达rset = allset;/* structure assignment */nready = Select(maxfd+1, &rset, NULL, NULL, NULL);//4.accept 新的连接if (FD_ISSET(listenfd, &rset)) {/* new client connection */clilen = sizeof(cliaddr);connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);#ifdefNOTDEFprintf("new client: %s, port %d\n",Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),ntohs(cliaddr.sin_port));#endif//用 client 数组中第一个未用项记录这个已连接描述符for (i = 0; i < FD_SETSIZE; i++)if (client[i] < 0) {client[i] = connfd;/* save descriptor */break;}if (i == FD_SETSIZE)err_quit("too many clients");//在 allset 描述符中打开已连接套接字 connfd 的对应位FD_SET(connfd, &allset);/* add new descriptor to set */if (connfd > maxfd)maxfd = connfd;/* for select */if (i > maxi)maxi = i;/* max index in client[] array *///nready 是 select 函数的返回值,表示跨所有描述符集的已就绪的总位数//就绪描述符数目减 1,若其值变为 0,就可以避免进入下一个 for 循环if (--nready <= 0)continue;/* no more readable descriptors */}//5.检查现有连接//对于每个现在的客户连接,测试其描述符是否在 select 返回的描述符集中 --> ? select 有返回描述符集吗 ?//如果是,就从客户读入一行文本并回射给它。//如果关闭了连接,那么 read 将返回 0,要相应地更新数据结构for (i = 0; i <= maxi; i++) {/* check all clients for data */if ( (sockfd = client[i]) < 0)continue;if (FD_ISSET(sockfd, &rset)) {if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {/*4connection closed by client */Close(sockfd);FD_CLR(sockfd, &allset);client[i] = -1;} elseWriten(sockfd, buf, n);if (--nready <= 0)break;/* no more readable descriptors */}}}}/* end fig02 */
poll 版本:在 select 版本中,必须分配一个 client 数组以及一个名为 rset 的描述符集。改用 poll 后,我们只需分配 一个 pollfd 结构的数组来
维护客户信息,而不必分配另一个数组。
/** * TCP/IPv4 协议相关,使用poll,单个进程处理所有客户 * **//* include fig01 */#include"unp.h"#include<limits.h>/* for OPEN_MAX */intmain(int argc, char **argv){inti, maxi, listenfd, connfd, sockfd;intnready;ssize_tn;charbuf[MAXLINE];socklen_tclilen; //1.分配 pollfd 结构数组struct pollfdclient[OPEN_MAX];struct sockaddr_incliaddr, servaddr;listenfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));Listen(listenfd, LISTENQ); //2.初始化 //把 client 数组的第一项用于监听套接字,并设置 events为 POLLRDNORM //把其余各项的成员转为 -1client[0].fd = listenfd;client[0].events = POLLRDNORM;for (i = 1; i < OPEN_MAX; i++)client[i].fd = -1;/* -1 indicates available entry */maxi = 0;/* max index into client[] array *//* end fig01 *//* include fig02 */for ( ; ; ) { //3.调用 poll,检查新的连接nready = Poll(client, maxi+1, INFTIM); //检查 client[0],即监听套接字上是否有新客户连接if (client[0].revents & POLLRDNORM) {/* new client connection */clilen = sizeof(cliaddr);connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);#ifdefNOTDEFprintf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen));#endif //保存已连接套接字for (i = 1; i < OPEN_MAX; i++)if (client[i].fd < 0) {client[i].fd = connfd;/* save descriptor */break;}if (i == OPEN_MAX)err_quit("too many clients");client[i].events = POLLRDNORM;if (i > maxi)maxi = i;/* max index in client[] array */if (--nready <= 0)continue;/* no more readable descriptors */} //4.检查某个现有连接上的数据 // i 从 1,开始,因为 client[0]是监听套接字for (i = 1; i <= maxi; i++) {/* check all clients for data */if ( (sockfd = client[i].fd) < 0)continue;if (client[i].revents & (POLLRDNORM | POLLERR)) { //读取数据并回射if ( (n = read(sockfd, buf, MAXLINE)) < 0) {if (errno == ECONNRESET) {/*4connection reset by client */#ifdefNOTDEFprintf("client[%d] aborted connection\n", i);#endifClose(sockfd);client[i].fd = -1;} elseerr_sys("read error"); //终止连接} else if (n == 0) {/*4connection closed by client */#ifdefNOTDEFprintf("client[%d] closed connection\n", i);#endifClose(sockfd);client[i].fd = -1;} elseWriten(sockfd, buf, n);if (--nready <= 0)break;/* no more readable descriptors */}}}}/* end fig02 */
多线程版本:这个版本的代码为每个客户使用一个线程,而不是子进程,同时它使用了 tcp_listen 函数来使得该程序与协议无关。
/** * TCP 每个用户一个线程 * **/#include"unpthread.h"static void*doit(void *);/* each thread executes this function */intmain(int argc, char **argv){intlistenfd, connfd;pthread_ttid;socklen_taddrlen, len;struct sockaddr*cliaddr; //1.创建监听套接字if (argc == 2)listenfd = Tcp_listen(NULL, argv[1], &addrlen);else if (argc == 3)listenfd = Tcp_listen(argv[1], argv[2], &addrlen);elseerr_quit("usage: tcpserv01 [ <host> ] <service or port>");cliaddr = Malloc(addrlen); //2.创建线程for ( ; ; ) {len = addrlen;connfd = Accept(listenfd, cliaddr, &len); //accept 返回之后,改为调用 pthread_create 取代调用 forkPthread_create(&tid, NULL, &doit, (void *) connfd);}}//3.线程函数static void *doit(void *arg){ //pthread_detach 使自身脱离主线程,这样主线程不用等待它Pthread_detach(pthread_self());str_echo((int) arg);/* same function as before */ //关闭已连接套接字Close((int) arg);/* done with connected socket */return(NULL);}
问题:上面的代码把整数变量 connfd 类型强制转换成 void 指针,这并不具备兼容性。
改善:可以把一个整数指针类型强制转换为 void *,然后把这个 void * 指针类型强制转换回原来的整数指针。
但如果主线程中只有一个整数变量 connfd 的话,每次调用 accept 该变量都会被覆写为另一个新值。
因此还应该专门为每一个线程 malloc 一个整数变量的空间,用来存放该线程的处理的已连接套接字描述符,
同时还要在线程中 free 掉该空间。
/** * TCP 每个客户一个线程,可移植的参数传递 * **/#include"unpthread.h"static void*doit(void *);/* each thread executes this function */intmain(int argc, char **argv){intlistenfd, *iptr;thread_ttid;socklen_taddrlen, len;struct sockaddr*cliaddr; //1.创建监听套接字if (argc == 2)listenfd = Tcp_listen(NULL, argv[1], &addrlen);else if (argc == 3)listenfd = Tcp_listen(argv[1], argv[2], &addrlen);elseerr_quit("usage: tcpserv01 [ <host> ] <service or port>");cliaddr = Malloc(addrlen); //2.创建线程for ( ; ; ) {len = addrlen;iptr = Malloc(sizeof(int));*iptr = Accept(listenfd, cliaddr, &len); Pthread_create(&tid, NULL, &doit, iptr);}}//3.线程函数static void *doit(void *arg){intconnfd;connfd = *((int *) arg);free(arg);Pthread_detach(pthread_self());str_echo(connfd);/* same function as before */Close(connfd);/* done with connected socket */return(NULL);}
0 0
- UNIX网络编程卷1 回射服务器程序 TCP服务器程序设计范式 四个版本
- 《UNIX网络编程 卷1》 笔记: 服务器程序设计范式
- UNIX网络编程卷1 回射客户程序 TCP客户程序设计范式
- UNIX网络编程卷1 服务器程序设计范式0 迭代服务器
- UNIX网络编程卷1 服务器程序设计范式1 并发服务器,为每个客户请求fork一个进程
- 【UNIX网络编程】客户/服务器程序设计范式
- UNIX网络编程卷1 服务器程序设计范式6 并发服务器,为每个客户请求创建一个线程
- 《UNIX网络编程 卷1》 笔记: 使用select函数的单进程TCP回射服务器程序
- 《UNIX网络编程 卷1》 笔记: 使用poll函数的单进程TCP回射服务器程序
- UNIX网络编程卷一:第三十章 客户/服务器程序设计范式
- UNIX网络编程卷1 服务器程序设计范式2 预先派生子进程,每个子进程调用accept
- UNIX网络编程卷1 服务器程序设计范式3 预先派生子进程,以文件上锁方式保护accept
- UNIX网络编程卷1 服务器程序设计范式7 预先创建线程,以互斥锁上锁方式保护accept
- UNIX网络编程卷1 服务器程序设计范式8 预先创建线程,由主线程调用accept
- UNIX网络编程卷1 时间获取程序服务器 TCP 协议相关性
- 《UNIX网络编程 卷1》 笔记: TCP 客户/服务器程序示例
- UNIX网络编程卷1:套接字联网-第5章:TCP客户/服务器程序示例
- Unix网络编程 第一卷 套接口API 第五章 TCP客户/服务器程序例子
- (9)ubuntu下hadoop1.0.4源码编译
- 解压缩版MySQL安装及使用
- 算法笔记--最短路径之SPFA算法
- hdu2838——Cow Sorting
- NodeJS 初学者学习推荐(原题 NodeJS常用模块推荐)
- UNIX网络编程卷1 回射服务器程序 TCP服务器程序设计范式 四个版本
- 加载UIImage的两种方式
- 【shell】【10】文件处理
- CodeForces 34D Road Map
- HDU1039 Easier Done Than Said?
- s3c2440 的 rtc 操作
- 堆
- 非多项式CRC16校验算法
- UVa 348 Optimal Array Multiplication Sequence (DP 最优矩阵链乘)