SOCKET【3】-select+getsockopt客户端检测connect是否成功


前言

  1. 使用select+getsockopt如何知道是否真的成功的连接到远程服务器?
  2. 客户端一旦链接成功服务器 fd 的状态是什么?是确定不变的?还是多种多样的?
  3. getsockopt获取SO_ERROR等于0一定是没有问题吗?
  4. 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 返回的几种情况:

  1. connect 返回0,则直接连接成功了。
  2. connect 返回错误,errno 等于EINPROGRESS,表明socket正在连接,这种情况需要后续使用epoll wait 后者select 函数确认。
  3. connect 返回错误,errno不等于EINPROGRESS,那就是连接失败了

1.2 针对1.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连接的三次握手产生的问题,具体参考

网络编程Socket之TCP之connect详解

四、 select 服务器如何编程能快速知道对端是否已经断开?

使用select重写客户端程序,目的是服务器进程一终止,客户就能马上得到通知。新版程序不是阻塞在套接字事件或者IO调用上,而是阻塞于select调用上,或是等待套接字或是等待标准IO输入。
客户的套接字上的处理条件:

  1. 如果对端TCP发送数据,那么套接字变为可读,并且read返回一个大于0的值
  2. 如果对端TCP发送一个FIN(对端进程终止),那么套接字变为可读,并且read返回0(EOF)
  3. 如果对端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));
        }
	}
}

总结

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值