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.mzph.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…