嵌入式养成计划-31-网络编程----TCP的并发服务器模型------IO模型--IO多路复用

六十七、 TCP的并发服务器模型

67.1 循环服务器模型

  • 一次只能处理一个客户端,当上一个客户端退出后,才能处理下一个客户端
  • 缺点:无法同时处理多个客户端

代码模型

sfd = socket();
bind();
listen();
while(1){newfd = accept();while(1){recv();send();    }close(newfd);
}
close(sfd);

67.2 并发服务器模型

  • 目的:可以同时处理多个客户端的请求。
  • 实现:创建多进程或者创建多线程实现
    • 父进程 / 主线程 只负责连接(accept)
    • 子进程 / 分支线程只负责与客户端交互(recv / send);

67.2.1 多进程并发服务器

67.2.1.1 代码模型

void handler(int sig){while(waitpid(-1, NULL, WNOHANG) > 0);
}signal(17, handler);
sfd = socket();
bind();
listen();
while(1){newfd = accept();cpid = fork();if(0 == cpid){close(sfd);while(1){recv();send();        }close(newfd);exit(0);         //退出子进程    }close(newfd);
}
close(sfd);

67.2.1.2 代码示例

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/wait.h>#define ERR_MSG(msg) do{\fprintf(stderr, "__%d__ ", __LINE__);\perror(msg);\
}while(0)#define PORT 8888               //端口号的网络字节序,1024~49151
#define IP "192.168.125.55"     //本机IP,ifconfigint deal_cli_msg(int newfd, struct sockaddr_in cin);void handler(int sig)
{//循环回收僵尸进程//有子进程,没有僵尸进程  == 0//没有子进程,也没有僵尸进程 ==-1、while(waitpid(-1, NULL, WNOHANG) > 0); return ;
}int main(int argc, const char *argv[])
{//捕获17号 SIGCHLD信号if(signal(SIGCHLD, handler) == SIG_ERR){   ERR_MSG("signal");return -1; }   //创建流式套接字int sfd = socket(AF_INET, SOCK_STREAM, 0); if(sfd < 0){   ERR_MSG("socket");return -1; }   printf("socket create success  sfd=%d\n", sfd);//允许端口快速复用int reuse = 1;if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0){   ERR_MSG("setsockopt");return -1; }   printf("允许端口快速复用成功\n");//填充地址信息结构体给bind函数绑定使用;//真实的地址信息结构体根据地址族指定,AF_INET:man 7 ipstruct sockaddr_in sin;sin.sin_family      = AF_INET;          //必须填AF_INET;sin.sin_port        = htons(PORT);      //端口号的网络字节序,1024~49151sin.sin_addr.s_addr = inet_addr(IP);    //本机IP的网络字节序,ifconfig//绑定服务器的地址信息---》必须绑定if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0){   ERR_MSG("bind");return -1; }   printf("bind success\n");//将套接字转换成被动监听状态if(listen(sfd, 128) < 0){   ERR_MSG("listen");                                                                                    return -1; }   printf("listen success\n");struct sockaddr_in cin;     //存储客户端的地址信息socklen_t addrlen = sizeof(cin);int newfd = -1; pid_t cpid = -1; while(1){   //父进程只负责连接newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);if(newfd < 0){ERR_MSG("accept");return -1; }printf("[%s:%d] newfd=%d 客户端连接成功__%d__\n", \inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);//能运行到当前位置,则代表有客户端连接成功,//此时需要创建一个子进程,专门用于与客户端交互cpid = fork();if(0 == cpid){close(sfd);     //子进程只负责交互,sfd没有用deal_cli_msg(newfd, cin);close(newfd);exit(0);        //退出子进程,子进程只负责交互,不允许回到accept函数。}else if(cpid < 0){ERR_MSG("fork");;return -1; }close(newfd);       //在父进程中newfd没有用}   //关闭套接字close(sfd);return 0;
}//子进程负责与客户端交互的函数
int deal_cli_msg(int newfd, struct sockaddr_in cin)
{char buf[128] = ""; ssize_t res = 0;while(1){   bzero(buf, sizeof(buf));//接收数据res = recv(newfd, buf, sizeof(buf), 0); if(res < 0){ERR_MSG("recv");return -1; }else if(0 == res){printf("[%s:%d] newfd=%d 客户端下线__%d__\n", \inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);break;}printf("[%s:%d] newfd=%d : %s __%d__\n", \inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, buf, __LINE__);if(strcmp(buf, "quit") == 0)break;//发送数据strcat(buf, "*_*");     //数据可以选择冲终端获取if(send(newfd, buf, sizeof(buf), 0) < 0){ERR_MSG("send");return -1; }printf("send success\n");}   return 0;
}

67.2.2 多进程并发服务器

67.2.2.1 代码模型

sfd = socket();
bind();
listen();
while(1){newfd = accept();pthread_create( , , deal_cli_msg, );pthread_detach(tid);
}
close(sfd);void* deal_cli_msg(void* arg){while(1){recv();send();    }close(newfd);pthread_exit();
}

67.2.2.1 代码示例

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>#define ERR_MSG(msg) do{\fprintf(stderr, "__%d__ ", __LINE__);\perror(msg);\
}while(0)#define PORT 8888               //端口号的网络字节序,1024~49151
#define IP "192.168.125.55"     //本机IP,ifconfig//传递给线程执行体的数据封装成结构体
struct Climsg
{int newfd;struct sockaddr_in cin;
};void* deal_cli_msg(void* arg) ;     //void* arg = &infoint main(int argc, const char *argv[])
{//创建流式套接字int sfd = socket(AF_INET, SOCK_STREAM, 0);if(sfd < 0){ERR_MSG("socket");return -1;}printf("socket create success  sfd=%d\n", sfd);//允许端口快速复用int reuse = 1;if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0){ERR_MSG("setsockopt");return -1;}printf("允许端口快速复用成功\n");//填充地址信息结构体给bind函数绑定使用;//真实的地址信息结构体根据地址族指定,AF_INET:man 7 ipstruct sockaddr_in sin;sin.sin_family      = AF_INET;          //必须填AF_INET;sin.sin_port        = htons(PORT);      //端口号的网络字节序,1024~49151sin.sin_addr.s_addr = inet_addr(IP);    //本机IP的网络字节序,ifconfig//绑定服务器的地址信息---》必须绑定if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0){ERR_MSG("bind");return -1;}printf("bind success\n");//将套接字转换成被动监听状态if(listen(sfd, 128) < 0){ERR_MSG("listen");return -1;}printf("listen success\n");struct sockaddr_in cin;     //存储客户端的地址信息socklen_t addrlen = sizeof(cin);int newfd = -1;pthread_t tid;      //存储线程tid号struct Climsg info;while(1){//代码先阻塞在accept函数,再断开连接关闭的文件描述符时//accept函数每次在阻塞的时候,会先预选一个没有被占用的文件描述符//当解除阻塞的时候,若预选的文件描述符没有被占用,则直接返回预选的文件描述符//若预选的文件描述符被占用,则会重新遍历一个没有被使用的文件名描述符返回//主线程只负责连接(accept)newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);if(newfd < 0){ERR_MSG("accept");return -1;}printf("[%s:%d] newfd=%d 客户端连接成功__%d__\n", \inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);info.newfd = newfd;info.cin = cin;//能运行到当前位置,则代表有客户端连接成功,//此时需要创建一个分支线程,专门用于处理客户端的交互if(pthread_create(&tid, NULL, deal_cli_msg, (void*)&info) != 0){fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);return -1;}pthread_detach(tid);    //分离线程}//关闭套接字close(sfd);return 0;
}//线程执行体 ---> 分支线程只负责交互
void* deal_cli_msg(void* arg)       //void* arg = &info
{//newfd和cin必须另存,每个客户端都有自己独立的通信文件描述符和地址信息。//如果使用全局变量,或者指针方式间接访问,会导致所有线程共用一份newfd和cin,//那么newfd和cin会被覆盖int newfd = ((struct Climsg*)arg)->newfd;struct sockaddr_in cin = ((struct Climsg*)arg)->cin;char buf[128] = "";ssize_t res = 0;while(1){bzero(buf, sizeof(buf));//接收数据res = recv(newfd, buf, sizeof(buf), 0);if(res < 0){ERR_MSG("recv");break;}else if(0 == res){printf("[%s:%d] newfd=%d 客户端下线__%d__\n", \inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);break;}printf("[%s:%d] newfd=%d : %s __%d__\n", \inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, buf, __LINE__);if(strcmp(buf, "quit") == 0)break;//发送数据strcat(buf, "*_*");     //数据可以选择冲终端获取if(send(newfd, buf, sizeof(buf), 0) < 0){ERR_MSG("send");break;}printf("send success\n");}close(newfd);pthread_exit(NULL);
}

六十八、IO模型

68.1 阻塞IO

  1. 最常用,最简单,效率最低的。
  2. 创建套接字文件描述符后,默认处于阻塞IO模式;
  3. read, write, recv, send, recvfrom ,sendto,accept

68.2 非阻塞IO

  1. .防止进程阻塞在IO函数上,当一个程序使用了非阻塞IO模式的套接字,那么它需要使用一个循环来不停的判断该文件描述符是否有数据可读,称之为polling;
  2. 应用程序不停的polling内核监测IO事件是否产生,cpu消耗率高;
  3. IO中导致函数阻塞的原因是因为文件描述符有阻塞属性。
read阻塞:文件描述符有阻塞属性:0号文件描述符有阻塞属性 + 读属性---0号文件描述符加上非阻塞属性
  • 修改 IO 为非阻塞方式
1. 先获取0号文件描述符原有属性
2. 在原有属性的基础上将阻塞  设置为  非阻塞
3. 将修改后的属性重新设置回0号文件描述符中

fcntl函数

功能:获取/设置文件描述属性;
原型:#include <unistd.h>#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
参数:int fd:指定要设置或者获取属性的文件描述符int cmd:F_GETFL (void):获取属性,第三个参数不用填,获取到的属性在返回值返回;F_SETFL (int):设置属性,第三个参数是int类型;

68.3 信号驱动IO

  1. 异步通信方式;
  2. 信号驱动IO是指预先告诉内核,使得某个文件描述符发生IO事件的时候,内核会通知相关进程
    SIGIO;
  3. 对于TCP而言,信号驱动IO对TCP没有用。因为信号产生过于频繁,而且不能区分是哪个文件描述符发生的。

68.4 IO多路复用(重点!!!)

  1. 进程中如果同时需要处理多路输入输出流,
  2. 在无法用多进程多线程,可以选择用IO多路复用;
  3. 由于不需要创建新的进程和线程,减少系统的资源开销,减少上下文切换的次数。
    1. 上下文:运行一个进程所需要的所有资源
    2. 上下文切换:从A进程切换到B进程,A进程的资源要完全替换成B进程的,是一个耗时操作。
  4. 增加并发量的时候可以使用IO多路复用
  5. 允许同时对多个IO进行操作,内核一旦发现进程执行一个或多个IO事件,会通知该进程。
    在这里插入图片描述

68.4.1 select

68.4.1.1 select函数

功能:阻塞函数,让内核监测集合中是否有文件描述符准备就绪,若准备就绪则解除阻塞;当函数解除阻塞后,集合中会只剩下产生事件的文件描述符;例如:0号准备就绪,则集合中只剩下0号sfd准备就绪,则集合中只能下sfd;0和sfd均准备就绪,则0和sfd均存在若不将数据从触发事件的文件描述符对应的空间中取出,此时该文件描述符一直处于就绪状态。
原型:#include <sys/select.h>#include <sys/time.h>#include <sys/types.h>#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数:int nfds:需要填充三个集合中最大的文件描述符编号+1;fd_set *readfds, fd_set *writefds,fd_set *exceptfds:读集合,写,其他集合,若集合不使用,填NULL; 一般只用读集合;struct timeval *timeout:设置超时时间;   1. 若不想设置超时时间,填NULL,则当前函数会一直阻塞,直到集合中有文件描述符准备就绪。2. 设置超时时间; 若时间到后依然没有事件产生,则该函数解除阻塞,且返回失败情况。struct timeval {long    tv_sec;         /* seconds */long    tv_usec;        /* microseconds */};
返回值:>0, 成功返回成功触发事件的文件描述符个数;=0, 超时了;失败,返回-1,更新errno;操作集合的函数   void FD_CLR(int fd, fd_set *set);    //将fd从集合中删除int  FD_ISSET(int fd, fd_set *set);  //判断fd是否在集合中void FD_SET(int fd, fd_set *set);    //将fd添加到结合中void FD_ZERO(fd_set *set);           //清空集合

68.4.1.2 select的TCP模型

sfd = socket();
bind();
listen();
while(1){tempfds = readfds;select(maxfd+1, &tempfds, NULL, NULL, NULL);for(int i=0; i<=maxfd; i++){if(FD_ISSET(i, &tempfds) == 0) continue;if(0 == i){fgets();        }else if(sfd == i){newfd = accept()FD_SET(newfd, &readfds);maxfd = maxfd>newfd?maxfd:newfd;        }    else{res = recv(i, );if(0 == res){close(i);    FD_CLR(i, &readfds);while(!FD_ISSET(maxfd, &readfds) && maxfd-->=0);            }send();                 }}
}
close(sfd);

68.4.1.3 select的TCP服务器代码

68.4.1.4 select的TCP客户端代码

这俩的代码都在这个链接里面,再放进来就太长了,写文档都在卡

IO多路复用实现TCP客户端与TCP并发服务器

68.4.2 poll

68.4.2.1 poll函数

功能:阻塞函数,阻塞等待集合中有文件描述符准备就绪,若准备就绪,则立即解除阻塞。
原型:#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:struct pollfd *fds:指定要监测的集合struct pollfd {int   fd;         /* file descriptor */     指定要监测的文件描述符short events;     /* requested events */    指定要监测的事件short revents;    /* returned events */     实际产生的事件};事件:POLLIN     这里有数据可读POLLOUT    可写POLLERR    错误事件,只有在revents中有效nfds_t nfds:指定要监测的文件描述符的个数;int timeout:超时时间,以ms为单位>0, 设置超时时间,以ms为单位;=0, 不阻塞,即使没有文件描述符准备就绪,该函数不阻塞;<0, 不设置超时时间,一直阻塞,直到当集合中有文件描述符准备就绪,该函数解除阻塞;
返回值:>0, 实际产生事件的文件描述符个数;=0, 超时了=-1,更新errno;

68.4.2.2 poll的TCP客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>#define ERR_MSG(msg) do{\fprintf(stderr, "__%d__ ", __LINE__);\perror(msg);\
}while(0)#define SER_PORT 8888           //服务器绑定的端口号
#define SER_IP "192.168.125.55"     //服务器绑定的IP                                                  int main(int argc, const char *argv[])
{//创建流式套接字int cfd = socket(AF_INET, SOCK_STREAM, 0);if(cfd < 0){ERR_MSG("socket");return -1;}printf("socket create success  cfd=%d\n", cfd);//允许端口快速复用int reuse = 1;if(setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0){ERR_MSG("setsockopt");return -1;}printf("允许端口快速复用成功\n");//绑定客户端的地址信息---》非必须绑定//客户端若不绑定,则操作系统会自动给客户端绑定本机IP及随机端口//填充服务器的地址信息结构体给connect函数使用;//真实的地址信息结构体根据地址族指定,AF_INET:man 7 ipstruct sockaddr_in sin;sin.sin_family      = AF_INET;              //必须填AF_INET;sin.sin_port        = htons(SER_PORT);      //服务器绑定的端口号sin.sin_addr.s_addr = inet_addr(SER_IP);    //服务器绑定的IP//连接服务器,想要连接哪个服务器就需要填充哪个服务器绑定的地址信息if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin)) < 0){ERR_MSG("connect");return -1;}printf("connect server success\n");//创建集合struct pollfd fds[2] = {0};//将需要的文件描述符添加到集合中fds[0].fd       = 0;        //指定要监测0号文件描述符fds[0].events   = POLLIN;   //指定要监测读事件fds[1].fd       = cfd;      //指定要监测cfdfds[1].events   = POLLIN;int p_res = 0;char buf[128] = "";ssize_t res = 0;while(1){p_res = poll(fds, sizeof(fds)/sizeof(struct pollfd), -1);if(p_res < 0){ERR_MSG("poll");return -1;}else if(0 == p_res){printf("time out");break;}//能运行到当前位置,则代表集合中有文件描述符准备就绪//即revents成员中有数据了,//判断revents中是否有POLLIN事件if(fds[0].revents & POLLIN){printf("触发键盘输入事件\n");bzero(buf, sizeof(buf));fgets(buf, sizeof(buf), stdin);buf[strlen(buf)-1] = '\0';//发送数据if(send(cfd, buf, sizeof(buf), 0) < 0)//if(write(cfd, buf, sizeof(buf)) < 0){ERR_MSG("send");return -1;}printf("send success\n");}if(fds[1].revents & POLLIN){bzero(buf, sizeof(buf));//接收数据res = recv(cfd, buf, sizeof(buf), 0);//res = read(cfd, buf, sizeof(buf));if(res < 0){ERR_MSG("recv");return -1;}else if(0 == res){printf("[%s:%d] cfd=%d 服务器下线__%d__\n", \SER_IP, SER_PORT, cfd, __LINE__);break;}printf("[%s:%d] cfd=%d :%s __%d__\n", \SER_IP, SER_PORT, cfd, buf, __LINE__);}}//关闭套接字close(cfd);return 0;
}

68.4.2.3 epoll(到驱动部分再讲解)(是重点)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/130011.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

web基础及http协议

web基础 全称 world wide web 全球广域网也就是万维网 web1.0 只能看 web2.0 页面交互&#xff1a;静态页面和动态页面 静态页面url&#xff1a;文本文件&#xff0c;可以修改&#xff0c;一般以html .htm保存的文本文件。网站的基础。静态页面和后台数据库没有任何交互不包含…

找到所有数组中消失的数字

题目链接 找到所有数组中消失的数字 题目描述 注意点 在不使用额外空间且时间复杂度为 O(n) 的情况下解决这个问题 解答思路 要想找到消失的数字需要使用哈希表&#xff0c;因为本题要在不使用额外空间且时间复杂度为 O(n) 的情况下解决这个问题&#xff0c;而所有数字出现…

安全性第一!OpenWRT配置SFTP远程文件传输,实现数据安全保护

文章目录 前言1. openssh-sftp-server 安装2. 安装cpolar工具3.配置SFTP远程访问4.固定远程连接地址 前言 本次教程我们将在OpenWRT上安装SFTP服务&#xff0c;并结合cpolar内网穿透&#xff0c;创建安全隧道映射22端口&#xff0c;实现在公网环境下远程OpenWRT SFTP&#xff…

如何使用Docker轻松构建和管理应用程序(一)

如今Docker的使用已经非常普遍&#xff0c;特别在一线互联网公司。使用Docker技术可以帮助企业快速水平扩展服务&#xff0c;从而到达弹性部署业务的能力。在云服务概念兴起之后&#xff0c;Docker的使用场景和范围进一步发展&#xff0c;如今在微服务架构越来越流行的情况下&a…

信息增益,经验熵和经验条件熵——决策树

目录 1.经验熵 2.经验条件熵 3.信息增益 4.增益比率 5.例子1 6.例子2 在决策树模型中&#xff0c;我们会考虑应该选择哪一个特征作为根节点最好&#xff0c;这里就用到了信息增益 通俗上讲&#xff0c;信息增益就是在做出判断时&#xff0c;该信息对你影响程度的大小。比…

数字孪生与GIS数据为何高度互补?二者融合后能达到什么样的效果?

山海鲸可视化作为一款数字孪生软件&#xff0c;在GIS的融合方面处于业内领先水平&#xff0c;那么为什么一款数字孪生软件要花费巨大的精力&#xff0c;去实现GIS的融合&#xff0c;实现后又能达到什么样的效果呢&#xff1f;下面就让我们来一探究竟。 一、为什么数字孪生需要…

Java使用模板导出word、pdf

使用deepoove根据模板导出word文档&#xff0c;包括文本、表格、图表、图片&#xff0c;使用WordConvertPdf可将word文档转换为pdf导出 模板样例&#xff1a; 导出结果&#xff1a; 一、引入相关依赖 <!-- 工具类--><dependency><groupId>cn.hutool&…

H5逆向之远程RPC

引言前一讲说过H5 怎么去抓包,逆向分析。其中说到RPC。这一节详细讲一下。有一种情况,JS 比较复杂,混淆的厉害。 这个时候就用到RPC。原理就是,hook web 浏览器,直接调用js 里边的方法。 Node 服务。为什么用到Node 服务,先来看下这架构 Node 对外提供各种接口,外部可以…

this关键字在不同上下文中的值是如何确定的?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

MyBatisPlus(十)判空查询

说明 判空查询&#xff0c;对应SQL语句中的 IS NULL语句&#xff0c;查询对应字段为 NULL 的数据。 isNull /*** 查询用户列表&#xff0c; 查询条件&#xff1a;电子邮箱为 null 。*/Testvoid isNull() {LambdaQueryWrapper<User> wrapper new LambdaQueryWrapper<…

【c#】adapter.fill(dt)报错specified cast is not valid

报错信息&#xff1a; 报错specified cast is not valid,指定转换类型无效。 原因 查出来的数据有小数&#xff0c;且小数位数较多&#xff0c;问题就出现在这里&#xff0c;ORacle可以查出精确度高的数据&#xff0c;但是C#没办法查出来&#xff0c;就导致了有数据类型转换&…

关于:未同意隐私政策,应用获取ANDROID ID问题2

一、环境 Unity2018 4.21f1、Android Studio、Windows10 二、问题描述 在发布应用到华为应用市场时&#xff0c;提示“在用户同意隐私政策前&#xff0c;您的应用获取了用户的ANDROID ID&#xff0c;不符合华为应用市场审核标准。” 如果你想去掉获取ANDROID ID的代码可以参…