TCP 与 UDP 基础

Posted by KalosAner on January 13, 2025

一、引言

TCP 和 UDP 都是传输层协议,意味着其都是控制传输的协议。这里的传输并不是从 A 地到 B 地的传输,而是忽略掉网络层及其之下层之后的端口到端口的传输,至于从 A 地到 B 地的传输则由网络层 IP 协议进行寻路和传输。TCP 和 UDP 主要做的是控制从端口到端口的传输过程,隐藏具体寻路细节,但是考虑寻路过程可能发生的丢包。

TCP 和 UDP 都会用到 bind 函数,这个函数可以将套接字跟 IP 和 port 绑定,如果不进行绑定,在发送信息前没有明确的 port 导致其他程序也无法发送信息到该套接字。

TCP 和 UDP 可以在系统上同时使用相同的端口号,因为它们是不同的协议。

二、TCP(Transmission Control Protocol)

TCP 是基于流的套接字协议。

服务端开始时会先调用 socket 创建套接字,然后使用 bind 为套接字分配 IP 和 port,然后调用 listen 函数会进入等待连接请求状态并创建一个等待请求队列,之后客户端才能调用 connect。之后服务端再调用 accept(accept 有阻塞和非阻塞两种,可以通过套接字设置)从等待请求队列中响应其中一个请求并返回一个套接字用于通信,该套接字只用与这一个连接。listen 使用的套接字仅用于监听。

客户端开始时也会先调用 socket 创建套接字,然后调用 connect 连接服务端,connect 会为客户端的套接字分配 IP 和 port。

然后就是读写部分,Linux 上写可以使用 writesend,读可以使用 readrecv。Windows 上写可以使用 send,读可以使用 recv

但是由于 TCP 传输不存在数据边界,因此调用读函数的次数和写函数可能不同,如果只调用一次读函数并且过早地调用读函数可以能导致读的数据不全,如果可以知道需要读的长度可以使用循环调用读函数解决这个问题。

三、UDP(User Datagram Protocol)

UDP 是基于消息的套接字协议。

服务端开始时会先调用 socket 创建套接字,然后使用 bind 为套接字分配 IP 和 port,然后就可以传输数据了。

客户端开始时也会先调用 socket 创建套接字,然后也可以调用 connect 函数,也可不调用这个函数,然后也可以进行传输数据了。

UDP 也调用 connect 函数时,但它并不会像 TCP 那样触发三次握手的过程。UDP 是一种无连接的协议,而 connect 函数在 UDP 中的作用与 TCP 中的作用不同。当对一个 UDP 套接字调用 connect 时,实际上并未建立一个真正的连接。相反,它只是为该套接字指定了一个默认的目标地址和端口号,使后续的发送 (sendsendto) 和接收 (recvrecvfrom) 操作变得更加方便。

然后是读写部分

客户端不调用 connect 时和服务端读写使用同样的函数,读使用 recvfrom,写使用 sendto

客户端调用 connect 后可以使用与 TCP 一样的读写方式,虽然没有 TCP 的拥塞控制。

UDP 是有数据边界的,所以读和写的次数是对应相等的。

四、函数原型

Windows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <winsock2.h>

SOCKET socket(int af, int type, int protocol);

int listen(SOCKET s, int backlog);

int bind(SOCKET s, const struct sockaddr *name, int namelen);

int connect(SOCKET s, const struct sockaddr *name, int namelen);

int send(SOCKET s, const char *buf, int len, int flags);

int recv(SOCKET s, char *buf, int len, int flags);

int recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen);

int sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen);

Linux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

int listen(int sockfd, int backlog);

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

ssize_t write(int fd, const void *buf, size_t count);

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t read(int fd, void *buf, size_t count);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

源码

TCP

hello_client.c

hello_server.c

hello_client_win.c

hello_server_win.c

UDP

uecho_client.c

uecho_server.c

uecho_client_win.c

uecho_server_win.c