select模型(一 改进客户端)
一、改程序使用select来改进客户端对标准输入和套接字输入的处理,否则关闭服务器之后
循环中的内容都要被gets阻塞。原程序中https://www.cnblogs.com/wsw-seu/p/8413290.html,若服务器端先关闭发送FIN,客户端处于CLOSE WAIT状态,服务端到FIN_WAIT2。由于程序阻塞在fgets,因此无法到readline,也就无法break循环从而调用close。所有套接口状态不能再往前推进了。
二、select管理多个I/O,一旦其中一个I/O或者多个I/O检测出我们所感兴趣的事件,select函数返回
返回值是检测到的事件个数。并且可以返回哪些I/O发生了事件,进而遍历这些事件去处理。
三、int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数1、读、写、异常集合中的文件描述符的最大值+1(例如读集合放入描述符3,4,5 ,写集合放入7,9,异常集合填空,那么nfds为10)
参数2、可读集合(是一个输入输出参数,例如我们对3,4,5描述符的读感兴趣,当3,5发生读事件,select函数改变集合内容为3,5返回)
参数5、超时时间
后四个参数是输入输出参数,参数2,3,4返回发生的I/O事件。参数5返回剩余时间
四、修改描述符集的宏
void FD_CLR(int fd, fd_set *set); //将fd描述符从集合set中移除
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set); //将fd描述符添加到集合set中
void FD_ZERO(fd_set *set);
下面利用select来改造之前的客户端服务器程序,防止服务器端关闭,客户端还在等stdin输入,而不能退出。
客户端程序:
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include <sys/time.h> #define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
ssize_t readn(int fd,void *buf,size_t count)
{
size_t nleft=count;
ssize_t nread;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR)
continue;
else
return -1;
}
else if(nread==0)
return (count-nleft);
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<=0)
{
if(errno==EINTR)
continue;
return -1;
}else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count; }
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
while(1)
{
int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
if(ret==-1&&errno==EINTR)
continue;
return ret;
}
}
//偷窥方案实现readline避免一次读取一个字符
ssize_t readline(int sockfd,void * buf,size_t maxline)
{
int ret;
int nread;
size_t nleft=maxline;
char *bufp=(char*)buf;
while(1)
{
ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
if(ret<0)
return ret;
else if(ret==0)
return ret;
nread=ret;
int i;
for(i=0;i<nread;i++)
{
if(bufp[i]=='\n')
{
ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
if(ret!=i+1)
exit(EXIT_FAILURE);
return ret;
}
}
if(nread>nleft)
exit(EXIT_FAILURE);
nleft-=nread;
ret=readn(sockfd,bufp,nread);
if(ret!=nread)
exit(EXIT_FAILURE);
bufp+=nread;//移动指针继续窥看
}
return -1;
}
void echo_cli(int sock)
{
/*
char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)//默认有换行符
{ writen(sock,sendbuf,strlen(sendbuf));
int ret=readline(sock,recvbuf,1024);
if(ret==-1)
ERR_EXIT("readline");
else if(ret==0)
{
printf("service closed\n");
break;
}
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
*/
char sendbuf[1024]={0};
char recvbuf[1024]={0};
fd_set rset;
FD_ZERO(&rset);//初始化
int nready;//准备好的个数
int maxfd;
int fd=fileno(stdin);//为何不使用STDIN_FILLENO(0),防止标准输入被重定向
if(fd>sock)
maxfd=fd;
else
maxfd=sock;
while(1)
{
FD_SET(fd,&rset);//循环中。每次要重新设置rset。
FD_SET(sock,&rset);
nready=select(maxfd+1,&rset,NULL,NULL,NULL);
if(nready==-1)
ERR_EXIT("select error");
if(nready==0)
continue;
if(FD_ISSET(sock,&rset))
{
int ret=readline(sock,recvbuf,sizeof(recvbuf));
if(ret==-1)
ERR_EXIT("readline error");
else if(ret==0)
{
ERR_EXIT("serve closed");
break;
}
fputs(recvbuf,stdout);
memset(recvbuf,0,sizeof(recvbuf));
}
if(FD_ISSET(fd,&rset))
{
if(fgets(sendbuf,sizeof(sendbuf),stdin)==NULL)
break;
writen(sock,sendbuf,strlen(sendbuf));
memset(sendbuf,0,sizeof(sendbuf));
}
}
close(sock);//当服务器端关闭,客户端readline读取到FIN退出循环,执行close }
void handle_sigpipe(int sig)
{
printf("recive a signal=%d\n",sig); }
int main(void)
{
signal(SIGPIPE,handle_sigpipe);//捕捉第二次write的SIGPIPE信号,默认终止进程
int sock;
if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
ERR_EXIT("socket error"); struct sockaddr_in servaddr;//本地协议地址赋给一个套接字
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188); servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器段地址
//inet_aton("127.0.0.1",&servaddr.sin_addr); if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("connect"); //利用getsockname获取客户端本身地址和端口,即为对方accept中的对方套接口
struct sockaddr_in localaddr;
socklen_t addrlen=sizeof(localaddr);
if(getsockname(sock,(struct sockaddr *)&localaddr,&addrlen)<0)
ERR_EXIT("getsockname error");
printf("local IP=%s, local port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
//使用getpeername获取对方地址
echo_cli(sock);//选择一个与服务器通信
return 0;
}
服务器程序不变,先关闭服务器程序,客户端也能正常退出了。(客户端select同时监测套接口和标准输入的读事件)
/*
主动关闭服务器端,客户端不会再while(fgets())处阻塞,而是会
接收到服务器的FIN从而进入TIME_WAIT状态 */ #include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<sys/wait.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
ssize_t readn(int fd,void *buf,size_t count)
{
size_t nleft=count;
ssize_t nread;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR)
continue;
else
return -1;
}
else if(nread==0)
return (count-nleft);
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<=0)
{
if(errno==EINTR)
continue;
return -1;
}else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count; }
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
while(1)
{
int ret=recv(sockfd,buf,len,MSG_PEEK);//从sockfd读取内容到buf,但不去清空sockfd,偷窥
if(ret==-1&&errno==EINTR)
continue;
return ret;
}
}
//偷窥方案实现readline避免一次读取一个字符
ssize_t readline(int sockfd,void * buf,size_t maxline)
{
int ret;
int nread;
size_t nleft=maxline;
char *bufp=(char*)buf;
while(1)
{
ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
if(ret<0)
return ret;
else if(ret==0)
return ret;
nread=ret;
int i;
for(i=0;i<nread;i++)
{
if(bufp[i]=='\n')
{
ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
if(ret!=i+1)
exit(EXIT_FAILURE);
return ret;
}
}
if(nread>nleft)
exit(EXIT_FAILURE);
nleft-=nread;
ret=readn(sockfd,bufp,nread);
if(ret!=nread)
exit(EXIT_FAILURE);
bufp+=nread;//移动指针继续窥看
}
return -1;
}
void echo_srv(int conn)
{
int ret;
char recvbuf[1024];
while(1)
{
memset(&recvbuf,0,sizeof(recvbuf));
//使用readn之后客户端发送的数据不足1024会阻塞
//在客户端程序中确定消息的边界,发送定长包
ret=readline(conn,recvbuf,1024);
//客户端关闭
if(ret==-1)
ERR_EXIT("readline");
else if(ret==0)
{
printf("client close\n");
break;//不用继续循环等待客户端数据
}
fputs(recvbuf,stdout);
writen(conn,recvbuf,strlen(recvbuf));
}
}
void handle_sigchld(int sig)
{ while(waitpid(-1,NULL, WNOHANG)>0)
; }
int main(void)
{ signal(SIGCHLD,handle_sigchld);
int listenfd;
if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
ERR_EXIT("socket error");
//if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0) //本地协议地址赋给一个套接字
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188);
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//表示本机地址 //开启地址重复使用,关闭服务器再打开不用等待TIME_WAIT
int on=1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
ERR_EXIT("setsockopt error");
//绑定本地套接字
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("bind error");
if(listen(listenfd,SOMAXCONN)<0)//设置监听套接字(被动套接字)
ERR_EXIT("listen error"); struct sockaddr_in peeraddr;//对方套接字地址
socklen_t peerlen=sizeof(peeraddr);
int conn;//已连接套接字(主动套接字)
pid_t pid;
while(1){
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
ERR_EXIT("accept error");
//连接好之后就构成连接,端口是客户端的。peeraddr是对端
printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
pid=fork();
if(pid==-1)
ERR_EXIT("fork");
if(pid==0){
close(listenfd);
echo_srv(conn);
//某个客户端关闭,结束该子进程,否则子进程也去接受连接
//虽然结束了exit退出,但是内核还保留了其信息,父进程并未为其收尸。
exit(EXIT_SUCCESS);
}else close(conn);
}
return 0;
}
select模型(一 改进客户端)的更多相关文章
- 基于select模型的udp客户端实现超时机制
参考:http://www.cnblogs.com/chenshuyi/p/3539949.html 多路选择I/O — select模型 其思想在于使用一个集合,该集合中包含需要进行读写的fd,通过 ...
- 服务器端升级为select模型处理多客户端
流程图: select会定时的查询socket查询有没有新的网络连接,有没有新的数据需要读,有没有新的请求需要处理,一旦有新的数据需要处理,select就会返回,然后我们就可以处理相应的数据,sele ...
- 使用select函数改进客户端/服务器端程序
一.当我们使用单进程单连接且使用readline修改后的客户端程序,去连接使用readline修改后的服务器端程序,会出现一个有趣的现象,先来看输出: 先运行服务器端,再运行客户端, simba@ub ...
- 基于Select模型的Windows TCP服务端和客户端程序示例
最近跟着刘远东老师的<C++百万并发网络通信引擎架构与实现(服务端.客户端.跨平台)>,Bilibili视频地址为C++百万并发网络通信引擎架构与实现(服务端.客户端.跨平台),重新复习下 ...
- windows socket编程select模型使用
int select( int nfds, //忽略 fd_ser* readfds, //指向一个套接字集合,用来检测其可读性 ...
- socket编程的select模型
在掌握了socket相关的一些函数后,套接字编程还是比较简单的,日常工作中碰到很多的问题就是客户端/服务器模型中,如何让服务端在同一时间高效的处理多个客户端的连接,我们的处理办法可能会是在服务端不停的 ...
- linux下多路复用模型之Select模型
Linux关于并发网络分为Apache模型(Process per Connection (进程连接) ) 和TPC , 还有select模型,以及poll模型(一般是Epoll模型) Select模 ...
- 比较一下Linux下的Epoll模型和select模型的区别
一. select 模型(apache的常用) 1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Sel ...
- Epoll,Poll,Select模型比较
http://blog.csdn.net/liangyuannao/article/details/7776057 先说Select: 1.Socket数量限制:该模式可操作的Socket数由FD_S ...
随机推荐
- RLP序列化算法
RLP RLP(Recursive Length Prefix)递归长度前缀编码,是由以太坊提出的序列化/反序列化标准,相比json格式体积更小,相比protobuf对多语言的支持更强. RLP将数据 ...
- 智能指针(1)-std::unique_ptr
std::unique_ptr std::unique_ptr是一种几乎和原始指针一样高效的智能指针,对所管理的指针资源拥有独占权.由C++11标准引入,用于替代C++98中过时的std::auto_ ...
- 并查集算法Union-Find的思想、实现以及应用
并查集算法,也叫Union-Find算法,主要用于解决图论中的动态连通性问题. Union-Find算法类 这里直接给出并查集算法类UnionFind.class,如下: /** * Union-Fi ...
- linux(centos8):安装java jdk 14 (java 14.0.2)
一,下载jdk14 官方网站: https://www.oracle.com/java/ 下载页面: https://www.oracle.com/cn/java/technologies/javas ...
- spring boot:多个filter/多个interceptor/多个aop时设置调用的先后顺序(spring boot 2.3.1)
一,filter/interceptor/aop生效的先后顺序? 1,filter即过滤器,基于servlet容器,处于最外层, 所以它会最先起作用,最后才停止 说明:filter对所有访问到serv ...
- react基础准备知识
1.模块化:将重复的(可复用的)代码抽离为单个模块 2.组件化:将重复的 (可复用的) 的前端页面元素抽离单个组件 * Vue中实现组件化:1.Vue.component() 2.Vue.extend ...
- 【应用服务 App Service】Azure 应用服务测试网络访问其他域名及请求超时限制(4分钟 ≈ 230秒)
测试App Service是否可以访问其他DNS 当应用服务(Azure App Service)创建完成后,想通过ping命令来查看是否可以访问其他站点或解析DNS,但是发现ping命令无法使用.这 ...
- pyqt5屏幕坐标系
我们直接用代码去理解屏幕坐标系 import sys from PyQt5.QtWidgets import QHBoxLayout,QMainWindow,QApplication,QPushBut ...
- window.open浏览器弹出新窗口被拦截—原因分析和解决方案
最近在做项目的时候碰到了使用window.open被浏览器拦截的情况,在本机实验没问题,到了服务器就被拦截了,火狐有拦截提示,360浏览器拦截提示都没有,虽然在自己的环境可以对页面进行放行,但是对用户 ...
- MVC框架的代码审计小教程
介绍 YxtCMF在线学习系统是一个以thinkphp+bootstrap为框架进行开发的网络学习平台系统. 在线学习系统,为现代学习型组织提供了卓有成效的学习与培训方案, 能够通过在线学习和在线评估 ...