源ip地址和目的ip地址 和端口号
ip地址是找到唯一的主机 端口号是找到主机上对应网络通信的进程
ip+port就可以找到唯一通信的主机进程
端口号:
端口号是一个16位的二进制整数,占用2字节存储空间。
- 端口号用于标识特定进程,告知操作系统应将接收到的数据交由哪个进程处理;
- 通过IP地址与端口号的组合,可以精确定位网络中某台主机的特定进程;
- 每个端口号在同一时刻只能被一个进程独占使用。
一个进程可以有多个端口,而一个端口只能绑定一个进程。
Socket
- IP地址与端口号的组合(IP+Port)能够唯一标识互联网中的一个进程
- 网络通信本质上是通过互联网进程实现的,可通过{源IP,源端口,目标IP,目标端口}这组四元组来精确定位通信双方
- 因此,网络通信本质上也是进程间通信的一种形式
- 我们将IP地址与端口号的组合称为套接字(socket)
网络字节序
由于内存中的多字节数据有大端小端的区分,网络数据流同样有大小端的区分。但收发之间应该统一,所以TCP/IP协议规定网络字节序按照大端收发,如果发送主机是小端就转换成大端后发送。
主机字节序和网络字节序的转换
c
uint32_t htonl; // Convert host long to network byte order
uint16_t htons; // Convert host short to network byte order
uint32_t ntohl; // Convert network long to host byte order
uint16_t ntohs; // Convert network short to host byte order
• 若主机采用小端字节序,这些函数会先将参数进行大小端转换,随后返回结果。
Socket编程API
c
// 创建socket文件描述符 (支持TCP/UDP协议,客户端和服务器通用)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP协议,服务器端使用)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 开始监听socket连接 (TCP协议,服务器端使用)
int listen(int socket, int backlog);
// 接收客户端连接请求 (TCP协议,服务器端使用)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立连接 (TCP协议,客户端使用)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
socket参数:
domain (地址族/协议族)
指定通信使用的协议族,常见取值包括:
AF_INET:IPv4协议,最常用的互联网协议AF_INET6:IPv6协议,下一代互联网协议AF_UNIX或AF_LOCAL:本地通信协议,用于同一主机上的进程间通信AF_PACKET:底层数据包接口,允许直接访问网络层AF_NETLINK:内核与用户空间通信接口
type (套接字类型)
指定通信的语义,常见取值包括:
SOCK_STREAM:面向连接的字节流套接字(TCP)SOCK_DGRAM:无连接的数据报套接字(UDP)SOCK_RAW:原始套接字,允许直接访问底层协议SOCK_SEQPACKET:固定长度的、有序的、可靠的基于连接的数据传输
protocol (具体协议)
指定使用的具体协议,通常设为0让系统根据前两个参数自动选择,但也可以显式指定:
IPPROTO_TCP:TCP传输协议IPPROTO_UDP:UDP传输协议IPPROTO_ICMP:ICMP协议(常用于ping)IPPROTO_RAW:原始IP数据包
bind参数详解:
socket参数:
- 这是一个已经创建的套接字描述符
address参数:
- 这是一个指向sockaddr结构的指针
- 包含要绑定的IP地址和端口号
address_len参数:
- 指定address结构体的长度
- 通常使用sizeof()运算符获取
Listen参数详解
bindlog 是并发连接数 指定最大的监听队列长度 决定了可以连接的最大长度
Accept和Bind参数
accept和connection和bind接口参数类似
Sockaddr结构
sockaddr 是网络编程中用于表示套接字地址的基础结构体,根据不同的协议族和使用场景,主要分为以下三种类型:
-
struct sockaddr
通用的套接字地址结构体,定义在
<sys/socket.h>中。- 包含
sa_family(地址族,如AF_INET、AF_UNIX)和sa_data(地址数据)。 - 通常作为参数类型用于套接字函数(如
bind()、connect()),实际使用时需转换为具体类型(如sockaddr_in)。
- 包含
-
struct sockaddr_in
IPv4 地址专用结构体,定义在
<netinet/in.h>。-
包含
sin_family(地址族,固定为AF_INET)、sin_port(16位端口号,需网络字节序)、sin_addr(IPv4 地址,struct in_addr类型)。 -
示例:绑定到 0.0.0.0:8080:
cstruct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8080); addr.sin_addr.s_addr = INADDR_ANY;
-
-
struct sockaddr_un
Unix 域套接字(本地进程间通信)地址结构体,定义在
<sys/un.h>。-
包含
sun_family(地址族,固定为AF_UNIX)和sun_path(文件路径名)。 -
示例:绑定到
/tmp/my_socket:cstruct sockaddr_un addr; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, "/tmp/my_socket");
-
应用场景差异:
sockaddr_in用于 TCP/UDP 网络通信(如 Web 服务)。sockaddr_un用于同一主机内高效进程间通信(如数据库客户端/服务端)本地通信。- 强制类型转换是常见做法:调用
bind()时需将sockaddr_in或sockaddr_un指针转换为sockaddr*。此举类似于C++中的多态继承机制 父类指针指向子类对象;
UDP套接字基本流程详解
创建UDP套接字
c
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// AF_INET表示IPv4协议
// SOCK_DGRAM表示数据报套接字(UDP)
// 返回值是套接字描述符,失败返回-1
初始化本地地址结构
c
struct sockaddr_in local;
memset(&local, 0, sizeof(local)); // 清空结构体
local.sin_family = AF_INET; // 地址族
local.sin_port = htons(_port); // 端口号(网络字节序)
local.sin_addr.s_addr = INADDR_ANY; // 监听所有网络接口
绑定套接字
c
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
// 将套接字与本地地址绑定
// 成功返回0,失败返回-1
接收数据
c
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0,
(struct sockaddr *)&peer, &len);
// 接收数据并保存发送方地址信息
// buffer: 接收缓冲区
// 最后一个参数是地址结构长度(输入输出参数)
发送数据
c
sendto(_sockfd, buffer, strlen(buffer), 0,
(struct sockaddr *)&peer, len);
// 向指定地址发送数据
// peer: 目标地址结构
// len: 地址结构长度
TCP套接字基本流程详解
服务器端流程
c
// 1. 创建套接字
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 设置地址重用
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 3. 绑定地址
struct sockaddr_in servaddr;
// ...初始化servaddr...
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// 4. 监听连接
listen(listenfd, 5); // 5是等待队列长度
// 5. 接受连接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &len);
// 6. 收发数据
recv(connfd, buffer, sizeof(buffer), 0);
send(connfd, buffer, strlen(buffer), 0);
// 7. 关闭连接
close(connfd);
客户端流程
c
// 1. 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 连接服务器
struct sockaddr_in servaddr;
// ...初始化servaddr...
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// 3. 收发数据
send(sockfd, buffer, strlen(buffer), 0);
recv(sockfd, buffer, sizeof(buffer), 0);
// 4. 关闭连接
close(sockfd);
主要区别:
- TCP需要建立连接(connect/accept),UDP不需要
- TCP使用send/recv,UDP使用sendto/recvfrom
- TCP保证可靠传输,UDP不保证
- TCP是面向流的,UDP是面向数据报的