1.迭代服务器模型

  1.1 迭代服务器是处理多个请求时一种最简单直接的思路,即使用while循环,它不具有并发能力,即必须一个一个的处理客户的请求。

  1.2 程序示例。

#include "def.h"int  listenfd_init(); //返回一个处于监听状态的套接字描述符
void do_service(int peerfd); // 处理客户端的请求int main(int argc, const char *argv[])
{if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)ERR_EXIT("signal");int listenfd = listenfd_init(); // 生成一个套接字并使其处于监听状态struct sockaddr_in peeraddr;int len = sizeof(peeraddr);int peerfd;while(1){if((peerfd = accept(listenfd, (struct sockaddr*)&peeraddr, &len)) == -1) //接受一个TCP连接请求ERR_EXIT("accpet");do_service(peerfd); // 处理请求close(peerfd);}close(listenfd);return 0;
}int listenfd_init(){int listenfd = socket(AF_INET, SOCK_STREAM, 0);int on = 1;if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)ERR_EXIT("setsockopt");struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);serveraddr.sin_port = htons(9999);if(listenfd == -1)ERR_EXIT("socket");if(bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1)ERR_EXIT("bind");if(listen(listenfd, 10) == -1)ERR_EXIT("listen");return listenfd;
}void do_service(int peerfd){char recvbuf[1024] = {0};int ret;while(1){ret = readline(peerfd, recvbuf, 1024);if(ret <= 0)break;printf("recv data : %s", recvbuf);writen(peerfd, recvbuf, strlen(recvbuf));}
}

2.多进程服务器模型

  2.1 使用多进程编写并发服务器的一般步骤:

    a)while(1)循环,每次accept一个连接都fork一个进程;

    b)在子进程中要close(listenfd),最后要exit(这里子进程一定要退出,否则会继续执行);

    c)父进程要关闭accept返回的peerfd。

  2.2 一些要点

    a)父进程要关闭peerfd,这主要是因为close根据引用计数关闭fd,如果父进程不这样做,那么所有通过accept创建的fd都不会被真正释放,这将造成资源耗尽

    b)父进程不可以直接采用waitpid来回收子进程,这样会使得server变为一个迭代服务器,而不具备并发的能力,,必须采用信号这种异步的处理手段;

    c)使用信号处理必须注意,使用while而不是if,尽可能多处理僵尸进程,这是为了防止信号的额阻塞和丢失问题,waitpid要使用WNOHANG悬选项。

    d)子进程要关闭listenfd;

    e)子进程执行do_service之后务必exit(EXIT_SUCCESS)

  2.3 程序示例。

#include "def.h"
/** 服务器端使用 多进程 模型**/
int  listenfd_init(); //返回一个处于监听状态的套接字描述符
void do_service(int peerfd); // 处理客户端的请求
void handler(int signum); //处理sigchld信号 回收子进程资源int main(int argc, const char *argv[])
{if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)ERR_EXIT("signal");if(signal(SIGCHLD, handler) == SIG_ERR)ERR_EXIT("signal");int listenfd = listenfd_init(); // 生成一个套接字并使其处于监听状态//多进程模型while(1){struct sockaddr_in peeraddr;memset(&peeraddr, 0,sizeof(peeraddr));int len = sizeof(peeraddr);int peerfd;// 接受一个TCP连接请求if((peerfd = accept(listenfd, (struct sockaddr*)&peeraddr, &len)) == -1)ERR_EXIT("accpet");pid_t pid;if((pid = fork()) < 0)ERR_EXIT("fork");else if(pid == 0){close(listenfd); //子进程要关闭listenfddo_service(peerfd); // 处理请求exit(EXIT_SUCCESS);}close(peerfd); //这里必须关闭peerfd,否则导致资源耗尽}close(listenfd);return 0;
}int listenfd_init(){int listenfd = socket(AF_INET, SOCK_STREAM, 0);int on = 1;if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)ERR_EXIT("setsockopt");struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);serveraddr.sin_port = htons(9999);if(listenfd == -1)ERR_EXIT("socket");if(bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1)ERR_EXIT("bind");if(listen(listenfd, 10) == -1)ERR_EXIT("listen");return listenfd;
}void do_service(int peerfd){char recvbuf[1024] = {0};int ret;while(1){ret = readline(peerfd, recvbuf, 1024);if(ret <= 0){close(peerfd);exit(EXIT_SUCCESS);}printf("recv data : %s", recvbuf);writen(peerfd, recvbuf, strlen(recvbuf));}close(peerfd);
}void handler(int signum){while(waitpid(-1, 0, WNOHANG) > 0) ; //while尽可能多回收子进程
}

3.多线程服务器模型

  3.1 使用多线程编写并发服务器的一般步骤: while(1)循环内,每次accpet一个连接,都创建一个线程。

  3.2 一些要点:

    a)往线程中传fd,最好使用动态分配内存(有些机器int和void*不兼容),在线程中务必释放内存,防止内存泄露;

    b)线程务必使用detach函数,将自己设置为分离状态,自动回收资源。

  3.3 程序示例。

#include "def.h"
/** 服务器端使用 多线程 模型**/
int  listenfd_init(); //返回一个处于监听状态的套接字描述符
void do_service(int peerfd); // 处理客户端的请求
void handler(int signum); //处理sigchld信号 回收子进程资源
void *thread_func(void *arg); //线程处理函数int main(int argc, const char *argv[])
{if(signal(SIGPIPE, SIG_IGN) == SIG_ERR)ERR_EXIT("signal");if(signal(SIGCHLD, handler) == SIG_ERR)ERR_EXIT("signal");int listenfd = listenfd_init(); // 生成一个套接字并使其处于监听状态//多线程模型while(1){struct sockaddr_in peeraddr;memset(&peeraddr, 0,sizeof(peeraddr));int len = sizeof(peeraddr);int peerfd;if((peerfd = accept(listenfd, (struct sockaddr*)&peeraddr, &len)) == -1)ERR_EXIT("accpet");int *pfd = (int *)malloc(sizeof(int));*pfd = peerfd;pthread_t tid;pthread_create(&tid, NULL, thread_func, pfd);}close(listenfd);return 0;
}int listenfd_init(){int listenfd = socket(AF_INET, SOCK_STREAM, 0);int on = 1;if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)ERR_EXIT("setsockopt");struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);serveraddr.sin_port = htons(9999);if(listenfd == -1)ERR_EXIT("socket");if(bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1)ERR_EXIT("bind");if(listen(listenfd, 10) == -1)ERR_EXIT("listen");return listenfd;
}void *thread_func(void *arg){ //线程处理函数pthread_detach(pthread_self()); //将当前线程设置为分离状态 自己主动回收资源int *pfd = (int *)arg;int peerfd = *pfd;free(pfd);do_service(peerfd);
}void do_service(int peerfd){char recvbuf[1024] = {0};int ret;while(1){ret = readline(peerfd, recvbuf, 1024);if(ret <= 0){close(peerfd);exit(EXIT_SUCCESS);}printf("recv data : %s", recvbuf);writen(peerfd, recvbuf, strlen(recvbuf));}close(peerfd);
}void handler(int signum){while(waitpid(-1, 0, WNOHANG) > 0) ; //while尽可能多回收子进程
}

4.select服务器模型

  4.1 使用 select 编写并发服务器的一般步骤:

    a)初始化参数,这里包括readset(描述符集合,用作readyset的备份),readyset用作select函数的传出参数,clients数组(存储所有已连接的fd,这里的数组长度FD_SETSIZE是一个系统定义的宏),maxi(数组的最大下标,便于提高效率),maxfd(要监听的fd的最大值,用作select的第一个参数),nready(用作select的返回值);

    b)进入while(1)循环,readyset = readset;

    c)执行select函数,并检查其返回值;

    d)检查listenfd是否在准备好的集合中,此时这里需要accpet一个连接,将返回的fd加入到clients数组中和readset结合中,并且还要更新maxi和maxfd;

    e)遍历clients数组,依次查看每个fd是否在准备好的集合readyset中,这里要注意,当某一个客户关闭连接时,本地需要close这个fd,更新clients数组,将该fd从readset集合中移除。

  4.2 程序示例。 

void do_select(int listenfd){//初始化参数fd_set readset, readyset;//readset 用作备份 存储要监听的所有fd,readyset用作返回FD_ZERO(&readset);FD_ZERO(&readyset);FD_SET(listenfd, &readset);//定义数组,存储所有已连接的客户fd 初始化为-1int clients[FD_SETSIZE]; //FD_SETSIZE 是一个系统的宏定义int i;for(i = 0; i < FD_SETSIZE; i++){clients[i] = -1;}int maxi = -1; //数组的最大下标 //?int nready; //select的返回值int maxfd = listenfd;//监听的最大fdwhile(1){//执行select,检查返回值readyset = readset;nready = select(maxfd + 1, &readyset, NULL, NULL, NULL);if(nready == -1){if(errno == EINTR)continue;ERR_EXIT("select");}//检查listenfd 是否在准备好的集合中if(FD_ISSET(listenfd, &readyset)){int peerfd = accept(listenfd, NULL, NULL);if(peerfd == -1)ERR_EXIT("accept");//为新的fd在clients数组中找一个空位,并更新maxiint i;for(i = 0; i < FD_SETSIZE; i++){if(clients[i] == -1){clients[i] = peerfd;if(i > maxi)maxi = i;break;}}if(i == FD_SETSIZE){//找不到一个空闲fd位置给新的fdfprintf(stderr, "too many clients\n");close(peerfd);continue;}//将新的fd添加到集合中, 更新maxfdFD_SET(peerfd, &readset);if(peerfd > maxfd)maxfd = peerfd;if(--nready <= 0)//执行下一次selectcontinue;}//if listenfd//依次检查每个普通fd是否在准备好的集合中int i;char recvbuf[1024] = {0};for(i = 0; i <= maxi; i++){if(FD_ISSET(clients[i], &readyset)){int ret = readline(clients[i], recvbuf, 1024);if(ret == -1)ERR_EXIT("readline");else if(ret == 0){ //对端已关闭tcp连接//从监听集合中移除fd ,并关闭连接printf("client closed\n");close(clients[i]);FD_CLR(clients[i], &readset);clients[i] = -1;break;}printf("recv data: %s", recvbuf);writen(clients[i], recvbuf, strlen(recvbuf));
if(--nready <= 0)break; //不在轮询后面的fd}}//for}//while(1)
}

5.poll服务器模型

  5.1 使用poll编写并发服务器的一般步骤:

    a)创建struct pollfd events[]数组存放用来监听的fd,并且初始化为-1;

    b)将listenfd加入到该数组中;

    c)进入while(1)循环,执行poll系统调用,并检查返回值;

    d)检查listenfd是否可读,若可读,则需要accept一个连接,从events数组中找一个空闲的位置,将返回的fd加入到数组中;

    e)检查其他的fd,这里要注意fd关闭的问题(如果客户关闭了连接,本地需要关闭连接,并将数组元素置为-1)。

  5.2 程序示例。

void do_poll(int listenfd){struct pollfd clients[2048]; //存储所有要监听的fd//初始化数组int i;for(i = 0; i < 2048; i++){clients[i].fd = -1;}clients[0].fd = listenfd;clients[0].events = POLLIN;int maxi = 0; // clients数组的最大下标int nready; // 接收poll 的返回值//执行 poll 函数while(1){nready = poll(clients, maxi+1, -1);if(nready == -1){if(errno == EINTR)continue;ERR_EXIT("poll");}// 1.处理 listenfdif(clients[0].revents & POLLIN){int peerfd = accept(listenfd, NULL, NULL);if(peerfd == -1)ERR_EXIT("accept");// 为新的fd 找一个空位置int i;for(i = 0; i < 2048; i++){if(clients[i].fd  == -1){clients[i].fd = peerfd;clients[i].events = POLLIN;if(i > maxi) //更新maximaxi = i;break;}}if(i == 2048){fprintf(stderr, "too many clients\n");close(peerfd);continue;}if(--nready <= 0)continue;}// 2.依次轮询已连接的fdint i;for(i = 0; i <= maxi; i++){if(clients[i].fd == -1) //当前位置没有client 连接continue;char recvbuf[1024] = {0};if(clients[i].revents & POLLIN){//处理请求 回显int ret = readline(clients[i].fd, recvbuf, 1024);if(ret == -1)ERR_EXIT("readline");else if(ret == 0){close(clients[i].fd);clients[i].fd = -1;printf("client closed\n");continue;}printf("recv data: %s", recvbuf);writen(clients[i].fd, recvbuf, strlen(recvbuf));if(--nready <= 0)break;}}}
}

6.epoll服务器模型

  6.1 使用epoll编写并发服务器的一般步骤:

    a)创建epoll句柄(使用epoll_create函数),把listenfd加入到epollfd中(这里使用epoll_ctl函数);

    b)创建一个数组,用于接收epoll_wait的返回结果;

    c)进入while(1)循环,执行epoll_wait,并检查返回值;

    d)判断数组中的每个fd,如果是listenfd,那么需要accept,如果是普通fd,需要echo服务。

  6.2 程序示例。

void do_epoll(int listenfd){//创建epoll句柄int epollfd = epoll_create(2048);if(epollfd == -1)ERR_EXIT("epoll_create");//把listenfd加入到epollfd中struct epoll_event ev;ev.data.fd = listenfd;ev.events = EPOLLIN;if(epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) == -1)ERR_EXIT("epoll_ctl");//创建数组 用作返回struct epoll_event events[2048];int nready;while(1){//执行wait 检查返回值nready = epoll_wait(epollfd, events, 2048, -1);if(nready == -1){if(errno == EINTR)continue;ERR_EXIT("epoll_wait");}//遍历events数组int i;for(i = 0; i < nready; i++){if(events[i].data.fd == listenfd){ //listenfdint peerfd = accept(listenfd, NULL, NULL);if(peerfd == -1)ERR_EXIT("accept");//新的fd加入到句柄中struct epoll_event ev;ev.data.fd = peerfd;ev.events = EPOLLIN;if(epoll_ctl(epollfd, EPOLL_CTL_ADD, peerfd, &ev) == -1)ERR_EXIT("epoll_ctl");}else{//普通fd 回显int fd = events[i].data.fd;char recvbuf[1024] = {0};int ret = readline(fd, recvbuf, 1024);if(ret == -1)ERR_EXIT("readline");else if(ret == 0){ //客户端关闭连接//移除fdprintf("client closed\n");struct epoll_event ev;ev.data.fd = fd;if(epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev) == -1)ERR_EXIT("epoll_ctl");//关闭该连接close(fd);//注意这里要先移除在关闭 否则会出错continue;}printf("recv data: %s", recvbuf);writen(fd, recvbuf, strlen(recvbuf));}}}//关闭epoll句柄close(epollfd);
}

7.总结

  7.1 select、poll、epoll之间的区别:

    a)select 文件描述符的大小受到限制,而且FD_SETSIZE受内核参数的限制,如果需要更改,需要重新编译内核;

    b)poll没有文件描述符大小的限制;

    c)select和poll共同的缺点是:内部的数组不同在内核空间和用空间中相互拷贝。而epoll采用共享内存,避免了这一开销;

    d)select和poll内部都是采用“轮询”机制,随着fd的增多,select和poll的效率随之下降,而epoll只关心已经准备好的fd,不存在这个缺点。

  7.2 write是把数据从用户空间拷贝至内核空间,而read是把数据从内核空间拷贝至用户空间。因此,当用read,write读写文件时,效率很低,有一个sendfile函数直接将数据从在内核之间拷贝,不经过用户空间。这叫做零拷贝技术。

转载于:https://www.cnblogs.com/monicalee/p/3879520.html

0730------Linux网络编程----------服务器端模型(迭代,多进程,多线程,select,poll,epoll 等)...相关推荐

  1. 【Linux网络编程】并发服务器之多线程模型

    00. 目录 文章目录 00. 目录 01. 概述 02. 多线程服务器 03. 多线程服务器实现思路 04. 多线程服务器实现 05. 附录 01. 概述 服务器设计技术有很多,按使用的协议来分有 ...

  2. 【Linux网络编程学习】I/O多路复用——select和poll

    此为牛客Linux C++课程和黑马Linux系统编程笔记. 0. I/O多路复用 所谓I/O就是对socket提供的内存缓冲区的写入和读出. 多路复用就是指程序能同时监听多个文件描述符. 之前的学习 ...

  3. socket 编程篇六之IPO多路复用-select poll epoll

    http://blog.csdn.net/woxiaohahaa/article/details/51498951 文章参考自:http://blog.csdn.net/tennysonsky/art ...

  4. linux网络编程学习笔记之三 -----多进程并发服务端

    首先是fork()函数.移步APUE 8.3.  比較清晰的解释能够參考http://blog.csdn.net/lingdxuyan/article/details/4993883和http://w ...

  5. Linux网络编程 | IO模型 :阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO

    目录 IO模型 阻塞与非阻塞 同步与异步 阻塞IO 非阻塞IO 信号驱动IO 多路复用IO 异步IO IO模型 根据各自的特性不同,IO模型被分为阻塞IO.非阻塞IO.信号驱动IO.异步IO.多路复用 ...

  6. Linux网络编程——tcp并发服务器(多线程)

    https://blog.csdn.net/lianghe_work/article/details/46504243 tcp多线程并发服务器 多线程服务器是对多进程服务器的改进,由于多进程服务器在创 ...

  7. linux网络编程之多路I/O转接服务器poll函数

    (1)poll函数 头文件:#include<poll.h> int  poll(struct  pollfd*fds, nfds_t nfds,int timeout); struct  ...

  8. Linux网络编程一步一步学-select详解

    select系统调用是用来让我们的程序监视多个文件描述符(file descriptor)的状态变化的.程序会停在select这里等待,直到被监视的文件描述符有某一个或多个发生了状态改变. selec ...

  9. 【Linux】一步一步学Linux网络编程教程汇总(更新中......)

    00. 目录 文章目录 00. 目录 01. 基础理论知识 02. 初级编程 03. 高级编程 04. LibEvent库 05. 06. 07. 01. 基础理论知识 [Linux网络编程]网络协议 ...

最新文章

  1. 结婚和年龄有直接关系吗?
  2. 我应该在CSS中使用px或rem值单位吗?
  3. 关于“指针的指针”的认识(值传递、指针传递区分)
  4. 使用AspectJ审计Spring MVC Webapp。 第2部分
  5. 栈-顺序表(代码、分析、汇编)
  6. scikit_learn 官方文档翻译(集成学习)
  7. Java™ 教程(控制流语句)
  8. NIOS2-IDE环境下的一些操作技巧
  9. 是逻辑运算符 java_跟我学java编程—Java逻辑运算符
  10. 【OpenCV学习笔记】【函数学习】十八(保存图片)
  11. 常用股票软件linux,在 Linux 下看股票?
  12. 局域网传文件_秒杀QQ微信,这3个神器传输文件快10倍
  13. Windows电脑上最好的3个azw3阅读器
  14. bestcoder#22NPY and girls
  15. 赠人玫瑰,手有余香, 下面请听仙居义工专题报道
  16. 基于Selenium爬取动态网页
  17. 谷歌最强NLU模型BERT介绍
  18. android tv keep,Keep电视版
  19. HBuilderXHBuilder连接雷电模拟器“未检测到手机或模拟器” ---- 问题解决
  20. 扔掉Windows 中的盗版软件,使用免费正版软件

热门文章

  1. Xcode SVN配置
  2. .NET LINQ 筛选数据
  3. 时尚经典 体验云桌办公经典时尚办公方式
  4. log4j.xml如何配置
  5. C++ dll 动态链接库的创建与调用
  6. 退出系统并跳转到登录界面 JS代码
  7. linux df命令参数详解
  8. wpf控件开发基础(1)
  9. Fidder 抓取Android模拟器数据包
  10. 【Android】图像中Drawable向Bitmap的两种转换方法