A sample for TCP connection

/*
 * on Unix:
 *    cc -c connector.c
 *    cc -o connector connector.o
 *
 * on Windows NT:
 *    open connector.c in Visual Studio
 *    press 'F7' to link -- a project to be created
 *    add wsock32.lib to the link section under project setting
 *    press 'F7' again
 *
 * running:
 *    type 'connector' for usage
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <fcntl.h>
#ifdef WIN32
#include <winsock2.h>
#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif

#ifndef INADDR_NONE
#define INADDR_NONE     0xffffffff
#endif
#define MAX_STRING_LEN  1024
#define BUFSIZE  2048

#ifndef WIN32
#define SOCKET int
#else
#define errno WSAGetLastError()
#define close(a) closesocket(a)
#define write(a, b, c) send(a, b, c, 0)
#define read(a, b, c) recv(a, b, c, 0)
#endif

char buf[BUFSIZE];

static char i_host[MAX_STRING_LEN];  /* site name */
static char i_port[MAX_STRING_LEN];  /* port number */

void err_doit(int errnoflag, const char *fmt, va_list ap);
void err_quit(const char *fmt, ...);
int tcp_connect(const char *host, const unsigned short port);
void print_usage();

//xnet_select x defines
#define READ_STATUS  0
#define WRITE_STATUS 1
#define EXCPT_STATUS 2

/*
s    - SOCKET
sec  - timeout seconds
usec - timeout microseconds
x    - select status
*/

SOCKET xnet_select(SOCKET s, int sec, int usec, short x)
{
 int st = errno;
 struct timeval to;
 fd_set fs;
 to.tv_sec = sec;
 to.tv_usec = usec;
 FD_ZERO(&fs);
 FD_SET(s, &fs);
 switch(x){
  case READ_STATUS:
  st = select(s+1, &fs, 0, 0, &to);
  break;
  case WRITE_STATUS:
  st = select(s+1, 0, &fs, 0, &to);
  break;
  case EXCPT_STATUS:
  st = select(s+1, 0, 0, &fs, &to);
  break;
 }
 return(st);
}

int tcp_connect(const char *host, const unsigned short port)
{
    unsigned long non_blocking = 1;
    unsigned long blocking = 0;
    int ret = 0;
    char * transport = "tcp";
    struct hostent      *phe;   /* pointer to host information entry    */
    struct protoent *ppe;       /* pointer to protocol information entry*/
    struct sockaddr_in sin;     /* an Internet endpoint address  */
    SOCKET s;                    /* socket descriptor and socket type    */
    int error;

#ifdef WIN32
    {
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;
 
        wVersionRequested = MAKEWORD( 2, 0 );
 
        err = WSAStartup( wVersionRequested, &wsaData );
        if ( err != 0 ) {
            /* Tell the user that we couldn't find a usable */
            /* WinSock DLL.                               */
            printf("can't initialize socket library/n");
            exit(0);
        }
    }
#endif    
    
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    
    if ((sin.sin_port = htons(port)) == 0)
        err_quit("invalid port /"%d/"/n", port);
    
    /* Map host name to IP address, allowing for dotted decimal */
    if ( phe = gethostbyname(host) )
        memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
    else if ( (sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE )
        err_quit("can't get /"%s/" host entry/n", host);
    
    /* Map transport protocol name to protocol number */
    if ( (ppe = getprotobyname(transport)) == 0)
        err_quit("can't get /"%s/" protocol entry/n", transport);
    
    /* Allocate a socket */
    s = socket(PF_INET, SOCK_STREAM, ppe->p_proto);
    if (s < 0)
        err_quit("can't create socket: %s/n", strerror(errno));
    
    /* Connect the socket with timeout */
#ifdef WIN32
    ioctlsocket(s,FIONBIO,&non_blocking);
#else
    ioctl(s,FIONBIO,&non_blocking);
#endif
    //fcntl(s,F_SETFL, O_NONBLOCK);
    if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == -1){
        struct timeval tv; 
        fd_set writefds;
        // 设置连接超时时间
        tv.tv_sec = 10; // 秒数
        tv.tv_usec = 0; // 毫秒
        FD_ZERO(&writefds); 
        FD_SET(s, &writefds); 
        if(select(s+1,NULL,&writefds,NULL,&tv) != 0){ 
            if(FD_ISSET(s,&writefds)){
                int len=sizeof(error); 
                //下面的一句一定要,主要针对防火墙 
                if(getsockopt(s, SOL_SOCKET, SO_ERROR,  (char *)&error, &len) < 0)
                    goto error_ret; 
                if(error != 0) 
                    goto error_ret; 
            }
            else
                goto error_ret; //timeout or error happen 
        }
        else goto error_ret; ; 

#ifdef WIN32
        ioctlsocket(s,FIONBIO,&blocking);
#else
        ioctl(s,FIONBIO,&blocking);
#endif

    }
    else{
error_ret:
        close(s);
        err_quit("can't connect to %s:%d/n", host, port);
    }
    return s;
}

void err_doit(int errnoflag, const char *fmt, va_list ap)
{
    int errno_save;
    char buf[MAX_STRING_LEN];

    errno_save = errno; 
    vsprintf(buf, fmt, ap);
    if (errnoflag)
        sprintf(buf + strlen(buf), ": %s", strerror(errno_save));
    strcat(buf, "/n");
    fflush(stdout);
    fputs(buf, stderr);
    fflush(NULL);
    return;
}

/* Print a message and terminate. */
void err_quit(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    err_doit(0, fmt, ap);
    va_end(ap);
    exit(1);
}

#ifdef WIN32
char *optarg;

char getopt(int c, char *v[], char *opts)
{
    static int now = 1;
    char *p;

    if (now >= c) return EOF;

    if (v[now][0] == '-' && (p = strchr(opts, v[now][1]))) {
        optarg = v[now+1];
        now +=2;
        return *p;
    }

    return EOF;
}

#else
extern char *optarg;
#endif

#define required(a) if (!a) { return -1; }

int init(int argc, char *argv[])
{
    char c;
    //int i,optlen;
    //int slashcnt;

    i_host[0]  =  '/0';
    i_port[0]  =  '/0';

    while ((c = getopt(argc, argv, "h:p:?")) != EOF) {
        if (c == '?')
            return -1;
        switch (c) { 
        case 'h':
            required(optarg);
            strcpy(i_host, optarg);
            break;
        case 'p':
            required(optarg);
            strcpy(i_port, optarg);
            break;
        default:
            return -1;
        }
    }

    /* 
     * there is no default value for hostname, port number, 
     * password or uri
     */

    if (i_host[0] == '/0' || i_port[0] == '/0')
        return -1;

    return 1;
}

void print_usage()
{
    char *usage[] =
    {
        "Usage:",
        "    -h    host name",
        "    -p    port",
        "example:",
        "    -h 127.0.0.1 -p 4001",
    };   
    int i;

    for (i = 0; i < sizeof(usage) / sizeof(char*); i++)
        printf("%s/n", usage[i]);
    
    return;
}

int main(int argc, char *argv[])
{
    SOCKET fd;
    int n;

    /* parse command line etc ... */
    if (init(argc, argv) < 0) {
        print_usage();
        exit(1);
    }

    buf[0] = '/0';

    /* pack the info into the buffer */     
    strcpy(buf, "HelloWorld");

    /* make connection to the server */
    fd = tcp_connect(i_host, (unsigned short)atoi(i_port));

    if(xnet_select(fd, 0, 500, WRITE_STATUS)>0){
        /* send off the message */
        write(fd, buf, strlen(buf));
    }
    else{
        err_quit("Socket I/O Write Timeout %s:%s/n", i_host, i_port);
    }

    if(xnet_select(fd, 3, 0, READ_STATUS)>0){
        /* display the server response */
        printf("Server response:/n");
        n = read(fd, buf, BUFSIZE);
        buf[n] = '/0';
        printf("%s/n", buf);
    }
    else{
        err_quit("Socket I/O Read Timeout %s:%s/n", i_host, i_port);
    }
    close(fd);

#ifdef WIN32
    WSACleanup();
#endif

    return 0;
}

TCP重传报文是TCP协议为保证数据可靠传输而采用的重要机制,其原理、机制及特点如下: ### 原理 TCP建立在不可靠的分组传输服务之上,报文可能丢失、延迟、重复和乱序,所以协议必须使用超时和重传机制。若在连接设定的RTO(Retransmission TimeOut,重传超时时间)内,TCP没有收到被计时报文段的ACK(Acknowledgment,确认应答),将会触发超时重传。比如在某些网络环境中,由于网络拥塞等原因,发送方发出的报文可能无法及时到达接收方,接收方也就无法发送对应的ACK,当超过RTO时间时,发送方就会重传该报文[^2][^3]。 ### 机制 - **重传次数**:TCP报文重传的次数根据系统设置的不同而有区分。有些系统,一个报文只会被重传3次,如果重传三次后还未收到该报文的确认,那么就不再尝试重传,直接reset重置该TCP连接;但有些要求很高的业务应用系统,则会不断地重传被丢弃的报文,以尽最大可能保证业务数据的正常交互[^1]。 - **超时重传与退避机制**:TCP将超时重传视为相当重要的事件,当发生这种情况时,它通过降低当前数据发送率来对此进行快速响应。实现它有两种方法:第一种方法是基于拥塞控制机制减小发送窗口大小;另一种方法为每当一个重传报文段被再次重传时,则增大RTO的退避因子。特别是当同一报文段出现多次重传时,RTO值(暂时性地)乘上值γ来形成新的超时退避值。在通常环境下,γ值为1,随着多次重传,γ呈加倍增长:2,4,8,等等。通常γ不能超过最大退避因子(Linux确保其RTO设置不能超过TCP_RTO_MAX,其默认值为120s),一旦接收到相应的ACK,γ会重置为1[^2]。 - **利用重传报文标记丢失报文**:以RACK机制为例,当某个重传报文被确认时,会把该重传报文发出时间点之前发送的还未被ack number或者SACK确认的数据包标记为lost状态,进而触发这些标记为丢失的报文的重传等操作。如No16这个报文确认了No15这个重传报文,No15报文发出的时间点为1.123581s,server端在收到No16这个报文的时候,RACK会把1.122581s之前发送的还未被ack number或者SACK确认的数据包标记为lost,因此No9、No10、No12报文都会被标记为lost状态[^4]。 ### 特点 - **保证可靠性**:通过重传机制,TCP能够确保数据可靠地传输到接收方,即使在网络不稳定的情况下,也能尽量保证业务数据的正常交互,这对于一些对数据准确性要求较高的应用场景(如文件传输、数据库同步等)至关重要[^1]。 - **应对网络拥塞**:TCP会根据超时重传等情况,通过降低数据发送率(如减小发送窗口大小、增大RTO退避因子)来应对网络拥塞,避免网络进一步恶化,保证网络的稳定性和公平性[^2]。 ```python # 以下是一个简单的伪代码示例,模拟TCP超时重传的基本逻辑 import time # 模拟发送报文 def send_packet(packet): print(f"Sending packet: {packet}") # 模拟接收ACK def receive_ack(): # 模拟网络延迟,随机决定是否收到ACK import random if random.random() < 0.8: # 80%的概率收到ACK return True return False # 模拟TCP超时重传 def tcp_retransmission(packet, max_retries=3, initial_rto=1): retries = 0 rto = initial_rto while retries < max_retries: send_packet(packet) start_time = time.time() while time.time() - start_time < rto: if receive_ack(): print(f"Received ACK for packet: {packet}") return True print(f"Timeout! Retrying packet: {packet}, Retry count: {retries + 1}") rto *= 2 # 增大RTO退避因子 retries += 1 print(f"Max retries reached. Resetting TCP connection for packet: {packet}") return False # 测试 packet = "Sample Packet" tcp_retransmission(packet) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值