网络协议学习——TCP

TCP

传输控制协议(英语:Transmission Control Protocol,缩写为TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据包协议(UDP)是同一层内另一个重要的传输协议。

在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。

以上是我在维基百科抄的。

认识TCP

  • 传输层协议
    传输层有两个协议,各有不同的应用场景,如何使用取决于应用场景,因此两个协议特点尤为重要,相比于UDP协议,TCP用的更加广泛,目前大部分你能使用到的软件(QQ是UDP)基本都是TCP传输协议
  • 有连接
    TCP具有三次握手来建立连接,相比于UDP多了连接这一步,UDP无需连接便可发送接收数据
  • 可靠传输
    可靠性通过发送方检测到丢失的传输数据并重传这些数据。包括超时重传与重复累计确认
  • 面向字节流
    比较灵活,数据无明显边界,但是容易产生粘包问题

TCP协议段格式

协议段数据

  • 源/目标端口:表明数据从哪个进程来去哪个进程
  • 序列号码:32位序列号
    • 如果含有同步化旗标(SYN),则此为最初的序列号;第一个数据比特的序列码为本序列号加一。
    • 如果没有同步化旗标(SYN),则此为第一个数据比特的序列码。
  • 确认号码:期望收到数据开始的序列号,也就是已接收数据序列号+1
  • 数据偏移:4位TCP报头长度,表示TCP头部有多少个32位bit,以4字节来计算数据开始的偏移地址,4位最大可表示15,4字节为1个单位,所以TCP的头部最长为60字节
  • 标志位:6位标志
    • NS:ECN-nonce。
    • CWR:Congestion Window Reduced。
    • ECE:ECN-Echo有两种意思,取决于SYN标志的值。
    • URG:为1表示高优先级数据包,紧急指针字段有效。
    • ACK:为1表示确认号字段有效
    • PSH:为1表示是带有PUSH标志的数据,提示接收端应该尽快将这个报文读取走
    • RST:为1表示出现严重差错。可能需要重现创建TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求。
    • SYN:为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步,我们把带SYN表示的称为同步报文段
    • FIN:为1表示发送方没有数据要传输了,要求释放连接,我们称携带FIN的报文称为结束报文段
  • 窗口:16位长,表示从确认号开始,本报文的接受方可以接收的字节数,即接收窗口大小。用于流量控制。
  • 校验和:16位长,对整个的TCP报文段,包括TCP头部和TCP数据,以16位字进行计算所得。这是一个强制性的字段,接收端校验不通过,则认为数据有问题。
  • 紧急指针:16位长,本报文段中的紧急数据的最后一个字节的序号。
  • 选项字段:最多40字节。每个选项的开始是1字节的kind字段,说明选项的类型。

客户端和服务端连接过程

首先来看大致的连接示意图
TCP示意图

服务端初始化

服务端初始化可分为以下几个部分:

  1. 调用socket,创建套接字文件
  2. 调用bind绑定,绑定服务端地址信息,将套接字文件ip、port、网络类型全部参数给予结构体,绑定到套接字
  3. 调用listen开始监听,声明当前套接字文件作为一个接收连接服务端的套接字文件
  4. 调用accept开始接受连接,没有连接就阻塞等待连接

三次挥手

三次握手建立连接,客户端在创建socket网络套接字后,会通过三次握手这个过程来进行连接:

  1. 调用connect向服务端发起连接请求,发送了一个SYN,阻塞等待服务端回复(第一次握手)
  2. 服务端收到客户端发送的SYN,为了让客户端知道他这准备好了,于是回了一个ACK告诉客户端我准备好了,并且发了一个SYN问客户端准备好没(第二次握手)
  3. 客户端收到服务端回复的ACK,进入ESTABLELISHED状态,准备就绪,并回了一个ACK给服务端说我也准备好了,服务端收到ACK确认了对方状态,于是也进入ESTABLELISHED状态,连接建立(第三次握手)

发送/接收数据

对于第一条数据的发送和接收,服务端永远是等待请求的那个,客户端永远是先发请求的那个。

于是便有了连接建立成功后,便有了服务端调用recv(或调用read)阻塞等待数据接收,接收请求后调用send(或write)回复;客户端则是先调用send(或write)发送一个请求,然后调用recv(或read)等待请求。

重复上面的过程便是发送和接收数据的过程。

TCP提供全双工通信服务,也就是一端读的同时,另一端可以写,与之相对的是半双工,只能有一端读或者一端写。

四次挥手

如果客户端没有请求了,主动关闭,那么便会调用close关闭连接

  1. 客户端调用close,发送了一个FIN包给服务端,告诉服务端要断开了(第一次挥手)
  2. 服务端收到FIN后,告诉客户端我知道了要断开连接了,于是发了一个ACK给客户端,同时recv(或read)会返回0(第二次挥手)
  3. recv和read返回0,于是服务端也调用了close关闭套接字问价,并给客户端发了一个FIN包,告诉客户端我也要关了(第三次挥手)
  4. 客户端收到FIN,回了一个ACK,说我知道了,断了吧(第四次挥手)

以上过程称为TCP的四次挥手,断开连接

TCP可靠传输如何保证

  1. 确认应答机制——发送的每条数据都要确认回复
  2. 超时重传机制——等待回复的过程中,如果没有收到接收端的回复,则会重传数据,超时等待的时间递增,每次等待时间都会较前一次更长,但是有重传次数,重传超过次数便会断开连接
  3. 序号确认机制——为了保证数据接收有序,每段数据都会带有序号信息,如果碰到网络问题,后面的数据先到了,前面数据后道了,仍能通过序号保证有序

TCP服务端和客户端实现代码

服务端流程

  1. 创建套接字(socket)
  2. 绑定地址信息(bind)
  3. 开始监听(listen)
  4. 接收连接(accept)
  5. 接收(recv或read)/发送(send或write)数据
  6. 关闭套接字(close)

客户端流程

  1. 创建套接字(socket)
  2. 绑定地址(不推荐绑定,推荐直接系统分配)
  3. 开始连接(connect)
  4. 发送(send或write)/接收(recv或read)数据
  5. 关闭套接字(close)

代码

github地址:TCP服务端单线程版本
github地址:TCP服务端多线程版本
github地址:TCP客户端

下面贴出的是TCP单线程服务端简单实现代码

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char* argv[])
{
	//1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd < 0)
    {
        perror("socket failed");
        return -1;
    }
	//2.绑定地址
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(argv[2]));
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t server_addr_len = sizeof(server_addr);
    int ret = bind(sockfd, (struct sockaddr*)&server_addr, server_addr_len);
    if (ret < 0)
    {
        perror("bind failed");
        return -1;
    }
	//3.开始监听(listen)
    ret = listen(sockfd, 5);
    if (ret < 0)
    {
        perror("listen failed");
        return -1;
    }
    char buf[1024] = {0};
    while (1)
    {
        int new_sockfd;
        struct sockaddr_in client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
		//4.连接成功后获取新的套接字文件描述符
        new_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addr_len);
        if (new_sockfd < 0)
        {
            perror("accept failed");
            continue;
        }
        while (1)
        {
            memset(buf, 0, 1024);
			//5.接收数据
            ssize_t recv_len = recv(new_sockfd, buf, 1023, 0);
            if (recv_len < 0)
            {
                perror("recv error");
                continue;
            }
            else if (recv_len == 0)
            {
                printf("client link to termitate\n");
                close(new_sockfd);
                break;
            }
            else
            {
                printf("client[ip:%s, prot:%d]\n%s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buf);
                memset(buf, 0, 1024);
                scanf("%s", buf);
				//6.发送数据
                send(new_sockfd, buf, strlen(buf), 0);
            }
        }
        close(new_sockfd);
    }
	//7.关闭套接字
    close(sockfd);
    return 0;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值