深入理解TCP协议及其源代码

TCP/IP数据包结构

序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。

确认号ack:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。

确认ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效

同步SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。

终止FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接

PS:ACK、SYN和FIN这些大写的单词表示标志位,其值要么是1,要么是0;ack、seq小写的单词表示序号。

  1. 字段含义
    URG 紧急指针是否有效。为1,表示某一位需要被优先处理
    ACK 确认号是否有效,一般置为1。
    PSH 提示接收端应用程序立即从TCP缓冲区把数据读走。
    RST 对方要求重新建立连接,复位。
    SYN 请求建立连接,并在其序列号的字段进行序列号的初始值设定。建立连接,设置为1
    FIN 希望断开连接。
    三次握手过程理解
    深入理解TCP协议及其源代码_第1张图片

第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

client端代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define N 64

int main( int argc, char *argv[] )
{
    int sockfd;
    struct sockaddr_in servaddr, myaddr;
    char buf[N] = {0};

    if (argc < 3)
    {
        printf("usage : %s ip port\n", argv[0]);
        return 0;
    }

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(-1);
    }


#if 0
    memset(&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(8000);
    myaddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(bind(sockfd, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1)
    {
        error("bind");
        exit(-1);
    }

#endif

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(atoi(argv[2]));
    servaddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
    {
        perror("socket");
        exit(-1);
    }


    printf(">");
    while(fgets(buf, N, stdin) != NULL)
    {
        send(sockfd, buf, strlen(buf), 0);
        memset(buf, 0, sizeof(buf));
        recv(sockfd, buf, N, 0);
        printf("%s\n", buf);

        printf(">");
    }

    close(sockfd);

    return 0;
}

server端代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define N 64

int main( int argc, char *argv[] )
{
    int listenfd, connfd;
    struct sockaddr_in myaddr, peeraddr;
    socklen_t len;
    char buf[N] = {0};
    ssize_t n;

    if(argc < 3)
    {
        printf("Usage: %s ip port\n", argv[0]);
        return 0;
    }

    if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("socket");
        exit(-1);
    }

    memset(&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(atoi(argv[2]));
    myaddr.sin_addr.s_addr = inet_addr(argv[1]);

    if(bind(listenfd, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1)
    {
        perror("bind");
        exit(-1);
    }

    if(-1 == listen(listenfd, 5))
    {
        perror("listen");
        exit(-1);
    }


    memset(&peeraddr, 0, sizeof(peeraddr));
    len = sizeof(peeraddr);

    while(1)
    {
        if((connfd = accept(listenfd, (struct sockaddr *)&peeraddr, &len)) == -1)
        {
            perror("accept");
            exit(-1);
        }

        printf("from %s:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

        while(1)
        {
            memset(buf, 0, sizeof(buf));
            n = recv(connfd, buf, N, 0);
            if( n == 0 )
            {
                break;
            }

            buf[n] = '\0';
            printf("n=%d %s", n, buf);

            send(connfd, buf, n, 0);
        }
        close(connfd);
    }

    return 0;
}

运行结果:
深入理解TCP协议及其源代码_第2张图片

代码分析

 TCP编程的客户端一般步骤是:
  1、创建一个socket,用函数socket();
  2、设置socket属性,用函数setsockopt();* 可选
  3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
  4、设置要连接的对方的IP地址和端口等属性;
  5、连接服务器,用函数connect();
  6、收发数据,用函数send()和recv(),或者read()和write();
  7、关闭网络连接;

你可能感兴趣的:(深入理解TCP协议及其源代码)