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 = ,
EP_REQ_RSP = ,
}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建立成功;
int TcpServer(uint32_t lcoalip, int port)
{
int fd;
struct sockaddr_in addr; //socket建立
if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < )
{
printf("IN TcpServer() scoket created failed,errno is %d, strerror is %s\n", errno, strerror(errno));
return -;
} //设置socket为非阻塞模式
int flags = fcntl(fd, F_GETFL, );
fcntl(fd, F_SETFL, flags | O_NONBLOCK); memset(&addr, , sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = localip;
addr.sin_port = port; //绑定本地端口和IP
if (bind(fd, (struct sockaddr_in)&addr, sizeof(addr) < ))
{
printf("IN TcpServer() bind failed,fd is%d, errno is %d, strerror is %s\n", fd, errno, strerror(errno));
return -;
} if (listen(fd, < ))
{
printf("IN TcpServer() listen failed,fd is%d, errno is %d, strerror is %s\n", fd, errno, strerror(errno));
return -;
} //add the socket to epoll event
if (SubscribeFd(fd, SOCKET_EV) != 0)
{
return -1;
}
return fd;
}
而SubscribeFd函数功能是将socket添加到epoll的监听事件中
实现如下:
输入参数:fd,待监听的fd;type,枚举型变量,表明TCP类型,是客户端还是服务端;port:服务端的监听端口号;输出:返回-1,表示监听失败;返回0,表示将该socket成功添加到维护在全局变量g_epoll(TCP_EPOLL类型结构体)中的监听事件中;其中TCP_TYPE枚举变量和TCP_EPOLL结构体的定义如下:
typedef enum
{
CLIENT = ,
SERVER = ,
}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 server
int 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 = ;
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) != )
{
printf("epoll_ctl failed for fd %d, errno is %d, strerror is %s\n", fd, errno, strerror(errno));
return -;
} printf("successfully subscribe fd %d\n", fd); return ;
}
(2)TCP客户端socket建立C代码
基本原理:利用linux网络通信API(scoket(),connect())来创建客户端socket;
代码如下:输入参数:peerip,服务端IP;localip,本地ip;port:服务端的监听端口号;输出:返回-1,表示失败;返回>0的fd,表示socket建立成功;
int TCPClient(uint32_t peerip,uint32_t localip,uint16_t port)
{
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < )
{
printf("TCPClient() socket failed");
return -;
} struct sockaddr_in localaddr = {};
localaddr.sin_family = AF_INET;
localaddr.sin_addr.s_addr = localip;
//localaddr.sin_port = htons(port); int ret = bind(fd, (struct sockaddr *)&localaddr, sizeof(localaddr));
if (ret < )
{
printf("TCPClient() bind failed localip %u", localip);
return -;
} int flags = fcntl(fd, F_GETFL, );
fcntl(fd, F_SETFL, flags | O_NONBLOCK); struct sockaddr_in servaddr = {};
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = peerip;
servaddr.sin_port = htons(port); ret = connect(fd, (sockaddr *)&servaddr, sizeof(servaddr));
if(ret < )
{
if (errno != EINPROGRESS)
{
printf("TCPClient() connect failed, peerip %u, port %u", peerip, port);
return -;
}
} printf("TCPClient() connect success, fd = %u,peerip %u, port %u",fd, peerip, port); return fd;
}
(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 do
7 {
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,,MAX_LEN_BUFF);
uint32_t bsize = ; 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,);
if(ret != bsize)
{
printf("Failed to send tcp msg,errno=%u,ret=%d, strerror is %s\n", errno, ret, strerror(errno));
return -;
} printf("Success to send tcp msg, msg type is %d\n", type); return ret; }
linux epoll机制对TCP 客户端和服务端的监听C代码通用框架实现的更多相关文章
- c++ 网络编程(一)TCP/UDP windows/linux 下入门级socket通信 客户端与服务端交互代码
原文作者:aircraft 原文地址:https://www.cnblogs.com/DOMLX/p/9601511.html c++ 网络编程(一)TCP/UDP 入门级客户端与服务端交互代码 网 ...
- 基于node的tcp客户端和服务端的简单通信
1.简单介绍下TCP/IP TCP/IP是互联网相关协议的集合,分为以下四层:应用层.传输层.网络层.数据链路层. 分成四层的好处是,假如只有一层,某个地方需要改变设计时,就必须把所有整体替换掉,而分 ...
- linux socket编程:简易客户端与服务端
什么是socket? socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来 ...
- Linux中通过ssh将客户端与服务端的远程连接
前提需要:1.在VMware中装上两台linux虚拟机,本博客使用的都是CentOS 7.2.两部虚拟机可以通过命令ping通.3.两部虚拟机中已经通过yum本地仓库安装了sshd服务. 首先 1. ...
- TCP中的服务端与客户端的实现
TCP中首先要在服务端开启监听,这样才可以从客户端链接 using System; using System.Collections.Generic; using System.Linq; using ...
- 基于socket的客户端和服务端聊天机器人
服务端代码如下: using System;using System.Net;using System.Net.Sockets;using System.Text;using System.Threa ...
- 在Java中使用Socket模拟客户端和服务端(多线程)
1:Socket与ServerSocket的交互 2.Socket和ServerSocket介绍 Socket 构造函数 Socket() Socket(InetAddress address, in ...
- QTcpSocket-Qt使用Tcp通讯实现服务端和客户端
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QTcpSocket-Qt使用Tcp通讯实现服务端和客户端 本文地址:https:// ...
- 二、网络编程-socket之TCP协议开发客户端和服务端通信
知识点:之前讲的udp协议传输数据是不安全的,不可靠不稳定的,tcp协议传输数据安全可靠,因为它们的通讯机制是不一样的.udp是用户数据报传输,也就是直接丢一个数据包给另外一个程序,就好比寄信给别人, ...
随机推荐
- poj2104(划分树模板)
poj2104 题意 给出一个序列,每次查询一个区间,要求告诉这个区间排序后的第k个数. 分析 划分树模板,O(mlogn). 建树.根据排序之后的数组,对于一个区间,找到中点的数,将整个区间分为左右 ...
- Reverse Words in a String II -- LeetCode
Given an input string, reverse the string word by word. A word is defined as a sequence of non-space ...
- eclipse无法导入Android工程的解决办法
我以前在windows平台下写的android源代码无法通过import"existing project into workspace"导入到mac的eclipse中,直接搜不见 ...
- 如何隐藏 Safari 中 input 标签的 autofill 图标
Safari 浏览器会为 <input type="passport"> 标签自动添加一个小锁的图标(如下图),本意上是让用户可以从这里选择相应的 用户名/密码 进行自 ...
- Android的file文件操作详解
Android的file文件操作详解 android的文件操作要有权限: 判断SD卡是否插入 Environment.getExternalStorageState().equals( android ...
- Android中调用系统所装的软件打开文件(转)
Android中调用系统所装的软件打开文件(转) 在应用中如何调用系统所装的软件打开一个文件,这是我们经常碰到的问题,下面是我所用到的一种方法,和大家一起分享一下! 这个是打开文件的一个方法: /** ...
- cocurrent包semaphore信号量
semaphore英[ˈseməfɔ:(r)]美[ˈsɛməˌfɔr, -ˌfor]n. 臂板信号系统,(铁道)臂板信号装置; Semaphore 用法 信号量主要有两种用途: 保护一个重要(代码)部 ...
- Android使用FFMpeg实现推送视频直播流到服务器
背景 在过去的2015年中,视频直播页的新宠无疑是户外直播.随着4G网络的普及和覆盖率的提升,主播可以在户外通过手机进行直播.而观众也愿意为这种可以足不出户而观天下事的服务买单.基于这样的背景,本文主 ...
- Android自定义View(二)
前言 魅族手机的闹钟应用中有个倒计时,这个控件还是蛮有趣的.左边是魅族闹钟,右边是我们最终实现的效果,虽然有些细节还需优化,不过基本上已经达到了想要的效果,我们先来就来看看如何实现吧. 分析 确定宽高 ...
- 新人补钙系列教程之:AS3 与 PHP 简单通信基础
package { import flash.display.Loader; import flash.events.Event; import flash.net.URLLoader; import ...