引言:
客户端connect接口, 代表着三次握手, 默认为阻塞模式, 在握手期间, 客户端程序只能处于等待状态, 不能处理其他的业务,
其次,如果握手失败, 还会等connect计时器时间, 大约是在75s左右。
一、阻塞connect存在的几大问题。
1、阻塞期间不能处理其他业务。
2、如果连接失败, 等待的时间过长。
二、实现非阻塞connect的方法
1、将创建sockfd设置为阻塞模式
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
2、connect连接
- 此时连接会出现error错误【EINPROGRESS】, 这种错误发生在非阻塞socket调用connect中,连接没有立即建立。
- 程序不会处于阻塞状态,可以执行别的代码,并且三次握手仍然处理中。
3、调用select监控文件描述符(别的也行, 不过就一个fd,不值得epoll)
- 虽然说已经执行别的代码, 但三次握手仍然处理, 返回的信息会在socfd文件描述中显示。
- 采用select监控sockfd。 填充可写事件数组
4、分析select监控信息
- 如果返回值<=0, 监控失败或者三次握手失败, 直接退出。
- 采用FD_ISSET()判断sockfd是否处在返回的可写事件数组中。 如果不是,证明不是sockfd就绪, 连接失败。
5、采用getsockopt()读取错误码并清除socket错误信息
函数说明:getsockopt()会将参数s 所指定的socket 状态返回.
- 如果返回值为0,代表连接成功, 后续要把sockfd重新设置为阻塞模式
- 返回值不为代表连接错误,退出
三、代码测试
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include "tcpser.hpp"
const int BUFFER_SIZE = 1023;
//设置fd为非阻塞类型
int setnonblockint(int fd) {
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
int unblock_connect(const char* ip, int port, int time) {
int ret = 0;
/*绑定地址信息*/
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr = inet_addr(ip);
/*创建socket监听套接字*/
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
/*设置为非阻塞的模式*/
int fdopt = setnonblockint(sockfd);
/*连接服务器*/
ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address));
if(ret == 0) {
/*连接成功,一般设置为非阻塞是不会连接成功的, 这里如果是同一台主机会连接成功*/
printf("connect with server immediately\n");
fcntl(sockfd, F_SETFL, fdopt); //重新设置为阻塞
return sockfd;
}
else if(errno != EINPROGRESS) {
/*连接没有立即建立, 然会的错误码*/
printf("unblock connect not support\n");
}
/*正常逻辑*/
fd_set readfds;
fd_set writefds;
struct timeval timeout;
FD_ZERO(&readfds);
FD_SET(sockfd, &writefds);
timeout.tv_sec = time; //秒
timeout.tv_usec = 0; //毫秒
ret = select(sockfd + 1, NULL, &writefds, NULL, &timeout);
if(ret <= 0) {
/*select超时或者出错, 立即返回*/
printf("connection timeout\n");
close(sockfd);
return -1;
}
if(!FD_ISSET(sockfd, &writefds)) {
/*不是sockfd就绪*/
printf("no events on sockfd found\n");
close(sockfd);
return -1;
}
int error = 0;
socklen_t length = sizeof(error);
/*调用getsockopt获取错误并清除sockfd上的错误*/
if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0) {
printf("get socket option failed\n");
close(sockfd);
return -1;
}
if(error != 0) {
printf("connection failed after select with the error: %d\n", error);
close(sockfd);
return -1;
}
/*连接成功*/
printf("connection successfully!\n");
/*重新设置阻塞模式*/
fcntl(sockfd, F_SETFL, fdopt);
return sockfd;
}
int main(int argc, char* argv[]) {
if(argc <= 2) {
printf("IP : Port, Please input!\n");
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int sockfd = unblock_connect(ip, port, 10);
if(sockfd < 0) { return 1; }
Tcpser tp;
tp.SetSockfd(sockfd);
while(1) {
//客户端
/*发送数据*/
std::string str_msg;
printf("client says: ");
std::getline(std::cin, str_msg);
tp.Send(str_msg);
str_msg.clear();
tp.Recv(str_msg);
printf("server respond: %s\n", str_msg.c_str());
sleep(1);
}
close(sockfd);
return 0;
}