前言
網(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í)序:
- 啟動(dòng)服務(wù)端
- 啟動(dòng)client_select,發(fā)送消息"client_select"
- 啟動(dòng)client_poll,發(fā)送消息"client_poll"
- 啟動(dòng)client_epoll,發(fā)送消息"client_epoll"
- 客戶端client_select,使用Ctrl+C異常終止
- 客戶端client_poll,使用Ctrl+D正常終止
- 服務(wù)端,使用Ctrl+C異常終止
- 客戶端client_epoll,被迫終止
server:
client_select:
client_poll:
client_epoll:
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-431662.html
總結(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)!