我们知道,tcp客户端要与服务端通信,必须先建立连接,即调用connect函数完成三次握手,而默认情况下connect是阻塞方式的,也就是说调用connect函数会发生阻塞,超时时间可能在75s至几分钟之间。当然同一主机除外,同一主机上调用connect通常会立即成功。
为避免长时间的connect阻塞,可以使用如下非阻塞connect方式来处理:
一、 创建socket,返回套接口描述符
二、 调用fcntl把套接口描述符设置成非阻塞
三、 调用connect开始建立连接
四、 判断连接是否成功建立
A: 如果connect返回0,表示连接成功(服务器和客户端在同一台机器上时就有可能发生这种情况)
B: 调用select来等待连接建立成功完成
(Berkeley的实现(和Posix.1g)有两条与select和非阻塞IO相关的规则:
1.当连接建立成功时,套接口描述符变成可写;
2.当连接出错时,套接口描述符变成既可读又可写;
注意:当一个套接口出错时,它会被select调用标记为既可读又可写;)
五、 继续判断select返回值
如果select返回0,则表示建立连接超时; 我们返回超时错误给用户,同时关闭连接,以防止三路握手操作继续进行下去
如果select返回大于0的值,则需要检查套接口描述符是否可读或可写;如果套接口描述符可读或可写,则我们可以通过调用getsockopt来得到套接口上待处理的错误(SO_ERROR),如果连接建立成功,这个错误值将是0,如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等).
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define DSTADDR "192.168.1.44"
#define DSTPORT 30040
int main(int argc, char* argv[])
{
int ret = 0;
int sockfd = 0;
char buffer[1024] = {0};
struct sockaddr_in servAddr;
socklen_t addr_size = 0;
char destAddr[16] = {0};
int destPort = 0;
int recv_len = 0;
char who[64] = {0};
char sndbuffer[256] = {0};
struct timeval tvBegin;
struct timeval tvEnd;
if(argc < 3)
{
strcpy(destAddr, DSTADDR);
destPort = DSTPORT;
}
else
{
strcpy(destAddr, argv[1]);
destPort = atoi(argv[2]);
}
/*---- Create socket ----*/
if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
exit(-1);
}
/*---- Set non-block ----*/
int options = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, options | O_NONBLOCK);
/*---- Configure settings of connection ----*/
bzero(&servAddr, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(destPort);
servAddr.sin_addr.s_addr = inet_addr(destAddr);
/* Set padding field to 0 */
//memset(servAddr.sin_zero, 0, sizeof(servAddr.sin_zero));
/*---- Connect ----*/
ret = connect(sockfd, (struct sockaddr *)&servAddr, sizeof(servAddr));
if(ret == 0)
{
printf("connected.\n");
}
else if(ret < 0)
{
//perror("connect");
if(errno == EINPROGRESS)
{
printf("connecting...%s:%d\n", destAddr,destPort);
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(sockfd, &writefds);
struct timeval timeout;
timeout.tv_sec = 6;
timeout.tv_usec = 0;
ret = select(sockfd + 1, NULL, &writefds, NULL, &timeout );
if(ret < 0)
{
perror("select");
close(sockfd);
exit(-1);
}
if (ret == 0)
{
printf( "connection timeout\n" );
close(sockfd);
exit(-1);
}
else
{
if(!FD_ISSET(sockfd, &writefds))
{
printf("err, no events found!\n");
close(sockfd);
exit(-1);
}
else
{
int err = 0;
socklen_t elen = sizeof(err);
ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char *)&err, &elen);
if(ret < 0)
{
perror("getsockopt");
close(sockfd);
exit(-1);
}
if(err != 0)
{
printf("connect failed with the error: (%d)%s\n", err, strerror(err));
close(sockfd);
exit(-1);
}
else
{
printf("connected.\n");
}
}
}
}
}
/*---- Send ----*/
strcpy(sndbuffer,"Hello!\r\n");
if((ret = send(sockfd, sndbuffer, strlen(sndbuffer), 0)) > 0)
{
printf(" >>: %s", sndbuffer);
}
else
{
perror("send");
close(sockfd);
exit(-1);
}
gettimeofday(&tvBegin, NULL);
while(1)
{
/*---- Recv ----*/
recv_len = recv(sockfd, buffer, 1024, 0);
if(recv_len < 0)
{
if((errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN))
{
//printf("again..\n");
usleep(100);
//continue;
}
else
{
printf("recv err1!\n");
break;
}
}
else if(recv_len == 0)
{
printf("recv err2!\n");
break;
}
else
{
// print recv msg
buffer[recv_len] = '\0';
printf(" <<: %s\n",buffer);
char *p = NULL;
if(p = strstr(buffer, "Login OK."))
{
strncpy(who, p+9);
printf("== login success[%s] ==\n\n", who);
}
else if(strstr(buffer, "heartbeat"))
{
//printf("heartbeat.\n");
// do sth response heartbeat and other.
memset(sndbuffer, 0, sizeof(sndbuffer));
sprintf(sndbuffer, "%s response heartbeat\r\n", who);
if((ret = send(sockfd,sndbuffer,strlen(sndbuffer),0)) > 0)
{
printf(" >>: %s", sndbuffer);
}
usleep(100);
memset(sndbuffer, 0, sizeof(sndbuffer));
sprintf(sndbuffer, "%s request other...\r\n", who);
if((ret = send(sockfd,sndbuffer,strlen(sndbuffer),0)) > 0)
{
printf(" >>: %s", sndbuffer);
}
}
}
//
gettimeofday(&tvEnd, NULL);
unsigned int subTime = tvEnd.tv_sec-tvBegin.tv_sec;
//printf(".............%d....\n", subTime);
if(subTime > 3)
{
gettimeofday(&tvBegin, NULL);
memset(sndbuffer, 0, sizeof(sndbuffer));
sprintf(sndbuffer, "I am %s,still alive.\r\n", who);
if((ret = send(sockfd,sndbuffer,strlen(sndbuffer),0)) > 0)
{
printf("->>: %s", sndbuffer);
}
}
usleep(1000);
}
return 0;
}