TCP客户端--IO多路转接模型
Posted zhaocx111222333
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP客户端--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 //多路复用接口
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) {以上是关于TCP客户端--IO多路转接模型的主要内容,如果未能解决你的问题,请参考以下文章
基于TCP协议 I/O多路转接(select) 的高性能回显服务器客户端模型
五种高阶IO模型以及多路转接技术(selectpoll和epoll)及其代码验证
五种高阶IO模型以及多路转接技术(selectpoll和epoll)及其代码验证