- 前言
- 一、使用`select+getsockopt`如何知道是否真的成功的连接到远程服务器?
- 1.1 connect 返回的几种情况:
- 1.2 针对1.1中的第二种情况的处理
- 三、 `getsockopt`获取`SO_ERROR`等于0一定是没有问题吗?
- 四、 select 服务器如何编程能快速知道对端是否已经断开?
- 总结
前言
- 使用select+getsockopt如何知道是否真的成功的连接到远程服务器?
- 客户端一旦链接成功服务器 fd 的状态是什么?是确定不变的?还是多种多样的?
- getsockopt获取SO_ERROR等于0一定是没有问题吗?
- select 服务器如何编程能快速知道对端是否已经断开?
一、使用select+getsockopt如何知道是否真的成功的连接到远程服务器?
最近在看前辈的网络客户端编程,遇到以下代码:
fd = socket(AF_INET, SOCK_STREAM, 0);
ret = connect(fd, ( struct sockaddr* )&addr, sizeof(addr));
if (ret == -1 && errno != EINPROGRESS) // connect失败
{
ret = CONNECT_ERROR;
}else{
while(1){
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_SET(fd, &read_fds);
FD_SET(fd, &write_fds);
switch (flag)
{
case CHECKSOCKET_CONN:
ret = select(fd + 1, &read_fds, &write_fds, NULL, &tv);
if (ret <= 0)
{ // error
ret = CONNECT_ERROR;
}
break;
case CHECKSOCKET_READ:
ret = select(fd + 1, &read_fds, NULL, NULL, &tv);
if (ret < 0)
{ // error
ret = RECV_ERROR;
}
break;
case CHECKSOCKET_WRITE:
ret = select(fd + 1, NULL, &write_fds, NULL, &tv);
if (ret < 0)
{ // error
ret = SEND_ERROR;
}
break;
default: // never go here
ret = RET_UNKNOWN;
break;
}
if (ret == 0)
{ // timeout
ret = TIMEOUT_ERROR;
}
if (ret < 0) // select error or timeout ,exit
{
break;
}
ret = RET_OK; // set ret OK
switch (flag)
{
case CHECKSOCKET_CONN:
if (!FD_ISSET(fd, &read_fds) && !FD_ISSET(fd, &write_fds))
{
ret = CONNECT_ERROR;
}
else if (FD_ISSET(fd, &read_fds) && FD_ISSET(fd, &write_fds))
{
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &sock_error, &tmp);
if (ret == 0 && sock_error == 0)
{ // connect succ
}
else if (ret == 0 && sock_error != 0)
{
ret = RET_SOCKET_CONNECT_ERROR;
}
else
{
ret = RET_SOCKET_CONNECT_ERROR;
}
}
else if (!FD_ISSET(fd, &read_fds) && FD_ISSET(fd, &write_fds))
{ // connect succ
}
else if (FD_ISSET(fd, &read_fds) && !FD_ISSET(fd, &write_fds))
{
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &sock_error, &tmp);
if (ret == 0)
{
}
ret = RET_SOCKET_CONNECT_ERROR;
}
else // never go here
{
ret = RET_UNKNOWN;
}
break;
case CHECKSOCKET_READ:
if (!FD_ISSET(fd, &read_fds))
{
ret = RET_SOCKET_RECV_ERROR;
}
break;
case CHECKSOCKET_WRITE:
if (!FD_ISSET(fd, &write_fds))
{
ret = SEND_ERROR;
}
break;
default: // never go here
ret = RET_UNKNOWN;
break;
}
}
}
在网络编程中,经常使用的是异步的socket也就是非阻塞socket,但是在在异步的情况下如何判断connect是否连接成功成为了重中之重。
1.1 connect 返回的几种情况:- connect 返回0,则直接连接成功了。
- connect 返回错误,errno 等于EINPROGRESS,表明socket正在连接,这种情况需要后续使用epoll wait 后者select 函数确认。
- connect 返回错误,errno不等于EINPROGRESS,那就是连接失败了
- select 模型:将连接的文件描述符加入到读写错误的三个事件中,调用select函数,
如果socket连接失败,则socket同时变的可读可写。
如果socket连接成功,则第一socket变得可写,第二就是socket同时变的可读(连接成功,对端马上有数据发过来)可写。
那么如何判断connect是否真正的成功了呢?
第15行,程序在connect之后,紧接着调用select,如果返回错误,或者因为超时返回0,这两种情况需要后续通过查看socket的可写可读的情况进行分析。
所以在可读可写的情况下,调用getsockopt函数选定SO_ERROR,通过该选项我们可以判断出socket是否连接成功,注意不是获取该函数的返回值而是函数的参数返回值,函数调用中的第四个参数会返回socket连接错误的错误码,如果成功错误码为0,否则不为0。
TCP socket连接失败后getsockopt获取SO_ERROR等于0误以为连接成功;
在Linux下使用非阻塞TCP连接的方式连接一个错误的地址,第一次可以connect返回失败,getsockopt能够得到正确的错误码。但使用此socket进行第二次连接的时候,connect也返回失败,getsockopt返回获取错误(SO_ERROR)为0,从而认为连接成功。
解决方法:tcp 在调用connect失败后需要重新创建socket。
这是因为TCP连接的三次握手产生的问题,具体参考
网络编程Socket之TCP之connect详解
四、 select 服务器如何编程能快速知道对端是否已经断开?使用select重写客户端程序,目的是服务器进程一终止,客户就能马上得到通知。新版程序不是阻塞在套接字事件或者IO调用上,而是阻塞于select调用上,或是等待套接字或是等待标准IO输入。
客户的套接字上的处理条件:
- 如果对端TCP发送数据,那么套接字变为可读,并且read返回一个大于0的值
- 如果对端TCP发送一个FIN(对端进程终止),那么套接字变为可读,并且read返回0(EOF)
- 如果对端TCP发送一个RST(对端主机崩溃并重启),那么套接字变为可读,并且返回 -1
#include "unp.h"
void str_cli(FILE *fp, int sockfd)
{
int maxfdp1;
fd_set rset;
char sendline[MAXLINE],recvline[MAXLINE];
FD_ZERO(&rset);
for(;;){
FD_SET(fileno(fp),&rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
select(maxfdp1,&rset,NULL,NULL,NULL);
if (FD_ISSET(sockfd, &rset)){
if(readline(sockfd, recvline,MAXLINE) == 0)
err_quit("str_cli:server terminated prematurely");
Fputs(recvline, stdout);
}
if (FD_ISSET(fileno(fp), &rset)) {
if (Fgets(sendline, MAXLINE, fp) == NULL)
return;
Writen(sockfd, sendline, strlen(sendline));
}
}
}
总结



