linux epoll機制對TCP 客戶端和服務端的監聽C代碼通用框架實現

1 TCP簡介

tcp是一種基于流的應用層協議,其“可靠的數據傳輸”實現的原理就是,“擁塞控制”的滑動窗口機制,該機制包含的算法主要有“慢啟動”,“擁塞避免”,“快速重傳”。

?2 TCP socket建立和epoll監聽實現

數據結構設計

linux環境下,應用層TCP消息體定義如下:

typedef struct TcpMsg_s
{TcpMsgHeader head;void* msg;
}TcpMsg;

其中,head表示自定義的TCP消息頭,它的定義如下:

?

//TCP消息類型,根據業務需求定義
typedef enum MSGTYPE _e {EP_REG_REQ = 0,EP_REQ_RSP = 1, }MSGTYPE;
//TCP消息頭定義的通用框架 typedef
struct TcpMsgHead_s {int len;//消息長度(用作TCP粘包處理)MSGTYPE type;//消息類型(用作接收端消息的解析) }TcpMsgHead;

socket建立C代碼

TCP客戶端和服務端都采用linux提供的epoll機制(epoll_create(),epoll_wait(),epoll_ctl())對socket實現監聽(可讀,可寫事件等)。

開源事件驅動庫lievent對socket事件的監聽也是通過對epoll事件的封裝實現的。

(1)TCP服務端socket建立C代碼

基本原理:利用linux網絡通信API(scoket(),bind(),listen())來創建服務器端socket;

代碼如下:輸入參數:localip,本地ip;port:服務端本地的監聽端口號;輸出:返回-1,表示失敗;返回>0的fd,表示socket建立成功;

 1    int TcpServer(uint32_t lcoalip, int port)
 2    {
 3        int fd;
 4        struct sockaddr_in addr;
 5 
 6        //socket建立
 7        if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)  
 8        {
 9            printf("IN TcpServer() scoket created failed,errno is %d, strerror is %s\n", errno, strerror(errno));
10            return -1;
11        }  
12 
13        //設置socket為非阻塞模式
14        int flags = fcntl(fd, F_GETFL, 0);
15        fcntl(fd, F_SETFL, flags | O_NONBLOCK);
16 
17        memset(&addr, 0 , sizeof(addr));
18        addr.sin_family = AF_INET;
19        addr.sin_addr.s_addr = localip;
20        addr.sin_port = port;
21 
22        //綁定本地端口和IP
23        if (bind(fd, (struct sockaddr_in)&addr, sizeof(addr) < 0))
24        {
25            printf("IN TcpServer() bind failed,fd is%d, errno is %d, strerror is %s\n", fd, errno, strerror(errno));
26            return -1;
27        }
28 
29        if (listen(fd, 20< 0))
30        {
31            printf("IN TcpServer() listen failed,fd is%d, errno is %d, strerror is %s\n", fd, errno, strerror(errno));
32            return -1;
33        }
34     
35        //add the socket to epoll event
36        if (SubscribeFd(fd, SOCKET_EV) != 0)
{
return -1;
}
37 return fd; 38 }

?

而SubscribeFd函數功能是將socket添加到epoll的監聽事件中

實現如下:

輸入參數:fd,待監聽的fd;type,枚舉型變量,表明TCP類型,是客戶端還是服務端;port:服務端的監聽端口號;輸出:返回-1,表示監聽失敗;返回0,表示將該socket成功添加到維護在全局變量g_epoll(TCP_EPOLL類型結構體)中的監聽事件中;其中TCP_TYPE枚舉變量和TCP_EPOLL結構體的定義如下:

typedef enum
{CLIENT = 0,SERVER = 1,
}TCP_TYPE;#define MAX_NUM_EPOLL 1000//最多可監聽的socket數目
typedef struct TCP_EPOLL_s
{struct epoll_event* p_event;int nb_evnet;
int nb_client;//for tcp server
int epoll_fd;int sock_listen;//for tcp serverint sock[MAX_NUM_EPOLL];TCP_NL_MSG* p_tcp_nl_msg;//TCP粘包處理數據結構 }TCP_EPOLL;

SubscribeFd函數實現如下:

int SubscribeFd (int fd, TCP_TYPE type)
{struct epoll_event event;if (CLIENT == type){event.events = EPOLLOUT | EPOLLET;//監聽類型為可寫事件
    }else if (SERVER == type){event.events = EPOLLIN | EPOLLET;//監聽類型為可讀事件
    }event.date.u64 = 0;evnet.data.fd = fd;g_epoll.nb_event++;g_epoll.p_event = realloc(g_epoll.p_event, g_epoll.nb_event * sizeof(struct epoll_event));//add epoll control event if (epoll_ctl(g_epoll.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0){printf("epoll_ctl failed for fd %d, errno is %d, strerror is %s\n", fd, errno, strerror(errno));return -1;}printf("successfully subscribe fd %d\n", fd);return 0;
}

?

(2)TCP客戶端socket建立C代碼

基本原理:利用linux網絡通信API(scoket(),connect())來創建客戶端socket;

代碼如下:輸入參數:peerip,服務端IP;localip,本地ip;port:服務端的監聽端口號;輸出:返回-1,表示失敗;返回>0的fd,表示socket建立成功;

 1 int TCPClient(uint32_t peerip,uint32_t localip,uint16_t port)
 2 {
 3      int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 4      if (fd < 0)
 5      {
 6        printf("TCPClient() socket failed");
 7        return -1;
 8      }
 9 
10     struct sockaddr_in localaddr = {0};
11     localaddr.sin_family = AF_INET;
12     localaddr.sin_addr.s_addr = localip;
13     //localaddr.sin_port = htons(port);
14     
15     int ret = bind(fd, (struct sockaddr *)&localaddr, sizeof(localaddr));
16     if (ret < 0)
17     {
18         printf("TCPClient() bind failed localip %u", localip);
19         return -1;
20     }
21     
22     int flags = fcntl(fd, F_GETFL, 0);
23     fcntl(fd, F_SETFL, flags | O_NONBLOCK);
24 
25     struct sockaddr_in servaddr = {0};
26     servaddr.sin_family = AF_INET;
27     servaddr.sin_addr.s_addr = peerip;
28     servaddr.sin_port = htons(port);
29     
30     ret = connect(fd, (sockaddr *)&servaddr, sizeof(servaddr));
31     if(ret < 0)
32     {
33         if (errno != EINPROGRESS)
34         {
35             printf("TCPClient() connect failed, peerip %u, port %u", peerip, port);
36             return -1;
37         }
38     }
39     
40     printf("TCPClient() connect success, fd = %u,peerip %u, port %u",fd, peerip, port);
41     
42     return fd;
43 }

?

(3) TCP客戶端和服務端利用epoll_wait()實現對socket的監聽和消息的接收的通用框架

TCP服務端監聽到監聽socket有EPOLLIN事件到來時,調用int accept_fd = accept();接收此連接請求,然后服務端要利用epoll_create()為accept_fd創建新的監聽事件;

?

linux利用epoll機制實現socket事件的消息接收的C代碼(TCP接收線程的入口)如下:

 1 void tcp_thread()2 {3     CreateEpoll();4     CreateSocketFdEpoll(g_tcp_type);5     6     while (1)7  { 8 //wait for a message 9  EpollRecvMsg(); 10  } 11 }


CreateEpoll函數是調用epoll_create來創建epoll事件:

1 TCP_EPOLL g_epoll;//全局Epoll變量
2 
3 //EPOLL事件的建立
4 void CreateEpoll()
5 {
6      g_epoll.epoll_fd = epoll_create1(0);
7      g_epoll.nb_event = 0;
8 }

?

CreateSocketFdEpoll函數功能為創建TCP socket和TCP粘連處理數據結構初始化:

 1 int CreateSocketFdEpoll(TCP_TYPE type)2 {3     uint32_t server_ip = inet_addr(SERVER_IP);4     uint32_t local_ip = inet_addr(LOCAL_IP);5 6     int fd;7     if (CLIENT == type)8     {9         fd = TcpClient(server_ip, SERVER_PORT, local_ip);
10         g_epoll.sock = fd;
11     }
12     else if (SERVER == type)
13     {
14         fd = TcpServer(local_ip, LOCAL_PORT);
15         g_epoll.sock_listen = fd;
16     }
17 
18     g_epoll.p_tcpNLMsg = (TCP_NL_MSG)malloc(sizeof(TCP_NL_MSG));
19 
20     InitTcpNLMsg(g_epoll.p_tcpNLMsg);
21 }

InitTcpNLMsg函數是對TCP粘連處理數據結構的初始化:

1 void InitTcpNLMsg(TCP_NL_MSG* pTcpNLMsg)
2 {
3     pTcpNLMsg->g_recv_len = 0;
4     pTcpNLMsg->flag_in_NL_proc = FALSE;
5     memset(pTcpNLMsg->g_recv_buff, 0, MAX_MSG_LEN);
6 }

?其中,TCP粘包處理的數據結構設計和處理邏輯分析詳見另一篇博文:

TCP粘包處理通用框架--C代碼

EpollRecvMsg函數是調用epoll_wait()實現對Socket事件的監聽和消息的接收:

 1 void EpollRecvMsg()2 {3     int epoll_ret = 0;4     int epoll_timeout = -1;5 6     do7     {8         epoll_ret = epoll_wait(g_epoll.epoll_fd, g_epoll.p_event, g_epoll.nb_event, epoll_timeout); 9 }while(epoll_ret < 0 && errno == EINTR); 10 11 if (epoll_ret < 0) 12  { 13 printf("epoll_wait failed: %s\n", strerror(errno)); 14 return; 15  } 16 17 //遍歷處理每一個當前監聽到的事件 18 for (int i=0;i<epoll_ret;++i) 19  { 20 int fd = g_epoll.p_event[i].data.fd; 21 22 if (CLIENT == g_tcp_type) 23  { 24 if (g_epoll.p_event[i].events & EPOLLOUT) //the socket is writable,socket可寫,表明服務端已accept該客戶端的connect請求 25  { 26 if (JudgeIfConnSucc(fd) == 0)//判斷TCP連接是否建立成功 27  { 28 struct epoll_event* p_ev = &(g_epoll.p_event[i]); 29 p_ev ->events = EPOLLIN | EPOLLET; 30 31  epoll_ctl(g_epoll.epoll_fd, EPOLL_CTL_MOD,fd, p_ev );//對TCP客戶端socket修改其監聽類型,由可寫改為可讀 32 33 printf("tcp_fd_client %d can be written\n", fd); 34  } 35  } 36 else if(g_epoll.p_event[i].events & EPOLLIN) //the socket is readable 37  { 38  RecvTcpMsg(fd); 39  } 40  } 41 else if (SERVER== g_tcp_type) 42 { if (g_epoll.p_event[i].events & EPOLLIN) //the socket is readable,服務端socket可讀 43  { 44 if (fd == g_epoll.sock_listen)//服務端接收到一個TCP連接請求 45  { 46 struct sockaddr s_addr; 47 socklen_t length = sizeof(struct sockaddr); 48 49 int conn_fd = accept(fd, &s_addr, &length);//服務端接收來自客戶端的連接請求 50 51 int flags = fcntl(conn_fd, F_GETFL, 0); 52 fcmt(conn_fd, F_SETFL, flags | O_NONBLOCK); 53 54 g_epoll.sock[g_epoll.nb_client++] = conn_fd; 55 56  SubscribeFd(conn_fd, SERVER);//服務端將新建立的TCP連接建立新的epoll監聽事件,并維護在全局變量中 57 58 printf("Receive a tcp conn request, conn_fd is %d\n", fd); 59  } 60 else //support multi tcp client 61  { 62  RecvTcpMsg(fd);//接收TCP消息(先進行粘包處理,然后根據消息類型進入不同的處理分支) 63  } 64  } 65  } 66  } 67 } 

?

(4)通用的TCP消息發送函數

函數實現如下:

輸入:fd,發送socket;type,業務定義的tcp消息類型;msg指針:指向待發送的消息地址;length,待發送的msg的字節數;

輸出:成功,返回發送的字節數;失敗,返回-1;

#define MAX_LEN_BUFF 65535
int
SendTcpMsg(int fd, MSGTYPE type, void* msg, int length) {uint8_t buf[MAX_LEN_BUFF];memset(buf,0,MAX_LEN_BUFF);uint32_t bsize = 0;TcpMsgHead* head = (TcpMsgHead*)buf;bsize += sizeof(TcpMsgHead);
//將待發送消息內容拷貝到待發送緩存中memcpy(buf
+bsize, msg, length);bsize += length;
//封裝TCP消息頭,指明消息類型(用作接收端消息的解析)和消息長度(用作TCP粘包處理)head
->type = type;head->msglen = bsize; int ret = send(fd,(const void*)buf,bsize,0);if(ret != bsize){printf("Failed to send tcp msg,errno=%u,ret=%d, strerror is %s\n", errno, ret, strerror(errno));return -1;}printf("Success to send tcp msg, msg type is %d\n", type); return ret;}

?

轉載于:https://www.cnblogs.com/studyofadeerlet/p/7265616.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/391429.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/391429.shtml
英文地址,請注明出處:http://en.pswp.cn/news/391429.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux中安裝robot環境

https://www.cnblogs.com/lgqboke/p/8252488.html&#xff08;文中驗證robotframework命令應該為 robot --version&#xff09; 可能遇到的問題&#xff1a; 1、python版本太低 解決&#xff1a;升級python https://www.cnblogs.com/huaxingtianxia/p/7986734.html 2、pip安裝報…

angular 模塊構建_我如何在Angular 4和Magento上構建人力資源門戶

angular 模塊構建Sometimes trying a new technology mashup works wonders. Both Magento 2 Angular 4 are very commonly talked about, and many consider them to be the future of the development industry. 有時嘗試新技術的mashup會產生奇跡。 Magento 2 Angular 4都…

tableau破解方法_使用Tableau瀏覽Netflix內容的簡單方法

tableau破解方法Are you struggling to perform EDA with R and Python?? Here is an easy way to do exploratory data analysis using Tableau.您是否正在努力使用R和Python執行EDA&#xff1f; 這是使用Tableau進行探索性數據分析的簡單方法。 Lets Dive in to know the …

六周第三次課

2019獨角獸企業重金招聘Python工程師標準>>> 六周第三次課 9.6/9.7 awk awk也是流式編輯器&#xff0c;針對文檔中的行來操作&#xff0c;一行一行地執行。 awk比sed更強大的功能是它支持了分段。 -F選項的作用是指定分隔符&#xff0c;如果不加-F選項&#xff0c;…

面試題字符集和編碼區別_您和理想工作之間的一件事-編碼面試!

面試題字符集和編碼區別A recruiter calls you for a position with your dream company. You get extremely excited and ask about their recruiting process. He replies saying “Its nothing big, you will have 5 coding rounds with our senior tech team, just the sta…

初探Golang(1)-變量

要學習golang&#xff0c;當然要先配置好相關環境啦。 1. Go 安裝包下載 https://studygolang.com/dl 在Windows下&#xff0c;直接下載msi文件&#xff0c;在安裝界面選擇安裝路徑&#xff0c;然后一直下一步就行了。 在cmd下輸入 go version即可看到go安裝成功 2. Golan…

macaca web(4)

米西米西滴&#xff0c;吃過中午飯來一篇&#xff0c;話說&#xff0c;上回書說道macaca 測試web&#xff08;3&#xff09;&#xff0c;參數驅動來搞&#xff0c;那么有小伙本又來給雷子來需求&#xff0c; 登錄模塊能不能給我給重新封裝一下嗎&#xff0c; 我說干嘛封裝&…

linux中安裝cx_Oracle

https://blog.csdn.net/w657395940/article/details/41144225 各種嘗試都&#xff0c;最后 pip install cx-Oracle 成功導入 轉載于:https://www.cnblogs.com/gcgc/p/11447583.html

rfm模型分析與客戶細分_如何使用基于RFM的細分來確定最佳客戶

rfm模型分析與客戶細分With some free time at hand in the midst of COVID-19 pandemic, I decided to do pro bono consulting work. I was helping a few e-commerce companies with analyzing their customer data. A common theme I encountered during this work was tha…

leetcode 208. 實現 Trie (前綴樹)

Trie&#xff08;發音類似 “try”&#xff09;或者說 前綴樹 是一種樹形數據結構&#xff0c;用于高效地存儲和檢索字符串數據集中的鍵。這一數據結構有相當多的應用情景&#xff0c;例如自動補完和拼寫檢查。 請你實現 Trie 類&#xff1a; Trie() 初始化前綴樹對象。 void…

那些年收藏的技術文章(一) CSDN篇

#Android ##Android基礎及相關機制 Android Context 上下文 你必須知道的一切 Android中子線程真的不能更新UI嗎&#xff1f; Android基礎和運行機制 Android任務和返回棧完全解析&#xff0c;細數那些你所不知道的細節 【凱子哥帶你學Framework】Activity啟動過程全解析 【凱子…

chrome json插件_如何使用此免費的Chrome擴展程序(或Firefox插件)獲取易于閱讀的JSON樹

chrome json插件JSON is a very popular file format. Sometimes we may have a JSON object inside a browser tab that we need to read and this can be difficult.JSON是一種非常流行的文件格式。 有時我們可能需要在瀏覽器選項卡中包含一個JSON對象&#xff0c;這很困難。…

test10

test10 轉載于:https://www.cnblogs.com/Forever77/p/11447638.html

數據倉庫項目分析_數據分析項目:倉庫庫存

數據倉庫項目分析The code for this project can be found at my GitHub.該項目的代碼可以在我的GitHub上找到 。 介紹 (Introduction) The goal of this project was to analyse historic stock/inventory data to decide how much stock of each item a retailer should hol…

leetcode 213. 打家劫舍 II(dp)

你是一個專業的小偷&#xff0c;計劃偷竊沿街的房屋&#xff0c;每間房內都藏有一定的現金。這個地方所有的房屋都 圍成一圈 &#xff0c;這意味著第一個房屋和最后一個房屋是緊挨著的。同時&#xff0c;相鄰的房屋裝有相互連通的防盜系統&#xff0c;如果兩間相鄰的房屋在同一…

HTTP緩存的深入介紹:Cache-Control和Vary

簡介-本文范圍 (Introduction - scope of the article) This series of articles deals with caching in the context of HTTP. When properly done, caching can increase the performance of your application by an order of magnitude. On the contrary, when overlooked o…

059——VUE中vue-router之路由嵌套在文章系統中的使用方法:

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>vue-router之路由嵌套在文章系統中的使用方法&#xff1a;</title><script src"vue.js"></script><script src"v…

web前端效率提升之瀏覽器與本地文件的映射-遁地龍卷風

1.chrome瀏覽器&#xff0c;機制是攔截url&#xff0c;      1.在瀏覽器Element中調節的css樣式可以直接同步到本地文件&#xff0c;反之亦然&#xff0c;瀏覽器會重新加載css&#xff0c;省去刷新   2.在source面板下對js的編輯可以同步到本地文件&#xff0c;反之亦然…

linux : 各個發行版中修改python27默認編碼為utf-8

該方法可解決robot報錯&#xff1a;ascii codec cant encode character u\xf1 in position 16: ordinal not in range(128) 在下面目錄中新增文件&#xff1a;sitecustomize.py 內容為 #codingutf-8 import sysreload(sys) sys.setdefaultencoding(utf8) 各個發行版放置位置&a…

歸因分析_歸因分析:如何衡量影響? (第2部分,共2部分)

歸因分析By Lisa Cohen, Ryan Bouchard, Jane Huang, Daniel Yehdego and Siddharth Kumar由 麗莎科恩 &#xff0c; 瑞安布沙爾 &#xff0c; 黃美珍 &#xff0c; 丹尼爾Yehdego 和 亞洲時報Siddharth庫馬爾 介紹 (Introduction) This is our second article in a series wh…