TCP(transmission control protocol)协议全称传输控制协议。它是TCP/IP协议簇中一个非常重要的协议,它工作在IP协议层之上,应用层下面。对网络报文提供可靠的传输保障,采用的技术有发送确认(ACK)机制,超时重传机制,拥塞控制等机制。
•使用TCP进行通信,在传输数据前需要建立连接,连接建立成功之后才能输出数据。建立连接的两端分配内核资源,像端口号,socket描述符。建立连接的过程需要3步,称这个过程为3次握手。
•TCP数据传输是全双工的,即读写操作可以在同一个连接上进行。
•TCP断开连接,传输数据完成之后,需要进程4次挥手断开连接,释放相关的资源。
•TCP是字节流服务,对应的UDP是数据报服务。对应到实际的编程中,理解为发送方和接收方是否进行相等次的读写操作。发送端调用3次send, 接收方可能只需调用1次recv操作就读取了所有的数据。即发送的次数和接收次数并不相等。
•TCP传输是可靠的,采用了ACK机制。发送端发送的每个报文段都必须得到接收方的应答确认,才视为该报文段发送成功。还采用了重传机制,对未收到ACK的报文段进行重传。TCP报文段最终是放在IP数据报中发送,而IP数据报到达接收端可能乱序和重复。接收端的必须对收到的TCP报文段重排整理,然后交付给应用层。
•端口号,分为源端口号和目的端口号,分别占用16bit。源端口号标识报文发送方端口地址,目的端口号标识报文接收方端口地址。我们知道网络层(IP)提供了点到点的传输,即机器A和机器B之间数据传输。而传输层(TCP在传输层)提供了端到端的传输,这里的端指端口,可以通俗理解成‘进程A和进程B’之间的数据交换。这里的端口提供了进程的区分能力。所以IP:PORT可以唯一确定某台机器的某个服务。
•32位序列号,TCP通信包含建立连接、数据传输、释放连接过程中的每个报文都有一个编号。32位序列号表示的正是这个数值。主机A和主机B通信,A发送给B的第一个TCP报文中,序列号被初始化某个随机值ISN(initial sequence number)。在后序的A到B的传输方向上,序列号的值将被系统设置成ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移。从B到A的传输方向上,序列号值确定与A到B是一致的。
•32位确认序列号,用作对另一方发送来的TCP报文段的响应,它的值是收到的TCP报文段的序号值加1。
•4位头部长度,标识该TCP头部数据的长度,单位为32bit,即4个字节。4位最大能表示15,所以TCP头部最长是60字节。
•URG,表示紧急指针是否有效。
•ACK,表示确认号是否有效,携带ACK标志的TCP报文段为确认报文段。
•PSH,提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后序数据腾出空间。
•RST,表示要求对方重新建立连接,有RST标志的报文段为复位报文段。
•SYN,表示请求建立一个连接,携带SYN标志的TCP报文段为同步报文段。
•FIN, 表示通知对方本端要关闭连接了,携带FIN标志的TCP报文段为结束报文段。
•16位窗口大小,表示本端TCP接收缓冲区还能容纳多少个字节的数据,告诉对端发送数据的速度。
•16位校验和,发送方填充,内容是对TCP头部和数据部分的数据计算CRC,用于接收方检验TCP报文段在传输过程中是否损坏。
•16位紧急指针,紧急指针相对当前32位序号的偏移。TCP紧急指针是发送方向接收方发送紧急数据的方法。
•选项,TCP头部是一个变长的结构,前面的20个字节是固定的,整个头部最长为60个字节,所以选项这部分最大长度为40个字节。选项结构如下所示:
常见的TCP选项有7种:
1)kind=0表示选项部分内容结束
2)kind=1没有实际含义,表示空操作,一般用于将TCP选项的总长度填充为4字节的整数倍
3)kind=2表示最大报文长度,通信双方使用该字段选项协商最大报文段长度MSS(max segment size),在TCP协议中将MSS设置为(MTU-40)个字节。在以太网中MSS的值是1460(1500-40)字节。
4)kind=3为窗口扩大系数,前面说的16位窗口,能表示的大小为65525(2^16)字节,实际上TCP模块允许接收的窗口大小远比这个大,窗口扩大系数用于放大这个窗口值,计算公式为 N*2^M, N是16位窗口值,M为位移数。
5)kind=4是选择确认选项,如果某个TCP报文段丢失,则TCP模块会重传最后被确认的TCP报文段后续的所有报文段,这样之前已正确传输的TCP报文段也可能重复发送,降低了TCP性能。开启选择确认选项,不用发送所有未被确认的TCP报文段。
6)kind=5是4)中实际工作的选项,该参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以根据这个并重发丢失的数据块。
7)kind=8是时间戳选项,该选项提供了比较准确计算通信双方之间的回路时间RTT(round trip time)方法,从而为TCP流量控制提供重要信息。
•服务端
1)创建套接字
int socket(int domain, int type, int protocol)
domain参数
可选值 | 类型 |
---|---|
PF_INET | ipv4 |
PF_INET6 | ipv6 |
PF_LOCAL | 本地套接字 |
type参数
可选值 | 说明 |
---|---|
SOCK_STREAM | 字节流,对应TCP |
SOCK_DGRAM | 数据报,对应UDP |
SOCK_RAW | 原始套接字 |
protocol一般写成0
2)bind
int bind(int fd, sockaddr *addr, socklen_t len)
fd为socket调用创建的socket句柄,addr 是一个通用的地址格式,可以传入IPv4或IPv6, len表示传入的地址长度
addr.sin_family = AF_INET //IPv4
addr.sin_family = AF_INET6 //IPv6
addr.sin_port //绑定的端口号
addr.sin_addr.s_addr = htonl(INADDR_ANY) //对于有多张网卡的主机,监听所有的网卡
3)listen
int listen(int socketfd, int backlog)
socketfd 为套接字描述符,backlog为未完成连接队列的大小。这个参数的大小决定了可以接收的并发数目。这个参数越大,并发数目理论上也会越大,但参数设置的过大也会占用过多的系统资源。
4)accept
int accept(int listensockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
listensockfd为监听套接字,就是前面通过bind, listen一系列操作而得到的套接字。返回值有2部分,cliaddr返回连接的客户端的地址, 函数的返回值表示已建立连接的套接字描述符。
•客户端
1)connect
int connect(int sockfd, const struct sockaddr *servaddr , socklen_t addrlen)
sockfd是连接套接字,通过前面的socket 函数创建。servaddr和addrlen分别表示指向套接字地址结构的指针和结构的大小。套接字地址结构必须含有服务器IP地址和端口号
•客户端第一次握手, 调用connect发起握手请求,客户端会给服务端发送SYN报文,服务器会回复客户端ACK。当客户端收到ACK之后,connect函数调用会返回,此时,客户端处于ESTABLISHED状态。
•服务端调用accept并阻塞,当收到客户端连接请求之后,返回请求ACK和服务端与客户端建立连接, 此时处于SEND_RECV状态,当收到客户端发送的对服务端建立连接请求的ACK之后, accept返回。此时,服务端也处于ESTABLISHED状态。