TCP和UDP协议是TCP/IP协议的核心。 TCP (Transmission Control Protocol)和UDP(User Datagram Protocol)协议属于传输层协议。其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;而UDP则不为IP提供可靠性、流控或差错恢复功能。一般来说,TCP对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。
传输控制协议(TCP):TCP(传输控制协议)定义了两台计算机之间进行可靠的传输而交换的数据和确认信息的格式,以及计算机为了确保数据的正确到达而采取的措施。协议规定了TCP软件怎样识别给定计算机上的多个目的进程如何对分组重复这类差错进行恢复。协议还规定了两台计算机如何初始化一个TCP数据流传输以及如何结束这一传输。TCP最大的特点就是提供的是面向连接、可靠的字节流服务。
用户数据报协议(UDP):UDP(用户数据报协议)是一个简单的面向数据报的传输层协议。提供的是非面向连接的、不可靠的数据流传输。UDP不提供可靠性,也不提供报文到达确认、排序以及流量控制等功能。它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。因此报文可能会丢失、重复以及乱序等。但由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
核心特点
面向连接:通信前需通过“三次握手”建立连接,通信结束后通过“四次挥手”释放连接。
可靠性:序列号与确认应答:每个数据包标记序号,接收方收到后发送确认(ACK),确保数据按序到达且无遗漏。
超时重传:若未收到ACK,发送方会在超时后重新发送数据包。
流量控制:通过滑动窗口机制动态调整发送速率,防止接收方被数据淹没。
拥塞控制:根据网络拥塞情况(如丢包)调整发送速率,避免网络瘫痪。
全双工通信:同一时间可双向传输数据。
字节流服务:将数据视为无结构的字节流,而非独立的数据包(与UDP的报文传输不同)。
面向连接的TCP
“面向连接”就是在正式通信前必须要与对方建立起连接,是按照电话系统建模的。比如你给别人打电话,必须等线路接通了、对方拿起话筒才能相互通话。
TCP协议是一种可靠的、一对一的、面向有连接的通信协议,TCP主要通过下列几种方式保证数据传输的可靠性:
(1)在使用TCP协议进行数据传输时,往往需要客户端和服务端先建立一个“通道“、且这个通道只能够被客户端和服务端使用,所以TCP传输协议只能面向一对一的连接。
(2)为了保证数据传输的准确无误,TCP传输协议将用于传输的数据包分为若干个部分(每个部分的大小根据当时的网络情况而定),然后在它们的首部添加一个检验字节。当数据的一个部分被接收完毕之后,服务端会对这一部分的完整性和准确性进行校验,校验之后如果数据的完整度和准确度都为100%,在服务端会要求客户端开始数据下一个部分的传输,如果数据的完整性和准确性与原来不相符,那么服务端会要求客户端再次传输这个部分。
客户端与服务端在使用TCP传输协议时要先建立一个“通道”,在传输完毕之后又要关闭这“通道”,前者可以被形象地称为“三次握手”,而后者则可以被称为“四次挥手”。
通道的建立——三次握手:
(1)在建立通道时,客户端首先要向服务端发送一个SYN同步信号。
(2)服务端在接收到这个信号之后会向客户端发出SYN同步信号和ACK确认信号。
(3)当服务端的ACK和SYN到达客户端后,客户端与服务端之间的这个“通道”就会被建立起来。
通道的关闭——四次挥手:
(1)在数据传输完毕之后,客户端会向服务端发出一个FIN终止信号。
(2)服务端在收到这个信号之后会向客户端发出一个ACK确认信号。
(3)如果服务端此后也没有数据发给客户端时服务端会向客户端发送一个FIN终止信号。
(4)客户端在收到这个信号之后会回复一个确认信号,在服务端接收到这个信号之后,服务端与客户端的通道也就关闭了。
TCP协议能为应用程序提供可靠的通信连接,使一台计算机发出的字节流无差错地发往网络上的其他计算机,对可靠性要求高的数据通信系统往往使用TCP协议传输数据。
TCP客户端示例
#include
#include
#include
#include
#include
int main()
{
int sock = 0, valread;
struct sockaddr_in serv_addr;
char *hello = "Hello from client";
char buffer[1024] = {0};
// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
std::cout << "Socket creation error" << std::endl;
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
// 将IPv4地址从文本转换为二进制形式
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0)
{
std::cout << "Invalid address/ Address not supported" << std::endl;
return -1;
}
// 连接到服务器
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
std::cout << "Connection Failed" << std::endl;
return -1;
}
// 发送数据到服务器
send(sock, hello, strlen(hello), 0);
std::cout << "Hello message sent" << std::endl;
// 接收服务器的响应
valread = read(sock, buffer, 1024);
std::cout << "Server: " << buffer << std::endl;
// 关闭套接字
close(sock);
return 0;
}
TCP服务器示例
#include
#include
#include
#include
#include
#include // for memset
int main()
{
int server_fd, new_socket, valread;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
char *hello = "Hello from server";
// 创建套接字文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
{
std::cout << "socket failed" << std::endl;
exit(EXIT_FAILURE);
}
// 绑定套接字到IP地址和端口号上
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)))
{
std::cout << "setsockopt" << std::endl;
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // 使用任何可用的IP地址绑定端口号8080到服务器上。INADDR_ANY表示任何可用的IP地址。
address.sin_port = htons(8080); // 设置端口号8080。htons是主机字节顺序到网络字节顺序的转换。
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0)
{
std::cout << "bind failed" << std::endl;
exit(EXIT_FAILURE);
}
// 监听端口,最多可以有10个未处理的连接请求。backlog设置为10。这意味着最多可以有10个客户端等待连接。listen函数会将套接字转换为被动模式,等待客户端
}
UDP核心特点
无连接性:直接发送数据报(Datagram),无需提前建立连接(如三次握手)。发送方和接收方可以同时发送数据,无需同步。
不可靠传输:不保证数据包的顺序、完整性或重复性。发送方不会等待接收方的确认(ACK),也不会重传丢失的数据包。
低开销与高效率:头部开销小(仅8字节),传输速度快。适合带宽敏感或实时性要求高的场景。
尽最大努力交付:网络层(如IP)负责尽力传输数据包,但不保证最终送达。
无连接的UDP协议
无连接”就是在正式通信前不必与对方先建立连接,不管对方状态就直接发送。与手机短信非常相似:你在发短信的时候,只需要输入对方手机号就OK了。
UDP传输协议是一种不可靠的、面向无连接、可以实现多对一、一对多和一对一连接的通信协议。UDP在传输数据前既不需要建立通道,在数据传输完毕后也不需要将通道关闭。只要客户端给服务端发送一个请求,服务端就会一次性地把所有数据发送完毕。UDP在传输数据时不会对数据的完整性进行验证,在数据丢失或数据出错时也不会要求重新传输,因此也节省了很多用于验证数据包的时间,所以以UDP建立的连接的延迟会比以TCP建立的连接的延迟更低。UDP不会根据当前的网络情况来控制数据的发送速度,因此无论网络情况是好是坏,服务端都会以恒定的速率发送数据。虽然这样有时会造成数据的丢失与损坏,但是这一点对于一些实时应用来说是十分重要的。基于以上三点,UDP在数据传输方面速度更快,延迟更低,实时性更好,因此被广泛地用于通信领域和视频网站当中。
UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。比如,我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方主机发送ICMP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。例如,在默认状态下,一次“ping”操作发送4个数据包。大家可以看到,发送的数据包数量是4包,收到的也是4包(因为对方主机收到后会发回一个确认收到的数据包)。这充分说明了UDP协议是面向非连接的协议,没有建立连接的过程。正因为UDP协议没有连接的过程,所以它的通信效率高;但也正因为如此,它的可靠性不如TCP协议高。QQ就使用UDP发消息,因此有时会出现收不到消息的情况。
//使用socket()函数创建一个UDP套接字。
int udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (udpSocket < 0)
{
std::cerr << "无法创建套接字" << std::endl;
return -1;
}
//定义一个sockaddr_in结构体来存储目标地址和端口。
sockaddr_in serverAddr;
std::memset(&serverAddr, 0, sizeof(serverAddr)); // 清零结构体
serverAddr.sin_family = AF_INET; // 使用IPv4地址
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 目标IP地址,例如本地回环地址
serverAddr.sin_port = htons(12345); // 目标端口号,使用htons进行网络字节序转换
//使用sendto()函数发送数据到指定的地址和端口。
const char *message = "Hello, UDP!";
int messageLen = strlen(message) + 1; // +1为了包括字符串结束符'\0'
int sentBytes = sendto(udpSocket, message, messageLen, 0, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
if (sentBytes < 0)
{
std::cerr << "发送失败" << std::endl;
close(udpSocket);
return -1;
}
std::cout << "已发送 " << sentBytes << " 字节" << std::endl;
//使用recvfrom()函数从指定的地址和端口接收数据。
char buffer[1024]; // 接收缓冲区
sockaddr_in clientAddr; // 可以用来获取发送方的地址信息(可选)
socklen_t clientAddrLen = sizeof(clientAddr);
int receivedBytes = recvfrom(udpSocket, buffer, sizeof(buffer), 0, (struct sockaddr*)&clientAddr, &clientAddrLen);
if (receivedBytes < 0)
{
std::cerr << "接收失败" << std::endl;
close(udpSocket);
return -1;
}
buffer[receivedBytes] = '\0'; // 确保字符串正确结束
std::cout << "接收到: " << buffer << std::endl;
//完成通信后,关闭套接字。
close(udpSocket);