天天看點

非阻塞connect實作代碼

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


           

繼續閱讀