zoukankan      html  css  js  c++  java
  • 【linux高级程序设计】(第十四章)TCP高级应用 2

    socket多路复用应用

    int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) 

    功能:轮循等待的方式,从多个文件描述符中获取状态变化后的情况

    readfds :包含所有可能因状态变成可读而触发select()函数返回的文件描述符

    writefds :包含所有可能因状态变成可写而触发select()函数返回的文件描述符

    exceptfds :包含所有可能因状态发生特殊异常(如带外数据到来)而触发select()函数返回的文件描述符

    针对文件描述符集合的操作如下:

    #define FD_SET(fd, fdsetp)          //把fd添加到fdsetp中
    #define FD_CLR(fd, fdsetp)          //从fdsetp中删除fd
    #define FD_ISSET(fd, fdsetp)       //检测fdsetp中的fd是否出现异常
    #define FD_ZERO(fdsetp)            //初始化fdsetp为空

    参数1限制上面要检测的文件描述符的范围,范围在0到最大文件描述符值之间

    最后一个参数:表示阻塞超时时限

    struct timeval {
        long tv_sec;
        long tv_usec;
    };

    返回值:函数错误,返回-1; 超时返回0,将时间结构体清空为0;有文件需要处理,返回相应的文件描述符,在文件描述符集合中清除不需要处理的文件描述符

    例子

    1.检测某个socket是否可读

    fd_set rdfds;        //声明一个fd_set集合来保存要检测的socket
    struct timeval tv;   //保存时间
    int ret;             //保存返回值
    FD_ZERO(&rdfds);     //集合清零
    FD_SET(socket, &rdfds);    //把要检测的文件描述符加入集合
    tv.tv_sec = 1; 
    tv.tv_usec = 500;          //设置select等待的最大时间为1s+500ms
    ret = select(socket + 1, &rdfds, NULL, NULL, &tv);  //检测集合中是否有可读信息
    if(ret < 0)           //出错
        perror("select");
    else if(ret == 0)     //超时
        printf("超时
    ");
    else                  //有状态变化
    {
        printf("ret = %d
    ", ret);
        //判断socket是否变成可读
        if(FD_ISSET(socket, &rdfds))
        {
            recv(...);  //读取
        }
    }

    2.检测用户键盘输入。需要把标准输入文件描述符0放入select检测

    FD_ZERO(&rdfds);     
    FD_SET(0, &rdfds);    
    tv.tv_sec = 1; 
    tv.tv_usec = 500;          
    ret = select(1, &rdfds, NULL, NULL, &tv);  
    if(ret < 0)           //出错
        perror("select");
    else if(ret == 0)     //超时
        printf("超时
    ");
    else                  //有输入
        scanf("%s", buf);

    int pselect (int __nfds, fd_set *__restrict __readfds, fd_set *__restrict __writefds, fd_set *__restrict __exceptfds, const struct timespec *__restrict __timeout, const __sigset_t *__restrict __sigmask)

    该函数与select()函数功能几乎相同,只是时间精度更高,同时设置了阻塞的信号集合。

    时间的结构体声明如下:

    struct timespec{
        long ts_sec;
        long ts_nsec;  //ns
    };

    poll与ppoll函数

    可以实现比select/pselect函数更强大的功能,更细粒的等待时间

    int poll (struct pollfd *fds, nfds_t nfds, int timeout)

    int ppoll (struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts, const sigset_t *sigmask)

    其中文件描述符的结构体定义为:

    struct pollfd{
        int fd;         //文件描述符
        short events;   //请求事件
        short revents;  //返回的事件
    };

    请求或返回的事件类型如下:

    ppoll()函数也可以在阻塞过程中屏蔽某些信号,而且timeout上ppoll的精度更高。

    调用

    ready = ppoll(&fds, nfds, timeout_ts, &sigmask);

    相当于调用

    sigset_t origmask;
    int timeout;
    timeout = (timeout_ts == NULL) ? -1 : (timeout_ts.tv_sec * 1000 + timeout_ts.tv_nesc / 1000000);
    sigprocmask(SIG_SETMASK, &sigmask, &origmask);
    ready = poll(&fds, nfds, timeout);
    sigprocmask(SIG_SETMASK, &origmask, NULL);

    示例

    基于多路复用的服务器客户端聊天程序

    这个的效果是目前为止最好的,可以实现一对多的通信。消息也比较清晰。

    服务器端

    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<netinet/in.h>
    #include<sys/socket.h>
    #include<sys/wait.h>
    #include<unistd.h>
    #include<arpa/inet.h>
    #include<sys/time.h>
    #define MAXBUF 1024
    int main(int argc, char * argv[])
    {
        int sockfd, new_fd;
        socklen_t len;
        struct sockaddr_in my_addr, their_addr;
        unsigned int myport, lisnum;
        char buf[MAXBUF + 1];
        fd_set rfds;   //文件描述符集合
        struct timeval tv;
        int retval, maxfd = -1;
        if(argv[2])
            myport = atoi(argv[2]);   //参数2为端口号
        else
            myport = 7838;            //默认端口号
        if(argv[3])
            lisnum = atoi(argv[3]);   //命令行第3个参数为listen队列大小 即可以等待多少个客户端
        else
            lisnum = 2;
        //创建socket对象 ipv4 TCP 默认协议
        if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
        {
            perror("socket");
            exit(EXIT_FAILURE);
        }
        bzero(&my_addr, sizeof(my_addr));
        my_addr.sin_family = PF_INET;
        my_addr.sin_port = htons(myport); 
        if(argv[1])
            my_addr.sin_addr.s_addr = inet_addr(argv[1]);  //参数1为IP地址
        else 
            my_addr.sin_addr.s_addr = INADDR_ANY;
        //绑定地址信息
        if(bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1)
        {
            perror("bind");
            exit(EXIT_FAILURE);
        }
        //服务器监听网络
        if(listen(sockfd, lisnum) == -1)
        {
            perror("listen");
            exit(EXIT_FAILURE);
        }
        while(1)
        {
            printf("
    ---------wait for new connect
    ");
            len = sizeof(struct sockaddr);
            //接收客户端连接
            if((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &len)) == -1)
            {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            else
            {
                //打印连接信息
                printf("server: got connection from %s, port %d, socket %d
    ", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
                while(1)
                {
                    FD_ZERO(&rfds);
                    FD_SET(0, &rfds);
                    FD_SET(new_fd, &rfds);
                    maxfd = new_fd;   //只有两个文件描述符0和new_fd,最大值为sockfd
                    tv.tv_sec = 1;
                    tv.tv_usec = 0;
                    //多路复用
                    retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
                    if(retval == -1) //函数出错
                    {
                        perror("select");
                        exit(EXIT_FAILURE);
                    }
                    else if(retval == 0) //超时
                    {
                        continue;
                    }
                    else
                    {
                        //检测是否为标准输入引起异常
                        if(FD_ISSET(0, &rfds))   
                        {
                            bzero(buf, MAXBUF + 1);
                            fgets(buf, MAXBUF, stdin);  //从标准输入读数据
                            if(!strncasecmp(buf, "quit", 4))  //如果quit退出
                            {
                                printf("i will quit!
    ");
                                break;
                            }
                            //将数据发送给客户端
                            len = send(new_fd, buf, strlen(buf) - 1, 0); //为什么要-1 ??
                            if(len > 0)
                                printf("send successful, %d byte send!
    ", len);
                            else
                            {
                                printf("send failure!");
                                break;
                            }
                        }
                        //如果是当前sockfd引起的异常
                        if(FD_ISSET(new_fd, &rfds))
                        {
                            bzero(buf, MAXBUF + 1);
                            //从中读取数据
                            len = recv(new_fd, buf, MAXBUF, 0);
                            if(len > 0)
                                printf("recv success :'%s', %dbyte recv
    ", buf, len);
                            else if(len == 0)
                            {
                                printf("the other one end quit
    ");
                                break;
                            }
                        }
                    }
                }
            }
            close(new_fd);
            printf("need other connect (no->quit)");  //是否需要等待其他客户端连接
            fflush(stdout);  //刷新标准输出
            bzero(buf, MAXBUF + 1);
            fgets(buf, MAXBUF, stdin);
            if(!strncasecmp(buf, "no", 2))
            {
                printf("quit!
    ");
                break;
            }
        }
        close(sockfd);
        return 0;
    }

    客户端

    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<string.h>
    #include<sys/types.h>
    #include<netinet/in.h>
    #include<sys/socket.h>
    #include<sys/wait.h>
    #include<unistd.h>
    #include<arpa/inet.h>
    #include<sys/time.h>
    #define MAXBUF 1024
    int main(int argc, char **argv)
    {
        int sockfd, len;
        struct sockaddr_in dest;
        char buffer[MAXBUF + 1];
        fd_set rfds;
        struct timeval tv;
        int retval, maxfd = -1;
        if(argc != 3)
        {
            printf("argv format errno, pls:
    		%s IP port
    ", argv[0]);
            exit(0);
        }
        //创建socket
        if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            perror("Socket");
            exit(EXIT_FAILURE);
        }
        bzero(&dest, sizeof(dest));
        dest.sin_family = AF_INET;
        dest.sin_port = htons(atoi(argv[2]));  //参数2为端口号
        if(inet_aton(argv[1], (struct in_addr *)&dest.sin_addr.s_addr) == 0) //参数1为IP地址
        {
            perror(argv[1]);
            exit(EXIT_FAILURE);
        }
        //发起连接
        if(connect(sockfd, (struct sockaddr *)&dest, sizeof(dest)) != 0) 
        {
            perror("Connect");
            exit(EXIT_FAILURE);
        }
        printf("
    get ready pls chat
    ");
        while(1)
        {
            FD_ZERO(&rfds);
            FD_SET(0, &rfds);
            FD_SET(sockfd, &rfds);
            maxfd = sockfd;
            tv.tv_sec = 1;
            tv.tv_usec = 0;
            //多路复用
            retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
            if(retval == -1)
            {
                printf("select %s", strerror(errno));
                break;
            }
            else if(retval == 0)  //超时
                continue;
            else
            {
                if(FD_ISSET(sockfd, &rfds))
                {
                    bzero(buffer, MAXBUF + 1);
                    len = recv(sockfd, buffer, MAXBUF, 0);
                    if(len > 0)
                    {
                        printf("recv message:'%s', %dbyte recv
    ", buffer, len);
                    }
                    else if(len < 0)
                    {
                        printf("message recv failure
    ");
                    }
                    else
                    {
                        printf("the other quit, quit
    ");
                        break;
                    }
                }
                if(FD_ISSET(0, &rfds))
                {
                    bzero(buffer, MAXBUF + 1);
                    fgets(buffer, MAXBUF, stdin);
                    if(!strncasecmp(buffer, "quit", 4))
                    {
                        printf("i will quit!
    ");
                        break;
                    }
                    len = send(sockfd, buffer, strlen(buffer) - 1, 0);
                    if(len > 0)
                        printf("send successful, %d byte send!
    ", len);
                    else
                        printf("send failure!");
                }
            }
        }
        close(sockfd);
        return 0;
    }

    服务器效果

    客户端1效果

    客户端2效果

  • 相关阅读:
    Linux命令:cp (copy)复制文件或目录
    使用 robots.txt 文件阻止或删除网页说明
    ecshop优化修改sitemap.xml到根目录
    我虚拟机上装的CentOS系统显示的ip配置是127.0.0.1,请问如何解决?
    Servlet/JSP vs. ASP.NET MVC
    Ubuntu Linux 上安装Apache的过程
    Ubuntu Linux 上安装Eclipse的过程
    sudo的意义
    Dependency Injection
    Ubuntu Linux 上安装TomCat的过程
  • 原文地址:https://www.cnblogs.com/dplearning/p/4702401.html
Copyright © 2011-2022 走看看