【网络编程】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);

使用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;
}

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

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值