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 ...
随机推荐
- 多测师讲解自动化测试 _RF连接数据库_高级讲师肖sir
RF连接数据库:1.Connect To Database(连接数据库)2.Table Must Exist(表必须存在)3.Check If Exists In Database(查询某条件是否存在 ...
- ucore操作系统学习笔记(二) ucore lab2物理内存管理分析
一.lab2物理内存管理介绍 操作系统的一个主要职责是管理硬件资源,并向应用程序提供具有良好抽象的接口来使用这些资源. 而内存作为重要的计算机硬件资源,也必然需要被操作系统统一的管理.最初没有操作系统 ...
- k8s- centos7.8搭建
vmware16.0 centos7.8 1. 使用vmware安装 centos环境 cpu4个 内存4G 网络nat模式 2.配置网络 vim /etc/sysconfig/network-sc ...
- swoole为什么不建议使用static和global
$http = new swoole_http_server("0.0.0.0", 9501); $http->on("request", functio ...
- centos8安装zookeeper(单机方式)
一,下载zookeeper: 1,官网地址 http://zookeeper.apache.org/ 找到这个地址: https://mirrors.tuna.tsinghua.edu.cn/apac ...
- java抓取东方财富股票数据(附源码)
背景 前段时间给朋友写了一个自动抓取同花顺股票数据的程序,不少人觉得不错. 这几天后台有粉丝给我留言让我也抓一下东方财富的数据,说东方财富的数据特别难抓,我还真不一定能搞得定. 本来我是一个德艺双磬且 ...
- Java 悲观锁 synchronized (member){代码块}
Java 如果遇到会出现高并发的情况,一般建议使用悲观锁 :synchronized (member){代码块} 需要对数据库进行修改或新增的时候,建议写上事务--@Transactional @T ...
- frida框架hook常用字符串模板总结
ArrayBuffer转String: 解决中文乱码(模板) function ab2str(buf) { return new Uint16Array(buf) // encodedString = ...
- ScheduledExecutor定时器
为了弥补Timer 的上述缺陷,在Java 5的时候推出了基于线程池设计的 ScheduledExecutor.其设计思想是:每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互 ...
- Linux的top命令及交换分区
TOP命令关键指标 %MEM,在内存中的占用率 %CPU,使用率,如果两核,最大可到200% TIME+, 占用cpu的总时间/s SHR,分享内存 RES, 常驻内存,进程当前使用的内存大小,不包括 ...