文章目录
前言
- 使用
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
,那就是连接失败了
1.2 针对1.1中的第二种情况的处理
- select 模型:将连接的文件描述符加入到
读写错误
的三个事件中,调用select
函数,
如果socket
连接失败,则socket
同时变的可读可写。
如果socket
连接成功,则第一socket
变得可写,第二就是socket
同时变的可读(连接成功,对端马上有数据发过来)可写。
那么如何判断connect
是否真正的成功了呢?
第15行,程序在connect
之后,紧接着调用select
,如果返回错误,或者因为超时返回0,这两种情况需要后续通过查看socket
的可写可读的情况进行分析。
所以在可读可写的情况下,调用getsockopt
函数选定SO_ERROR
,通过该选项我们可以判断出socket
是否连接成功,注意不是获取该函数的返回值而是函数的参数返回值,函数调用中的第四个参数会返回socket
连接错误的错误码
,如果成功错误码为0,否则不为0。
三、 getsockopt
获取SO_ERROR
等于0一定是没有问题吗?
TCP socket连接失败后getsockopt获取SO_ERROR等于0误以为连接成功;
在Linux下使用非阻塞TCP连接的方式连接一个错误的地址,第一次可以connect
返回失败,getsockopt
能够得到正确的错误码。但使用此socket
进行第二次连接的时候,connect
也返回失败,getsockopt
返回获取错误(SO_ERROR)为0,从而认为连接成功。
解决方法:tcp 在调用connect失败后需要重新创建socket。
如果fd上出现了错误,那么第一次调用getsockopt会通过status返回错误原因。如果此时并没有调用close(fd),按理说这个错误在fd上依然存在,但是如果再次调用上面的getsockopt,则会告知用户此fd上没有任何错误。。。这种情况经常会发生在函数之间传递fd时,一个函数A里面做了getsockopt判断,之后将fd传至别的函数B,函数B不知道fd的状态,再次调用getsockopt,会误认为fd上没有错误了。
这是因为TCP连接的三次握手产生的问题,具体参考
四、 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)){ /*socket is readable*/
if(readline(sockfd, recvline,MAXLINE) == 0)
err_quit("str_cli:server terminated prematurely");
Fputs(recvline, stdout);
}
if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if (Fgets(sendline, MAXLINE, fp) == NULL)
return; /* all done */
Writen(sockfd, sendline, strlen(sendline));
}
}
}