• Linux下Socket连接超时的一种实现方法(转载)


    揭开Socket编程的面纱 - 博客 - 伯乐在线 - Google Chrome (2013/9/22 19:28:24)

    揭开Socket编程的面纱

    原文出处: 源码工作室的博客

    对TCP/IP、UDP、Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。那么我想问:
    1.         什么是TCP/IP、UDP?
    2.         Socket在哪里呢?
    3.         Socket是什么呢?
    4.         你会使用它们吗?

    什么是TCP/IP、UDP?

    TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

    UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。

    这里有一张图,表明了这些协议的关系。
    socket1 图1

    TCP/IP协议族包括运输层、网络层、链路层。现在你知道TCP/IP与UDP的关系了吧。

    Socket在哪里呢?

    在图1中,我们没有看到Socket的影子,那么它到底在哪里呢?还是用图来说话,一目了然。

    socket2
    图2

    原来Socket在这里。

    Socket是什么呢?
    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
    你会使用它们吗?
    前人已经给我们做了好多的事了,网络间的通信也就简单了许多,但毕竟还是有挺多工作要做的。以前听到Socket编程,觉得它是比较高深的编程知识,但是只要弄清Socket编程的工作原理,神秘的面纱也就揭开了。
    一个生活中的场景。你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。    生活中的场景就解释了这工作原理,也许TCP/IP协议族就是诞生于生活中,这也不一定。

    socket3

    图3

    先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

    在这里我就举个简单的例子,我们走的是TCP协议这条路(见图2)。例子用MFC编写,运行的界面如下:

    socket4
    图4

    socket5

    图5

    在客户端输入服务器端的IP地址和发送的数据,然后按发送按钮,服务器端接收到数据,然后回应客户端。客户端读取回应的数据,显示在界面上。
    下面是接收数据和发送数据的函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    int    Receive(SOCKET fd,char *szText,int len)
     
    {
           int cnt;
           int rc;
           cnt=len;
     
           while(cnt>0)
           {
                  rc=recv(fd,szText,cnt,0);
                  if(rc==SOCKET_ERROR)
                  {
                         return -1;
                 }
     
                 if(rc==0)
     
                         return len-cnt;
     
                  szText+=rc;
     
                  cnt-=rc;
     
           }
     
           return len;
     
    }
     
    int Send(SOCKET fd,char *szText,int len)
    {
     
           int cnt;
     
           int rc;
     
           cnt=len;
     
           while(cnt>0)
     
           {
     
                  rc=send(fd,szText,cnt,0);
     
                  if(rc==SOCKET_ERROR)
     
                  {
     
                         return -1;
     
                  }
     
                  if(rc==0)
     
                         return len-cnt;
     
                  szText+=rc;
     
                  cnt-=rc;
     
           }
     
           return len;
     
    }

    服务器端:

    在服务器端,主要是启动Socket和监听线程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    #define DEFAULT_PORT      2000
     
    void CServerDlg::OnStart()
     
    {
     
           sockaddr_in local;
     
           DWORD dwThreadID = 0;
     
           local.sin_family=AF_INET;
     
           //设置的端口为DEFAULT_PORT。
     
           local.sin_port=htons(DEFAULT_PORT);
     
           //IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。
     
           local.sin_addr.S_un.S_addr=INADDR_ANY;
     
           //初始化Socket
     
           m_Listening = socket(AF_INET,SOCK_STREAM,0);
     
           if(m_Listening == INVALID_SOCKET)
     
           {
     
                  return ;
     
           }
     
           //将本地地址绑定到所创建的套接字上
     
           if(bind(m_Listening,(LPSOCKADDR)&local,sizeof(local)) == SOCKET_ERROR )
     
           {
     
                  closesocket(m_Listening);
     
                  return ;
     
           }
     
           //创建监听线程,这样也能响应界面上操作。
     
           m_hListenThread = ::CreateThread(NULL,0,ListenThread,this,0,&dwThreadID);
     
           m_StartBtn.EnableWindow(FALSE);
     
           m_StopBtn.EnableWindow(TRUE);
     
    }

    监听线程函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    DWORD WINAPI CServerDlg::ListenThread(LPVOID lpparam)
    {
     
           CServerDlg* pDlg = (CServerDlg*)lpparam;
     
           if(pDlg == NULL)
     
                  return 0;
     
           SOCKET  Listening = pDlg->m_Listening;
     
           //开始监听是否有客户端连接。
     
           if(listen(Listening,40) == SOCKET_ERROR)
     
           {
     
                  return 0;
     
           }
     
           char szBuf[MAX_PATH];
     
           //初始化
     
           memset(szBuf,0,MAX_PATH);
     
           while(1)
     
           {
     
                  SOCKET ConnectSocket;
     
                  sockaddr_in    ClientAddr;
     
                  int                  nLen = sizeof(sockaddr);
     
                  //阻塞直到有客户端连接,不然多浪费CPU资源。
     
                  ConnectSocket = accept(Listening,(sockaddr*)&ClientAddr,&nLen);
     
                  //都到客户端的IP地址。
     
                  char *pAddrname = inet_ntoa(ClientAddr.sin_addr);
     
                  pDlg->Receive(ConnectSocket,szBuf,100);
     
                  //界面上显示请求数据。
     
                  pDlg->SetRequestText(szBuf);
     
                  strcat(szBuf," :我是老猫,收到(");
     
                  strcat(szBuf,pAddrname);
     
                  strcat(szBuf,")");
     
                  //向客户端发送回应数据
     
                  pDlg->Send(ConnectSocket,szBuf,100);
     
           }
     
           return 0;
     
    }

    服务器端一直在监听是否有客户端连接,如有连接,处理客户端的请求,给出回应,然后继续监听。

    客户端:

    客户端的发送函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    #define DEFAULT_PORT      2000
     
    void CClientDlg::OnSend()
     
    {
     
           DWORD dwIP = 0;     
     
           TCHAR szText[MAX_PATH];
     
           memset(szText,0,MAX_PATH);
     
           m_IP.GetWindowText(szText,MAX_PATH);
     
           //把字符串形式的IP地址转成IN_ADDR结构需要的形式。
     
           dwIP = inet_addr(szText);
     
           m_RequestEdit.GetWindowText(szText,MAX_PATH);
     
           sockaddr_in local;
     
           SOCKET socketTmp;
     
           //必须是AF_INET,表示该socket在Internet域中进行通信
     
           local.sin_family=AF_INET;
     
           //端口号
     
           local.sin_port=htons(DEFAULT_PORT);
     
           //服务器的IP地址。
     
           local.sin_addr.S_un.S_addr=dwIP;
     
           ////初始化Socket
     
           socketTmp=socket(AF_INET,SOCK_STREAM,0);
     
           //连接服务器
     
           if(connect(socketTmp,(LPSOCKADDR)&local,sizeof(local)) < 0)
     
           {
     
                  closesocket(socketTmp);
     
                  MessageBox("连接服务器失败。");
     
                  return ;
     
           }
     
           //发送请求,为简单只发100字节,在服务器端也规定100字节。
     
           Send(socketTmp,szText,100);
     
           //读取服务器端返回的数据。
     
           memset(szText,0,MAX_PATH);
     
           //接收服务器端的回应。
     
           Receive(socketTmp,szText,100);
     
           TCHAR szMessage[MAX_PATH];
     
           memset(szMessage,0,MAX_PATH);
     
           strcat(szMessage,szText);
     
           //界面上显示回应数据。
     
           m_ReplyBtn.SetWindowText(szMessage);
     
           closesocket(socketTmp);
     
    }

    客户端就一个函数完成了一次通信。在这里IP地址为何用127.0.0.1呢?使用这个IP地址,服务器端和客户端就能运行在同一台机器上,这样调试方便多了。当然你可以在你朋友的机器上运行Server程序(本人在局域网中测试过),在自己的机器上运行Client程序,当然输入的IP地址就该是你朋友机器的IP地址了。
    简单的理论和实践都说了,现在Socket编程不神秘了吧?希望对你有些帮助。

    Linux下Socket连接超时的一种实现方法(转载) - USBdrivers的专栏 - 博客频道 - CSDN.NET - Google Chrome (2013/8/31 12:54:34)

    Linux下Socket连接超时的一种实现方法(转载) - USBdrivers的专栏 - 博客频道 - CSDN.NET - Google Chrome

     

    Linux下Socket连接超时的一种实现方法(转载)

     125人阅读 评论(0) 收藏 举报


    Linux下Socket连接超时的一种实现方法(转载)

    目前各平台通用的设置套接字(Socket)连接超时的办法是:


    1. 创建套接字,将其设置成非阻塞状态。


    2. 调用connect连接对端主机,如果失败,判断当时的errno是否为EINPROGRESS,也就是说是不是连接正在进行中,如果是,转到步骤3,如果不是,返回错误。

    3. 用select在指定的超时时间内监听套接字的写就绪事件,如果select有监听到,证明连接成功,否则连接失败。

      以下是Linux环境下的示例代码:


    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <errno.h>
    #include <time.h>

    int main(int argc, char *argv[])
    {
            int fd, retval;
            struct sockaddr_in addr;
            struct timeval timeo = {3, 0};
            socklen_t len = sizeof(timeo);
            fd_set set;

            fd = socket(AF_INET, SOCK_STREAM, 0);
            if (argc == 4)
                    timeo.tv_sec = atoi(argv[3]);
            fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
            addr.sin_family = AF_INET;
            addr.sin_addr.s_addr = inet_addr(argv[1]);
            addr.sin_port = htons(atoi(argv[2]));
            printf("%d ", time(NULL));
            if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
                    printf("connected ");
                    return 0;
            }
            if (errno != EINPROGRESS) {
                    perror("connect");
                    return -1;
            }
            FD_ZERO(&set);
            FD_SET(fd, &set);
            retval = select(fd + 1, NULL, &set, NULL, &timeo);
            if (retval == -1) {
                    perror("select");
                    return -1;
            } else if(retval == 0) {
                    fprintf(stderr, "timeout ");
                    printf("%d ", time(NULL));
                    return 0;
            }
            printf("connected ");

            return 0;
    }


      实际运行结果如下:

    xiaosuo@gentux perl $ ./a.out 10.16.101.1 90
    1180289276
    timeout
    1180289279
    xiaosuo@gentux perl $ ./a.out 10.16.101.1 90 1
    1180289281
    timeout
    1180289282


      可以看到,以上代码工作的很好,并且如果你想知道连接发生错误时的确切信息的话,你可以用getsocketopt获得:

    int error;
    socklen_t errorlen = sizeof(error);

    getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &errorlen);


      但是多少有些复杂,如果有象SO_SNDTIMO/SO_RCVTIMO一样的套接字参数可以让超时操作跳过select的话,世界将变得更美好。当然你还可以选用象apr一样提供了简单接口的库,但我这里要提的是另一种方法。

      呵呵,引子似乎太长了点儿。读Linux内核源码的时候偶然发现其connect的超时参数竟然和用SO_SNDTIMO操作的参数一致:

      File: net/ipv4/af_inet.c

       559      timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
       560
       561      if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
       562          /* Error code is set above */
       563          if (!timeo || !inet_wait_for_connect(sk, timeo))
       564              goto out;
       565
       566          err = sock_intr_errno(timeo);
       567          if (signal_pending(current))
       568              goto out;
       569      }


      这意味着:在Linux平台下,可以通过在connect之前设置SO_SNDTIMO来达到控制连接超时的目的。简单的写了份测试代码:


    #include <stdlib.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <errno.h>

    int main(int argc, char *argv[])
    {
            int fd;
            struct sockaddr_in addr;
            struct timeval timeo = {3, 0};
            socklen_t len = sizeof(timeo);

            fd = socket(AF_INET, SOCK_STREAM, 0);
            if (argc == 4)
                    timeo.tv_sec = atoi(argv[3]);
            setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, len);
            addr.sin_family = AF_INET;
            addr.sin_addr.s_addr = inet_addr(argv[1]);
            addr.sin_port = htons(atoi(argv[2]));
            if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
                    if (errno == EINPROGRESS) {
                            fprintf(stderr, "timeout ");
                            return -1;
                    }
                    perror("connect");
                    return 0;
            }
            printf("connected ");

            return 0;
    }


      执行结果:

    xiaosuo@gentux perl $ ./a.out 10.16.101.1 90
    1180290583
    timeout
    1180290586
    xiaosuo@gentux perl $ ./a.out 10.16.101.1 90 2
    1180290590
    timeout
    1180290592

      和设想完全一致!

    (责任编辑:凌云通)

    原文:http://linux.ccidnet.com/art/302/20070605/1102009_1.html

    嵌入式linux网络编程之connect()函数的高级应用--嵌入式学院(华清远见旗下品牌) - Google Chrome (2013/8/31 12:11:59)

    我们是否想到了一个select函数那,他具备监听文件描述符的功能,如果我们把之前的socket让select监听他是否可写,是不是问题也就解决了。

    好了,那么我们总结下整个思路:

    1.建立socket 
            2.将该socket设置为非阻塞模式 
            3.调用connect() 
            4.使用select()检查该socket描述符是否可写
            5.根据select()返回的结果判断connect()结果 
            6.将socket设置为阻塞模式

    对于第六步,为什么还要设置为阻塞模式那,留给我们同学自己思考下。

    那么根据上面的6个步骤,我们写一个简单的模块程序来调试看下:

    {
                    int sockfd = socket(AF_INET, SOCK_STREAM, 0); 
                    if(sockfd < 0) exit(1); 
                    struct sockaddr_in serv_addr; 
                    ………//以服务器地址填充结构serv_addr 
                    int error=-1, len; 
                    len = sizeof(int); 
                    timeval tm; 
                    fd_set set; 
                    unsigned long ul = 1; 
                    ioctl(sockfd, FIONBIO, &ul); //设置为非阻塞模式 
                    bool ret = false; 
                    if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) 
                    {
                            tm.tv_set = TIME_OUT_TIME; 
                            tm.tv_uset = 0; 
                            FD_ZERO(&set); 
                            FD_SET(sockfd, &set); 
                            if( select(sockfd+1, NULL, &set, NULL, &tm) > 0) 
                            { 
                                    getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len); 
                                    if(error == 0) ret = true; 
                                    else ret = false; 
                            } else ret = false; 
                    } 
                    else ret = true; 
                    ul = 0; 
                    ioctl(sockfd, FIONBIO, &ul); //设置为阻塞模式 
                    //下面还可以进行发包收包操作 
                    ……………
            }

    用setsockopt()来控制recv()与send()的超时[转]_.Net编程 - 好工具站长分享平台 - Google Chrome (2013/8/21 15:27:09)

    用setsockopt()来控制recv()与send()的超时[转]

    作者:飘啊飘 | 出处:博客园 | 阅读121次 2011/11/12 17:40:16

    在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,而设置收发超时控制
    在Linux下需要注意的是时间的控制结构是struct timeval而并不是某一整型数,以下是来自于网上一篇文章中的摘录,它是这样写的:
    int nNetTimeout=1000;//1秒,
    //设置发送超时
    setsockopt(socket,SOL_SOCKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
    //设置接收超时
    setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
    这样做在Linux环境下是不会产生效果的,须如下定义:struct timeval timeout = {3,0}; 
     //设置发送超时
    setsockopt(socket,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(struct timeval));
    //设置接收超时
    setsockopt(socket,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval));

    有两点注意就是:
    1)recv ()的第四个参数需为MSG_WAITALL,在阻塞模式下不等到指定数目的数据不会返回,除非超时时间到。还要注意的是只要设置了接收超时,在没有MSG_WAITALL时也是有效的。说到底超时就是不让你的程序老在那儿等,到一定时间进行一次返回而已。
    2)即使等待超时时间值未到,但对方已经关闭了socket, 则此时recv()会立即返回,并收到多少数据返回多少数据。

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/newger/archive/2008/05/19/2459113.aspx

    设置socket的Connect超时 - chenguangf的专栏 - 博客频道 - CSDN.NET - Google Chrome (2013/5/17 20:24:05)

     

    设置socket的Connect超时

     13551人阅读 评论(3) 收藏 举报
    [From]http://dev.cbw.com/c/c/200510195601_4292587.shtml

    1.
    首先将标志位设为Non-blocking模式,准备在非阻塞模式下调用connect函数
    2.
    调用connect,正常情况下,因为TCP三次握手需要一些时间;而非阻塞调用只要不能立即完成就会返回错误,所以这里会返回EINPROGRESS,表示在建立连接但还没有完成。
    3.
    在读套接口描述符集(fd_set rset)和写套接口描述符集(fd_set wset)中将当前套接口置位(用FD_ZERO()FD_SET()宏),并设置好超时时间(struct timeval *timeout)
    4.
    调用select( socket, &rset, &wset, NULL, timeout )
    返回0表示connect超时
    如果你设置的超时时间大于75秒就没有必要这样做了,因为内核中对connect有超时限制就是75秒。


    [From]http://www.ycgczj.com.cn/34733.html
    网络编程中socket的分量我想大家都很清楚了,socket也就是套接口,在套接口编程中,提到超时的概念,我们一下子就能想到3个:发送超时,接收超时,以及select超时(注: select函数并不是只用于套接口的,但是套接口编程中用的比较多),在connect到目标主机的时候,这个超时是不由我们来设置的。不过正常情况下这个超时都很长,并且connect又是一个阻塞方法,一个主机不能连接,等着connect返回还能忍受,你的程序要是要试图连接多个主机,恐怕遇到多个不能连接的主机的时候,会塞得你受不了的。我也废话少说,先说说我的方法,如果你觉得你已掌握这种方法,你就不用再看下去了,如果你还不了解,我愿意与你分享。本文是已在Linux下的程序为例子,不过拿到Windows中方法也是一样,无非是换几个函数名字罢了。
      Linux中要给connect设置超时,应该是有两种方法的。一种是该系统的一些参数,这个方法我不讲,因为我讲不清楚:P,它也不是编程实现的。另外一种方法就是变相的实现connect的超时,我要讲的就是这个方法,原理上是这样的:
     1
    .建立socket
     2
    .将该socket设置为非阻塞模式
     3
    .调用connect()
     4
    .使用select()检查该socket描述符是否可写(注意,是可写)
     5
    .根据select()返回的结果判断connect()结果
     6
    .将socket设置为阻塞模式(如果你的程序不需要用阻塞模式的,这步就省了,不过一般情况下都是用阻塞模式的,这样也容易管理)
    如果你对网络编程很熟悉的话,其实我一说出这个过程你就知道怎么写你的程序了,下面给出我写的一段程序,仅供参考。
    /******************************
    * Time out for connect()  
    *  Write by Kerl W
    ******************************/
    #include <sys/socket.h>
    #include <sys/types.h>
    #define TIME_OUT_TIME 20 //connect超时时间20
    int main(int argc , char **argv)
    {
       ………………
       int sockfd = socket(AF_INET, SOCK_STREAM, 0);
       if(sockfd < 0) exit(1);
       struct sockaddr_in serv_addr;
       ………//
    以服务器地址填充结构serv_addr
       int error=-1, len;
       len = sizeof(int);
       timeval tm;
       fd_set set;
       unsigned long ul = 1;
       ioctl(sockfd, FIONBIO, &ul); //
    设置为非阻塞模式
       bool ret = false;
       if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
       {
         tm.tv_set  = TIME_OUT_TIME;
         tm.tv_uset = 0;
         FD_ZERO(&set);
         FD_SET(sockfd, &set);
         if( select(sockfd+1, NULL, &set, NULL, &tm) > 0)
         {
           getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
           if(error == 0) ret = true;
     else ret = false;
        } else ret = false;
      }
     else ret = true;
     ul = 0;
     ioctl(sockfd, FIONBIO, &ul); //
    设置为阻塞模式
     if(!ret) 
     {
      close( sockfd );
      fprintf(stderr , "Cannot Connect the server!/n");
       return;
       }
      fprintf( stderr , "Connected!/n");
      //
    下面还可以进行发包收包操作
      ……………
    }

      
    以上代码片段,仅供参考,也是为初学者提供一些提示,主要用到的几个函数,select, ioctl, getsockopt都可以找到相关资料,具体用法我这里就不赘述了,你只需要在linux中轻轻的敲一个man <函数名>就能够看到它的用法。
     此外我需要说明的几点是,虽然我们用ioctl把套接口设置为非阻塞模式,不过select本身是阻塞的,阻塞的时间就是其超时的时间由调用select 的时候的最后一个参数timeval类型的变量指针指向的timeval结构变量来决定的,timeval结构由一个表示秒数的和一个表示微秒数(long类型)的成员组成,一般我们设置了秒数就行了,把微妙数设为0(注:1秒等于100万微秒)。而select函数另一个值得一提的参数就是上面我们用到的fd_set类型的变量指针。调用之前,这个变量里面存了要用select来检查的描述符,调用之后,针对上面的程序这里面是可写的描述符,我们可以用宏FD_ISSET来检查某个描述符是否在其中。由于我这里只有一个套接口描述符,我就没有使用FD_ISSET宏来检查调用select之后这个sockfd是否在set里面,其实是需要加上这个判断的。不过我用了getsockopt来检查,这样才可以判断出这个套接口是否是真的连接上了,因为我们只是变相的用select来检查它是否连接上了,实际上select检查的是它是否可写,而对于可写,是针对以下三种条件任一条件满足时都表示可写的:
    1
    )套接口发送缓冲区中的可用控件字节数大于等于套接口发送缓冲区低潮限度的当前值,且或者i)套接口已连接,或者ii)套接口不要求连接(UDP方式的)
    2
    )连接的写这一半关闭。
    3
    )有一个套接口错误待处理。
    这样,我们就需要用getsockopt函数来获取套接口目前的一些信息来判断是否真的是连接上了,没有连接上的时候还能给出发生了什么错误,当然我程序中并没有标出那么多状态,只是简单的表示可连接/不可连接。
     下面我来谈谈对这个程序测试的结果。我针对3种情形做了测试:
    1
     目标机器网络正常的情况
      
    可以连接到目标主机,并能成功以阻塞方式进行发包收包作业。
    2 目标机器网络断开的情况
      
    在等待设置的超时时间(上面的程序中为20秒)后,显示目标主机不能连接。
    3 程序运行前断开目标机器网络,超时时间内,恢复目标机器的网络
    在恢复目标主机网络连接之前,程序一只等待,恢复目标主机后,程序显示连接目标主机成功,并能成功以阻塞方式进行发包收包作业。
     以上各种情况的测试结果表明,这种设置connect超时的方法是完全可行的。我自己是把这种设置了超时的connect封装到了自己的类库,用在一套监控系统中,到目前为止,运行还算正常。这种编程实现的connect超时比起修改系统参数的那种方法的有点就在于它只用于你的程序之中而不影响系统。
     
    connect 超时,socket connect,socket 超时,socket连接超时设置,connect,mysql connect,connect by,connect by prior,connect player,media connect

    关于c/s socket中超时问题的总结
    [size=18:ac54d21053]在客户端与服务器端通过socket连接时,有两个问题必须考虑 
    1
    connect连接时可能会发生连接不上的情况,需要实现超时退出程序。 
    2
    、连接后在接收数据的过程中,可能发生网络中断,不能接受数据的情况,需要退出程序。 

    这两个问题应该很常见,希望高手给大家详细地讲解一下,谢谢。[/size:ac54d21053]
     

     gadfly 回复于:2003-08-11 14:40:32
    这两个都可以用非阻塞socketselect控制超时

     yuanyawei 回复于:2003-08-12 09:08:00
    我觉得第一种情况用select可以很好解决。 
    但第二种情况在遇到客户端直接拔网线的情况时,server端的情况较难判断,要看内核的参数,linux下较好处理,BSD也没问题,HPAIX也能处理,但SCO下就不好办了(参数老调不好)。

     minsky 回复于:2003-08-12 10:52:27
    1.connect超时: 
    1)setsockopt();//
    socket置为非阻塞模式; 
    2)connect(); 
    3)
    判断connect()的返回值,一般情况会返回-1,这时你还必须判断错误码如果是EINPROGRESS,那说明connect还在继续;如果错误码不是前者那么就是有问题了,不必往下执行,必须关掉socket;待下次重联; 
    4)select();
    设置好函数中的超时时间,select()中的readwrite项置上,在超时时间内,如果select返回1,即描述字变为了可写,那么连接成功;如果返回2,即描述字变为即可读又可写,那么出错;如果返回0,那么超时; 
    ============================================ 
    2.
    网络中断: 
    如果你的程序是客户端.select检查描述符的状态,如果可读就recv(),根据recv()的返回值来判断网络情况;

     calfen 回复于:2003-12-18 15:18:55
    unp上明确说setsockopt只能用在读写时候不能用在connect上啊...

     grouploo 回复于:2004-06-25 23:06:35
    /********************************************/ 
    /****   
    作者::夕君                **/ 
    /****   
    时间:2004.04.04                     **/ 
    /****   
    北京金万维科技 http://www.gnway.com           **/ 
    /*******************************************/ 
    /*
    此函数实现判断m_serverm_port端口是否可以连上,超时限制为nTimeOut*/ 
    BOOL ConnectTest(char * m_server,int m_port) 
    { 

            struct hostent* host = NULL; 
            struct sockaddr_in saddr; 
            unsigned int s = 0; 
            BOOL  ret; 
            time_t start; 
            int error; 
            host = gethostbyname (m_server); 
            if (host==NULL)return  FALSE; 

            saddr.sin_family = AF_INET; 
            saddr.sin_port = htons(m_port); 
            saddr.sin_addr = *((struct in_addr*)host->h_addr); 


            if( (s=socket(AF_INET, SOCK_STREAM, 0))<0){ 
                    return FALSE; 
            } 


            fcntl(s,F_SETFL, O_NONBLOCK); 

            if(connect(s,(struct sockaddr*)&saddr, sizeof(saddr)) == -1) { 
                    if (errno == EINPROGRESS){// it is in the connect process 
                            struct timeval tv; 
                            fd_set writefds; 
                            tv.tv_sec = m_nTimeOut; 
                            tv.tv_usec = 0; 
                            FD_ZERO(&writefds); 
                            FD_SET(s, &writefds); 
                            if(select(s+1,NULL,&writefds,NULL,&tv)>0){ 
                                    int len=sizeof(int); 
                                   //
    下面的一句一定要,主要针对防火墙 
                                    getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len); 
                                    if(error==0) ret=TRUE; 
                                    else ret=FALSE; 
                            }else   ret=FALSE;//timeout or error happen 
                    }else ret=FALSE; 
            } 
            else    ret=TRUE; 

            close(s); 
            return ret; 


    }
     
     
     
    setsockopt函数解析(转) - [IT]{#timeline}
    Tag:IT
    int setsockopt (
      SOCKET
     s,                 
      int level,                
      int optname,              
      const char FAR * optval 
      int optlen                
    );

    The Windows Sockets setsockopt function sets a socket option.

    中文解释好像是:设置套接字的选项。

    先看如下代码:
    setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int))

    这里是设置SockRaw这个套接字的ip选项中的IP_HDRINCL

    参考以下资料:

    ***************************************************************************************************

    Linux网络编程--8. 套接字选项 
    有时候我们要控制套接字的行为(如修改缓冲区的大小),这个时候我们就要控制套接字的选项了. 

    8.1 getsockopt和setsockopt 
    int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen) 
    int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t *optlen)
    level指定控制套接字的层次.可以取三种值: 
    1)SOL_SOCKET:通用套接字选项. 
    2)IPPROTO_IP:IP选项. 
    3)IPPROTO_TCP:TCP选项. 
    optname指定控制的方式(选项的名称),我们下面详细解释 
    optval获得或者是设置套接字选项.根据选项名称的数据类型进行转换 

    选项名称        说明                  数据类型 
    ======================================================================== 
                SOL_SOCKET 
    ------------------------------------------------------------------------ 
    SO_BROADCAST      允许发送广播数据            int 
    SO_DEBUG        允许调试                int 
    SO_DONTROUTE      不查找路由               int 
    SO_ERROR        获得套接字错误             int 
    SO_KEEPALIVE      保持连接                int 
    SO_LINGER        延迟关闭连接              struct linger 
    SO_OOBINLINE      带外数据放入正常数据流         int 
    SO_RCVBUF        接收缓冲区大小             int 
    SO_SNDBUF        发送缓冲区大小             int 
    SO_RCVLOWAT       接收缓冲区下限             int 
    SO_SNDLOWAT       发送缓冲区下限             int 
    SO_RCVTIMEO       接收超时                struct timeval 
    SO_SNDTIMEO       发送超时                struct timeval 
    SO_REUSERADDR      允许重用本地地址和端口         int 
    SO_TYPE         获得套接字类型             int 
    SO_BSDCOMPAT      与BSD系统兼容              int 
    ========================================================================== 
                IPPROTO_IP 
    -------------------------------------------------------------------------- 
    IP_HDRINCL       在数据包中包含IP首部          int 
    IP_OPTINOS       IP首部选项               int 
    IP_TOS         服务类型 
    IP_TTL         生存时间                int 
    ========================================================================== 
                IPPRO_TCP 
    -------------------------------------------------------------------------- 
    TCP_MAXSEG       TCP最大数据段的大小           int 
    TCP_NODELAY       不使用Nagle算法             int 
    =========================================================================
    关于这些选项的详细情况请查看 Linux Programmer"s Manual 
    8.2 ioctl 
    ioctl可以控制所有的文件描述符的情况,这里介绍一下控制套接字的选项. 
    int ioctl(int fd,int req,...) 
     
    ========================================================================== 
                ioctl的控制选项 
    -------------------------------------------------------------------------- 
    SIOCATMARK       是否到达带外标记            int 
    FIOASYNC        异步输入/输出标志            int 
    FIONREAD        缓冲区可读的字节数           int 
    ==========================================================================
    详细的选项请用 man ioctl_list 查看. 



     
    1.closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
    BOOL bReuseaddr=TRUE;
    setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
    2. 如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历
    TIME_WAIT的过程:
    BOOL bDontLinger = FALSE;
    setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
    3.在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
    int nNetTimeout=1000;//1秒
    //发送时限
    setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
    //接收时限
    setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
    4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
    (异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据
    和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
    // 接收缓冲区
    int nRecvBuf=32*1024;//设置为32K
    setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
    //发送缓冲区
    int nSendBuf=32*1024;//设置为32K
    setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
    5. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响
    程序的性能:
    int nZero=0;
    setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
    6.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
    int nZero=0;
    setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
    7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
    BOOL bBroadcast=TRUE;
    setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
    8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可
    以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的
    作用,在阻塞的函数调用中作用不大)
    BOOL bConditionalAccept=TRUE;
    setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
    9.如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们
    一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体
    应用的要求(即让没发完的数据发送出去后在关闭socket)?
    struct linger {
    u_short l_onoff;
    u_short l_linger;
    };
    linger m_sLinger;
    m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
    // 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;
    m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
    setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));
     




  • 相关阅读:
    [转载]各种计算机语言的经典书籍
    [转载]VC 常用快捷键
    [转载]Visual Studio中的debug和release版本的区别
    [转载]Visual C++开发工具与调试技巧整理
    [转载]一个游戏程序员的学习资料
    [转载]C++资源之不完全导引(完整版)
    [转载]一个图形爱好者的书架/白话说学计算机图形学
    [摘录]这几本游戏编程书籍你看过吗?
    Oracle分析函数的使用
    [C/C++]C++下基本类型所占位数和取值范围
  • 原文地址:https://www.cnblogs.com/stlong/p/6290364.html
Copyright © 2020-2023  润新知