国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

網(wǎng)絡(luò)編程代碼實(shí)例:IO復(fù)用版

這篇具有很好參考價(jià)值的文章主要介紹了網(wǎng)絡(luò)編程代碼實(shí)例:IO復(fù)用版。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

前言

網(wǎng)絡(luò)編程代碼實(shí)例:IO復(fù)用版。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-431662.html


代碼倉(cāng)庫(kù)

  • yezhening/Environment-and-network-programming-examples: 環(huán)境和網(wǎng)絡(luò)編程實(shí)例 (github.com)
  • Environment-and-network-programming-examples: 環(huán)境和網(wǎng)絡(luò)編程實(shí)例 (gitee.com)

內(nèi)容

  • 使用傳輸控制協(xié)議(TCP)
  • 服務(wù)端多進(jìn)程,一個(gè)服務(wù)端可連接多個(gè)客戶端
  • 用戶在客戶端終端輸入,可多次手動(dòng)通信
  • 在上一份代碼實(shí)例:多進(jìn)程版的基礎(chǔ)上,服務(wù)端增加獲取客戶端地址的邏輯;更新部分函數(shù)使用、錯(cuò)誤處理、注釋和Makefile文件;為保證代碼簡(jiǎn)潔,部分輸入輸出和字符串處理函數(shù)未進(jìn)行錯(cuò)誤檢測(cè)
  • 3個(gè)客戶端代碼實(shí)例分別使用IO復(fù)用的select、poll和epoll技術(shù),同時(shí)監(jiān)聽用戶輸入和網(wǎng)絡(luò)接收,可即時(shí)接收服務(wù)端進(jìn)程終止和服務(wù)端主機(jī)關(guān)機(jī)消息
  • 客戶端使用shutdown()而不是close()關(guān)閉連接,當(dāng)客戶端主動(dòng)關(guān)閉寫半部連接后,服務(wù)端仍能夠接收而不是丟棄批量輸入的緩沖區(qū)數(shù)據(jù)

代碼(有詳細(xì)注釋)

server.c

// 頭文件————————————————————
// #include <sys/socket.h> // socket()、sockaddr{}、bind()、listen()、accept()、recv()、send()
#include <stdio.h>  // perror()、printf()、sprintf()
#include <stdlib.h> // exit()
// #include <netinet/in.h> // sockaddr_in{}、htons()、ntohs()
#include <string.h>    // memset()、strcpy()、strcat()
#include <arpa/inet.h> // inet_pton()、inet_ntop()
// #include <unistd.h>  // close()、fork()
#include <errno.h> // errno

// 全局常量————————————————————
const char g_serv_listen_ip[INET_ADDRSTRLEN] = "0.0.0.0"; // 服務(wù)端監(jiān)聽的IP地址
const uint16_t g_serv_listen_port = 6000;                 // 服務(wù)端監(jiān)聽的端口號(hào)
const int g_listen_max_count = 5;                         // 監(jiān)聽的最大連接數(shù)
const int g_buff_size = 32;                               // 消息緩沖區(qū)的大小。單位:字節(jié)

// 函數(shù)聲明————————————————————
void handle_request(int, struct sockaddr_in); // 處理請(qǐng)求

// 主函數(shù)————————————————————
int main(int arg, char *argv[])
{
    // 網(wǎng)絡(luò)連接————————————————————
    int listen_fd; // 監(jiān)聽套接字文件描述符
    //  創(chuàng)建套接字并獲取套接字文件描述符
    if ((listen_fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
    {
        perror("socket() error");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serv_addr; // 服務(wù)端網(wǎng)絡(luò)信息結(jié)構(gòu)體
    // 初始化服務(wù)端網(wǎng)絡(luò)信息結(jié)構(gòu)體
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(g_serv_listen_port);
    if ((inet_pton(AF_INET, g_serv_listen_ip, &serv_addr.sin_addr)) != 1)
    {
        perror("inet_pton() error");
        exit(EXIT_FAILURE);
    }

    // 綁定套接字與網(wǎng)絡(luò)信息
    if ((bind(listen_fd, (struct sockaddr *)(&serv_addr), sizeof(serv_addr))) == -1)
    {
        if ((close(listen_fd)) == -1)
        {
            perror("bind() close() error");
            exit(EXIT_FAILURE);
        }

        perror("bind() error");
        exit(EXIT_FAILURE);
    }

    // 套接字設(shè)置被動(dòng)監(jiān)聽狀態(tài)
    if ((listen(listen_fd, g_listen_max_count)) == -1)
    {
        if ((close(listen_fd)) == -1)
        {
            perror("listen() close() error");
            exit(EXIT_FAILURE);
        }

        perror("listen() error");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in clie_addr; // 客戶端網(wǎng)絡(luò)信息結(jié)構(gòu)體
    int clie_addr_size;           // 客戶端網(wǎng)絡(luò)信息結(jié)構(gòu)體大小
    int connect_fd;               // 連接套接字文件描述符
    memset(&clie_addr, 0, sizeof(clie_addr));
    clie_addr_size = sizeof(struct sockaddr);
    pid_t pid; // 進(jìn)程標(biāo)識(shí)符
    
    // 循環(huán)監(jiān)聽客戶端請(qǐng)求
    // 原則:父進(jìn)程不能退出,子進(jìn)程可以退出
    while (1)
    {
        // 與客戶端建立連接
        if ((connect_fd = accept(listen_fd, (struct sockaddr *)(&clie_addr), &clie_addr_size)) == -1)
        {
            perror("accept() error");
            continue; // 繼續(xù)監(jiān)聽
        }

        // 創(chuàng)建子進(jìn)程處理請(qǐng)求
        pid = fork();
        if (pid == -1) // 錯(cuò)誤
        {
            perror("fork() error");
            continue; // 繼續(xù)監(jiān)聽
        }
        else if (pid == 0) // 子進(jìn)程
        {
            if ((close(listen_fd)) == -1) // 1.關(guān)閉監(jiān)聽套接字文件描述符
            {
                if ((close(connect_fd)) == -1)
                {
                    perror("fork() close() connect_fd child_process error");
                    exit(EXIT_FAILURE); // 子進(jìn)程退出
                }

                perror("fork() close() listen_fd child_process error");
                exit(EXIT_FAILURE); // 子進(jìn)程退出
            }

            handle_request(connect_fd, clie_addr); // 2.處理請(qǐng)求

            if ((close(connect_fd)) == -1) // 3.關(guān)閉連接套接字文件描述符
            {
                perror("fork() close() connect_fd2 child_process error");
                exit(EXIT_FAILURE); // 子進(jìn)程退出
            }

            exit(EXIT_SUCCESS);
        }
        else if (pid > 0) // 父進(jìn)程
        {
            if ((close(connect_fd)) == -1) // 關(guān)閉連接套接字文件描述符
            {
                perror("fork() close() connect_fd parent_process error");
                continue; // 繼續(xù)監(jiān)聽
            }
        }
    }

    if ((close(listen_fd)) == -1) // 父進(jìn)程關(guān)閉監(jiān)聽套接字文件描述符。實(shí)際不會(huì)執(zhí)行
    {
        perror("close() listen_fd error");
        exit(EXIT_FAILURE);
    }

    return 0;
}

// 函數(shù)定義————————————————————
// 處理請(qǐng)求
// 參數(shù):連接套接字文件描述符,客戶端網(wǎng)絡(luò)信息結(jié)構(gòu)體
void handle_request(int connect_fd, struct sockaddr_in clie_addr)
{
    // 獲取客戶端的IP地址和端口————————————————————
    char clie_ip[INET_ADDRSTRLEN];          // 客戶端的IP地址    如:127.0.0.1
    uint16_t clie_port;                     // 客戶端的端口  如:42534
    char clie_port_str[5];                  // 客戶端的端口,char[]類型  如:42534
    char clie_ip_port[INET_ADDRSTRLEN + 5]; // 客戶端的IP地址和端口  如:127.0.0.1:42534

    if ((inet_ntop(AF_INET, &clie_addr.sin_addr, clie_ip, sizeof(clie_ip))) == NULL)
    {
        perror("inet_ntop() error");
        return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束子進(jìn)程
    }
    // 注意:返回值和第3個(gè)參數(shù)值相同,區(qū)別為一個(gè)是常量一個(gè)是變量
    clie_port = ntohs(clie_addr.sin_port);
    sprintf(clie_port_str, "%d", clie_port);
    strcpy(clie_ip_port, clie_ip);
    strcat(clie_ip_port, ":");
    strcat(clie_ip_port, clie_port_str);
    printf("Client connection: %s\n", clie_ip_port);

    // 傳輸消息————————————————————
    char msg_recv[g_buff_size]; // 從客戶端接收的消息緩沖區(qū)
    char msg_send[g_buff_size]; // 發(fā)送到客戶端的消息緩沖區(qū)
    int recv_byte;              // 接收的消息字節(jié)數(shù)

    while (1) // 循環(huán)接收和發(fā)送消息
    {
        memset(&msg_recv, 0, sizeof(*msg_recv));
        memset(&msg_send, 0, sizeof(*msg_send));

        recv_byte = recv(connect_fd, msg_recv, g_buff_size, 0); // 接收消息
        if (recv_byte > 0)                                      // 有消息
        {
            printf("From %s received message: %s", clie_ip_port, msg_recv); // 接收的消息

            strcpy(msg_send, msg_recv);                             // 發(fā)送的消息
            if ((send(connect_fd, msg_send, g_buff_size, 0)) == -1) // 發(fā)送消息
            {
                perror("send() error");
                return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束子進(jìn)程
            }
        }
        else if (recv_byte == 0) // 文件末尾EOF:在客戶端標(biāo)準(zhǔn)輸入Ctrl+D或Ctrl+C
        {
            printf("From %s received the end of file\n", clie_ip_port);
            return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束子進(jìn)程
        }
        else if ((recv_byte == -1) && (errno == EINTR)) // 信號(hào)或網(wǎng)絡(luò)中斷recv()
        {
            continue; // 繼續(xù)接收消息
        }
        else if (recv_byte == -1) // 錯(cuò)誤
        {
            perror("recv() error");
            return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束子進(jìn)程
        }
    }

    return;
}

client_select.c

// 頭文件————————————————————
// #include <sys/socket.h> // socket()、sockaddr{}、connect()、send()、recv()、shutdown()
#include <stdio.h>  // perror()、printf()、gets()
#include <stdlib.h> // exit()
// #include <netinet/in.h> // sockaddr_in{}、htons()
#include <string.h>     // memset()、strcat()
#include <arpa/inet.h>  // inet_pton()
#include <unistd.h>     // close()、STDIN_FILENO
#include <errno.h>      // errno
#include <sys/select.h> // select()

// 全局常量————————————————————
const char g_connect_serv_ip[INET_ADDRSTRLEN] = "127.0.0.1"; // 連接服務(wù)端的IP地址
const uint16_t g_connect_serv_port = 6000;                   // 連接服務(wù)端的端口號(hào)
const int g_buff_size = 32;                                  // 消息緩沖區(qū)大小。單位:字節(jié)

// 函數(shù)聲明————————————————————
void handle(int); // 處理

// 主函數(shù)————————————————————
int main(int argc, char *argv[])
{
    // 網(wǎng)絡(luò)連接————————————————————
    int sock_fd; // 套接字文件描述符
    // 創(chuàng)建套接字并獲取套接字文件描述符
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
    {
        perror("socket() error");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serv_addr; // 服務(wù)端網(wǎng)絡(luò)信息結(jié)構(gòu)體
    // 初始化服務(wù)端網(wǎng)絡(luò)信息結(jié)構(gòu)體
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(g_connect_serv_port);
    if ((inet_pton(AF_INET, g_connect_serv_ip, &serv_addr.sin_addr)) != 1)
    {
        perror("inet_pton() error");
        exit(EXIT_FAILURE);
    }

    // 與服務(wù)端建立連接
    if ((connect(sock_fd, (struct sockaddr *)(&serv_addr), sizeof(serv_addr))) == -1)
    {
        if ((close(sock_fd)) == -1)
        {
            perror("connect() close() error");
            exit(EXIT_FAILURE);
        }

        perror("connect() error");
        exit(EXIT_FAILURE);
    }

    handle(sock_fd); // 處理

    // 關(guān)閉套接字文件描述符
    if ((close(sock_fd)) == -1)
    {
        perror("close() error");
        exit(EXIT_FAILURE);
    }

    return 0;
}

// 函數(shù)定義————————————————————
// 處理
void handle(int sock_fd) // 參數(shù):套接字文件描述符
{
    // select()準(zhǔn)備————————————————————
    // 監(jiān)聽標(biāo)準(zhǔn)輸入文件描述符和套接字文件描述符的可讀條件
    int maxfdp1;        // 要監(jiān)聽描述符的最大值+1
    fd_set read_fd_set; // 可讀文件描述符集合
    if (STDIN_FILENO >= sock_fd)
    {
        maxfdp1 = STDIN_FILENO + 1;
    }
    else
    {
        maxfdp1 = sock_fd + 1;
    }
    FD_ZERO(&read_fd_set);

    // 傳輸消息————————————————————
    char msg_send[g_buff_size]; // 發(fā)送到服務(wù)端的消息緩沖區(qū)
    char msg_recv[g_buff_size]; // 從服務(wù)端接收的消息緩沖區(qū)
    int recv_byte;              // 接收的消息字節(jié)數(shù)

    int stdin_eof = 0; // 標(biāo)準(zhǔn)輸入文件描述符讀到文件末尾的標(biāo)志:0未讀到EOF,1讀到EOF

    printf("Please input the message to be sent directly below:\n");
    while (1) // 循環(huán)發(fā)送和接收消息
    {
        // 1.select()調(diào)用
        // 因?yàn)閟elect()返回會(huì)修改fd_set,所以每循環(huán)都要重新設(shè)置監(jiān)聽的fd_set
        // 當(dāng)stdin_eof == 1時(shí),寫半部關(guān)閉,不需要再設(shè)置監(jiān)聽標(biāo)準(zhǔn)輸入文件描述符
        if (stdin_eof == 0)
        {
            FD_SET(STDIN_FILENO, &read_fd_set);
        }
        FD_SET(sock_fd, &read_fd_set);
        if ((select(maxfdp1, &read_fd_set, NULL, NULL, NULL)) == -1)
        {
            perror("select() error");
            continue; // 若有錯(cuò)誤在下個(gè)循環(huán)繼續(xù)調(diào)用
        }

        memset(&msg_send, 0, sizeof(*msg_send));
        memset(&msg_recv, 0, sizeof(*msg_recv));

        // 2.select()檢測(cè)
        if (FD_ISSET(STDIN_FILENO, &read_fd_set)) // 標(biāo)準(zhǔn)輸入文件描述符可讀
        {
            if ((fgets(msg_send, g_buff_size, stdin)) == NULL)
            // 從標(biāo)準(zhǔn)輸入獲取消息。錯(cuò)誤或遇到文件結(jié)尾(EOF):在客戶端標(biāo)準(zhǔn)輸入Ctrl+D或Ctrl+C,相當(dāng)于關(guān)閉連接
            {
                printf("End of connection\n");

                stdin_eof = 1;                          // 設(shè)置標(biāo)志
                FD_CLR(STDIN_FILENO, &read_fd_set);     // 清理fd_set
                if ((shutdown(sock_fd, SHUT_WR)) == -1) // 寫半部關(guān)閉
                {
                    perror("shutdown() error");
                    return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束進(jìn)程
                }
                continue;
                // 不是return,因?yàn)榭赡苓€需要從網(wǎng)絡(luò)套接字文件描述符讀
                // 不需要進(jìn)入下面的send(),服務(wù)端會(huì)recv()接收EOF
            }

            if ((send(sock_fd, msg_send, g_buff_size, 0)) == -1) // 發(fā)送消息
            {
                perror("send() error");
                return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束進(jìn)程
            }
            printf("Send message: %s", msg_send);
        }
        else if (FD_ISSET(sock_fd, &read_fd_set)) // 套接字文件描述符可讀
        {
            recv_byte = recv(sock_fd, msg_recv, g_buff_size, 0); // 接收消息
            if (recv_byte > 0)                                   // 有數(shù)據(jù)
            {
                printf("Received message: %s", msg_recv); // 接收的消息
            }
            else if (recv_byte == 0) // 服務(wù)端進(jìn)程提前終止,在服務(wù)端標(biāo)準(zhǔn)輸入Ctrl+C中斷進(jìn)程
            {
                // 如果已經(jīng)調(diào)用shutdown()寫半部關(guān)閉,當(dāng)服務(wù)端recv()EOF后調(diào)用close()時(shí),是正常的結(jié)束連接
                // 否則,是服務(wù)端ctrl+c提前關(guān)閉連接
                if (stdin_eof == 1)
                {
                    return; // 函數(shù)返回后,關(guān)閉套接字文件描述符,結(jié)束進(jìn)程
                }

                printf("Server terminated prematurely\n");
                return; // 函數(shù)返回后,關(guān)閉套接字文件描述符,結(jié)束進(jìn)程
            }
            else if ((recv_byte == -1) && (errno == EINTR)) // 信號(hào)或網(wǎng)絡(luò)中斷recv()
            {
                continue; // 繼續(xù)發(fā)送和接收數(shù)據(jù)
            }
            else if (recv_byte == -1) // 錯(cuò)誤
            {
                perror("recv() error");
                return; // 函數(shù)返回后,關(guān)閉套接字文件描述符,結(jié)束進(jìn)程
            }
        }
    }

    return;
}

client_poll.c

// 頭文件————————————————————
// #include <sys/socket.h> // socket()、sockaddr{}、connect()、send()、recv()、shutdown()
#include <stdio.h>  // perror()、printf()、gets()
#include <stdlib.h> // exit()
// #include <netinet/in.h> // sockaddr_in{}、htons()
#include <string.h>    // memset()、strcat()
#include <arpa/inet.h> // inet_pton()
#include <unistd.h>    // close()、STDIN_FILENO
#include <errno.h>     // errno
#include <poll.h>      // poll()

// 全局常量————————————————————
const char g_connect_serv_ip[INET_ADDRSTRLEN] = "127.0.0.1"; // 連接服務(wù)端的IP地址
const uint16_t g_connect_serv_port = 6000;                   // 連接服務(wù)端的端口號(hào)
const int g_buff_size = 32;                                  // 消息緩沖區(qū)大小。單位:字節(jié)

// 函數(shù)聲明————————————————————
void handle(int); // 處理

// 主函數(shù)————————————————————
int main(int argc, char *argv[])
{
    // 網(wǎng)絡(luò)連接————————————————————
    int sock_fd; // 套接字文件描述符
    // 創(chuàng)建套接字并獲取套接字文件描述符
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
    {
        perror("socket() error");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serv_addr; // 服務(wù)端網(wǎng)絡(luò)信息結(jié)構(gòu)體
    // 初始化服務(wù)端網(wǎng)絡(luò)信息結(jié)構(gòu)體
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(g_connect_serv_port);
    if ((inet_pton(AF_INET, g_connect_serv_ip, &serv_addr.sin_addr)) != 1)
    {
        perror("inet_pton() error");
        exit(EXIT_FAILURE);
    }

    // 與服務(wù)端建立連接
    if ((connect(sock_fd, (struct sockaddr *)(&serv_addr), sizeof(serv_addr))) == -1)
    {
        if ((close(sock_fd)) == -1)
        {
            perror("connect() close() error");
            exit(EXIT_FAILURE);
        }

        perror("connect() error");
        exit(EXIT_FAILURE);
    }

    handle(sock_fd); // 處理

    // 關(guān)閉套接字文件描述符
    if ((close(sock_fd)) == -1)
    {
        perror("close() error");
        exit(EXIT_FAILURE);
    }

    return 0;
}

// 函數(shù)定義————————————————————
// 處理
void handle(int sock_fd) // 參數(shù):套接字文件描述符
{
    // poll()準(zhǔn)備————————————————————
    // 監(jiān)聽標(biāo)準(zhǔn)輸入文件描述符和套接字文件描述符的可讀條件
    struct pollfd pollfd_array[2]; // pollfd{}結(jié)構(gòu)數(shù)組
    pollfd_array[0].fd = STDIN_FILENO;
    pollfd_array[0].events = POLLIN;
    pollfd_array[1].fd = sock_fd;
    pollfd_array[1].events = POLLIN;

    // 傳輸消息————————————————————
    char msg_send[g_buff_size]; // 發(fā)送到服務(wù)端的消息緩沖區(qū)
    char msg_recv[g_buff_size]; // 從服務(wù)端接收的消息緩沖區(qū)
    int recv_byte;              // 接收的消息字節(jié)數(shù)

    int stdin_eof = 0; // 標(biāo)準(zhǔn)輸入文件描述符讀到文件末尾的標(biāo)志:0未讀到EOF,1讀到EOF

    printf("Please input the message to be sent directly below:\n");
    while (1) // 循環(huán)發(fā)送和接收消息
    {
        // 1.poll()調(diào)用
        // 當(dāng)stdin_eof == 1時(shí),寫半部關(guān)閉,不需要再設(shè)置監(jiān)聽標(biāo)準(zhǔn)輸入文件描述符
        // 不監(jiān)聽的成員fd設(shè)置為-1,會(huì)忽略成員events,返回時(shí)將成員revents置0
        if (stdin_eof == 1)
        {
            pollfd_array[0].fd = -1;
        }
        if ((poll(pollfd_array, 2, -1)) == -1)
        {
            perror("poll() error");
            continue; // 若有錯(cuò)誤在下個(gè)循環(huán)繼續(xù)調(diào)用
        }

        memset(&msg_send, 0, sizeof(*msg_send));
        memset(&msg_recv, 0, sizeof(*msg_recv));

        // 2.poll()檢測(cè)
        if (pollfd_array[0].revents & (POLLIN | POLLERR)) // 標(biāo)準(zhǔn)輸入文件描述符可讀
        {
            if ((fgets(msg_send, g_buff_size, stdin)) == NULL)
            // 從標(biāo)準(zhǔn)輸入獲取消息。錯(cuò)誤或遇到文件結(jié)尾(EOF):在客戶端標(biāo)準(zhǔn)輸入Ctrl+D或Ctrl+C,相當(dāng)于關(guān)閉連接
            {
                printf("End of connection\n");

                stdin_eof = 1;                          // 設(shè)置標(biāo)志
                if ((shutdown(sock_fd, SHUT_WR)) == -1) // 寫半部關(guān)閉
                {
                    perror("shutdown() error");
                    return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束進(jìn)程
                }
                continue;
                // 不是return,因?yàn)榭赡苓€需要從網(wǎng)絡(luò)套接字文件描述符讀
                // 不需要進(jìn)入下面的send(),服務(wù)端會(huì)recv()接收EOF
            }

            if ((send(sock_fd, msg_send, g_buff_size, 0)) == -1) // 發(fā)送消息
            {
                perror("send() error");
                return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束進(jìn)程
            }
            printf("Send message: %s", msg_send);
        }
        else if (pollfd_array[1].revents & (POLLIN | POLLERR)) // 套接字文件描述符可讀
        {
            recv_byte = recv(sock_fd, msg_recv, g_buff_size, 0); // 接收消息
            if (recv_byte > 0)                                   // 有數(shù)據(jù)
            {
                printf("Received message: %s", msg_recv); // 接收的消息
            }
            else if (recv_byte == 0) // 服務(wù)端進(jìn)程提前終止,在服務(wù)端標(biāo)準(zhǔn)輸入Ctrl+C中斷進(jìn)程
            {
                // 如果已經(jīng)調(diào)用shutdown()寫半部關(guān)閉,當(dāng)服務(wù)端recv()EOF后調(diào)用close()時(shí),是正常的結(jié)束連接
                // 否則,是服務(wù)端ctrl+c提前關(guān)閉連接
                if (stdin_eof == 1)
                {
                    return; // 函數(shù)返回后,關(guān)閉套接字文件描述符,結(jié)束進(jìn)程
                }

                printf("Server terminated prematurely\n");
                return; // 函數(shù)返回后,關(guān)閉套接字文件描述符,結(jié)束進(jìn)程
            }
            else if ((recv_byte == -1) && (errno == EINTR)) // 信號(hào)或網(wǎng)絡(luò)中斷recv()
            {
                continue; // 繼續(xù)發(fā)送和接收數(shù)據(jù)
            }
            else if (recv_byte == -1) // 錯(cuò)誤
            {
                perror("recv() error");
                return; // 函數(shù)返回后,關(guān)閉套接字文件描述符,結(jié)束進(jìn)程
            }
        }
    }

    return;
}

client_epoll.c

// 頭文件————————————————————
// #include <sys/socket.h> // socket()、sockaddr{}、connect()、send()、recv()、shutdown()
#include <stdio.h>  // perror()、printf()、gets()
#include <stdlib.h> // exit()
// #include <netinet/in.h> // sockaddr_in{}、htons()
#include <string.h>    // memset()、strcat()
#include <arpa/inet.h> // inet_pton()
#include <unistd.h>    // close()、STDIN_FILENO
#include <errno.h>     // errno
#include <sys/epoll.h> // epoll()

// 全局常量————————————————————
const char g_connect_serv_ip[INET_ADDRSTRLEN] = "127.0.0.1"; // 連接服務(wù)端的IP地址
const uint16_t g_connect_serv_port = 6000;                   // 連接服務(wù)端的端口號(hào)
const int g_buff_size = 32;                                  // 消息緩沖區(qū)大小。單位:字節(jié)

// 函數(shù)聲明————————————————————
void handle(int); // 處理

// 主函數(shù)————————————————————
int main(int argc, char *argv[])
{
    // 網(wǎng)絡(luò)連接————————————————————
    int sock_fd; // 套接字文件描述符
    // 創(chuàng)建套接字并獲取套接字文件描述符
    if ((sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
    {
        perror("socket() error");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serv_addr; // 服務(wù)端網(wǎng)絡(luò)信息結(jié)構(gòu)體
    // 初始化服務(wù)端網(wǎng)絡(luò)信息結(jié)構(gòu)體
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(g_connect_serv_port);
    if ((inet_pton(AF_INET, g_connect_serv_ip, &serv_addr.sin_addr)) != 1)
    {
        perror("inet_pton() error");
        exit(EXIT_FAILURE);
    }

    // 與服務(wù)端建立連接
    if ((connect(sock_fd, (struct sockaddr *)(&serv_addr), sizeof(serv_addr))) == -1)
    {
        if ((close(sock_fd)) == -1)
        {
            perror("connect() close() error");
            exit(EXIT_FAILURE);
        }

        perror("connect() error");
        exit(EXIT_FAILURE);
    }

    handle(sock_fd); // 處理

    // 關(guān)閉套接字文件描述符
    if ((close(sock_fd)) == -1)
    {
        perror("close() error");
        exit(EXIT_FAILURE);
    }

    return 0;
}

// 函數(shù)定義————————————————————
// 處理
void handle(int sock_fd) // 參數(shù):套接字文件描述符
{
    // epoll()準(zhǔn)備————————————————————
    // 監(jiān)聽標(biāo)準(zhǔn)輸入文件描述符和套接字文件描述符的可讀條件
    int epoll_fd;                           // epoll用的文件描述符
    struct epoll_event epoll_listen_event;  // epoll監(jiān)聽的事件
    struct epoll_event epoll_wait_event[2]; // epoll等待的事件
    int epoll_wait_event_num;               // epoll_wait()調(diào)用返回的就緒事件數(shù)

    if ((epoll_fd = epoll_create(2)) == -1)
    {
        perror("epoll_create() error");
        return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束進(jìn)程
    }
    epoll_listen_event.data.fd = STDIN_FILENO;
    epoll_listen_event.events = EPOLLIN; // 默認(rèn)水平觸發(fā),未設(shè)置邊緣觸發(fā)
    if ((epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &epoll_listen_event)) == -1)
    {
        if ((close(epoll_fd)) == -1) // 注意關(guān)閉epoll用的文件描述符
        {
            perror("epoll_ctl() STDIN_FILENO EPOLL_CTL_ADD close() error");
            return;
        }

        perror("epoll_ctl() STDIN_FILENO EPOLL_CTL_ADD error");
        return;
    }
    epoll_listen_event.data.fd = sock_fd; // 同一個(gè)變量可重復(fù)使用,修改后注冊(cè)到epoll_ctl()即可
    epoll_listen_event.events = EPOLLIN;
    if ((epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd, &epoll_listen_event)) == -1)
    {
        if ((close(epoll_fd)) == -1)
        {
            perror("epoll_ctl() sock_fd close() error");
            return;
        }

        perror("epoll_ctl() sock_fd error");
        return;
    }

    // 傳輸消息————————————————————
    char msg_send[g_buff_size]; // 發(fā)送到服務(wù)端的消息緩沖區(qū)
    char msg_recv[g_buff_size]; // 從服務(wù)端接收的消息緩沖區(qū)
    int recv_byte;              // 接收的消息字節(jié)數(shù)

    int stdin_eof = 0; // 標(biāo)準(zhǔn)輸入文件描述符讀到文件末尾的標(biāo)志:0未讀到EOF,1讀到EOF

    printf("Please input the message to be sent directly below:\n");
    while (1) // 循環(huán)發(fā)送和接收消息
    {
        // 1.epoll()調(diào)用
        // 當(dāng)stdin_eof == 1時(shí),寫半部關(guān)閉,不需要再設(shè)置監(jiān)聽標(biāo)準(zhǔn)輸入文件描述符
        if (stdin_eof == 1)
        {
            if ((epoll_ctl(epoll_fd, EPOLL_CTL_DEL, STDIN_FILENO, NULL)) == -1) // 刪除事件時(shí),第4個(gè)參數(shù)設(shè)置為NULL
            {
                if ((close(epoll_fd)) == -1)
                {
                    perror("epoll_ctl() STDIN_FILENO EPOLL_CTL_DEL close() error");
                    return;
                }

                perror("epoll_ctl() STDIN_FILENO EPOLL_CTL_DEL error");
                return;
            }
        }
        if ((epoll_wait_event_num = epoll_wait(epoll_fd, epoll_wait_event, 2, -1)) == -1)
        {
            if ((close(epoll_fd)) == -1)
            {
                perror("epoll_wait() close() error");
                return;
            }

            perror("epoll_wait() error");
            continue; // 若有錯(cuò)誤在下個(gè)循環(huán)繼續(xù)調(diào)用
        }

        memset(&msg_send, 0, sizeof(*msg_send));
        memset(&msg_recv, 0, sizeof(*msg_recv));

        // 2.epoll()檢測(cè)
        for (int i = 0; i < epoll_wait_event_num; ++i)
        {
            if ((epoll_wait_event[i].data.fd == STDIN_FILENO) && (epoll_wait_event[i].events & (EPOLLIN | EPOLLERR))) // 標(biāo)準(zhǔn)輸入文件描述符可讀
            {
                if ((fgets(msg_send, g_buff_size, stdin)) == NULL)
                // 從標(biāo)準(zhǔn)輸入獲取消息。錯(cuò)誤或遇到文件結(jié)尾(EOF):在客戶端標(biāo)準(zhǔn)輸入Ctrl+D或Ctrl+C,相當(dāng)于關(guān)閉連接
                {
                    printf("End of connection\n");

                    stdin_eof = 1;                          // 設(shè)置標(biāo)志
                    if ((shutdown(sock_fd, SHUT_WR)) == -1) // 寫半部關(guān)閉
                    {
                        perror("shutdown() error");
                        return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束進(jìn)程
                    }
                    continue;
                    // 不是return,因?yàn)榭赡苓€需要從網(wǎng)絡(luò)套接字文件描述符讀
                    // 不需要進(jìn)入下面的send(),服務(wù)端會(huì)recv()接收EOF
                }

                if ((send(sock_fd, msg_send, g_buff_size, 0)) == -1) // 發(fā)送消息
                {
                    perror("send() error");
                    return; // 函數(shù)返回后,關(guān)閉連接套接字文件描述符,結(jié)束進(jìn)程
                }
                printf("Send message: %s", msg_send);
            }
            else if ((epoll_wait_event[i].data.fd == sock_fd) && (epoll_wait_event[i].events & (EPOLLIN | EPOLLERR))) // 套接字文件描述符可讀
            {
                recv_byte = recv(sock_fd, msg_recv, g_buff_size, 0); // 接收消息
                if (recv_byte > 0)                                   // 有數(shù)據(jù)
                {
                    printf("Received message: %s", msg_recv); // 接收的消息
                }
                else if (recv_byte == 0) // 服務(wù)端進(jìn)程提前終止,在服務(wù)端標(biāo)準(zhǔn)輸入Ctrl+C中斷進(jìn)程
                {
                    // 如果已經(jīng)調(diào)用shutdown()寫半部關(guān)閉,當(dāng)服務(wù)端recv()EOF后調(diào)用close()時(shí),是正常的結(jié)束連接
                    // 否則,是服務(wù)端ctrl+c提前關(guān)閉連接
                    if (stdin_eof == 1)
                    {
                        return; // 函數(shù)返回后,關(guān)閉套接字文件描述符,結(jié)束進(jìn)程
                    }

                    printf("Server terminated prematurely\n");
                    return; // 函數(shù)返回后,關(guān)閉套接字文件描述符,結(jié)束進(jìn)程
                }
                else if ((recv_byte == -1) && (errno == EINTR)) // 信號(hào)或網(wǎng)絡(luò)中斷recv()
                {
                    continue; // 繼續(xù)發(fā)送和接收數(shù)據(jù)
                }
                else if (recv_byte == -1) // 錯(cuò)誤
                {
                    perror("recv() error");
                    return; // 函數(shù)返回后,關(guān)閉套接字文件描述符,結(jié)束進(jìn)程
                }
            }
        }
    }

    if ((close(epoll_fd)) == -1)
    {
        perror("close()");
        return;
    }

    return;
}

結(jié)果

操作時(shí)序:

  1. 啟動(dòng)服務(wù)端
  2. 啟動(dòng)client_select,發(fā)送消息"client_select"
  3. 啟動(dòng)client_poll,發(fā)送消息"client_poll"
  4. 啟動(dòng)client_epoll,發(fā)送消息"client_epoll"
  5. 客戶端client_select,使用Ctrl+C異常終止
  6. 客戶端client_poll,使用Ctrl+D正常終止
  7. 服務(wù)端,使用Ctrl+C異常終止
  8. 客戶端client_epoll,被迫終止

server:

網(wǎng)絡(luò)編程代碼實(shí)例:IO復(fù)用版

client_select:

網(wǎng)絡(luò)編程代碼實(shí)例:IO復(fù)用版

client_poll:

網(wǎng)絡(luò)編程代碼實(shí)例:IO復(fù)用版
client_epoll:

網(wǎng)絡(luò)編程代碼實(shí)例:IO復(fù)用版


總結(jié)

網(wǎng)絡(luò)編程代碼實(shí)例:IO復(fù)用版。


參考資料

  • 《UNIX環(huán)境高級(jí)編程(第3版)》作者:W.Richard Stevens,Stephen A.Rago
  • 《UNIX網(wǎng)絡(luò)編程(第3版)》作者:W.Richard Stevens,Bill Fenner,Andrew M.Rudoff

作者的話

  • 感謝參考資料的作者/博主
  • 作者:夜悊
  • 版權(quán)所有,轉(zhuǎn)載請(qǐng)注明出處,謝謝~
  • 如果文章對(duì)你有幫助,請(qǐng)點(diǎn)個(gè)贊或加個(gè)粉絲吧,你的支持就是作者的動(dòng)力~
  • 文章在描述時(shí)有疑惑的地方,請(qǐng)留言,定會(huì)一一耐心討論、解答
  • 文章在認(rèn)識(shí)上有錯(cuò)誤的地方, 敬請(qǐng)批評(píng)指正
  • 望讀者們都能有所收獲

到了這里,關(guān)于網(wǎng)絡(luò)編程代碼實(shí)例:IO復(fù)用版的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【Linux網(wǎng)絡(luò)編程】TCP并發(fā)服務(wù)器的實(shí)現(xiàn)(IO多路復(fù)用select)

    【Linux網(wǎng)絡(luò)編程】TCP并發(fā)服務(wù)器的實(shí)現(xiàn)(IO多路復(fù)用select)

    服務(wù)器模型主要分為兩種, 循環(huán)服務(wù)器 和 并發(fā)服務(wù)器 。 循環(huán)服務(wù)器 : 在同一時(shí)間只能處理一個(gè)客戶端的請(qǐng)求。 并發(fā)服務(wù)器 : 在同一時(shí)間內(nèi)能同時(shí)處理多個(gè)客戶端的請(qǐng)求。 TCP的服務(wù)器默認(rèn)的就是一個(gè)循環(huán)服務(wù)器,原因是有兩個(gè)阻塞 accept函數(shù) 和recv函數(shù) 之間會(huì)相互影響。

    2024年02月03日
    瀏覽(100)
  • 【APUE】網(wǎng)絡(luò)socket編程溫度采集智能存儲(chǔ)與上報(bào)項(xiàng)目技術(shù)------多路復(fù)用

    【APUE】網(wǎng)絡(luò)socket編程溫度采集智能存儲(chǔ)與上報(bào)項(xiàng)目技術(shù)------多路復(fù)用

    作者簡(jiǎn)介: 一個(gè)平凡而樂于分享的小比特,中南民族大學(xué)通信工程專業(yè)研究生在讀,研究方向無(wú)線聯(lián)邦學(xué)習(xí) 擅長(zhǎng)領(lǐng)域:驅(qū)動(dòng)開發(fā),嵌入式軟件開發(fā),BSP開發(fā) 作者主頁(yè):一個(gè)平凡而樂于分享的小比特的個(gè)人主頁(yè) 文章收錄專欄:網(wǎng)絡(luò)socket編程之溫度采集智能存儲(chǔ)與上報(bào)項(xiàng)目,本

    2024年04月10日
    瀏覽(34)
  • 【網(wǎng)絡(luò)編程】五種網(wǎng)絡(luò)IO模式

    【網(wǎng)絡(luò)編程】五種網(wǎng)絡(luò)IO模式

    對(duì)于一次IO訪問(以read為例),數(shù)據(jù)會(huì) 先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū) 中,然后 才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間 。所以說(shuō),當(dāng)一個(gè)read操作發(fā)生時(shí),會(huì)經(jīng)歷兩個(gè)階段: 1、等待數(shù)據(jù)準(zhǔn)備 ? ? ? ? 2 、將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 linux 系統(tǒng)產(chǎn)生了下面

    2024年02月13日
    瀏覽(17)
  • Socket實(shí)例,實(shí)現(xiàn)多個(gè)客戶端連接同一個(gè)服務(wù)端代碼&TCP網(wǎng)絡(luò)編程 ServerSocket和Socket實(shí)現(xiàn)多客戶端聊天

    Socket實(shí)例,實(shí)現(xiàn)多個(gè)客戶端連接同一個(gè)服務(wù)端代碼&TCP網(wǎng)絡(luò)編程 ServerSocket和Socket實(shí)現(xiàn)多客戶端聊天

    Java socket(套接字)通常也稱作\\\"套接字\\\",用于描述ip地址和端口,是一個(gè)通信鏈的句柄。應(yīng)用程序通常通過(guò)\\\"套接字\\\"向網(wǎng)絡(luò)發(fā)出請(qǐng)求或者應(yīng)答網(wǎng)絡(luò)請(qǐng)求。 使用socket實(shí)現(xiàn)多個(gè)客戶端和同一客戶端通訊;首先客戶端連接服務(wù)端發(fā)送一條消息,服務(wù)端接收到消息后進(jìn)行處理,完成后再

    2024年02月12日
    瀏覽(89)
  • TCP/IP網(wǎng)絡(luò)編程 第十六章:關(guān)于IO流分離的其他內(nèi)容

    TCP/IP網(wǎng)絡(luò)編程 第十六章:關(guān)于IO流分離的其他內(nèi)容

    兩次I/O流分離 我們之前通過(guò)2種方法分離過(guò)IO流,第一種是第十章的“TCPI/O過(guò)程(Routine)分離”。這種方法通過(guò)調(diào)用fork函數(shù)復(fù)制出1個(gè)文件描述符,以區(qū)分輸入和輸出中使用的文件描述符。雖然文件描述符本身不會(huì)根據(jù)輸入和輸出進(jìn)行區(qū)分,但我們分開了2個(gè)文件描述符的用途

    2024年02月16日
    瀏覽(29)
  • 10.NIO 網(wǎng)絡(luò)編程應(yīng)用實(shí)例-群聊系統(tǒng)

    需求:進(jìn)一步理解 NIO 非阻塞網(wǎng)絡(luò)編程機(jī)制,實(shí)現(xiàn)多人群聊 編寫一個(gè) NIO 群聊系統(tǒng),實(shí)現(xiàn)客戶端與客戶端的通信需求(非阻塞) 服務(wù)器端:可以監(jiān)測(cè)用戶上線,離線,并實(shí)現(xiàn)消息轉(zhuǎn)發(fā)功能 客戶端:通過(guò) channel 可以無(wú)阻塞發(fā)送消息給其它所有客戶端用戶,同時(shí)可以接受其它客戶端用

    2024年02月15日
    瀏覽(44)
  • ESP8266-Arduino網(wǎng)絡(luò)編程實(shí)例-HTTPS客戶端數(shù)據(jù)請(qǐng)求

    超文本傳輸協(xié)議安全 (HTTPS) 是 HTTP的安全版本,HTTP 是用于在 Web 瀏覽器和網(wǎng)站之間發(fā)送數(shù)據(jù)的主要協(xié)議。HTTPS 經(jīng)過(guò)加密,以提高數(shù)據(jù)傳輸?shù)陌踩?。?dāng)用戶傳輸敏感數(shù)據(jù)(例如通過(guò)登錄銀行賬戶、電子郵件服務(wù)或健康保險(xiǎn)提供商)時(shí),這一點(diǎn)尤其重要。 從技術(shù)上來(lái)講,HTTPS

    2023年04月08日
    瀏覽(18)
  • 【Linux Network】網(wǎng)絡(luò)編程套接字(代碼練習(xí))—UDP

    【Linux Network】網(wǎng)絡(luò)編程套接字(代碼練習(xí))—UDP

    目錄 1. 常用接口 2. C/S 回聲模擬 3. C/S myshell 的制作 ?Linux網(wǎng)絡(luò)編程? 1. 常用接口 socket:創(chuàng)建套接字: 返回值: 套接字創(chuàng)建成功返回一個(gè)文件描述符 ,創(chuàng)建失敗返回-1,同時(shí)錯(cuò)誤碼會(huì)被設(shè)置。 參數(shù): domain: 網(wǎng)絡(luò)通信 設(shè)置為 AF_INET(IPv4)或AF_INET6(IPv6) ; type:基于 UDP的網(wǎng)

    2024年02月03日
    瀏覽(234)
  • 計(jì)算機(jī)網(wǎng)絡(luò)編程 | 并發(fā)服務(wù)器代碼實(shí)現(xiàn)(多進(jìn)程/多線程)

    計(jì)算機(jī)網(wǎng)絡(luò)編程 | 并發(fā)服務(wù)器代碼實(shí)現(xiàn)(多進(jìn)程/多線程)

    歡迎關(guān)注博主 Mindtechnist 或加入【Linux C/C++/Python社區(qū)】一起學(xué)習(xí)和分享Linux、C、C++、Python、Matlab,機(jī)器人運(yùn)動(dòng)控制、多機(jī)器人協(xié)作,智能優(yōu)化算法,濾波估計(jì)、多傳感器信息融合,機(jī)器學(xué)習(xí),人工智能等相關(guān)領(lǐng)域的知識(shí)和技術(shù)。 專欄:《網(wǎng)絡(luò)編程》 當(dāng)涉及到構(gòu)建高性能的服務(wù)

    2024年02月08日
    瀏覽(35)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包