【Linux】UDP与TCP协议
目录
UDP协议
1.1通信流程
1.2函数
socket
bind
sendto
recvfrom
close
1.3实现udp通信
TCP协议
1.1TCP头部结构
1.2通信流程
三次握手
正式通信
四次挥手
1.3协议特性
面向字节流
可靠传输
序列号和确认号
重传机制
流量控制和拥塞控制
1.4常用函数
socket
bind
listen
accept
connect
send
recv
close
1.5实现tcp通信
socket又称套接字,他的存在是为了解决:
- 不同协议的识别TCP UDP
- 不同主机的识别(哪个IP发 哪个IP收)
- 不同进程的识别(哪个端口发 哪个端口收)
UDP协议
UDP(user datagram protocol)的中文叫用户数据协议报,属于传输层。UDP是面向非连接的协议,它不与对方建立连接,而是直接把我要发的数据报发给对方。所以UDP适用于一次传输数据量很少、对可靠性要求不高的或对实时性要求高的应用场景。
1.1通信流程
udp通信直接传输数据,不关心数据是否丢包不会进行重传。
1.2函数
socket
#include <sys/socket.h>
int socket(int family,int type,int protocol);
- 功能:创建一个用于网络通信的socket套接字
- family:协议族(AF_INET(IPv4)、AF_INET6(IPv6)、PF_PACKET(链路层编程))
- type:套接字类(SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报式套接字)SOCK_RAW(原始套接字))
- protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP)一般置0
- 返回值:套接字
bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
- 功能:将socket与本机上的一个端口绑定,随后就可以在该端口监听服务请求
- sockfd:正在监听端口的套接口文件描述符,通过socket获得
- addr:需要绑定的IP和端口
- addrlen:addr的结构体的大小
- 返回值:失败返回一个小于0的值,成功返回大于等于0的值
一般只有服务器端才需要“显示”绑定端口;client的端口号一般由client的OS随机选择,client不需要“显示”地绑定IP和端口号,当client第一次给服务器发消息时OS会自动绑定IP和端口号。
struct sockaddr
{sa_family_t sa_family; // 2字节char sa_data[14] //14字节
};
- struct sockaddr_in:IPv4地址结构
- struct sockaddr:通用地址结构
_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);//填服务器端的信息
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_localport);
local.sin_addr.s_addr = INADDR_ANY;//将套接字与服务器端信息绑定
int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
sendto
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
- 功能:发送数据、
- sockfd:正在监听端口的套接字文件描述符,通过socket获得。
- buf:发送缓冲区,往往是使用者定义的数组,该数组装有要发送的数据
- len:发送缓冲区的大小,单位是字节
- flags:填0即可
- dest_addr:指向接收数据的主机地址信息的结构体,也就是该参数指定数据要发送到哪个主机哪个进程
- addrlen:表示第五个参数所指向内容的长度
- 返回值:成功返回发送数据长度,失败返回-1。
recvfrom
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
- sockfd:正在监听端口的套接口文件描述符,通过socket获得
- buf:接收缓冲区,往往是使用者定义的数组,该数组装有接收到的数据
- len:接收缓冲区的大小,单位是字节
- flags:一般置0
- src_addr:指向发送数据的主机地址信息的结构体,也就是我们可以从该参数获取到数据是谁发出的
- addrlen:表示第五个参数所指向内容的长度
- 返回值:成功返回接收成功的数据长度,失败返回-1
close
#include <unistd.h>
int close(int fd);
- 功能:关闭某个文件描述符
- fd:socket产生的fd(返回值)
1.3实现udp通信
udp通信的简单实现:Linux/udp_echo_server · swi/c++ - 码云 - 开源中国
TCP协议
TCP是面向连接的协议,这是因为在一个应用进程可以开始向另一个应用进程发送数据之前,这两个进程必须先相互“握手”,即它们必须相互发送某些预备报文段,以建立确保数据传输的参数。它有以下几个特点:
- 面向连接:TCP一定是“一对一”的,无法像 UDP 协议那样在同一时刻像多个主机发送消息,即无法做到一对多;
- 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端(当然不是说绝对可靠);
- 基于字节流:消息是“没有边界”的,所以无论我们消息有多大都可以进行传输。并且消息是“有序的”,当前一个消息没有收到的时候,即使它先收到了后面的字节已经收到,那么也不能扔给应用层去处理,同时对重复的报文会自动丢弃。
1.1TCP头部结构
- 序列号:在连接建立时由计算机计算出的初始值,通过 SYN 包传给对端主机,每发送一次新的数据包,就累加一次该序列号的大小。用来解决网络包乱序问题
- 确认应答号:指下次期望收到的数据的序列号,发送端收到这个确认应答以后可以确认确认应答号减一的数据包已经被正常接收。主要用来解决不丢包的问
- ACK:用以指示确认字段中的值是有效的,即该报文段包括一个对已被成功接收的报文段的确认
- RST:用以指示连接的强制拆除,当接收到错误连接时会发送RST位置为1的报文
- SYN:用以指示连接的建立,该位为1的报文表示希望建立连接
- FIN:用以指示连接的终止,该位为1的报文表示希望断开连接
1.2通信流程
三次握手
正式通信
建立连接过程:
服务端创建完监听套接字使用accept等待用户连接,当用户连接时监听套接字会起到"接客"的作用,此时再创建一个新的套接字来进行服务,而监听套接字继续等待新的用户。用于服务的套接字由accept函数返回。
四次挥手
最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。
- 第一次挥手 客户端发出连接释放报文,并且停止发送数据。此时客户端进入FIN-WAIT-1(终止等待1)状态。
- 第二次挥手 服务器端接收到连接释放报文后,发出确认报文(应答)。此时服务端就进入了CLOSE-WAIT 关闭等待状态。
- 第三次挥手 客户端接收到服务器端的确认请求后,客户端就会进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文,服务器将数据发送完毕后,就向客户端发送连接释放报文,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
- 第四次挥手 客户端收到服务器的连接释放报文后,发出应答此时,此时客户端就进入了TIME-WAIT(时间等待)状态,但此时TCP连接还未终止,必须要经过2MSL后(等待最长报文寿命时间,确保发送的应答被服务端收到),当客户端撤销相应的TCB后,客户端才会进入CLOSED关闭状态,服务器端接收到确认报文后,会立即进入CLOSED关闭状态,到这里TCP连接就断开了,四次挥手完成。
1.3协议特性
面向字节流
TCP以字节流的方式传输数据,没有消息边界,需要应用层进行数据的分包和组包12。这种方式使得TCP能够灵活地处理各种长度的数据,但也可能导致黏包问题。黏包问题可以通过以下方法解决:
-
数据定长:规定数据的长度。
-
特殊字符进行间隔:使用特殊字符分隔数据。
-
在不定长数据的应用层头部中添加数据长度字段:接收方根据头部长度,接收该长度的数据。
可靠传输
TCP提供可靠的数据传输服务,能够在数据传输过程中检测和纠正错误,确保数据的完整性、顺序性和可靠性。TCP通过以下机制确保数据的可靠传输:
-
序列号和确认号:每个数据包都有一个序列号,接收方通过确认号告知发送方哪些数据已成功接收。
-
重传机制:如果发送方未收到确认,会重新发送数据包。
-
流量控制:通过滑动窗口机制,动态调整发送速率,避免接收方缓冲区溢出。
-
拥塞控制:通过慢启动、拥塞避免等算法,动态调整发送速率,避免网络拥塞。
序列号和确认号
序列号(seq)
序列号的主要作用是跟踪每个数据包中的数据在整个数据流中的位置。当TCP连接建立时,每一端都会随机选择一个初始序列号(ISN),之后传输的数据包的序列号将基于这个初始值递增。例如,如果一个数据包的序列号是100,且包含100字节的数据,那么下一个数据包的序列号将从200开始。这样,即使数据包在传输过程中到达顺序被打乱,接收端也能根据序列号重新组装数据,确保数据的顺序性和完整性。
确认号(ack)
确认号则是接收端用来告诉发送端哪些数据已经被成功接收的。当接收端收到数据后,它会发送一个确认包,其中包含的确认号是接收到的数据的序列号加1。发送端通过检查这个确认号来确定数据是否需要重传。例如,如果发送端收到的确认号是101,那么它知道序列号为100的数据包已被接收端成功接收。
应答的情况有以下两种:
重传机制
超时重传
当TCP发送一个数据包后,它会启动一个定时器等待接收方的确认应答(ACK)。如果在定时器到期之前没有收到ACK,TCP会认为数据包可能已丢失,并重新发送该数据包。这个过程称为超时重传。超时重传时间(RTO)的设置非常重要,它应该略大于数据包往返时间(RTT)。
快速重传
快速重传是另一种重传机制,它不依赖于定时器,而是基于接收到的重复ACK信号。如果发送方连续收到三个相同的ACK,它会立即重传那些被认为丢失的数据包,而不需要等待定时器超时。
超时重传:
(数据丢包)
(应答丢包)
为什么要等待一个特定时间?
如果该等待时间过长会导致网络空隙时间增大,降低网络传输效率。如果等待时间过短,可能会在应答未到来时进行重传,导致网络负荷增大。
快速重传:
流量控制和拥塞控制
流量控制:
作用:为了解决发送方和接收方发送和接收能力不匹配而导致的数据丢失问题,当发送方发送的太快,接收方来不及接受就会导致数据丢失;
方式:由接收端采用滑动窗口的形式,告知发送方允许/停止发包解决TCP丢包问题。
拥塞控制:
作用:为了解决过多的数据注入到网络导致网络崩溃和超负荷问题;
方式:由发送方采用拥塞窗口的形式去判断网络状态,从而采取不同算法执行TCP动态发包解决网络整体质量问题。
慢启动算法:
发送方先探测网络拥塞程度,并不是一开始就发送大量的数据,发送方会根据拥塞程度增大拥塞窗口。(例如:发2个数据段得到应答,再去尝试发4个数据段...直到找到合适数量的数据段)
1.4常用函数
socket
#include <sys/socket.h>
int socket(int family,int type,int protocol);
- 功能:创建一个用于网络通信的socket套接字
- family:协议族(AF_INET(IPv4)、AF_INET6(IPv6)、PF_PACKET(链路层编程))
- type:套接字类(SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报式套接字)SOCK_RAW(原始套接字))
- protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP)一般置0
- 返回值:套接字
bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
- 功能:将socket与本机上的一个端口绑定,随后就可以在该端口监听服务请求
- sockfd:正在监听端口的套接口文件描述符,通过socket获得
- addr:需要绑定的IP和端口
- addrlen:addr的结构体的大小
- 返回值:失败返回一个小于0的值,成功返回大于等于0的值
listen
#include <sys/types.h>
#include <sys/socket.h>int listen(int sockfd,int backlog)
- 功能:listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。
- sockfd:传入bind的sockfd。
- backlog:请求连接队列的最大长度,这个队列指的是多个客户端都请求建立连接时,会把这些连接暂时存入队列中,以便下一步调用accept接受连接。
- 返回值:成功则返回0,否则-1。
accept
#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd, struct sockaddr* addr, socklen_t addrlen);
- 功能:让服务器接受客户的连接请求。
- sockfd:经过bind和listen的sockfd。
- addr:此处addr参数是传出的,函数成功执行后,存的是客户端的IP和端口。
- addrlen:addr的结构体的大小
- 返回值:如果成功,则会返回一个非负整数,也就是accepted socket的文件描述符,后续的通信则用这个文件描述符,如果失败则返回-1。
connect
#include <sys/types.h>
#include <sys/socket.h>int connect(int sockfd, struct sockaddr* addr, socklen_t addrlen);
- 功能:让服务器接受客户的连接请求。
- sockfd:由socket函数创建的文件描述符。
- addr:保存服务端的IP和端口
- addrlen:addr的结构体的大小
- 返回值:0表示成功,-1表示错误失败。
send
#include <sys/types.h>
#include <sys/socket.h>
int send( SOCKET s, const char FAR *buf, int len, int flags );
- 功能:数据发送函数。
- s:指定发送端套接字描述符。
- buf:指明一个存放应用程序要发送数据的缓冲区。
- len:要发送的数据的字节数。
- flags:一般置0。
- 返回值:n(n大于0意为成功发送 n 个字节;n等于0意为对端关闭连接;n小于0意为出错或者被信号中断或者对端 TCP 窗口太小数据发不出去(send)或者当前网卡缓冲区已无数据可收(recv))
recv
#include <sys/types.h>
#include <sys/socket.h>
int recv(int s, void *buf, int len, unsigned int flags);
- 功能:数据接收函数。
- s:指定接收端套接字描述符
- buf:指向一个缓冲区,用于存放接收到的数据。
- len:缓冲区的长度。
- flags:一般置0。
- 返回值:n(n大于0意为成功接收 n 个字节;n等于0意为对端关闭连接;n小于0意为出错)
close
#include <unistd.h>
int close(int fd);
- 功能:关闭某个文件描述符
- fd:socket产生的fd(返回值)
1.5实现tcp通信
tcp通信的简单实现:Linux/tcp_echo_server · swi/c++ - 码云 - 开源中国