非阻塞connect实现代码
使用非阻塞IO连接服务端,可将sleep替换要执行的任务,客户主循环利用epoll监听事标准输入和socket上的事件,利用管道将标准输入写入socket
有个问题请教大家,为啥我用splice将通信文件描述符内容拷贝到STDOUT_FILENO会失败,报错无效的输出
```cpp
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/select.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <memory>
#include <sys/time.h>
#include <sys/epoll.h>
using namespace std;
//文件描述符的管理类
class FDHolder{
public:
FDHolder(int fd):_fd(new int(fd)){
}
~FDHolder(){
close(*_fd);
}
int get(){
return *_fd;
}
private:
shared_ptr<int> _fd;
};
int setnonblocking(int fd){
int old_flag = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,old_flag|O_NONBLOCK);
return old_flag;
}
//非阻塞connect,成功时返回文件描述符,失败时返回-1
int unblock_connect(int fd,const char* ip,int port){
//设置服务端的信息
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(port);
inet_pton(AF_INET,ip,&(ser.sin_addr.s_addr));
//设置fd为非阻塞状态
int old_flag = setnonblocking(fd);
//调用connect查看链接是否成功,如果失败且错误不等于EINPROGRESS,则链接失败
int ret = connect(fd,reinterpret_cast<struct sockaddr*>(&ser),sizeof(ser));
if(ret == 0){
cout << "connect success" << endl;
return fd;
}else if(errno != EINPROGRESS)
return -1;
//做自己的事
sleep(5);
//设置监听写文件描述符集
fd_set wrfds;
FD_ZERO(&wrfds);
FD_SET(fd,&wrfds);
//设置超时时间
struct timeval tv;
tv.tv_sec = 2;
tv.tv_usec = 0;
//调用select查看是否已经连接成功
ret = select(fd+1,nullptr,&wrfds,nullptr,&tv);
if(ret < 0)
return -1;
else if(ret == 0){
cerr << "连接超时" << endl;
return -1;
}
//如果没有事件,则失败
if(FD_ISSET(fd,&wrfds) == false){
cerr << "没有事件发生" << endl;
return -1;
}
//获取错误信息
int error = 0;
socklen_t error_len = sizeof(error);
if(getsockopt(fd,SOL_SOCKET,SO_ERROR,&error,&error_len) < 0){
cerr << "getsockopt faield" << endl;
return -1;
}
//如果error不为0,则链接失败
if(error != 0){
cerr << "Error: " << strerror(error) << endl;
return -1;
}
//连接成功则将文件描述符设回原状态并返回
cout << "connect success" << endl;
fcntl(fd,F_SETFL,old_flag);
return fd;
}
int main(int argc,char* argv[])
{
if(argc <= 2){
cerr << "参数太少" << endl;
return -1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
FDHolder hfd(socket(AF_INET,SOCK_STREAM,0));
if(hfd.get() < 0){
perror("socket failed");
return -1;
}
//非阻塞链接
int ret = unblock_connect(hfd.get(),ip,port);
if(ret < 0){
cerr << "connect failed" << endl;
return -1;
}
int psock[2];
ret = pipe(psock);
if(ret < 0){
perror("pipe failed");
return -1;
}
//利用epoll监听标准输入和客户端发来的
int epfd = epoll_create(2);
if(epfd < 0){
perror("epoll_create failed");
return -1;
}
//将标准输入和通信描述符上树
struct epoll_event ev;
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = STDIN_FILENO;
epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&ev);
ev.data.fd = hfd.get();
epoll_ctl(epfd,EPOLL_CTL_ADD,hfd.get(),&ev);
//成功则收发数据
char buf[1024];
struct epoll_event events[2];
while(1){
int nready = epoll_wait(epfd,events,2,-1);
if(nready < 0){
perror("epoll_wait failed");
return -1;
}
for(int i = 0;i < nready;i++){
if(events[i].data.fd == STDIN_FILENO){
//将管道接在标准输入和文件描述符两端
splice(STDIN_FILENO,nullptr,psock[1],nullptr,35536,SPLICE_F_MORE|SPLICE_F_MOVE);
splice(psock[0],nullptr,hfd.get(),nullptr,35536,SPLICE_F_MOVE|SPLICE_F_MORE);
}else{
memset(buf,0x00,sizeof(buf));
int n = read(hfd.get(),buf,sizeof(buf));
if(n < 0){
perror("read failed ");
return -1;
}else if(n == 0){
cout << "server close" << endl;
return 0;
}
cout << "server message: " << buf << endl;
}
}
}
return 0;
}