一个支持tcp多客户端连接的例子,轻松认识linux select函数的基本原理和使用方法

这本是libmodbus-3.0.6中的一个例子,觉得是学习select很好的例子,注释了一下分享出来!

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define NB_CONNECTION    5

modbus_t *ctx = NULL;
int server_socket;
modbus_mapping_t *mb_mapping;

static void close_sigint(int dummy)
{
    close(server_socket);
    modbus_free(ctx);
    modbus_mapping_free(mb_mapping);

    exit(dummy);
}

int main(void)
{
    int master_socket;
    int rc;
    fd_set refset;   //fd_set现在一共可以监听1024个文件描述符
    fd_set rdset;

    /*文件描述符的最大值,要保证fdmax是需要监视的最大的文件描述符值*/
    int fdmax;

    ctx = modbus_new_tcp("127.0.0.1", 1502);

    mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, 0,
                                    MODBUS_MAX_READ_REGISTERS, 0);
    if (mb_mapping == NULL) {
        fprintf(stderr, "Failed to allocate the mapping: %s\n",
                modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    server_socket = modbus_tcp_listen(ctx, NB_CONNECTION);  //这儿并不会阻塞
    /**
     * 这儿的server_socket值应该是3,因为标准输入流的文件描述符为0,标准输出流的文件描述符为1
     * 标准错误流的文件描述符为2,server_socket是本程序中的第一个文件描述符,所以其值为3
     */


    /**
     * 绑定SIGINT信号到处理函数
     * SIGINT信号由Interrupt Key产生,通常是CTRL+C或者DELETE。
     * 发送给所有ForeGround Group的进程
     */
    signal(SIGINT, close_sigint);  

    /*清空监视集合*/
    FD_ZERO(&refset);

    /*将server_socket文件描述符加入到监视集合中*/
    FD_SET(server_socket, &refset);

    /*保证fdmax是需要监视的最大的文件描述符值*/
    fdmax = server_socket;

    for (;;) {
        /**
         * select函数成功返回时会将未准备好的描述位清零,所以每次select开始之前都需要将refset的值重新赋值给rdset
         */
        rdset = refset;   

	/**
         * select(int maxfd, fd_set *rdset, fd_set *wrset, fd_set *exset, struct timeval *timeout); 
         * maxfd是需要监视的最大的文件描述符值+1(这个参数是为了优化性能,否则系统每一次要从0检测到1023)
         * select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型
         * rdset, wrset, exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合,不需要监视的项可以设为NULL
         * struct timeval 结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有发生变化则函数返回,返回值0。
         * (若timeout参数为NULL,则程序一直阻塞直到监视的描述符发生变化)
         * FD_ZERO(fd_set *fdset);将指定的文件描述符清空,在对文件描述符进行设置前,必须对其进行初始化。
         * FD_SET(fd_set *fdset);用于在文件描述符集合中增加一个新的文件描述符
         * FD_CLR(fd_set *fdset);用于在文件描述符集合中删除一个文件描述符
         * FD_ISSET(int fd, fd_set *fdset);用于测试指定的文件描述符是否存在该集合中
         * 过去,一个fd_set通常只能包含小于32的fd,因为fd_set其实只用了一个32位矢量来表示fd; 
         * 现在UNIX系统通常会在头文件中定义常量FD_SETSIZE,它是数据类型fd_set的描述自数量,其值通常是1024,
         * 这样就能表示小于1024的fd
         */
        if (select(fdmax+1, &rdset, NULL, NULL, NULL) == -1) {   
            perror("Server select() failure.");
            close_sigint(1);
        }

        /**其实master_socket=0处完全可以换为3,因为0/1/2号设备时系统的标准输入/输出/错误流,并不需要监视**/
        for (master_socket = 0; master_socket <= fdmax; master_socket++) {    
            if (FD_ISSET(master_socket, &rdset)) {  //检查master_socket是否存在该集合中,即对应的描述符是否发生变化
                if (master_socket == server_socket) { //socket客户端请求一个新的socket连接
                    socklen_t addrlen;
                    struct sockaddr_in clientaddr;
                    int newfd;

                    /*处理新的连接请求*/
                    addrlen = sizeof(clientaddr);
                    memset(&clientaddr, 0, sizeof(clientaddr));
                    newfd = accept(server_socket, (struct sockaddr *)&clientaddr, &addrlen);

                    if (newfd == -1) {
                        perror("Server accept() error");
                    } else {
                        FD_SET(newfd, &refset);   //将新连接的文件描述符加入监视集合中

                        if (newfd > fdmax) {
                            /*保证fdmax是需要监视的最大的文件描述符值*/
                            fdmax = newfd;
                        }
                        printf("New connection from %s:%d on socket %d\n",
                               inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, newfd);
                    }
                } else {//一个已连接的socket客户端(modbus主机)发送来了新的请求报文
                    uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];

		    //处理请求报文
                    modbus_set_socket(ctx, master_socket);
                    rc = modbus_receive(ctx, query);
                    if (rc != -1) {
                        modbus_reply(ctx, query, rc, mb_mapping);
                    } else {
                        /*读取出错,则说明连接断开或发生了不可恢复的错误,应该释放连接*/
                        printf("Connection closed on socket %d\n", master_socket);
                        close(master_socket);

                        /*将释放掉的连接的文件描述符从监视集合中清除*/
                        FD_CLR(master_socket, &refset);

                        if (master_socket == fdmax) {
                            /*保证fdmax是需要监视的最大的文件描述符值*/
                            fdmax--;
                        }
                    }
                }
            }
        }
    }

    return 0;
}



你可能感兴趣的:(linux,C++,总线知识,C语言)