代码开源(6)——UNIX并发编程
来源:互联网 发布:翻墙加速软件 编辑:程序博客网 时间:2024/05/17 13:45
之前在代码开源(3)——UNIX中CS简单实现 给出的代码,存在一个问题,那就是只支持单个连接。本文整理给出了三种方法:多进程、IO多路复用、多线程,主要参考《深入理解计算机系统》一书。对源码做了修改整理,加了些批注。下面一一给出,只修改服务器端的主程序,客户端代码不变。其中用到的rio在代码开源(2)——UNIX 健壮I/O函数 已给出。
首先给出原先的版本,即不支持多个连接。为了突出重点,做了些简化,比如去掉了一些异常的判断(accept调用失败,select调用失败等),实际编写中应该加上这些判断。
- #include "server.h"
- int main(int argc, char **argv)
- {
- int listenfd, connfd;
- unsigned int clientlen; //地址长度
- struct sockaddr_in clientaddr; //客户端地址
- if(argc != 2) //参数必须是2个
- {
- fprintf(stderr, "usage: %s <port>\n",argv[0]);
- return 0;
- }
- listenfd = open_listenfd(atoi(argv[1])); //进入监听状态
- clientlen = sizeof(clientaddr);
- while(1) //只支持单个连接
- {
- connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen); //建立连接
- exchange_data(connfd); //与客户端交换数据
- close(connfd); //客户端断开连接
- }
- return 0;
- }
下面给出多进程版本,主要利用了fork调用。注意一点:fork调用后,父进程需关闭连接描述符。这是因为fork调用后,父进程和子进程的连接描述符指向同一个文件表表项,当子进程终止时,由于父进程仍拥有该连接描述符,所以不会被释放,从而导致内存泄露,所以父进程应关闭连接描述符。另外,子进程最好关闭监听描述符。
多进程版本的主要缺点就是进程控制和IPC(进程间通信)开销很高,速度比较慢。
- //支持多个连接,多进程版
- #include "server.h"
- #include <signal.h> //signal函数需包含头文件
- #include <sys/wait.h> //waitpid函数需要包含头文件
- //子进程暂停或终止时,会产生信号SIGCHLD
- //从而调用下面这个函数,waitpid(-1, 0, WNOHANG)返回终止的子进程号
- void sigchld_handler(int sig)
- {
- while(waitpid(-1, 0, WNOHANG) > 0) //-1表示等待集合为父进程所有子进程
- ;
- return ;
- }
- int main(int argc, char **argv)
- {
- int listenfd, connfd;
- unsigned int clientlen; //地址长度
- struct sockaddr_in clientaddr; //客户端地址
- if(argc != 2) //参数必须是2个
- {
- fprintf(stderr, "usage: %s <port>\n",argv[0]);
- return 0;
- }
- signal(SIGCHLD, sigchld_handler); //注册信号处理程序
- listenfd = open_listenfd(atoi(argv[1])); //进入监听状态
- clientlen = sizeof(clientaddr);
- while(1) //支持多个连接
- {
- connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen); //建立连接
- if(fork() == 0) //建立子进程
- {
- close(listenfd); //关闭监听描述符
- exchange_data(connfd); //与客户端交换数据
- close(connfd); //客户端断开连接
- exit(0); //子进程终止
- }
- close(connfd); //关闭连接描述符,子进程中有
- }
- return 0;
- }
相比多进程版本,IO复用的版本比较复杂,实现起来不易。
- #include "server.h"
- #include "pool.h"
- int main(int argc, char **argv)
- {
- int listenfd, connfd;
- unsigned int clientlen; //地址长度
- struct sockaddr_in clientaddr; //客户端地址
- pool client_pool;
- if(argc != 2) //参数必须是2个
- {
- fprintf(stderr, "usage: %s <port>\n",argv[0]);
- return 0;
- }
- listenfd = open_listenfd(atoi(argv[1])); //进入监听状态
- clientlen = sizeof(clientaddr);
- init_pool(listenfd, &client_pool); //初始化描述符池
- while(1) //支持多个连接
- {
- client_pool.ready_set = client_pool.read_set;
- client_pool.nready = select(client_pool.maxfd + 1, &client_pool.ready_set, NULL, NULL, NULL); //等待描述符就绪
- if(FD_ISSET(listenfd, &client_pool.ready_set)) //监听描述符就绪,增加一个连接描述符
- {
- connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen); //建立连接
- add_client(connfd, &client_pool); //增加一个连接
- }
- check_clients(&client_pool); //检查所有的连接描述符,判断是否有数据从客户端发来
- }
- return 0;
- }
- #ifndef POOL_H_
- #define POOL_H_
- #include "rio.h"
- #define MAX_LINE 1024
- #define SET_SIZE 8
- typedef struct
- {
- int maxfd; //读集合的最大描述符
- fd_set read_set; //读集合
- fd_set ready_set; //读就绪集合
- int nready; //准备好的集合数
- int maxi; //当前使用的最高位置,即最大下标
- int clientfd[SET_SIZE];
- rio_t clientrio[SET_SIZE];
- }pool;
- void init_pool(int listenfd, pool *p); //初始化活动客户池
- void add_client(int connfd, pool *p); //增加一个客户连接
- void check_clients(pool *p); //为准备好的客户端连接服务
- //初始化活动客户池
- void init_pool(int listenfd, pool *p)
- {
- int i;
- for(i = 0; i < SET_SIZE; i++) //初始化为-1表示未用
- p->clientfd[i] = -1;
- FD_ZERO(&p->read_set);
- FD_SET(listenfd, &p->read_set);
- p->maxfd = listenfd; //只有监听描述符
- p->maxi = -1; //客户端连接数组未用
- }
- //增加一个客户连接
- void add_client(int connfd, pool *p)
- {
- int i;
- p->nready--; //准备就绪的连接符数减1,因为已处理完客户端的连接
- for(i = 0; i < SET_SIZE; i++) //在客户端连接数组中,寻找可用的空位
- {
- if(p->clientfd[i] < 0) //找到一个空位
- {
- //设置相关信息
- p->clientfd[i] = connfd;
- rio_readinitb(&p->clientrio[i], connfd);
- FD_SET(connfd, &p->read_set);
- if(connfd > p->maxfd) //更新读集合的最大描述符
- p->maxfd = connfd;
- if(i > p->maxi) //更新当前使用的最高位置
- p->maxi = i;
- break;
- }
- }
- }
- //为准备好的客户端连接服务
- void check_clients(pool *p)
- {
- int i, connfd, n;
- char buf[MAX_LINE];
- rio_t rio;
- for(i = 0; (i <= p->maxi) && (p->nready > 0); i++) //遍历,寻找准备好的客户端连接
- {
- connfd = p->clientfd[i];
- rio = p->clientrio[i];
- if((connfd > 0) && (FD_ISSET(connfd, &p->ready_set))) //已建立连接,并且读就绪
- {
- p->nready--;
- if((n = rio_readlineb(&rio, buf, MAX_LINE)) != 0)
- {
- printf("server received %d bytes\n", n); //收到的字节数
- rio_writen(connfd, buf, n);
- }
- else
- {
- close(connfd);
- FD_CLR(connfd, &p->read_set);
- p->clientfd[i] = -1;
- }
- }
- }
- }
- #endif /* POOL_H_ */
这里需要注意的一点,每次连接,都需要动态申请空间,用来放连接描述符。这是因为线程例程的参数只能是一个指针,如果连接描述符的空间不是动态分配的,那么一种可能的情况是对等线程执行int connfd = *((int *)argv)前,主线程又收到了连接请求,这时对等线程中的连接描述符就被设置成新的连接描述符。这显然是不对的。
- #include "server.h"
- #include <pthread.h>
- void* thread(void *argv)
- {
- int connfd = *((int *)argv); //获得已连接描述符
- pthread_detach(pthread_self()); //线程分离,它的资源在终止时由系统自动释放
- free(argv);
- exchange_data(connfd);
- close(connfd);
- return NULL;
- }
- int main(int argc, char **argv)
- {
- int listenfd, *connfdp;
- unsigned int clientlen; //地址长度
- struct sockaddr_in clientaddr; //客户端地址
- pthread_t tid;
- if(argc != 2) //参数必须是2个
- {
- fprintf(stderr, "usage: %s <port>\n",argv[0]);
- return 0;
- }
- listenfd = open_listenfd(atoi(argv[1])); //进入监听状态
- clientlen = sizeof(clientaddr);
- while(1) //支持多个连接
- {
- connfdp = malloc(sizeof(int)); //必须是动态生成,否则会有问题
- *connfdp = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen); //建立连接
- pthread_create(&tid, NULL, thread, connfdp); //创建线程
- }
- return 0;
- }
- 代码开源(6)——UNIX并发编程
- 代码开源(6)——UNIX并发编程
- UNIX网络编程——并发服务器(TCP)
- UNIX网络编程——并发服务器(多进程)
- UNIX网络编程——并发服务器(多线程)
- Unix编程艺术——代码生成
- UNIX网络编程——shutdown函数(I/O复用并发服务器)
- UNIX网络编程——并发服务器(I/O复用)
- 【Java并发编程】:生产者—消费者模型(含代码)
- 【Java并发编程】之二十二:并发新特性—障碍器CyclicBarrier(含代码)
- 【Java并发编程】之二十三:并发新特性—信号量Semaphore(含代码)
- 【Java并发编程】并发新特性—Lock锁和条件变量(含代码)
- 【Java并发编程】并发新特性—信号量Semaphore(含代码)
- 【Java并发编程】并发新特性—阻塞队列和阻塞栈(含代码)
- 【Java并发编程】并发新特性—Executor框架与线程池(含代码)
- 【Java并发编程】:并发新特性—阻塞队列和阻塞栈(含代码)
- 【Java并发编程】:并发新特性—障碍器CyclicBarrier(含代码)
- 【Java并发编程】:并发新特性—信号量Semaphore(含代码)
- iis开机不能自动启动,怎样让IIS服务开机自动启动?
- 代码开源(5)——彩色bmp图片转灰度
- C++ 获取文件长度
- 不使用第三个参数和库函数实现两个数字的交换(异或的妙用)
- iPhone 实现简单的CoverFlow 效果
- 代码开源(6)——UNIX并发编程
- WinFrom通过WebClient向Web服务器上传下载文件
- gwt-html5-graph(GWT Diagram Library Components)的结构图
- 时光易逝
- flexlib使用简介
- java.lang.NoClassDefFoundError和INSTALL_FAILED_MISSING_SHARED_LIBRARY
- 四大经典思维--归纳、类比、发散、逆向
- 风扇(FAN)生产厂商
- Linux下的nginx启动、重新启动