美文网首页Linux/C
linux c - socket select 聊天服务端

linux c - socket select 聊天服务端

作者: 国服最坑开发 | 来源:发表于2019-12-26 17:11 被阅读0次

    初学socket时, 只会 read, write, 那是阻塞的工作方式, 想和多个socket 工作, 要用到多线程,
    今天来学习一下使用select方式进行非阻塞方式的socket交互
    核心概念就是使用一个fd_set 去管理所有socket的fd.
    select 方法本身是阻塞型的, 但当它管理的从多socket中有一个有读写事件时, 就会返回.
    这样做的好处就是一个线程,管理多个socket的io.

    • 几个方法
        FD_ZERO(&all_set); // 初始化 fd_set
        FD_SET(listen_fd, &all_set); // 把fd 添加到 fd_set
        FD_ISSET(listen_fd, &r_set); // 检测新fd , 是否已经在 fd_set中存在
        FD_CLR(fd, &all_set); 从fd_set中移除一个fd
    

    话不多说, 上代码 :

    • wrap.h
    //
    // Created by gg on 26/12/2019.
    //
    
    #ifndef CHAT_WRAP_H
    #define CHAT_WRAP_H
    
    void perr_exit(const char *s);
    
    struct sockaddr_in * ServerAddr(int port);
    
    int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
    
    void Bind(int fd, const struct sockaddr *sa, socklen_t salen);
    
    void Connect(int fd, const struct sockaddr *sa, socklen_t salen);
    
    void Listen(int fd, int backlog);
    
    int Socket(int family, int type, int protocol);
    
    ssize_t Read(int fd, void *ptr, size_t nbytes);
    
    ssize_t Write(int fd, const void *ptr, size_t nbytes);
    
    void Close(int fd);
    
    #endif //CHAT_WRAP_H
    
    
    • wrap.c
    //
    // Created by gg on 26/12/2019.
    //
    
    #include <stdlib.h>
    #include <errno.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdio.h>
    #include "wrap.h"
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <string.h>
    
    struct sockaddr_in *ServerAddr(int port) {
        struct sockaddr_in *server_addr = (struct sockaddr_in *) malloc(sizeof(struct sockaddr_in));
        bzero(server_addr, sizeof(struct sockaddr_in));
        server_addr->sin_family      = AF_INET;
        server_addr->sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr->sin_port        = htons(port);
        return server_addr;
    }
    
    void perr_exit(const char *s) {
        perror(s);
        exit(1);
    }
    
    int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) {
        int n;
    
        again:
        if ((n = accept(fd, sa, salenptr)) < 0) {
            if ((errno == ECONNABORTED) || (errno == EINTR))
                goto again;
            else
                perr_exit("accept error");
        }
        return n;
    }
    
    void Bind(int fd, const struct sockaddr *sa, socklen_t salen) {
        if (bind(fd, sa, salen) < 0)
            perr_exit("bind error");
    }
    
    void Connect(int fd, const struct sockaddr *sa, socklen_t salen) {
        if (connect(fd, sa, salen) < 0)
            perr_exit("connect error");
    }
    
    void Listen(int fd, int backlog) {
        if (listen(fd, backlog) < 0)
            perr_exit("listen error");
    }
    
    int Socket(int family, int type, int protocol) {
        int n;
    
        if ((n = socket(family, type, protocol)) < 0)
            perr_exit("socket error");
        return n;
    }
    
    ssize_t Read(int fd, void *ptr, size_t nbytes) {
        ssize_t n;
    
        again:
        if ((n = read(fd, ptr, nbytes)) == -1) {
            if (errno == EINTR)
                goto again;
            else
                return -1;
        }
        return n;
    }
    
    ssize_t Write(int fd, const void *ptr, size_t nbytes) {
        ssize_t n;
    
        again:
        if ((n = write(fd, ptr, nbytes)) == -1) {
            if (errno == EINTR)
                goto again;
            else
                return -1;
        }
        return n;
    }
    
    void Close(int fd) {
        if (close(fd) == -1)
            perr_exit("close error");
    }
    
    • main.c
    #pragma clang diagnostic push
    #pragma ide diagnostic ignored "hicpp-signed-bitwise"
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/select.h>
    #include "wrap.h"
    
    #define MAX_LINE 80
    #define SERVER_PORT 7890
    
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wmissing-noreturn"
    
    int main(int argc, char *argv[]) {
        int     i, maxi, maxfd, listen_fd, conn_fd, sock_fd;
        int     n_ready, client[FD_SETSIZE];
        ssize_t n;
        /**  r_set : read sets, all_set : all i/o sets*/
        fd_set  r_set, all_set;
    
        char               buf[MAX_LINE];
        char               str[INET_ADDRSTRLEN];
        socklen_t          cli_addr_len;
        struct sockaddr_in cli_addr;
        struct sockaddr_in *server_addr;
    
        /** 1. create socket */
        listen_fd   = Socket(AF_INET, SOCK_STREAM, 0);
        server_addr = ServerAddr(SERVER_PORT);
    
        /** 2. bind address and port */
        Bind(listen_fd, (struct sockaddr *) server_addr, sizeof(struct sockaddr_in));
        free(server_addr);
    
        /** 3. listening on */
        Listen(listen_fd, 20);
    
        maxfd = listen_fd;
        maxi  = -1;
    
        /** init every client fd : -1 */
        for (i = 0; i < FD_SETSIZE; i++)
            client[i] = -1;
    
        /** 4.1 init all_set */
        FD_ZERO(&all_set);
        /** 4.2 register server fd to all_set */
        FD_SET(listen_fd, &all_set);
    
        printf("Stand by ... \n");
        for (;;) {
            /** 5.1 copy all_set to read set */
            r_set = all_set;
    
            /** 5.2 waiting for client connection ...
             * the first param is always the max active socket fd + 1
             * first param: "select" will monitor fd io event from 0 to maxfd + 1, the "maxfd + 1" equals the new connection fd
             */
            n_ready = select(maxfd + 1, &r_set, NULL, NULL, NULL);
            if (n_ready < 0)
                perr_exit("select error");
    
    
            /* new client connection, check listen_fd is readable */
            if (FD_ISSET(listen_fd, &r_set)) {
                cli_addr_len = sizeof(cli_addr);
                conn_fd      = Accept(listen_fd, (struct sockaddr *) &cli_addr, &cli_addr_len);
    
                printf("new client_fd : %d receive from %s at PORT %d\n", conn_fd, inet_ntop(AF_INET, &cli_addr.sin_addr, str, sizeof(str)), ntohs(cli_addr.sin_port));
    
                /* add client fd to client array */
                for (i = 0; i < FD_SETSIZE; i++)
                    if (client[i] < 0) {
                        client[i] = conn_fd;
                        break;
                    }
    
                /* max client count exceed */
                if (i == FD_SETSIZE) {
                    fputs("too many clients\n", stderr);
                    exit(1);
                }
    
                /* add to all_set */
                FD_SET(conn_fd, &all_set);
                if (conn_fd > maxfd)
                    maxfd = conn_fd;
    
                /* update client count index */
                if (i > maxi)
                    maxi = i;
    
                /* always true ? */
                if (--n_ready == 0)
                    continue;
            }
    
            for (i = 0; i <= maxi; i++) { /* check all clients for data */
                if ((sock_fd = client[i]) < 0)
                    continue;
    
                if (FD_ISSET(sock_fd, &r_set)) {
                    /** close lost socket client */
                    if ((n = Read(sock_fd, buf, MAX_LINE)) == 0) {
                        Close(sock_fd);
                        FD_CLR(sock_fd, &all_set);
                        client[i] = -1;
                    }
                    else {
                        for (int k = 0; k <= maxi; ++k) {
                            int client_fd = client[k];
                            if (client_fd > -1 && client_fd != sock_fd)
                                Write(client_fd, buf, n);
                        }
                    }
                    if (--n_ready == 0)
                        break;
                }
            }
        }
        return 0;
    }
    
    #pragma clang diagnostic pop
    #pragma clang diagnostic pop
    

    相关文章

      网友评论

        本文标题:linux c - socket select 聊天服务端

        本文链接:https://www.haomeiwen.com/subject/itxloctx.html