select模型(一 改进客户端)

摘要:
三、intselect;参数1、读、写、异常集合中的文件描述符的最大值+1参数2、可读集合参数5、超时时间后四个参数是输入输出参数,参数2,3,4返回发生的I/O事件。

一、改程序使用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;
    }
    returncount;
}
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;
    }
    returncount;

}
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;
        returnret;
    }
}
//偷窥方案实现readline避免一次读取一个字符
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
    intret;
    intnread;
    size_t nleft=maxline;
    char *bufp=(char*)buf;
    while(1)
    {
        ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
        if(ret<0)
            returnret;
        else if(ret==0)
            returnret;
        nread=ret;
        inti;
        for(i=0;i<nread;i++)
        {
            if(bufp[i]=='')
            {
                ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
                if(ret!=i+1)
                    exit(EXIT_FAILURE);
                returnret;
            }
        }
        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(intsock)
{
/*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
");
            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;//准备好的个数
    intmaxfd;
    int fd=fileno(stdin);//为何不使用STDIN_FILLENO(0),防止标准输入被重定向
    if(fd>sock)
        maxfd=fd;
    elsemaxfd=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(intsig)
{
    printf("recive a signal=%d
",sig);

}
int main(void)
{
        signal(SIGPIPE,handle_sigpipe);//捕捉第二次write的SIGPIPE信号,默认终止进程
        intsock;
        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中的对方套接口
        structsockaddr_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
",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;
    }
    returncount;
}
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;
    }
    returncount;

}
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;
        returnret;
    }
}
//偷窥方案实现readline避免一次读取一个字符
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
    intret;
    intnread;
    size_t nleft=maxline;
    char *bufp=(char*)buf;
    while(1)
    {
        ret=recv_peek(sockfd,bufp,nleft);//不清除sockfd,只是窥看
        if(ret<0)
            returnret;
        else if(ret==0)
            returnret;
        nread=ret;
        inti;
        for(i=0;i<nread;i++)
        {
            if(bufp[i]=='')
            {
                ret=readn(sockfd,bufp,i+1);//读出sockfd中的一行并且清空
                if(ret!=i+1)
                    exit(EXIT_FAILURE);
                returnret;
            }
        }
        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(intconn)
{
        intret;
        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
");
                break;//不用继续循环等待客户端数据
}
            fputs(recvbuf,stdout);
            writen(conn,recvbuf,strlen(recvbuf));
        }
}
void handle_sigchld(intsig)
{
    
    while(waitpid(-1,NULL, WNOHANG)>0)
        ;
        
}
int main(void)
{    
    
    signal(SIGCHLD,handle_sigchld);
    intlistenfd;
    if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
        ERR_EXIT("socket error");
    //if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0)
    

    //本地协议地址赋给一个套接字
    structsockaddr_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
",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);
        }elseclose(conn);
    }
    return 0;
}

免责声明:文章转载自《select模型(一 改进客户端)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇关联,依赖,泛化(又称继承分为扩展或包含),实现,聚合(共享),复合(组合)SSM 使用方法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

libpcap编程实例

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pcap.h> 4 #include <errno.h> 5 #include <sys/socket.h> 6 #include <netinet/in...

SQLServer中进行sql除法运算

转自:http://blog.sina.com.cn/s/blog_8020e41101019k7t.html SELECT field1/field2 FROM TB; 当 field1的数值 > field2的数值时,除法得出的结果是<1的,即0.xxxxxx 这个时候在DB2的环境下SELECT出来的值是0 解決方法: 先把field1转...

Mysql数据优化--DBA梳理珍藏篇

1. 优化SQL1)     通过show status了解各种sql的执行频率         show status like 'Com_%'        了解 Com_select,Com_insert 的执行次数 2)    通过Explain分析低效的sql语句 3)    建立合适的索引 4)    通过show status like '...

Mysql运维管理-Mysql增量备份及分库分表备份数据恢复实战12

1. MySQL数据库的备份与恢复 1.1 Mysqldump的工作原理? 利用mysqldump命令备份的过程,实际上就是把数据从mysql库里以逻辑的sql语句的形式直接输出或者生成备份的文件的过程。 备份的数据过滤注释都是sql语句,结果如下: [root@localhost opt]# egrep -v "#|*|--|^$" /opt/mysql...

android共享内存

在android下不能通过shm_open使用共享内存。 网上有好多关于android下使用Ashmem实现共享内存的,但经过尝试该方法可以mmap出内存,但是和另一个进程没有实现共享。 具体的使用方法: 1)fd = open("/dev/ashmem", O_RDWR); 2)ioctl(fd, ASHMEM_SET_NAME, region_name...

oracle nologging用法

一、oracle日志模式分为(logging,force logging,nologging) 默认情况是logging,就是会记录到redo日志中,force logging是强制记录日志,nologging是尽量减少日志。FORCE LOGGING可以在数据库级别、表空间级别进行设定、而LOGGING与NOLOGGING可以在表级别设定。 注:FORC...