TCP常见失败模式

         对比IP协议,TCP协议是一种可靠的协议,它的可靠性最主要体现在对各种复杂错误的修正与处理。当数据报从一端通过网络发往另一端的过程中,时时刻刻都会遇到数据报被损坏、数据报重复、乱序等常见问题,

         现在我们以一个基于WAN传输的系统为例,在应用程序之间进行通信的过程中可能遇到以下常见的中断:暂时或者永久的网络紊乱、对等方应用程序出现崩溃(进程挂掉)以及对等方应用程序运行的主机出现崩溃(电脑重启或者死机)。

       

         网络紊乱:

         在这种情况下,应用程序以及协议栈就不知道此时发生了紊乱,除非中间路由器利用ICMP消息告诉发送端目的主机或者网络不可达,在一些TCP实现上,会返回ENETUNREACH或者EHOSTUNREACH错误。

         对等方应用程序崩溃(进程挂掉):

         无论应用程序是如何结束的(close或者exit),对等方均会发送FIN消息给发送方,下面一段代码是用来模拟这种中断情况的:

/*********************************readn.h内容*****************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>


#define INIT()          ( program_name = \
                        strrchr( argv[ 0 ], '/' ) ) ? \
                        program_name++ : \
                        ( program_name = argv[ 0 ] )
#define EXIT(s)         exit( s )
#define CLOSE(s)        if ( close( s ) ) error( 1, errno, \
                        "close failed" )
#define set_errno(e)    errno = ( e )
#define isvalidsock(s)  ( ( s ) >= 0 )

#define NLISTEN         5       /* max waiting connections */
#define TRUE 1
#define FALSE 0

#define OPTIMIZE_READLINE
typedef unsigned int u_int32_t;
typedef int SOCKET;


/*-----------------Start Heartbeat include file--------------------*/
#define MSG_TYPE1       1       /* application specific msg */
#define MSG_TYPE2       2       /* another one */
#define MSG_HEARTBEAT   3       /* heartbeat message */

typedef struct                  /* message structure */
{
    u_int32_t type;             /* MSG_TYPE1, ... */
    char data[ 2000 ];
} msg_t;

#define T1              60      /* idle time before heartbeat */
#define T2              10      /* init a heartbeat and timeout to wait for response */
/*-----------------Heartbeat include file--------------------*/


SOCKET tcp_server( char *hname, char *sname ); //setup tcp server
int readvrec( SOCKET fd, char *bp, size_t len );
int readn( SOCKET fd, char *bp, size_t len);
SOCKET tcp_client( char *hname, char *sname );
SOCKET udp_server( char *, char * );
SOCKET udp_client( char *, char *, struct sockaddr_in * );
void set_address( char *hname, char *sname, struct sockaddr_in *sap, char *protocol );
#ifndef OPTIMIZE_READLINE
int readline(int fd, void *vptr, size_t maxlen);
#else
int readline( int fd, char *bufptr, size_t maxlen );
#endif


/******************************************readn.c内容**************************/
#include "readn.h"

//setup for a tcp server
SOCKET tcp_server( char *hname, char *sname )
{
    struct sockaddr_in local;
    SOCKET s;
    const int on = 1;

    set_address( hname, sname, &local, "tcp" );
    s = socket( AF_INET, SOCK_STREAM, 0 );
    if ( !isvalidsock( s ) )
  {
    error( 1, errno, "socket call failed" );
  } 
        
    if ( setsockopt( s, SOL_SOCKET, SO_REUSEADDR,   ( char * )&on, sizeof( on ) ) )
  {
    error( 1, errno, "setsockopt failed" );
  } 
  
    if ( bind( s, ( struct sockaddr * ) &local, sizeof( local ) ) )
  {
    error( 1, errno, "bind failed" );
  } 
        

    if ( listen( s, NLISTEN ) )
  {
    error( 1, errno, "listen failed" );
  }         

  return s;
}


/* readn - read exactly n bytes */
int readn( SOCKET fd, char *bp, size_t len)
{
    int cnt;
    int rc;
  int var;

  /*dragon, setup NO BLOCK option, and maybe cause error about "Resource temporarily unavailable", 
     *so should exclude errno is EAGAIN
  */
//  var = fcntl(fd, F_GETFL, 0);
//  fcntl(fd, F_SETFL, var|O_NONBLOCK);

    cnt = len;
    while ( cnt > 0 )
    {
      //dragon, default option of recv is block
//      rc = recv( fd, bp, cnt, 0 ); 
    rc = recv( fd, bp, cnt, MSG_DONTWAIT); //NON_BLOCK operation
        if ( rc < 0 )               /* read error? */
        {
            if ( errno == EINTR || errno == EAGAIN) /* interrupted? */
                continue;           /* restart the read */
            return -1;              /* return error */
        }
        if ( rc == 0 )              /* EOF? */
    {
      return len - cnt;   /* return short count */
    }  
            
        bp += rc;
        cnt -= rc;
    }
    return len;
}


/* readvrec - read a variable record */
int readvrec( SOCKET fd, char *bp, size_t len )
{
    u_int32_t reclen;
    int rc = 0;

    /* Retrieve the length of the record */

    rc = readn( fd, ( char * )&reclen, sizeof( u_int32_t ) );
    if ( rc != sizeof( u_int32_t ) )
  {
        return rc < 0 ? -1 : 0;
  } 
  
    reclen = ntohl( reclen );
    if ( reclen > len )
    {
        /*
         *  Not enough room for the record--
         *  discard it and return an error.
         *  Need to discard data in the receive buffer.
         */

    //How to discard receive buffer
        while ( reclen > 0 )
        {
            rc = readn( fd, bp, len );
            if ( rc != len )
                return rc < 0 ? -1 : 0;
            reclen -= len;
            if ( reclen < len )
                len = reclen;
        }
    
        return -1;
    }

    /* Retrieve the record itself */
    rc = readn( fd, bp, reclen );
    if ( rc != reclen )
  {
    return rc < 0 ? -1 : 0;
  } 
  
    return rc;
}

/*-------------------------Before optimize-------------------------*/
//PS: each read just only read one character, we can read more characters and store it to buffer, such as array,
//      that is trading time for space.
#ifndef OPTIMIZE_READLINE
int readline(int fd, void *vptr, size_t maxlen)
{
    int n, rc;
    char    c, *ptr;

    ptr = vptr;
    for (n = 1; n < maxlen; n++) {
        if ( (rc = read(fd, &c, 1)) == 1) {
            *ptr++ = c;
            if (c == '\n')
                break;
        } else if (rc == 0) {
            if (n == 1)
                return(0);  /* EOF, no data read */
            else
                break;      /* EOF, some data was read */
        } else
            return(-1); /* error */
    }

    *ptr = 0;
    return(n);
}
#else
int readline( int fd, char *bufptr, size_t len )
{
    char *bufx = bufptr;
    static char *bp;
    static int cnt = 0;
    static char b[ 1500 ];
    char c;

    while ( --len > 0 ) //should first plus to add an extra char buf to store ending character
    {
        if ( --cnt <= 0 )
        {
            cnt = recv( fd, b, sizeof( b ), 0 );
            if ( cnt < 0 )
            {
                if ( errno == EINTR )
                {
                    len++;      /* the while will decrement */
                    continue;
                }
                return -1;
            }
            if ( cnt == 0 )
      {
                return 0;
      }   
            bp = b;
        }
        c = *bp++;
        *bufptr++ = c;
        if ( c == '\n' )
        {
            *bufptr = '\0';
            return bufptr - bufx;
        }
    }
    set_errno( EMSGSIZE );  //return over size error
    return -1;
}
#endif
/*-------------------------End optimize-------------------------*/


/* tcp_client - set up for a TCP client */
SOCKET tcp_client( char *hname, char *sname )
{
    struct sockaddr_in peer;
    SOCKET s;

    set_address( hname, sname, &peer, "tcp" );
    s = socket( AF_INET, SOCK_STREAM, 0 );
    if ( !isvalidsock( s ) )
  {
    error( 1, errno, "socket call failed" );
  } 

    if ( connect( s, ( struct sockaddr * )&peer, sizeof( peer ) ) )
  {
    error( 1, errno, "connect failed" );
  } 

    return s;
}


/* set_address - fill in a sockaddr_in structure */
void set_address( char *hname, char *sname, struct sockaddr_in *sap, char *protocol )
{
    struct servent *sp;
    struct hostent *hp;
    char *endptr;
    short port;

    bzero( sap, sizeof( *sap ) );
    sap->sin_family = AF_INET;
    if ( hname != NULL )
    {
        if ( !inet_aton( hname, &sap->sin_addr ) )
        {
            hp = gethostbyname( hname );
            if ( hp == NULL )
                error( 1, 0, "unknown host: %s\n", hname );
            sap->sin_addr = *( struct in_addr * )hp->h_addr;
        }
    }
    else
        sap->sin_addr.s_addr = htonl( INADDR_ANY );
    port = strtol( sname, &endptr, 0 );
    if ( *endptr == '\0' )
        sap->sin_port = htons( port );
    else
    {
        sp = getservbyname( sname, protocol );
        if ( sp == NULL )
            error( 1, 0, "unknown service: %s\n", sname );
        sap->sin_port = sp->s_port;
    }
}

//setup an udp server
SOCKET udp_server( char *hname, char *sname )
{
    SOCKET s;
    struct sockaddr_in local;

    set_address( hname, sname, &local, "udp" );
    s = socket( AF_INET, SOCK_DGRAM, 0 );
    if ( !isvalidsock( s ) )
  {
        error( 1, errno, "socket call failed" );
  } 
  
    if ( bind( s, ( struct sockaddr * ) &local, sizeof( local ) ) )
  {
    error( 1, errno, "bind failed" );
  } 
        
    return s;
}

/******************************************* count_client.c******************************/
#include "readn.h"

static char *program_name = NULL;

static void error( int status, int err, char *fmt, ... )
{
    va_list ap;

    va_start( ap, fmt );
    fprintf( stderr, "%s: ", program_name );
    vfprintf( stderr, fmt, ap );
    va_end( ap );
    if ( err )
  {
    fprintf( stderr, ": %s (%d)\n", strerror( err ), err );
  } 
        
    if ( status )
  {
    EXIT( status );
  }         
}

int main( int argc, char **argv )
{
    SOCKET s;
    int rc = 0;
    int len = 0;
    char buf[ 120 ] = {0};

    INIT();
    s = tcp_client( argv[ 1 ], argv[ 2 ] );
    while ( fgets( buf, sizeof( buf ), stdin ) != NULL )
    {
        len = strlen( buf );
        rc = send( s, buf, len, 0 );
        if ( rc < 0 )
    {
      error( 1, errno, "send failed" );
    }  
            
        rc = readline( s, buf, sizeof( buf ) );
        if ( rc < 0 )
    {
      error( 1, errno, "readline failed" );
    }           
        else if ( rc == 0 )
    {
      error( 1, 0, "server terminated\n" );
    }           
        else
    {
      fputs( buf, stdout );
    }           
    }
  
    EXIT( 0 );
}

/********************************* count _server.c******************************/
#include "readn.h"

static char *program_name = NULL;

static void error( int status, int err, char *fmt, ... )
{
    va_list ap;

    va_start( ap, fmt );
    fprintf( stderr, "%s: ", program_name );
    vfprintf( stderr, fmt, ap );
    va_end( ap );
    if ( err )
        fprintf( stderr, ": %s (%d)\n", strerror( err ), err );
    if ( status )
        EXIT( status );
}


int main( int argc, char **argv )
{
    SOCKET s;
    SOCKET s1;
    int rc;
    int len;
    int counter = 1;
    char buf[ 120 ];

    INIT();
    s = tcp_server( NULL, argv[ 1 ] );
    s1 = accept( s, NULL, NULL );
    if ( !isvalidsock( s1 ) )
  {
    error( 1, errno, "accept failed" );
  } 
        
    while ( ( rc = readline( s1, buf, sizeof( buf ) ) ) > 0 )
    {
        sleep( 5 );
        len = sprintf( buf, "received message %d\n", counter++ );
        rc = send( s1, buf, len, 0 );
        if ( rc < 0 )
    {
      error( 1, errno, "send failed" );
    }           
    }
    EXIT( 0 );
}

相应的Makefile文件如下:

all: count_client count_server

CC = gcc

test_server:
        $(CC) count_server.c readn.c -o count _server

test_client:
        $(CC) count _client.c readn.c -o count_client


clean:
        rm -rf  count _server count _client *.o

     以上代码逻辑非常清晰,在TCP客户端与服务器成功建立连接之后,然后客户端接收标准输入中内容,并将其发送给服务器端,服务器在收到内容之后,等待5s之后返回结果。现在我们试图模拟两种情况,那就是在5s之前与之后Kill掉服务器进程,并进行对比。

      首先我们先观察在5s之后客户端成功收到返回结果之后,将服务器端kill掉之后,客户端立即失败,那是因为服务器被kill掉之后,会给客户端发送FIN信号,但是客户端应用程序在接收到FIN时发送出”45454541”是完全合理的,因为发送FIN信号仅仅只是代表它没有数据再发送。此时连接已经不存在,会返回一个RST信号,整个连接终结。整个过程如以下示意图:

         现在我们考虑下在5s之前就将服务器端kill掉的情况,如下图,当server端被kill掉之后(CTRL+C),此时客户端立马返回错误,而不是同之前的需要等待下一次输入之后才知道连接已经终结,二者的主要差别在于前一种情况是阻塞于客户端的fgets函数,而后者是阻塞在readline函数。

         整个过程如下图:


对等方主机崩溃:

       这种情况与应用程序崩溃又是存在区别的,因为后者可以通过发送FIN信号通知对方,而主机崩溃这种情况,只有等到主机重新启动才知道,因此,这种错误有点类似于网络紊乱。现在我们来考虑这样一种对等方主机是在本方TCP准备撤销连接(肯定是未达到重传的最大次数)之前启动的,那二者之间是如何交互的呢?当本方的重传段被对等方接收,但是对等方主机并没有相关的连接记录(重启之后初始化),TCP规范要求应该给发送方发送RST信号,这样会导致本方主动撤销连接,此时若本方应用程序中存在读等待,则会返回ECONNRESET错误,而对于下一次读操作,则会导致SIGPIPE或者EPIPE错误。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值