socket 使用select()非阻塞方式实现

5 篇文章 0 订阅

select函数原型如下:

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

select系统调用是用来让我们的程序监视多个文件句柄(socket 句柄)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变

有一片博文写得非常详细易理解http://blog.csdn.net/lingfengtengfei/article/details/12392449。推荐大家看看,这里就不说了。

主要贴代码,参考的也是别人的代码,但是发现有BUG,努力修正后实现多台客户段与一台服务器通信:在非阻塞模式下,服务器和客户端可以自由发消息,不必等待回答,目前服务器发的消息,所有客户端都会收到此消息。读者可以自己改一下,让服务器与指定的客户端通信(可以通过键盘输入要通信的客户端编号来控制,或者用栈或队列来保存客户端编号,服务器在分别发送消息):

服务器端代码:

   #include<stdio.h>  
    #include<stdlib.h>  
    #include<netinet/in.h>  
    #include<sys/socket.h>  
    #include<arpa/inet.h>  
    #include<string.h>  
    #include<unistd.h>  
    #define BACKLOG 5     //已经连接上的套接字个数
    #define CONCURRENT_MAX 8   //应用层同时可以处理的连接  
    #define SERVER_PORT 11332  
    #define BUFFER_SIZE 1024  
    #define QUIT_CMD ".quit"  
    int client_fds[CONCURRENT_MAX];  
    int main(int argc, const char * argv[])  
    {  
        char input_msg[BUFFER_SIZE];  
        char recv_msg[BUFFER_SIZE];  
        
        /*
 * struct sockaddr{
      unsigned short sa_family; //通信协议类型族AF_xx
      char sa_data[14];  //14字节协议地址,包含该socket的IP地址和端口号
  };
  struct sockaddr_in{
      short int sin_family; //通信协议类型族
      unsigned short int sin_port; //端口号
      struct in_addr sin_addr; //IP地址
      unsigned char si_zero[8];  //填充0以保持与sockaddr结构的长度相同
  };
 * */
        //本地地址  
        struct sockaddr_in server_addr;  
        server_addr.sin_family = AF_INET;  
        server_addr.sin_port = htons(SERVER_PORT);  
        server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
        bzero(&(server_addr.sin_zero), 8);

/*
 * int socket(int family,int type,int protocol)
  family:
      指定使用的协议簇:AF_INET(IPv4) AF_INET6(IPv6) AF_LOCAL(UNIX协议) AF_ROUTE(路由套接字) AF_KEY(秘钥套接字)
  type:
      指定使用的套接字的类型:SOCK_STREAM(字节流套接字) SOCK_DGRAM
  protocol:
      如果套接字类型不是原始套接字,那么这个参数就为0
 * */  
        //创建socket  
        int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  
        if(server_sock_fd == -1)  
        {  
            perror("socket error");  
            return 1;  
        }  
/*
 * int bind(int sockfd, struct sockaddr *myaddr, int addrlen)
  sockfd:
      socket函数返回的套接字描述符
  myaddr:
      是指向本地IP地址的结构体指针
  myaddrlen:
        结构体长度
 * */
        //绑定socket  
        int bind_result = bind(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
        if(bind_result == -1)  
        {  
            perror("bind error");  
            return 1;  
        }  
/*
 * int listen(int sockfd, int backlog)
 sockfd:
    socket函数绑定bind后套接字描述符
 backlog:
    设置可连接客户端的最大连接个数,当有多个客户端向服务器请求时,收到此值的影响。默认值20
 * */
        //listen  
        if(listen(server_sock_fd, BACKLOG) == -1)  
        {  
            perror("listen error");  
            return 1;  
        }  
        //fd_set  
        fd_set server_fd_set;  
        int max_fd = -1;  
        struct timeval tv;  //超时时间设置  
        while(1)  
        {  
            tv.tv_sec = 20;  
            tv.tv_usec = 0;  
            FD_ZERO(&server_fd_set);  
            FD_SET(STDIN_FILENO, &server_fd_set);  
            if(max_fd <STDIN_FILENO)  
            {  
                max_fd = STDIN_FILENO;  
            }  
            //printf("STDIN_FILENO=%d\n", STDIN_FILENO);  
        //服务器端socket  
            FD_SET(server_sock_fd, &server_fd_set);  
           // printf("server_sock_fd=%d\n", server_sock_fd);  
            if(max_fd < server_sock_fd)  
            {  
                max_fd = server_sock_fd;  
            }  
        //客户端连接  
            for(int i =0; i < CONCURRENT_MAX; i++)  
            {  
                //printf("client_fds[%d]=%d\n", i, client_fds[i]);  
                if(client_fds[i] != 0)  
                {  
                    FD_SET(client_fds[i], &server_fd_set);  
                    if(max_fd < client_fds[i])  
                    {  
                        max_fd = client_fds[i];  
                    }  
                }  
            }  
            int ret = select(max_fd + 1, &server_fd_set, NULL, NULL, &tv);  
            if(ret < 0)  
            {  
                perror("select 出错\n");  
                continue;  
            }  
            else if(ret == 0)  
            {  
                printf("select 超时\n");  
                continue;  
            }  
            else  
            {  
                //ret 为未状态发生变化的文件描述符的个数  
                if(FD_ISSET(STDIN_FILENO, &server_fd_set))  
                {  
                    printf("发送消息:\n");  
                    bzero(input_msg, BUFFER_SIZE);  
                    fgets(input_msg, BUFFER_SIZE, stdin);  
                    //输入“.quit"则退出服务器  
                    if(strcmp(input_msg, QUIT_CMD) == 0)  
                    {  
                        exit(0);  
                    }  
                    for(int i = 0; i < CONCURRENT_MAX; i++)  
                    {  
                        if(client_fds[i] != 0)  
                        {  
                            printf("client_fds[%d]=%d\n", i, client_fds[i]);  
                            send(client_fds[i], input_msg, BUFFER_SIZE, 0);  
                        }  
                    }  
                }  
                if(FD_ISSET(server_sock_fd, &server_fd_set))  
                {  
                    //有新的连接请求  
                    struct sockaddr_in client_address;  
                    socklen_t address_len;  

/*
 * int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen)
 sockfd:
     socket函数经过listen后套接字描述符
 cliaddr:
     客户端套接字接口地址结构
  addrlen:
     客户端地址结构长度
 * */
                    int client_sock_fd = accept(server_sock_fd, (struct sockaddr *)&client_address, &address_len);  
                    printf("new connection client_sock_fd = %d\n", client_sock_fd);  
                    if(client_sock_fd > 0)  
                    {  
                        int index = -1;  
                        for(int i = 0; i < CONCURRENT_MAX; i++)  
                        {  
                            if(client_fds[i] == 0)  
                            {  
                                index = i;  
                                client_fds[i] = client_sock_fd;  
                                break;  
                            }  
                        }  
                        if(index >= 0)  
                        {  
                            printf("新客户端(%d)加入成功 %s:%d\n", index, inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));  
                        }  
                        else  
                        {  
bzero(input_msg, BUFFER_SIZE);  
strcpy(input_msg, "服务器加入的客户端数达到最大值,无法加入!\n");  
send(client_sock_fd, input_msg, BUFFER_SIZE, 0);  
printf("客户端连接数达到最大值,新客户端加入失败 %s:%d\n", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));  
                        }  
                    }  
                }  
                for(int i =0; i < CONCURRENT_MAX; i++)  
                {  
                    if(client_fds[i] !=0)  
                    {  
                        if(FD_ISSET(client_fds[i], &server_fd_set))  
                        {  
                            //处理某个客户端过来的消息  
                            bzero(recv_msg, BUFFER_SIZE);  
                            long byte_num = recv(client_fds[i], recv_msg, BUFFER_SIZE, 0);  
                            if (byte_num > 0)  
                            {  
                                if(byte_num > BUFFER_SIZE)  
                                {  
                                    byte_num = BUFFER_SIZE;  
                                }  
                                recv_msg[byte_num] = '\0';  
                                printf("客户端(%d):%s\n", i, recv_msg);  
                            }  
                            else if(byte_num < 0)  
                            {  
                                printf("从客户端(%d)接受消息出错.\n", i);  
                            }  
                            else  
                            {  
                                FD_CLR(client_fds[i], &server_fd_set);  
                                client_fds[i] = 0;  
                                printf("客户端(%d)退出了\n", i);  
                            }  
                        }  
                    }  
                }  
            }  
        }  
        return 0;  
    }  

客户端代码:

  #include<stdio.h>  
    #include<stdlib.h>  
    #include<netinet/in.h>  
    #include<sys/socket.h>  
    #include<arpa/inet.h>  
    #include<string.h>  
    #include<unistd.h>  
    #define BUFFER_SIZE 1024  
      
    int main(int argc, const char * argv[])  
    {  
        struct sockaddr_in server_addr;  
        server_addr.sin_family = AF_INET;  
        server_addr.sin_port = htons(11332);  
        server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  
        bzero(&(server_addr.sin_zero), 8);  
      
        int server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);  
        if(server_sock_fd == -1)  
        {  
        perror("socket error");  
        return 1;  
        }  
        char recv_msg[BUFFER_SIZE];  
        char input_msg[BUFFER_SIZE];  
      /*
 * int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen)
 sockfd:
     socket函数返回套接字描述符
  serv_addr:
     服务器IP地址结构指针
 addrlen:
     结构体指针的长度
 * */
        if(connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) == 0)  
        {  
        fd_set client_fd_set;  
        struct timeval tv;  
      
        while(1)  
        {  
            tv.tv_sec = 20;  
            tv.tv_usec = 0;  
            FD_ZERO(&client_fd_set);  
            FD_SET(STDIN_FILENO, &client_fd_set);  
            FD_SET(server_sock_fd, &client_fd_set);  
      
           select(server_sock_fd + 1, &client_fd_set, NULL, NULL, &tv);  
            if(FD_ISSET(STDIN_FILENO, &client_fd_set))  
            {  
                bzero(input_msg, BUFFER_SIZE);  
                fgets(input_msg, BUFFER_SIZE, stdin);  
                if(send(server_sock_fd, input_msg, BUFFER_SIZE, 0) == -1)  
                {  
                    perror("发送消息出错!\n");  
                }  
            }  
            if(FD_ISSET(server_sock_fd, &client_fd_set))  
            {  
                bzero(recv_msg, BUFFER_SIZE);  
                long byte_num = recv(server_sock_fd, recv_msg, BUFFER_SIZE, 0);  
                if(byte_num > 0)  
                {  
                if(byte_num > BUFFER_SIZE)  
                {  
                    byte_num = BUFFER_SIZE;  
                }  
                recv_msg[byte_num] = '\0';  
                printf("服务器:%s\n", recv_msg);  
                }  
                else if(byte_num < 0)  
                {  
                printf("接受消息出错!\n");  
                }  
                else  
                {  
                printf("服务器端退出!\n");  
                exit(0);  
                }  
            }  
            }  
        //}  
        }  
        return 0;  
    }  

调试时发现,select函数每次调用后,如果超时,都会把struct timeval tv 设置为0,这样再次调用select时它会立即返回,根本不会监视socket句柄,导致一个超时的死循环。
所以每次调用select之后都要重新给struct timeval tv 赋值。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用 Python socket非阻塞通信,一般会使用 select 模块来实现select 模块可以监测多个 socket 的 I/O 状态,从而实现非阻塞通信。以下是一个简单的例子: ```python import select import socket # 创建 socket 对象 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置为非阻塞模式 s.setblocking(False) # 连接服务器 s.connect(('localhost', 8888)) # 监听 socket 对象 inputs = [s] while True: # 监听所有的 socket 对象 readable, writable, exceptional = select.select(inputs, [], []) for sock in readable: # 如果是 s 对象,则表示有新的连接 if sock is s: conn, addr = s.accept() print('Connected by', addr) inputs.append(conn) # 否则就是已连接的客户端发送消息 else: data = sock.recv(1024) if data: print(data.decode()) else: sock.close() inputs.remove(sock) ``` 在这个例子中,我们使用select 模块来监听 socket 对象的 I/O 状态,以实现非阻塞通信。我们首先创建了一个 socket 对象 s,并将其设置为非阻塞模式,然后连接服务器。接下来,我们将 s 对象加入到 inputs 列表中,然后通过 select.select() 方法来监听 inputs 列表中所有 socket 对象的 I/O 状态。如果是 s 对象,则表示有新的连接请求,我们会接受连接并将新连接的 socket 对象加入到 inputs 列表中。否则就是已连接的客户端发送消息,我们会通过 sock.recv() 方法来接收消息,然后进行处理。如果客户端关闭了连接,我们就会将 sock 对象从 inputs 列表中删除,并关闭该连接。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值