互联网真棒:感谢 TCP
互联网的强大和普及毋庸置疑,但其可靠性却令人担忧:数据包丢失、链路拥塞、比特错误、数据损坏等问题层出不穷。然而,我们的应用程序却能稳定运行,这背后功劳要归于 TCP 协议。
为什么需要 TCP?
IP 协议只能将数据包送达目标主机,而无法保证数据到达正确的进程并按顺序无损地传输。TCP 协议位于 IP 协议之上,通过端口号将数据交付给正确的进程。此外,TCP 协议能够应对路由器丢包和拥塞等问题,在用户端实现可靠传输,减轻路由器的负担。
TCP 协议负责处理数据包丢失、损坏、重复和乱序等问题,通过重传机制、校验和等手段保障数据传输的可靠性。如果没有 TCP,开发者需要自行实现这些功能,这将大大增加开发难度,并影响应用开发效率。
流量和拥塞控制
网络通信的本质是将数据从一台机器发送到另一台机器。接收方需要一个缓冲区来存储接收到的数据。如果接收方缓冲区不足,会导致数据溢出。因此,TCP 协议引入了流量控制机制,通过窗口字段告知发送方接收方可接受的数据量,防止发送方发送过多的数据。
为了防止网络拥塞,TCP 协议还引入了拥塞控制机制,通过“礼让”和“回退”的行为,避免网络拥堵。1986 年,互联网带宽曾因拥塞崩溃至 40 bps 的低速,拥塞控制机制的引入有效解决了这一问题。
简单 TCP 服务器示例 (C 代码)
以下 C 代码实现了一个简单的 TCP 服务器,用于回显客户端发送的数据:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
int sockfd = -1, clientfd = -1;
void handle_sigint(int sig) {
printf("\nCtrl+C caught, shutting down...\n");
if (clientfd != -1) close(clientfd);
if (sockfd != -1) close(sockfd);
exit(0);
}
int main() {
signal(SIGINT, handle_sigint);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 允许端口重用
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8080),
.sin_addr.s_addr = INADDR_ANY
};
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 5);
printf("Listening on 8080...\n");
clientfd = accept(sockfd, NULL, NULL);
char buf[1024], out[2048];
int n;
while ((n = recv(clientfd, buf, sizeof(buf) - 1, 0)) > 0) {
buf[n] = '\0';
int m = snprintf(out, sizeof(out), "you sent: %s", buf);
printf("response %s %d\n", out, m);
send(clientfd, out, m, 0);
}
close(clientfd);
close(sockfd);
return 0;
}
该服务器监听 8080 端口,接收客户端发送的数据,并在响应中添加 "you sent: " 前缀。
简易 HTTP 服务器
该示例代码还展示了如何创建一个简单的 HTTP/1.1 服务器,发送 HTTP 响应头和正文。
TCP 报文结构
TCP 报文包含源端口、目的端口、序列号、确认号、标志位、窗口大小、校验和、紧急指针和数据等字段。序列号和确认号用于保证数据的可靠传输,标志位用于控制连接的建立和断开,窗口大小用于流量控制。
总结
TCP 协议在互联网中扮演着至关重要的角色,它提供了可靠的数据传输服务,使得各种应用能够稳定运行。从底层协议到应用