目录
1.select、poll、epoll简介
- select:进行大量的描述符事件监控,返回描述符就绪了的事件集合。
- poll:针对大量描述符进行监控,但poll的监控是为每个描述符设置了一个事件结构体。
- epoll的监控是一个异步阻塞监控,监控过程由系统完成,epoll的事件触发方式包含水平触发和边缘触发。
2.select
例1:
#include<stdio.h>
#include<unistd.h>
#include<time.h>
#include<sys/select.h>
#include<sys/socket.h>
int main()
{
fd_set rfds;
FD_ZERO(&rfds);//初始化集合
while (1)
{
struct timeval tv;
tv.tv_usec = 0;
tv.tv_sec = 3;
FD_SET(0,&rfds);//因为select会修改rfds集合,因此每次要重新添加描述符到集合中
int max_fd = 0;
//描述符的监控会做一件事情:在返回前,将没有就绪的描述符从集合中移除
int ret = select(max_fd + 1,&rfds,NULL,NULL,&tv);
if(ret < 0)
{
perror("select error");
usleep(1000);
continue;
}
else if(ret == 0)
{
printf("wait timeout\n");
usleep(1000);
continue;
}
for(int i = 0;i <= max_fd;i++)//因为没有把所有的描述符保存起来,所以从0到当前最大的描述符逐个判断
{
if(FD_ISSET(i,&rfds))//如果i这个描述符在集合中,就表示就绪了事件
{
char buf[1024] = {0};
int res = read(i,buf,1023);
if(res <= 0)
{
perror("recv error");
FD_CLR(i,&rfds);
return -1;
}
printf("%d 描述符就绪了事件,读取数据:%s\n",i,buf);
}
}
}
return 0;
}
select1:select1.c
gcc $^ -o $@
结果如下:
例2:
select.hpp:
#include "tcp_socket.hpp"
#include<time.h>
#include<vector>
#include<sys/select.h>
class Select
{
private:
fd_set _rfds;//可读事件的描述集合的备份,每次监控都是从这个集合复制一份出来进行监控(select会修改集合)
int _max_fd;
public:
Select():_max_fd(-1)
{
//针对成员变量的初始化
FD_ZERO(&_rfds);
}
//将sock中的描述符fd,添加到rfds可读事件的描述符集合中
bool Add(TcpSocket &sock)
{
int fd = sock.GetFd();
FD_SET(fd,&_rfds);//将描述符添加到监控集合中
_max_fd = _max_fd >fd ?_max_fd:fd;//重新设置最大的描述符
return true;
}
//将sock中的描述符fd,从rfds可读事件的描述符集合中移除
bool Del(TcpSocket &sock)
{
int fd = sock.GetFd();
FD_CLR(fd,&_rfds);//从当前的监控集合中移除指定的描述符
//如果当前移除的刚好就是最大的描述符,这时候从最大描述符开始向前判断哪个描述符还在集合中,第一个就是最大的
for(int i = _max_fd;i >= 0;i--)
{
if(FD_ISSET(i,&_rfds))
{
_max_fd = i;
break;
}
}
return 0;
}
//开始监控,返回就绪的描述符的数组
bool Wait(std::vector<TcpSocket> *arry,int timeout = 3000)
{
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
fd_set tmp = _rfds;//使用临时集合进行监控,因为select会修改监控的集合
int ret = select(_max_fd +1,&tmp,NULL,NULL,&tv);
if(ret < 0)
{
perror("select error");
return false;
}
else if(ret ==0)
{
printf("select timeout!\n");
return false;
}
for(int i=0;i <= _max_fd;i++)
{
if(FD_ISSET(i,&tmp))
{
TcpSocket sock;
sock.SetFd(i);
arry->push_back(sock);
}
}
return true;
}
};
tcp_socket.hpp:
#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#define MAX_LISTEN 5
#define CHECK_RES(q) if((q)==false) { return -1;}
class TcpSocket{
private:
int _sockfd;
public:
TcpSocket():_sockfd(-1){}
int GetFd()
{
return _sockfd;
}
void SetFd(int fd)
{
_sockfd = fd;
}
//创建套接字
bool Socket()
{
_sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(_sockfd < 0)
{
perror("socket error");
return false;
}
return true;
}
//绑定地址信息
bool Bind(const std::string &ip,uint16_t port)
{
struct sockaddr_in addr;//先定义一个ipv4的地址结构
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
if(ret<0)
{
perror("bind error");
return false;
}
return true;
}
//向服务器发起连接
bool Connect(const std::string &ip,uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
if(ret < 0)
{
perror("connect error");
return false;
}
return true;
}
//服务器开始监听
bool Listen(int backlog = MAX_LISTEN)
{
int ret = listen(_sockfd,backlog);
if(ret < 0)
{
perror("listen error");
return false;
}
return true;
}
//获取新建连接
bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
{
struct sockaddr_in addr;
socklen_t len = sizeof(struct sockaddr_in);
int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
if(newfd<0)
{
perror("accept error");
return false;
}
sock->_sockfd = newfd;
if(ip != NULL)
{
*ip = inet_ntoa(addr.sin_addr);
}
if(port != NULL)
{
*port = ntohs(addr.sin_port);
}
return true;
}
//接受数据
bool Recv(std::string *body)
{
char tmp[4096] = {0};
int ret = recv(_sockfd,tmp,4096,0);
if(ret < 0)
{
perror("recv error");
return false;
}
else if(ret == 0)
{
std::cout<<"peer shutdown!\n";
return false;
}
body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
return true;
}
//发送数据
bool Send(const std::string &body)
{
int ret;
ret = send(_sockfd,body.c_str(),body.size(),0);
if(ret < 0)
{
perror("send error");
return false;
}
return true;
}
//关闭套接字
bool Close()
{
if(_sockfd!= -1)
{
close(_sockfd);
}
return true;
}
};
tcp_srv.cpp:
//#include "tcp_socket.hpp"
#include "select.hpp"
int main()
{
TcpSocket lst_sock;
//创建套接字
CHECK_RES(lst_sock.Socket());
//绑定地址信息
CHECK_RES(lst_sock.Bind("0.0.0.0",20000));
//开始监听
CHECK_RES(lst_sock.Listen());
Select s;
s.Add(lst_sock);//将监听套接字添加到集合中
while(1)
{
std::vector<TcpSocket> arry;
bool ret = s.Wait(&arry);
if(ret == false)
{
usleep(1000);
continue;
}
for(TcpSocket &a:arry)
{
//array里边就是就绪的描述符,一开始只有一个监听套接字,就绪的肯定就是监听套接字代表有新连接
if(a.GetFd()==lst_sock.GetFd())//如果就绪的套接字描述符与监听套接字描述符一样,就表示有新建连接
{
TcpSocket conn_sock;
std::string cliip;
uint16_t cliport;
bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
if(ret < 0)
{
continue;
}
std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;
s.Add(conn_sock);//将新建的套接字也添加监控
}
else
{
std::string buf;
ret = a.Recv(&buf);
if(ret == false)
{
s.Del(a);//出错了则移除监控
a.Close();
continue;
}
std::cout<<"client say:"<<buf<<std::endl;
std::cout<<"server say:";
fflush(stdout);
std::cin>>buf;
ret = a.Send(buf);
if(ret == false)
{
s.Del(a);
a.Close();
continue;
}
}
}
}
//关闭套接字
lst_sock.Close();
return 0;
}
tcp_cli.cpp:
#include "tcp_socket.hpp"
int main(int argc,char* argv[])
{
if(argc!= 3)
{
std::cout<<"please input server address!\n";
std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
return -1;
}
std::string srv_ip = argv[1];
uint16_t srv_port = std::stoi(argv[2]);
TcpSocket cli_sock;
//创建套接字
CHECK_RES(cli_sock.Socket());
//绑定地址信息(客户端不推荐)
//向服务器发起连接
CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
while(1)
{
//与服务器通信
std::string buf;
std::cout<<"client say:";
fflush(stdout);
std::cin>>buf;
bool ret = cli_sock.Send(buf);
if(ret == false)
{
cli_sock.Close();
return -1;
}
buf.clear();
ret = cli_sock.Recv(&buf);
if(ret == false)
{
cli_sock.Close();
return -1;
}
std::cout<<"server say:"<<buf<<std::endl;
}
//关闭套接字
cli_sock.Close();
return 0;
}
makefile:
all:tcp_cli tcp_srv select1
tcp_cli:tcp_cli.cpp
g++ -std=c++11 $^ -o $@
tcp_srv:tcp_srv.cpp
g++ -std=c++11 $^ -o $@
select1:select1.c
gcc $^ -o $@
测试结果:
服务器端:
客户端1:
客户端2:
服务器处于一直监听的状态,客户端1请求服务器,与服务器连接,可进行多次循环通信,此时客户端2请求建立连接后,也可和服务器进行多次循环通信,注意服务器端绑定的地址以及客户端执行代码输入的地址。
3.poll
poll.c:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<poll.h>
int main()
{
struct pollfd pfds[10];
int poll_count = 0;
pfds[poll_count].fd = 0;//要监控的描述符是标准输入
pfds[poll_count].events = POLLIN;//针对标准输入要监控的是可读事件
poll_count++;
while(1)
{
int ret = poll(pfds,poll_count,3000);
if(ret < 0)
{
perror("poll error");
usleep(1000);
continue;
}
else if(ret == 0)
{
printf("poll timeout!\n");
usleep(1000);
continue;
}
for(int i=0;i < poll_count;i++)
{
if(pfds[i].revents & POLLIN)
{
//pfds[i].fd就绪了可读事件
char buf[1024] = {0};
read(pfds[i].fd,buf,1023);
printf("%d 描述符就绪,读取数据:%s\n",pfds[i].fd,buf);
}
else if(pfds[i].revents & POLLOUT)
{
//就绪的是可写事件
}
}
}
return 0;
}
makefile:
poll:poll.c
gcc $^ -o $@
结果:
4.epoll
tcp_socket.hpp:
#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<fcntl.h>
#define MAX_LISTEN 5
#define CHECK_RES(q) if((q)==false) { return -1;}
class TcpSocket{
private:
int _sockfd;
public:
TcpSocket():_sockfd(-1){}
int GetFd()
{
return _sockfd;
}
void SetFd(int fd)
{
_sockfd = fd;
}
void SetNonBlock()
{
int flag = fcntl(_sockfd,F_GETFL,0);
fcntl(_sockfd,F_SETFL,flag | O_NONBLOCK);//在原有属性基础上新增非阻塞属性
}
//创建套接字
bool Socket()
{
_sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(_sockfd < 0)
{
perror("socket error");
return false;
}
return true;
}
//绑定地址信息
bool Bind(const std::string &ip,uint16_t port)
{
struct sockaddr_in addr;//先定义一个ipv4的地址结构
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
if(ret<0)
{
perror("bind error");
return false;
}
return true;
}
//向服务器发起连接
bool Connect(const std::string &ip,uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
if(ret < 0)
{
perror("connect error");
return false;
}
return true;
}
//服务器开始监听
bool Listen(int backlog = MAX_LISTEN)
{
int ret = listen(_sockfd,backlog);
if(ret < 0)
{
perror("listen error");
return false;
}
return true;
}
//获取新建连接
bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
{
struct sockaddr_in addr;
socklen_t len = sizeof(struct sockaddr_in);
int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
if(newfd<0)
{
perror("accept error");
return false;
}
sock->_sockfd = newfd;
if(ip != NULL)
{
*ip = inet_ntoa(addr.sin_addr);
}
if(port != NULL)
{
*port = ntohs(addr.sin_port);
}
return true;
}
//接收数据
bool Recv(std::string *body)
{
char tmp[4096] = {0};
int ret = recv(_sockfd,tmp,4096,0);
if(ret < 0)
{
perror("recv error");
return false;
}
else if(ret == 0)
{
std::cout<<"peer shutdown!\n";
return false;
}
body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
return true;
}
//接受数据
/*
bool Recv(std::string *body)
{
while(1)
{
char tmp[6] = {0};
int ret = recv(_sockfd,tmp,5,0);
if(ret < 0)
{
if(errno == EAGAIN)//EAGAIN这个错误表示当前缓冲区没有数据了,资源还没有准备好
{
return true;
}
perror("recv error");
return false;
}
else if(ret == 0)
{
std::cout<<"peer shutdown!\n";
return false;
}
*body += tmp;
}
return true;
}
*/
//发送数据
bool Send(const std::string &body)
{
int ret;
ret = send(_sockfd,body.c_str(),body.size(),0);
if(ret < 0)
{
perror("send error");
return false;
}
return true;
}
//关闭套接字
bool Close()
{
if(_sockfd!= -1)
{
close(_sockfd);
}
return true;
}
};
epoll.hpp:
#include "tcp_socket.hpp"
#include<time.h>
#include<cstdlib>
#include<vector>
#include<sys/epoll.h>
#define EPOLL_MAX 100
class Epoll
{
private:
int _epfd;//epoll的操作句柄
public:
Epoll():_epfd(-1)
{
//针对成员变量的初始化
_epfd = epoll_create(1);
if(_epfd < 0)
{
perror("epoll error");
exit(0);
}
}
bool Add(TcpSocket &sock)
{
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sock.GetFd();
int ret = epoll_ctl(_epfd,EPOLL_CTL_ADD,sock.GetFd(),&ev);
if(ret < 0)
{
perror("epoll_ctl error");
return false;
}
return true;
}
bool Del(TcpSocket &sock)
{
int ret = epoll_ctl(_epfd,EPOLL_CTL_DEL,sock.GetFd(),NULL);
if(ret < 0)
{
perror("epoll_ctl error");
return false;
}
return true;
}
bool Wait(std::vector<TcpSocket> *arry,int timeout = 3000)
{
struct epoll_event evs[EPOLL_MAX];
int ret = epoll_wait(_epfd,evs,EPOLL_MAX,timeout);
if(ret < 0)
{
perror("epoll_wait error");
return false;
}
else if(ret == 0)
{
printf("epoll timeout!\n");
return false;
}
for(int i = 0;i < ret;i++)
{
if(evs[i].events & EPOLLIN)//目前只管可读事件
{
TcpSocket sock;
sock.SetFd(evs[i].data.fd);
arry->push_back(sock);
}
}
return true;
}
};
tcp_srv.cpp:
/#include "tcp_socket.hpp"
//#include "select.hpp"
#include "epoll.hpp"
int main()
{
TcpSocket lst_sock;
//创建套接字
CHECK_RES(lst_sock.Socket());
//lst_sock.SetNonBlock();
//绑定地址信息
CHECK_RES(lst_sock.Bind("0.0.0.0",20000));
//开始监听
CHECK_RES(lst_sock.Listen());
//Select s;
Epoll s;
s.Add(lst_sock);//将监听套接字添加到集合中
while(1)
{
std::vector<TcpSocket> arry;
bool ret = s.Wait(&arry);
if(ret == false)
{
usleep(1000);
continue;
}
for(TcpSocket &a:arry)
{
//array里边就是就绪的描述符,一开始只有一个监听套接字,就绪的肯定就是监听套接字代表有新连接
if(a.GetFd()==lst_sock.GetFd())//如果就绪的套接字描述符与监听套接字描述符一样,就表示有新建连接
{
TcpSocket conn_sock;
std::string cliip;
uint16_t cliport;
bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
if(ret < 0)
{
continue;
}
std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;
//conn_sock.SetNonBlock();
s.Add(conn_sock);//将新建的套接字也添加监控
}
else
{
std::string buf;
ret = a.Recv(&buf);
if(ret == false)
{
s.Del(a);//出错了则移除监控
a.Close();
continue;
}
std::cout<<"client say:"<<buf<<std::endl;
std::cout<<"server say:";
fflush(stdout);
std::cin>>buf;
ret = a.Send(buf);
if(ret == false)
{
s.Del(a);
a.Close();
continue;
}
}
}
}
//关闭套接字
lst_sock.Close();
return 0;
}
tcp_cli.cpp:
#include "tcp_socket.hpp"
int main(int argc,char* argv[])
{
if(argc!= 3)
{
std::cout<<"please input server address!\n";
std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
return -1;
}
std::string srv_ip = argv[1];
uint16_t srv_port = std::stoi(argv[2]);
TcpSocket cli_sock;
//创建套接字
CHECK_RES(cli_sock.Socket());
//绑定地址信息(客户端不推荐)
//向服务器发起连接
CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
while(1)
{
//与服务器通信
std::string buf;
std::cout<<"client say:";
fflush(stdout);
std::cin>>buf;
bool ret = cli_sock.Send(buf);
if(ret == false)
{
cli_sock.Close();
return -1;
}
buf.clear();
ret = cli_sock.Recv(&buf);
if(ret == false)
{
cli_sock.Close();
return -1;
}
std::cout<<"server say:"<<buf<<std::endl;
}
//关闭套接字
cli_sock.Close();
return 0;
}
makefile:
all:tcp_cli tcp_srv
tcp_cli:tcp_cli.cpp
g++ -std=c++11 $^ -o $@
tcp_srv:tcp_srv.cpp
g++ -std=c++11 $^ -o $@
结果:
客户端1:
客户端2:
修改tcp_socket.hpp如下:
此时客户端发送helloalaotie,客户端只收到了hello
以上代码只改动两处:①修改epoll.hpp如下:
② 修改tcp_socket.hpp如下:
则结果为:
加了EPOLLET的作用:只有在新数据到来时才会触发事件。
客户端发送nihaoalaotie,服务器只收到nihao,如果不用EPOLLET,则服务器回复客户端后会直接出现缓冲区的剩余数据(eg alaot),如果用了EPOLLET,则只有在客户端发送数据时,才会在缓冲区依次拿数据。---两者都修改了tcp_socket.hpp中的recv函数(如上面所示)。
修改tcp_socket.hpp如下: (增加SetNonBlock方法以及Recv方法、增加头文件<fcntl.h>)
#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<fcntl.h>
#define MAX_LISTEN 5
#define CHECK_RES(q) if((q)==false) { return -1;}
class TcpSocket{
private:
int _sockfd;
public:
TcpSocket():_sockfd(-1){}
int GetFd()
{
return _sockfd;
}
void SetFd(int fd)
{
_sockfd = fd;
}
void SetNonBlock()
{
int flag = fcntl(_sockfd,F_GETFL,0);
fcntl(_sockfd,F_SETFL,flag | O_NONBLOCK);//在原有属性基础上新增非阻塞属性
}
//创建套接字
bool Socket()
{
_sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(_sockfd < 0)
{
perror("socket error");
return false;
}
return true;
}
//绑定地址信息
bool Bind(const std::string &ip,uint16_t port)
{
struct sockaddr_in addr;//先定义一个ipv4的地址结构
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd,(struct sockaddr*)&addr,len);
if(ret<0)
{
perror("bind error");
return false;
}
return true;
}
//向服务器发起连接
bool Connect(const std::string &ip,uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(_sockfd,(struct sockaddr*)&addr,len);
if(ret < 0)
{
perror("connect error");
return false;
}
return true;
}
//服务器开始监听
bool Listen(int backlog = MAX_LISTEN)
{
int ret = listen(_sockfd,backlog);
if(ret < 0)
{
perror("listen error");
return false;
}
return true;
}
//获取新建连接
bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL)
{
struct sockaddr_in addr;
socklen_t len = sizeof(struct sockaddr_in);
int newfd = accept(_sockfd,(struct sockaddr*)&addr,&len);
if(newfd<0)
{
perror("accept error");
return false;
}
sock->_sockfd = newfd;
if(ip != NULL)
{
*ip = inet_ntoa(addr.sin_addr);
}
if(port != NULL)
{
*port = ntohs(addr.sin_port);
}
return true;
}
//接收数据
bool RecvCli(std::string *body)
{
char tmp[4096] = {0};
int ret = recv(_sockfd,tmp,4096,0);
if(ret < 0)
{
perror("recv error");
return false;
}
else if(ret == 0)
{
std::cout<<"peer shutdown!\n";
return false;
}
body->assign(tmp,ret);//从tmp中截取ret长度大小的数据
return true;
}
//接受数据
bool Recv(std::string *body)
{
while(1)
{
char tmp[6] = {0};
int ret = recv(_sockfd,tmp,5,0);
if(ret < 0)
{
if(errno == EAGAIN)//EAGAIN这个错误表示当前缓冲区没有数据了,资源还没有准备好
{
return true;
}
perror("recv error");
return false;
}
else if(ret == 0)
{
std::cout<<"peer shutdown!\n";
return false;
}
*body += tmp;
}
return true;
}
//发送数据
bool Send(const std::string &body)
{
int ret;
ret = send(_sockfd,body.c_str(),body.size(),0);
if(ret < 0)
{
perror("send error");
return false;
}
return true;
}
//关闭套接字
bool Close()
{
if(_sockfd!= -1)
{
close(_sockfd);
}
return true;
}
};
修改tcp_srv.cpp:(创建套接字后设置非阻塞---两处)
//#include "tcp_socket.hpp"
//#include "select.hpp"
#include "epoll.hpp"
int main()
{
TcpSocket lst_sock;
//创建套接字
CHECK_RES(lst_sock.Socket());
lst_sock.SetNonBlock();
//绑定地址信息
CHECK_RES(lst_sock.Bind("0.0.0.0",20000));
//开始监听
CHECK_RES(lst_sock.Listen());
//Select s;
Epoll s;
s.Add(lst_sock);//将监听套接字添加到集合中
while(1)
{
std::vector<TcpSocket> arry;
bool ret = s.Wait(&arry);
if(ret == false)
{
usleep(1000);
continue;
}
for(TcpSocket &a:arry)
{
//array里边就是就绪的描述符,一开始只有一个监听套接字,就绪的肯定就是监听套接字代表有新连接
if(a.GetFd()==lst_sock.GetFd())//如果就绪的套接字描述符与监听套接字描述符一样,就表示有新建连接
{
TcpSocket conn_sock;
std::string cliip;
uint16_t cliport;
bool ret = lst_sock.Accept(&conn_sock,&cliip,&cliport);
if(ret < 0)
{
continue;
}
std::cout<<"new connect:"<<cliip<<":"<<cliport<<std::endl;
conn_sock.SetNonBlock();
s.Add(conn_sock);//将新建的套接字也添加监控
}
else
{
std::string buf;
ret = a.Recv(&buf);
if(ret == false)
{
s.Del(a);//出错了则移除监控
a.Close();
continue;
}
std::cout<<"client say:"<<buf<<std::endl;
std::cout<<"server say:";
fflush(stdout);
std::cin>>buf;
ret = a.Send(buf);
if(ret == false)
{
s.Del(a);
a.Close();
continue;
}
}
}
}
//关闭套接字
lst_sock.Close();
return 0;
}
修改客户端tcp_cli.cpp---仍用原来的Recv方法,将其重命名为RecvCli:
#include "tcp_socket.hpp"
int main(int argc,char* argv[])
{
if(argc!= 3)
{
std::cout<<"please input server address!\n";
std::cout<<"USsage:./tcp_cli 192.168.164.128 20000\n";
return -1;
}
std::string srv_ip = argv[1];
uint16_t srv_port = std::stoi(argv[2]);
TcpSocket cli_sock;
//创建套接字
CHECK_RES(cli_sock.Socket());
//绑定地址信息(客户端不推荐)
//向服务器发起连接
CHECK_RES(cli_sock.Connect(srv_ip,srv_port));
cli_sock.SetNonBlock();
while(1)
{
//与服务器通信
std::string buf;
std::cout<<"client say:";
fflush(stdout);
std::cin>>buf;
bool ret = cli_sock.Send(buf);
if(ret == false)
{
cli_sock.Close();
return -1;
}
buf.clear();
ret = cli_sock.RecvCli(&buf);
if(ret == false)
{
cli_sock.Close();
return -1;
}
std::cout<<"server say:"<<buf<<std::endl;
}
//关闭套接字
cli_sock.Close();
return 0;
}
测试结果:
客户端1:
客户端2:
问题:如果客户端在连接后设置非阻塞,用了while循环以及新的Recv方法,则会出现:客户端输入数据会直接显示server say client say,在服务器端输入数据在客户端是收不到的,原因有待挖掘。结果如下图: