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

使用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;
}
目录
相关文章
用MASM32按Time Protocol(RFC868)协议编写网络对时程序中的一些有用的函数代码
用MASM32按Time Protocol(RFC868)协议编写网络对时程序中的一些有用的函数代码
|
1月前
|
机器学习/深度学习 编解码
深度学习笔记(三):神经网络之九种激活函数Sigmoid、tanh、ReLU、ReLU6、Leaky Relu、ELU、Swish、Mish、Softmax详解
本文介绍了九种常用的神经网络激活函数:Sigmoid、tanh、ReLU、ReLU6、Leaky ReLU、ELU、Swish、Mish和Softmax,包括它们的定义、图像、优缺点以及在深度学习中的应用和代码实现。
125 0
深度学习笔记(三):神经网络之九种激活函数Sigmoid、tanh、ReLU、ReLU6、Leaky Relu、ELU、Swish、Mish、Softmax详解
|
1月前
|
机器学习/深度学习 数据可视化 算法
激活函数与神经网络------带你迅速了解sigmoid,tanh,ReLU等激活函数!!!
激活函数与神经网络------带你迅速了解sigmoid,tanh,ReLU等激活函数!!!
|
3月前
|
机器学习/深度学习 算法
神经网络中激活函数的重要性
【8月更文挑战第23天】
31 0
|
3月前
|
监控
【网络编程】poll函数
【网络编程】poll函数
29 0
|
3月前
|
机器学习/深度学习 Shell 计算机视觉
一文搞懂 卷积神经网络 卷积算子应用举例 池化 激活函数
这篇文章通过案例详细解释了卷积神经网络中的卷积算子应用、池化操作和激活函数,包括如何使用卷积算子进行边缘检测和图像模糊,以及ReLU激活函数如何解决梯度消失问题。
|
4月前
|
存储 Java Unix
(八)Java网络编程之IO模型篇-内核Select、Poll、Epoll多路复用函数源码深度历险!
select/poll、epoll这些词汇相信诸位都不陌生,因为在Redis/Nginx/Netty等一些高性能技术栈的底层原理中,大家应该都见过它们的身影,接下来重点讲解这块内容。
|
4月前
|
网络协议 安全 Java
Java中的网络编程:Socket编程详解
Java中的网络编程:Socket编程详解
|
4月前
|
Java API 网络安全
Java网络编程入门
Java网络编程入门
|
4月前
|
网络协议 安全 Java
Java中的网络编程:Socket编程详解
Java中的网络编程:Socket编程详解