当前位置:   article > 正文

【网络编程】select函数

【网络编程】select函数

select的优点是跨平台的,缺点是因为是轮询查询的,相对效率不高

使用 select 同时监听多个文件描述符, 将监控的操作交给内核去处理,当有监控操作时返回。select可以完成一个进程对多个客户端的处理

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds: 最大的文件描述符+1
readfds: 读文件描述符集合, 是一个传入传出参数,用于设置监听某些文件描述符的读操作,和返回有读操作的文件描述符集合。
	传入: 指的是告诉内核哪些文件描述符需要监控
	传出: 指的是内核告诉应用程序哪些文件描述符发生了变化
writefds: 写文件描述符集合(传入传出参数),同读文件描述符集合
execptfds: 异常文件描述符集合(传入传出参数),同读文件描述符集合
timeout: 
	NULL--表示永久阻塞, 直到有事件发生
	0 --表示不阻塞, 立刻返回, 不管是否有监控的事件发生
	>0--到指定事件或者有事件发生了就返回
    The time structures involved are defined in <sys/time.h> and look like

    struct timeval {
        long    tv_sec;         /* seconds */
        long    tv_usec;        /* microseconds */
    }
	
返回值:  成功返回发生变化的文件描述符的个数
		失败返回-1, 并设置errno值.

//初始化文件文件描述符集合
void FD_ZERO(fd_set *set);
//添加文件描述符到集合
void FD_SET(int fd, fd_set *set);
//将文件描述符从集合中删除
void FD_CLR(int fd, fd_set *set);
//判断文件描述符是否在集合中
int  FD_ISSET(int fd, fd_set *set);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

使用select处理多个客户端网络代码:

#include "socketwrap.h"
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>

int main()
{
    int sfd = Socket(AF_INET, SOCK_STREAM, 0);

    // 设置端口复用
    int opt = 1;
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));

    struct sockaddr_in soaddr;
    bzero(&soaddr, sizeof(soaddr));

    soaddr.sin_family = AF_INET;
    soaddr.sin_port = htons(9999);
    soaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    Bind(sfd, (struct sockaddr *)&soaddr, sizeof(soaddr));

    // 监听-listen
    Listen(sfd, 128);

    int connfd[FD_SETSIZE]; // 有效的文件描述符数组
    fd_set tmpfds, rdfds;   // 要监控的文件描述符集
    int maxfd;              // 当前需要监听的最大的文件描述符
    int nready;             // 返回的需要处理的个数
    int cfd;                // 通信描述符
    int i, maxi = 0;        // 通信描述符数组下标,及最大下标
    struct sockaddr_in clientsocket;
    socklen_t clilen;
    char buff[64]; // 通信数据

    FD_ZERO(&tmpfds);
    FD_ZERO(&rdfds);

    FD_SET(sfd, &rdfds);

    // 初始化有效的文件描述符数组
    for (int i = 0; i < FD_SETSIZE; i++)
    {
        connfd[i] = -1;
    }

    maxfd = sfd;

    while (1)
    {
        i = 0;
        clilen = sizeof(clientsocket);
        bzero(&clientsocket, clilen);

        tmpfds = rdfds;
        nready = select(maxfd + 1, &tmpfds, NULL, NULL, NULL);

        if (nready > 0)
        {
            if (FD_ISSET(sfd, &tmpfds))
            {
                cfd = Accept(sfd, (struct sockaddr *)&clientsocket, &clilen);
                if (cfd < 0)
                {
                    break;
                }

                // 先找位置, 然后将新的连接的文件描述符保存到connfd数组中
                for (i = 0; i < FD_SETSIZE; i++)
                {
                    if (connfd[i] == -1)
                    {
                        connfd[i] = cfd;
                        break;
                    }
                }
                // 若连接总数达到了最大值,则关闭该连接
                if (i == FD_SETSIZE)
                {
                    close(cfd);
                    printf("too many clients, i==[%d]\n", i);
                    // exit(1);
                    continue;
                }

                // 确保connfd中maxi保存的是最后一个文件描述符的下标
                if (i > maxi)
                {
                    maxi = i;
                }

                // 打印客户端的IP和PORT
                char sIP[16];
                memset(sIP, 0x00, sizeof(sIP));
                printf("client [%s:%d] connect\n", inet_ntop(AF_INET, &clientsocket.sin_addr.s_addr, sIP, sizeof(sIP)), htons(clientsocket.sin_port));

                FD_SET(cfd, &rdfds);

                if (maxfd < cfd)
                {
                    maxfd = cfd;
                }

                // 若没有其他变化的文件描述符,则无需执行后续代码
                if (--nready <= 0)
                {

                    continue;
                }
            }

            for (i = 0; i <= maxi; i++)
            {
                int sockfd = connfd[i];
                if (sockfd == -1)
                {
                    continue;
                }
                int n;
                if (FD_ISSET(sockfd, &tmpfds))
                {
                    memset(buff, 0x00, sizeof(buff));
                    n = Read(sockfd, buff, sizeof(buff));
                    if (n < 0)
                    {
                        perror("read over");
                        close(sockfd);
                        FD_CLR(sockfd, &rdfds);
                        connfd[i] = -1; // 将connfd[i]置为-1,表示该位置可用
                    }
                    else if (n == 0)
                    {
                        // printf("client is closed\n");
                        close(sockfd);
                        FD_CLR(sockfd, &rdfds);
                        connfd[i] = -1; // 将connfd[i]置为-1,表示该位置可用
                    }
                    else
                    {
                        printf("[%d]:[%s]\n", n, buff);
                        for (i = 0; i < n; i++)
                        {
                            buff[i] = toupper(buff[i]);
                        }

                        Write(sockfd, buff, n);
                    }

                    if (--nready <= 0)
                    {
                        break; // 注意这里是break,而不是continue, 应该是从最外层的while继续循环
                    }
                }
            }
        }
    }

    close(sfd);

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166

推荐一个零声教育学习教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:链接

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号