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代码通用框架实现的更多相关文章

  1. c++ 网络编程(一)TCP/UDP windows/linux 下入门级socket通信 客户端与服务端交互代码

    原文作者:aircraft 原文地址:https://www.cnblogs.com/DOMLX/p/9601511.html c++ 网络编程(一)TCP/UDP  入门级客户端与服务端交互代码 网 ...

  2. 基于node的tcp客户端和服务端的简单通信

    1.简单介绍下TCP/IP TCP/IP是互联网相关协议的集合,分为以下四层:应用层.传输层.网络层.数据链路层. 分成四层的好处是,假如只有一层,某个地方需要改变设计时,就必须把所有整体替换掉,而分 ...

  3. linux socket编程:简易客户端与服务端

    什么是socket? socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来 ...

  4. Linux中通过ssh将客户端与服务端的远程连接

    前提需要:1.在VMware中装上两台linux虚拟机,本博客使用的都是CentOS 7.2.两部虚拟机可以通过命令ping通.3.两部虚拟机中已经通过yum本地仓库安装了sshd服务. 首先  1. ...

  5. TCP中的服务端与客户端的实现

    TCP中首先要在服务端开启监听,这样才可以从客户端链接 using System; using System.Collections.Generic; using System.Linq; using ...

  6. 基于socket的客户端和服务端聊天机器人

    服务端代码如下: using System;using System.Net;using System.Net.Sockets;using System.Text;using System.Threa ...

  7. 在Java中使用Socket模拟客户端和服务端(多线程)

    1:Socket与ServerSocket的交互 2.Socket和ServerSocket介绍 Socket 构造函数 Socket() Socket(InetAddress address, in ...

  8. QTcpSocket-Qt使用Tcp通讯实现服务端和客户端

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QTcpSocket-Qt使用Tcp通讯实现服务端和客户端     本文地址:https:// ...

  9. 二、网络编程-socket之TCP协议开发客户端和服务端通信

    知识点:之前讲的udp协议传输数据是不安全的,不可靠不稳定的,tcp协议传输数据安全可靠,因为它们的通讯机制是不一样的.udp是用户数据报传输,也就是直接丢一个数据包给另外一个程序,就好比寄信给别人, ...

随机推荐

  1. jcl sort comp3 to 表示型

    Lets say your packed data is at 10th column and is of length 6, S9(4)V99 You could try the following ...

  2. Struts的线程安全

    Servlet/JSP技术和ASP.PHP等相比,由于其多线程运行而具有很高的执行效率.由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全性问题.然而 ...

  3. [Contest20180418]数学竞赛

    题意:初始时$x=0$(长度),当$x$为长度时,你可以把$x$变成$\sin^{-1}x,\cos^{-1}x,\tan^{-1}x$之一($x$变为角度),若$x$为角度,你可以把$x$变成$\s ...

  4. (转)在Unity3D的网络游戏中实现资源动态加载

    原文:http://zijan.iteye.com/blog/911102 用Unity3D制作基于web的网络游戏,不可避免的会用到一个技术-资源动态加载.比如想加载一个大场景的资源,不应该在游戏的 ...

  5. Orchard EventBus 事件总线及 IEventHandler作用

    事件总线接口定义: public interface IEventBus : IDependency { IEnumerable Notify(string messageName, IDiction ...

  6. Linux的五个查找命令:find,locate,whereis,which,type 及其区别

    1. find find是最常见和最强大的查找命令,你可以用它找到任何你想找的文件. find的使用格式如下: $ find <指定目录> <指定条件> <指定动作> ...

  7. linux之vim命令

    :tabe fn     在一个新的标签页中编辑文件fngt     切换到下一个标签页gT     切换到上一个标签页:tabr     切换到第一个标签页:tabl     切换到最后一个标签页: ...

  8. js常用函数和常用技巧

    学习和工作的过程中总结的干货,包括常用函数.常用js技巧.常用正则表达式.git笔记等.为刚接触前端的童鞋们提供一个简单的查询的途径,也以此来缅怀我的前端学习之路. PS:此文档,我会持续更新. Aj ...

  9. linux命令详解:df命令

    转:http://www.cnblogs.com/lwgdream/p/3413579.html 前言 df命令用来查看系统的space和inode使用情况,也是常用命令之一 使用说明 -a 显示所有 ...

  10. zookeeper 学习笔记2

    ephemeral 英[ɪˈfemərəl]美[ɪˈfɛmərəl]adj. 朝生暮死; 短暂的,瞬息的; 朝露; 一年生; ZooKeeper Watcher 机制 集群状态监控示例 为了确保集群能 ...