栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

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

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

文章目录
  • 前言
  • 一、使用`select+getsockopt`如何知道是否真的成功的连接到远程服务器?
  • 1.1 connect 返回的几种情况:
  • 1.2 针对1.1中的第二种情况的处理
  • 三、 `getsockopt`获取`SO_ERROR`等于0一定是没有问题吗?
  • 四、 select 服务器如何编程能快速知道对端是否已经断开?
  • 总结


前言
  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。

这是因为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)){ 
			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));
        }
	}
}

总结
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/946094.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号