非阻塞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;
}