【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效果

原文地址:https://www.cnblogs.com/dplearning/p/4702401.html