tcp/心跳包

1,http://blog.csdn.net/yuzhiyuxia/article/details/7857508

心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令字,按照一定的时间间隔发送,类似于心跳,所以叫做心跳包。   
     用来判断对方(设备,进程或其它网元)是否正常运行,采用定时发送简单的通讯包,如果在指定时间段内未收到对方响应,则判断对方已经离线。用于检测TCP的异常断开。基本原因是服务器端不能有效的判断客户端是否在线,也就是说,服务器无法区分客户端是长时间在空闲,还是已经掉线的情况。所谓的心跳包就是客户端定时发送简单的信息给服务器端告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开。   比如有些通信软件长时间不使用,要想知道它的状态是在线还是离线就需要心跳包,定时发包收包。发包方:可以是客户也可以是服务端,看哪边实现方便合理,一般是客户端。服务器也可以定时发心跳下去。一般来说,出于效率的考虑,是由客户端主动向服务器端发包,而不是服务器向客户端发。客户端每隔一段时间发一个包,使用TCP的,用send发,使用UDP的,用sendto发,服务器收到后,就知道当前客户端还处于“活着”的状态,否则,如果隔一定时间未收到这样的包,则服务器认为客户端已经断开,进行相应的客户端断开逻辑处理。
 
       心跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。
      在TCP/IP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项:SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。
    心跳包一般来说都是在逻辑层发送空的echo包来实现的。下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。
       其实,要判定掉线,只需要send或者recv一下,如果结果为零,则为掉线。但是,在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。
       在获知了断线之后,服务器逻辑可能需要做一些事情,比如断线后的数据清理呀,重新连接呀……当然,这个自然是要由逻辑层根据需求去做了。总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒.
 
2, http://blog.csdn.net/xuyuefei1988/article/details/8279812
心跳包的发送,通常有两种技术
方法1:应用层自己实现的心跳包 
由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个 Timer事件中定时 向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没 有收到服务器的心跳包,则认为连接不可用。

方法2:TCP的KeepAlive保活机制
因为要考虑到一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多 且稍显复杂,而利用TCP/IP协议层为内置的KeepAlive功能来实现心跳功能则简单得多。 不论是服务端还是客户端,一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。 因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启KeepAlive功 能,尽管这微不足道,但在按流量计费的环境下增加了费用,另一方面,KeepAlive设置不合理时可能会 因为短暂的网络波动而断开健康的TCP连接。并且,默认的KeepAlive超时需要7,200,000 MilliSeconds, 即2小时,探测次数为5次。对于很多服务端应用程序来说,2小时的空闲时间太长。因此,我们需要手工开启KeepAlive功能并设置合理的KeepAlive参数。
 
心跳检测步骤:
1客户端每隔一个时间间隔发生一个探测包给服务器
2客户端发包时启动一个超时定时器
3服务器端接收到检测包,应该回应一个包
4如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
5如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
转自:http://blog.sina.com.cn/s/blog_a459dcf5010153m5.html
 
 
 
3,http://blog.chinaunix.net/uid-26000296-id-3758651.html
TCP连接简介
当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,
当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,
连接的建立是需要三次握手的,而释放则需要4次握手,
所以说每个连接的建立都是需要资源消耗和时间消耗的
经典的三次握手示意图:


经典的四次握手关闭图:
 


一、长连接与短连接
长连接: 指在一个TCP连接上可以连续发送多个数据包,
        在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接;
        一般需要自己做在线维持。 
短连接: 指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接;
        一般银行都使用短连接。 
        它的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段 

比如http的,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。 
其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态。

长连接与短连接的操作过程 
通常的短连接操作步骤是: 
  连接→数据传输→关闭连接;

而长连接通常就是: 
  连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接; 

这就要求长连接在没有数据通信时,定时发送数据包(心跳),以维持连接状态,
短连接在没有数据传输时直接关闭就行了

什么时候用长连接,短连接?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。
每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,
所以每个操作完后都不断开,下次次处理时直接发送数据包就OK了,不用建立TCP连接。

例如:数据库的连接用长连接, 
如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。

二、发送接收方式
1、异步 
报文发送和接收是分开的,相互独立的,互不影响。这种方式又分两种情况: 
(1)异步双工:接收和发送在同一个程序中,由两个不同的子进程分别负责发送和接收 
(2)异步单工:接收和发送是用两个不同的程序来完成。 

2、同步 
报文发送和接收是同步进行,既报文发送后等待接收返回报文。 
同步方式一般需要考虑超时问题,即报文发出去后不能无限等待,需要设定超时时间,
超过该时间发送方不再等待读返回报文,直接通知超时返回。
 
在长连接中一般是没有条件能够判断读写什么时候结束,所以必须要加长度报文头。
读函数先是读取报文头的长度,再根据这个长度去读相应长度的报文。

三. 单工、半双工和全双工
根据通信双方的分工和信号传输方向可将通信分为三种方式:
单工、
半双工、
全双工。

在计算机网络中主要采用双工方式,其中:
局域网采用半双工方式,
城域网和广域网采用全双年方式。   

1. 单工(Simplex)方式:
通信双方设备中发送器与接收器分工明确,只能在由发送器向接收器的单一固定方向上传送数据。
采用单工通信的典型发送设备如早期计算机的读卡器,典型的接收设备如打印机。   

2. 半双工(Half Duplex)方式:
通信双方设备既是发送器,也是接收器,两台设备可以相互传送数据,但某一时刻则只能向一个方向传送数据。
例如,步话机是半双工设备,因为在一个时刻只能有一方说话。   

3. 全双工(Full Duplex)方式:
通信双方设备既是发送器,也是接收器,两台设备可以同时在两个方向上传送数据。
例如,电话是全双工设备,因为双方可同时说话。

而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,
而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,
如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。
所以并发量大,但每个用户无需频繁操作情况下需用短连好。

总之,长连接和短连接的选择要视情况而定。

四、一个最简单的长连接与心跳保持的示例程序
/*!
 ******************************************************************************
 * File
 *  
 * Brief
 *   
 * Author
 *  Hank
 ******************************************************************************
 */
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <arpa/inet.h>
  10. #include <unistd.h>
  11. #include <sys/time.h>
  12. #include <sys/types.h>
#define MAXBUF 1024


int main(int argc, char **argv)
{
  int sockfd, len;
  struct sockaddr_in dest;
  char buffer[MAXBUF];
  char heartbeat[20] = "hello server";
  fd_set rfds;
  struct timeval tv;
  int retval, maxfd = -1;

  if (argc != 3)
  {
    printf("error! the right format should be :
          %s IP port eg: %s127.0.0.1 80 ",
          argv[0], argv[0]);
    exit(0);
  }

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  {
    perror("Socket");
    exit(errno);
  }

  bzero(&dest, sizeof(dest));
  dest.sin_family = AF_INET;
  dest.sin_port = htons(atoi(argv[2]));
  memset(&(dest.sin_zero), 0, 8);
  if (inet_aton(argv[1], (struct in_addr*)&dest.sin_addr.s_addr) == 0)
  {
    perror(argv[1]);
    exit(errno);
  }

  if (connect(sockfd, (struct sockaddr*)&dest, sizeof(dest)) != 0)
  {
    perror("Connect");
    exit(errno);
  }
  printf(" Ready to start chatting.
        Direct input messages and
        enter to send messages to the server ");

  while (1)
  {
    FD_ZERO(&rfds);
    FD_SET(0, &rfds);
    maxfd = 0;

    FD_SET(sockfd, &rfds);
    if (sockfd > maxfd)
      maxfd = sockfd;

    tv.tv_sec = 2;
    tv.tv_usec = 0;

    retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
    if (retval == -1)
    {
      printf("Will exit and the select is error! %s", strerror(errno));
      break;
    }
    else if (retval == 0)
    {
      //printf("No message comes, no buttons, continue to wait ... ");
      len = send(sockfd, heartbeat, strlen(heartbeat), 0);
      if (len < 0)
      {
        printf("Message '%s' failed to send !
              The error code is %d, error message '%s' ",
              heartbeat, errno, strerror(errno));
        break;
      }
      else
      {
        printf("News: %s send, sent a total of %d bytes! ",
              heartbeat, len);
      }

      continue;
    }
    else
    {
      if (FD_ISSET(sockfd, &rfds))
      {
        bzero(buffer, MAXBUF+1);
        len = recv(sockfd, buffer, MAXBUF, 0);


        if (len > 0)
        {
          printf("Successfully received the message: '%s',%d bytes of data ",
                  buffer, len);
        }
        else
        {
          if (len < 0)
              printf("Failed to receive the message!
                    The error code is %d, error message is '%s' ",
                    errno, strerror(errno));
          else
              printf("Chat to terminate! ");


          break;
        }
      }


      if (FD_ISSET(0, &rfds))
      {
        bzero(buffer, MAXBUF+1);
        fgets(buffer, MAXBUF, stdin);


        if (!strncasecmp(buffer, "quit", 4))
        {
          printf("Own request to terminate the chat! ");
          break;
        }


        len = send(sockfd, buffer, strlen(buffer)-1, 0);
        if (len < 0)
        {
          printf("Message '%s' failed to send !
                The error code is %d, error message '%s' ",
                buffer, errno, strerror(errno));
          break;
        }
        else
        {
          printf("News: %s send, sent a total of %d bytes! ",
                buffer, len);
        }
      }
    }
  }


  close(sockfd);
  return 0;
}
原文地址:https://www.cnblogs.com/lushengcao/p/4903144.html