TCP--IO多路转接模型
Posted zhao111222333444
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP--IO多路转接模型相关的知识,希望对你有一定的参考价值。
IO多路转接模型
classtcp.hpp
E> 1 #pragma once
2 #include <cstdio>
3 #include <iostream>
4 #include <string>
5 #include <unistd.h>
6 #include <arpa/inet.h>
7 #include <netinet/in.h>
8 #include <sys/socket.h>
9 #include<sys/select.h>
//非阻塞
#include<fcntl.h>
#include<errno.h>
10
11 #define LISTEN_MAX 5
12 #define CHECK_RET(q) if((q)==false)return -1;
13 //tcp的流程大致如下“
14 //客户端:创建套接字,不推荐绑定地址信息,向服务器发送链接请求,收发数据,关闭套接字
15 //服务端:创建套接字,绑定地址信息,开始监听,获取新建连接,收发数据,关闭套接字
16 class TCPSocket
17 private:
18 int _sockfd;
19 public:
20 TCPSocket():_sockfd(-1)
21
22 //加入select的setfd和getfd
23 void SetFd(int fd)
24 _sockfd=fd;
25
26
27 int GetFd()
28 return _sockfd;
29
30
31 //地址重用的套接字选项,可以解决time_wait段时间不能重连的问题
32 void SetSocketOpt()
33 int opt=1;
E> 34 setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
35
36
37 //设置为非阻塞
bool SetNoBlock()
E> 42 int flag=fcntl(_sockfd,F_GETFL,0);
E> 43 fcntl(_sockfd,F_GETFL,flag|0_MOMBLOCK);
44
38
39 //创建套接字
40 //socket(地址域类型,套接字类型,通信协议)
41 //封装的接口需要获取操作句柄
42 bool Socket()
E> 43 _sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
44 if(_sockfd<0)
45 perror("socket error");
46 return false;
47
48 return true;
49
50
51 //绑定地址信息
52 //接口(句柄,规范接口struct结构体,结构体大小)、
53 //封装的接口(ip地址,port信息)
54 bool Bind(const std::string &ip,const uint16_t port)
E> 55 sockaddr_in addr;
E> 56 addr.sin_family = AF_INET;//16位地址类型
E> 57 addr.sin_port = htons(port);
58
59 //这两种都能拿到ip内容(str的首地址)
E> 60 addr.sin_addr.s_addr = inet_addr(&ip[0]);
61 //addr.sin_addr.s_addr = inet_addr(ip.c_str());
62
E> 63 socklen_t len = sizeof(sockaddr_in);
64 int ret=bind(_sockfd,(struct sockaddr*)&addr,len);
65
66 if (ret < 0)
67 perror("bind error");
68 return false;
69
70 return true;
71
72
73
74
75 //监听接口(操作句柄,最大连接数)
76 //封装接口(最大连接数)
77 bool Listen(int big =LISTEN_MAX)
78 int ret=listen(_sockfd,LISTEN_MAX);
79 if(ret<0)
80 return false;
81
82 return true;
83
84
85
86
87 //申请链接(操作句柄,服务端的地址,地址长度)
88 //封装的接口(服务器ip,服务器port)
89 bool Connect(const std::string &ip,uint16_t port)
90 sockaddr_in addr;
91 addr.sin_family = AF_INET;
92 addr.sin_port = htons(port);
93 addr.sin_addr.s_addr = inet_addr(&ip[0]);
94 socklen_t len = sizeof(sockaddr_in);
95 int ret = connect(_sockfd, (sockaddr*)&addr, len);
96 if (ret < 0)
97 perror("connect error");
98 return false;
99
100 return true;
101
102
103 //封装的接口:
104 //注意三个都是输出型参数(因为是处理请求的接口,所以不需要输入什么信息,只需要获得)
105 //服务器同意链接接口(新的套接字地址,客户端的地址结构指针,地址结构大小指针)
106 //注意这个的前提是客户端发送请求了,所以后两个数输出结构,用来获取请求的客户端的地址信息,当然可以不获取
107
108 //原始的的接口(监听套接字,获取客户端的ip,客户端的port地址)
109 //返回值是一个新的套接字,专门用来服务客户端,所以监听套接字相当于门迎~
110 //将返回值放到封装的接口参数1,原始接口2是输出参数,分开封装到封装接口的23
111 bool Accept(TCPSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
112 //建立一个结构体用来存客户端的地址信息
113 sockaddr_in addr;
114 socklen_t len = sizeof(sockaddr_in);
115
116 //注意这个len是输出型参数,所以是取地址!!!!
117 int newfd=accept(_sockfd,(sockaddr*)&addr,&len);
118 if (newfd < 0)
119 perror("accept error");
120 return false;
121
122
123 //将输出型参数1填复制的句柄
124 sock->_sockfd = newfd;
125 if (ip != NULL)
126 *ip = inet_ntoa(addr.sin_addr);
127
128 if (port != NULL)
129 *port = ntohs(addr.sin_port);
130
131 return true;
132
133
134
135
136 //接收数据(操作句柄,接收缓冲区,接收数据长度,0阻塞)
137 //封装的接口(接收缓冲区)
138 //注意返回值小于0错误
139 //等于0连接断开
140 //大于0,实际大小
141 bool Recv(std::string *buf)
142 char tmp[1024]=0;
143 int ret=recv(_sockfd,tmp,1024,0);
144 if (ret < 0)
145 perror("recv error");
146 return false;
147
148 else if (ret == 0)
149
150 printf("peer shutdown");
151 return false;
152
153 //把缓冲区的内容放入真正的缓冲区
154 //因为原始接口需要指定一次接受的数据大小
155 //而直接用缓冲区接收不能确定大小
156 buf->assign(tmp, ret);
157 return true;
158
//边缘触发使用的循环读取recv
172 //循环读取--边缘触发搭配循环读取非阻塞
173 bool Recv(std::string *buf)
174 char tmp[1024]=0;
175 int len=1024;
176 int total =0;
177 while(total<len)
178 int ret=recv(_sockfd,tmp+total,5,0);
179 if (ret < 0)
180 if(errno==EAGAIN)
181
182 break;
183
184 perror("recv error");
185 return false;
186
187 else if (ret == 0)
188
189 printf("peer shutdown");
190 return false;
191
192 total+=ret;
193
194 buf->assign(tmp, total);
195 return true;
196
159
160 //发送数据(描述符,数据,长度,标志位0阻塞)
161 //封装的接口(要发送数据)
162 //返回实际的长度
163 //这里需要考虑一次发送不完全部数据该怎么办
164 bool Send(const std::string &data)
165 int total;
166 while(total < data.size())
167
168 int ret = send(_sockfd, &data[0] + total,data.size() - total, 0);
169 if(ret<0)
170
171 perror("send error");
172 return false;
173
174 total+=ret;
175
176 return true;
177
178
179 bool Close()
180 if (_sockfd != -1)
181 close(_sockfd);
182
183 return true;
184
185
186 ;
select.hpp
1 #include<iostream>
2 #include<vector>
3 #include<time.h>
4 #include<sys/select.h>
5 #include"classtcp.hpp"
6
7 //使用select多路转接模型,为了方便使
8 //用可以先封装select相关函数,让Tcp
9 //客户端仅仅调用接口完成多路转接模型
10
11 class Select
12
13 public:
Select():_max_fd(-1)
FD_ZERO(&_rfds);
E> 14 bool Addfd(TCPSocket &sock)
15 int fd=sock.GetFd();//获取当前tcp描述符
16 FD_SET(fd,&_rfds);//加入到读的集合,第二个参数是地址
17 _max_fd=_max_fd > fd ? _max_fd : fd;//更新最大描述符,因为要遍历需要最大范围
18 return true;
19
20
21
E> 22 bool Delete(TCPSocket &sock)
23 int fd=sock.GetFd();//获取当前tcp描述符
24 FD_CLR(fd,&_rfds);//删除当前描述符
25 //删除了一个描述符就需要更新最大描述符
26 //从最大到最小遍历加快效率
27 for(int i=_max_fd;i>=0;--i)
28 if(FD_ISSET(i,&_rfds))
29 _max_fd=i;//if i存在就是最大的
30 break;
31
32
33 return true;
34
35
36
37 //wait接口用于对集合中的所有描述符进行监控
38 //并返回就绪的描述符,通过输出型参数来获取内容
E> 39 bool Wait(std::vector<TCPSocket> *v)
40 //int select(最大描述符+1,的三种集合地址(不要则置空),设置超时结构体);
41 struct timeval tv;
42 tv.tv_sec= 9;
43 tv.tv_usec=0;
E> 44 fd_set tmp=_rfds;//每次都要拿当前对象的集合的拷贝(每次会修改这个)
45 int ret=select(_max_fd+1,&tmp,NULL,NULL,&tv);
46 if(ret<0)//出错返回-1
E> 47 perror("select error");
48 return false;
49
50 else if(ret==0)
51 v->clear();//超时返回0
52 return true;
53
54 //返回值大于0,返回就绪的描述符个数
55 //知道个数但不知道是哪几个,所以需要遍历tmp集合中还有谁存在,因为
56 //没有就绪的的已经自动删除了
57 for(int i=0;i<=_max_fd;++i)
58 //拿到了就绪的描述符,此时需要加上TCP套接字来加入到就绪vector
59 //因为在监控加入的时候只是把TCP套接字的描述符加入了用来监控,
60 //所以返回出去要加上
61 if(FD_ISSET(i,&tmp))
E> 62 TCPSocket sock;
63 sock.SetFd(i);
64 v->push_back(sock);
65
66
67 return true;
68
69
70 private:
E> 71 fd_set _rfds;
72 //需要一个描述符集合来监控,每次都要拿拷贝过去监听,
73 //因为这个集合每次都会去掉没有就绪的
74 int _max_fd;//集合中最大描述符,方便select接口使用
75
76
77 ;
epoll.hpp
E> 1 #include <iostream>
2 #include <vector>
3 #include <cstdlib>
4 #include <sys/epoll.h>
5 #include "classtcp.hpp"
6
7 class Epoll
8 public:
9 //每当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成
10 //员与epoll的使用方式密切相关
11 //一个是红黑树的根节点用来保存需要监控的事件
12 //一个是双向链表存放着将要通过epoll_wait返回给用户的满足条件的事件
13 Epoll():_epfd(-1)
E> 14 _epfd = epoll_create(1);//大于0即可,linux2.6后被忽略
15 if (_epfd < 0)
E> 16 perror("epoll_create error");
E> 17 exit(-1);
18
19
20
21
22 //描述符是以事件结构体形式放入内核监控
23 //也就是放入红黑树
24 //epoll_ctl就是事件注册函数
E> 25 bool Addfd(TCPSocket &sock)
26 //epoll_ctl(epoll句柄,类型,描述符,以上是关于TCP--IO多路转接模型的主要内容,如果未能解决你的问题,请参考以下文章
五种高阶IO模型以及多路转接技术(selectpoll和epoll)及其代码验证