日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

epoll多路復用的一個實例程序(C實現)_C 語言

作者:yunfan188 ? 更新時間: 2022-10-16 編程語言

本文實例為大家分享了epoll多路復用一個實例程序的具體代碼,供大家參考,具體內容如下

1、實例程序描述

編寫一個echo server程序,功能是客戶端向服務端發送消息,服務端接收到消息后輸出,并原樣返回給客戶端,客戶端接收到服務端的應答消息并打印輸出。

2、公共接口函數部分

2.1、common.h 源文件

/**
**描述:公共頭文件
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
?
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
?
typedef struct epoll_event EPOLL_EVENT_T;
?
#define BUF_SIZE 2048
#define LISTENQ ?10
#define FD_SEZE ?1000
#define MAX_EVENTS 100
?
#define ERROR_SOCKET_SELECT ? -1
#define ERROR_SOCKET_TIMEOUT ?-2
#define ERROR_SOCKET_READ ? ? -3
#define ERROR_SOCKET_WRITE ? ?-4
#define ERROR_SOCKET_CLOSE ? ?-5
#define ERROR_SOCKET_CREATE ? -6
#define ERROR_SOCKET_BIND ? ? -7
#define ERROR_SOCKET_LISTEN ? -8
#define ERROR_SOCKET_CONNECT ?-9
?
#define ERROR_EPOLL_CREATE ? ?-10
#define ERROR_EPOLL_CTL_ADD ? -11
#define ERROR_EPOLL_CTL_DEL ? -12
#define ERROR_EPOLL_CTL_MOD ? -13
#define ERROR_ARGUMENT ? ? ? ? -999
?
int add_epoll_event(int epollfd, int fd, int events);
int del_epoll_event(int epoll, int fd, int events);
int mod_epoll_event(int epoll, int fd, int events);
int make_socket_nonblock(int sock_fd);
int listen_socket(char *ip, int port, int nonblock);
int connect_socket(char *ip, int port,int nonblock);

2.2、common.c 源文件

/**
**描述:公共函數
*/
#include "common.h"
?
int add_epoll_event(int epollfd, int fd, int events)
{
? ? EPOLL_EVENT_T ev;
? ? ev.events = events;
? ? ev.data.fd = fd;
? ? if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1){
? ? ? ? perror("epoll_ctl_add");
? ? ? ? return ERROR_EPOLL_CTL_ADD;
? ? }
}
?
int del_epoll_event(int epollfd, int fd, int events)
{
? ? EPOLL_EVENT_T ev;
? ? ev.events = events;
? ? ev.data.fd = fd;
? ? if(epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev) == -1){
? ? ? ? perror("epoll_ctl_del");
? ? ? ? return ERROR_EPOLL_CTL_ADD;
? ? }
}
?
int mod_epoll_event(int epollfd, int fd, int events)
{
? ? EPOLL_EVENT_T ev;
? ? ev.events = events;
? ? ev.data.fd = fd;
? ? if(epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev) == -1){
? ? ? ? perror("epoll_ctl_mod");
? ? ? ? return ERROR_EPOLL_CTL_MOD;
? ? }
}
?
//設置socket為非阻塞模式函數
int make_socket_nonblock(int sock_fd)
{
? ? int flags;
? ??
? ? if((flags = fcntl(sock_fd, F_GETFL, NULL)) < 0){
? ? ? ? printf("get socket fd flags error:%d %s", errno, strerror(errno));
? ? ? ? return -1;
? ? }
? ? if(fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) == -1){
? ? ? ? printf("set socket non-block error:%d %s", errno, strerror(errno));
? ? ? ? return -1;
? ? }
? ??
? ? return 0;
}
?
int listen_socket(char *ip, int port, int nonblock)
{
? ? int opt=1, sockfd;
? ? struct sockaddr_in svr_addr;
? ??
? ? if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
? ? ? ? close(sockfd);
? ? ? ? return ERROR_SOCKET_CREATE;
? ? }
? ? if(nonblock) //設置socket為非阻塞模式
? ? ? ? make_socket_nonblock(sockfd);
?
? ? //SO_REUSEADDR是讓端口釋放后立即就可以被再次使用
? ? setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
? ??
? ? memset(&svr_addr, 0, sizeof(struct sockaddr_in));
? ? svr_addr.sin_family = AF_INET;
? ? if(ip == NULL)
? ? ? ? svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
? ? else
? ? ? ? svr_addr.sin_addr.s_addr = inet_addr(ip);
? ? svr_addr.sin_port = htons(port);
? ??
? ? if(bind(sockfd, (struct sockaddr*)&svr_addr, sizeof(struct sockaddr)) == -1){
? ? ? ? close(sockfd);
? ? ? ? return ERROR_SOCKET_BIND;
? ? }
? ??
? ? if(listen(sockfd, LISTENQ) == -1){
? ? ? ? close(sockfd);
? ? ? ? return ERROR_SOCKET_LISTEN;
? ? }
? ??
? ? return sockfd;
}
?
int connect_socket(char *ip, int port,int nonblock)
{
? ? int sockfd;
? ? struct sockaddr_in svr_addr;
? ??
? ? if(ip==NULL || strlen(ip)==0 || port<=0)
? ? ? ? return ERROR_ARGUMENT;
? ??
? ? sockfd = socket(AF_INET, SOCK_STREAM, 0);
? ? if(sockfd < 0){
? ? ? ? perror("socket");
? ? ? ? return ERROR_SOCKET_CREATE;
? ? }
? ??
? ? memset(&svr_addr, 0, sizeof(svr_addr));
? ? svr_addr.sin_family = AF_INET;
? ? svr_addr.sin_addr.s_addr = inet_addr(ip);
? ? svr_addr.sin_port = htons(port);
?
? ? if(connect(sockfd, (struct sockaddr*)&svr_addr, sizeof(svr_addr)) == -1){
? ? ? ? perror("connect");
? ? ? ? close(sockfd);
? ? ? ? return ERROR_SOCKET_CONNECT;
? ? }
? ? if(nonblock)
? ? ? ? make_socket_nonblock(sockfd);
?
? ? return sockfd;
}

3、服務端源文件 epoll_server.c

/**
**程序描述:編寫一個echo server程序,功能是客戶端向服務器發送信息,服務器端接收數據后輸出并原樣返回給客戶端,客戶端接收到消息并輸出到終端。
*/
#include "common.h"
?
void do_epoll(int listen_fd);
void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int listen_fd, char *buf);
void on_accept(int epollfd, int listen_fd);
void on_read(int epollfd, int fd, char *buf);
void on_write(int epollfd, int fd, char *buf);
?
int main(int argc, int *argv[])
{
? ? char svr_ip[32]={0};
? ? int svr_port;
? ? int listen_fd, nonblock=0;
? ??
? ? if(argc < 3){
? ? ? ? printf("ERROR: too few command-line arguments\n");
? ? ? ? printf("Usage: %s <svr_ip> <svr_port>\n", argv[0]);
? ? ? ? return -1;
? ? }
? ? strncpy(svr_ip, argv[1], strlen(argv[1]));
? ? svr_port = atoi(argv[2]);
? ??
? ? listen_fd = listen_socket(svr_ip, svr_port, nonblock);
? ? if(listen_fd < 0){
? ? ? ? printf("listen socket error:%d %s\n", errno, strerror(errno));
? ? ? ? return -2;
? ? }
? ? printf("epoll_svr listen on[%s:%d] succ\n", svr_ip, svr_port);
? ? do_epoll(listen_fd);
? ??
? ? printf("exit epoll_svr succ\n");
? ? return 0;
}
?
void do_epoll(int listen_fd)
{
? ? EPOLL_EVENT_T events[MAX_EVENTS];
? ? int epollfd, conn_fd, nfds;
? ? char buf[BUF_SIZE]={0};
?
? ??
? ? //創建一個epoll文件描述符
? ? epollfd = epoll_create(FD_SEZE);
? ? if(epollfd == -1){
? ? ? ? perror("epoll_create");
? ? ? ? close(listen_fd);
? ? ? ? return;
? ? }
? ? //添加監聽描述符的讀事件
? ? add_epoll_event(epollfd, listen_fd, EPOLLIN);
? ??
? ? for(;;){
? ? ? ? nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
? ? ? ? if(nfds == -1){
? ? ? ? ? ? perror("epoll_wait");
? ? ? ? ? ? close(listen_fd);
? ? ? ? ? ? break;
? ? ? ? }
? ? ? ? handle_epoll_events(epollfd, events, nfds, listen_fd, buf);
? ? }
? ? close(epollfd);
}
?
void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int sockfd, char *buf)
{
? ? int i=0;
? ? int fd;
?
? ? //printf("nfds = %d\n", nfds);
? ? for(i=0; i < nfds; i++){
? ? ? ? fd = events[i].data.fd;
? ? ? ? //根據文件描述符類型和事件類型進行相應處理
? ? ? ? if(fd == sockfd && (events[i].events & EPOLLIN))
? ? ? ? ? ? on_accept(epollfd, sockfd);
? ? ? ? else if(events[i].events & EPOLLIN)
? ? ? ? ? ? on_read(epollfd, fd, buf);
? ? ? ? else if(events[i].events & EPOLLOUT)
? ? ? ? ? ? on_write(epollfd, fd, buf);
? ? }
}
?
void on_accept(int epollfd, int listen_fd)
{
? ? int conn_fd;
? ? struct sockaddr_in cli_addr;
? ? socklen_t cli_addr_len=sizeof(struct sockaddr);
? ??
? ? conn_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
? ? if(conn_fd == -1){
? ? ? ? perror("accept");
? ? }
? ? else{
? ? ? ? printf("accept a new client:[%s:%d], conn_fd=%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), conn_fd);
? ? ? ? //make_socket_nonblock(conn_fd);
? ? ? ? add_epoll_event(epollfd, conn_fd, EPOLLIN); //添加一個客戶端連接描述符的讀事件
? ? }
}
?
void on_read(int epollfd, int fd, char *buf)
{
? ? int nread;
? ??
? ? nread = read(fd, buf, BUF_SIZE);
? ? if(nread == -1){
? ? ? ? perror("read");
? ? ? ? close(fd);
? ? ? ? del_epoll_event(epollfd, fd, EPOLLIN);
? ? }
? ? else if(nread == 0){
? ? ? ? printf("ERROR:client close\n");
? ? ? ? close(fd);
? ? ? ? del_epoll_event(epollfd, fd, EPOLLIN);
? ? }
? ? else{
? ? ? ? printf("recv req msg from client succ, fd=%d, msg:%s", fd, buf);
? ? ? ? //修改描述符對應的事件,由讀改為寫
? ? ? ? mod_epoll_event(epollfd, fd, EPOLLOUT);
? ? }
}
?
void on_write(int epollfd, int fd, char *buf)
{
? ? int nwrite;
? ??
? ? nwrite=write(fd, buf, strlen(buf));
? ? if(nwrite == -1){
? ? ? ? perror("write");
? ? ? ? close(fd);
? ? ? ? del_epoll_event(epollfd, fd, EPOLLOUT);
? ? }
? ? else{
? ? ? ? printf("send resp msg to client succ, fd=%d, msg:%s\n", fd, buf);
? ? ? ? mod_epoll_event(epollfd, fd, EPOLLIN); //將描述符對應的事件,由寫改為讀
? ? }
? ? memset(buf, 0, sizeof(buf));
}

4、客戶端源文件 epoll_client.c

/**
**程序描述:回射程序echo客戶端。客戶端也要使用epoll實現對路復用,控制STDIN_FILENO、STDOUT_FILENO和sockfd這三個描述符對應的事件。
STDIN_FILENO:標準輸入描述符,只有一個讀事件
STDOUT_FILENO:標準輸出描述符,只有一個寫事件
sockfd: 有兩個事件,一個是讀事件,即讀取從服務端發送來的數據;另一個是寫事件,即發送數據給服務端。需要注意的是,在同一時刻,它只能有一個事件
*/
#include "common.h"
?
void do_epoll(int sockfd);
void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int sockfd, char *buf);
void on_read(int epollfd, int fd, int sockfd, char *buf);
void on_write(int epollfd, int fd, int sockfd, char *buf);
?
int main(int argc, char *argv[])
{
? ? char svr_ip[32]={0};
? ? int svr_port;
? ? int conn_fd, nonblock=0;
? ??
? ??
? ? if(argc < 3){
? ? ? ? printf("ERROR: too few command-line arguments\n");
? ? ? ? printf("Usage: %s <svr_ip> <svr_port>\n", argv[0]);
? ? ? ? return -1;
? ? }
? ? strncpy(svr_ip, argv[1], strlen(argv[1]));
? ? svr_port = atoi(argv[2]);
? ??
? ? conn_fd=connect_socket(svr_ip, svr_port, nonblock);
? ? if(conn_fd < 0){
? ? ? ? printf("connect to server failed!\n", conn_fd);
? ? ? ? return -2;
? ? }
? ? printf("connect to server succ, sockfd=%d\n", conn_fd);
? ? do_epoll(conn_fd);
? ??
? ? printf("exit epoll_cli succ\n");
? ? return 0;
}
?
void do_epoll(int sockfd)
{
? ? int epollfd;
? ? EPOLL_EVENT_T events[MAX_EVENTS];
? ? int nfds;
? ? char buf[BUF_SIZE]={0};
?
? ? epollfd = epoll_create(FD_SEZE);
? ? add_epoll_event(epollfd, STDIN_FILENO, EPOLLIN); //注冊標準輸入讀事件
? ? for(;;){
? ? ? ? nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
? ? ? ? if(nfds == -1){
? ? ? ? ? ? perror("epoll_wait");
? ? ? ? ? ? close(sockfd);
? ? ? ? ? ? break;
? ? ? ? }
? ? ? ? handle_epoll_events(epollfd, events, nfds, sockfd, buf);
? ? }
? ? close(epollfd);
}
?
void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int sockfd, char *buf)
{
? ? int i, fd;
?
? ? //printf("nfds = %d\n", nfds);
? ? for(i=0; i<nfds; i++){
? ? ? ? fd = events[i].data.fd;
? ? ? ? //根據文件描述符類型和事件類型進行相應處理
? ? ? ? if(events[i].events & EPOLLIN)
? ? ? ? ? ? on_read(epollfd, fd, sockfd, buf);
? ? ? ? else if(events[i].events & EPOLLOUT)
? ? ? ? ? ? on_write(epollfd, fd, sockfd, buf);
? ? }
}
?
void on_read(int epollfd, int fd, int sockfd, char *buf)
{
? ? int nread;
? ??
? ? nread = read(fd, buf, BUF_SIZE);
? ? if(nread < 0){
? ? ? ? perror("read error");
? ? ? ? close(fd);
? ? }
? ? else if(nread == 0){
? ? ? ? printf("epoll_svr close.\n");
? ? ? ? close(fd);
? ? ? ? exit(-1);
? ? }
? ? else{
? ? ? ? if(fd == STDIN_FILENO){ //標準輸入描述符的讀事件
? ? ? ? ? ? printf("stdin buf=%s", buf);
? ? ? ? ? ? add_epoll_event(epollfd, sockfd, EPOLLOUT); //注冊sockfd的寫事件
? ? ? ? }
? ? ? ? else{//sockfd描述符的讀事件
? ? ? ? ? ? del_epoll_event(epollfd, sockfd, EPOLLIN); //刪除當前sockfd描述符的讀事件
? ? ? ? ? ? add_epoll_event(epollfd, STDOUT_FILENO, EPOLLOUT); //注冊標準輸出寫事件
? ? ? ? }
? ? }
}
?
void on_write(int epollfd, int fd, int sockfd, char *buf)
{
? ? int nwrite;
? ??
? ? nwrite = write(fd, buf, strlen(buf));
? ? if(nwrite < 0){
? ? ? ? perror("write error");
? ? ? ? close(fd);
? ? }
? ? else{
? ? ? ? if(fd == STDOUT_FILENO) //標準輸出描述符的寫事件
? ? ? ? {
? ? ? ? ? ? printf("recv resp_msg from svr, msg=%s\n", buf);
? ? ? ? ? ? del_epoll_event(epollfd, fd, EPOLLOUT); //刪除輸出描述符的寫事件
? ? ? ? }
? ? ? ? else //sockfd描述符的寫事件
? ? ? ? {
? ? ? ? ? ? printf("send req_msg to svr succ, msg=%s", buf);
? ? ? ? ? ? mod_epoll_event(epollfd, fd, EPOLLIN); //修改sockfd描述符為讀事件
? ? ? ? }
? ? }
? ? memset(buf, 0, BUF_SIZE);
}

5、Makefile文件

#第1種方式
?
all: epoll_server epoll_client
?
epoll_server: epoll_server.o common.o
? ? $(LINK)
epoll_client: epoll_client.o common.o
? ? $(LINK)
?
%.o: %.c
? ? $(COMPILE)
?
#compile & link
CFLAGS += -g
COMPILE=gcc -c -g -std=gnu99 -o $@ $<
LINK=gcc -g -o $@ $^
?
clean:
? ? rm -rf *.o epoll_server epoll_client

6、總結分析

6.1 客戶端程序分析

1、對于客戶端程序而言,我們監聽3個文件描述符,分別是連接服務端的sockfd,標準輸入描述符STDIN_FILENO 以及 標準輸出描述符STDOUT_FILENO。在 do_poll函數中,我們首先注冊了標準輸入描述符的讀事件(EPOLLIN),然后在for循環中,循環調用epoll_wait系統調用,handle_epoll_events是整個epoll事件表的handler函數。當我們向終端輸入數據完畢的時候,就會觸發標準輸入描述符的讀事件,從而調用讀事件處理函數on_read,在on_read函數中,注冊了sockfd的寫事件。

2、在do_poll的for循環中,繼續調用epoll_wait 和 handle_epoll_events,發現sockfd的寫事件已就緒,轉向調用寫事件處理函數on_write。在on_write函數中,發送消息給服務端,然后修改sockfd描述符的讀事件就緒,準備接收服務端發來的應答消息。

3、在do_poll的for循環中,繼續調用epoll_wait 和 handle_epoll_events,發現sockfd的讀事件已就緒,轉向調用寫事件處理函數on_write。在on_read函數中,刪除當前sockfd描述符的讀事件并注冊標準輸出描述符的寫事件。之所以要刪除掉sockfd的讀事件,是避免其一直處于就緒狀態。

4、在do_poll的for循環中,繼續調用epoll_wait 和 handle_epoll_events,發現標準輸出描述符的寫事件已就緒,轉向調用寫事件處理函數on_write。在on_write函數中,輸出服務端發來的應答消息并刪除標準輸出描述符的寫事件。之所以要刪除掉標準輸出描述符的寫事件,還是為了避免其一直處于就緒狀態,具體表現就是不停地在終端打印信息。

6.2 服務端程序分析

1、對于服務端程序而言,我們監聽2個文件描述符,分別是接受多個客戶端連接請求的listen_fd和處理單個客戶端的數據讀寫的conn_fd。

2、在main函數中,首先創建了監聽連接請求的listen_fd文件描述符。然后在do_poll函數中,首先注冊了listen_fd描述符的讀事件(EPOLLIN)。在for循環中,循環調用epoll_wait系統調用和epoll事件handler函數handle_epoll_events。

3、當有客戶端發起連接請求時,會觸發listen_fd描述符的讀事件,轉向執行listen_fd的讀事件處理函數on_accept。在on_accept函數中,客戶端與服務端成功建立連接,并返回一個conn_fd文件描述符,然后注冊這個描述符的讀事件。

4、在do_poll的for循環中,繼續調用epoll_wait 和 handle_epoll_events,當客戶端有數據發送給服務端時,觸發conn_fd的讀事件,轉向執行on_read函數。在on_read函數中,接收客戶端發來的消息并在終端輸出,然后修改conn_fd描述符為寫事件就緒。如果接收到的數據大小為0,則說明連接已經斷開,則關閉conn_fd描述符并刪除其讀事件。

5、在do_poll的for循環中,繼續調用epoll_wait 和 handle_epoll_events,發現conn_fd的寫事件就緒,轉向執行on_write函數。在on_write函數中,發送應答消息給客戶端,并修改conn_fd描述符的讀事件就緒。

題外話

本epoll實例程序,本人已經在CentOS 7.6系統下測試通過了。

原文鏈接:https://blog.csdn.net/u010429831/article/details/118634456

欄目分類
最近更新