一、套接字地址结构
socket编程需要指定socket地址,不同的协议族有不同的地址定义方式以及由此带来的结构上的差异。这些地址结构的名称形如sockaddr_xxx。每个协议族都有一个唯一的后缀。例如,对于以太网,其结构为sockaddr_in。
(一)通用套接字数据结构

sa_family_t 是一个用于表示地址族(Address Family)的数据类型,在网络编程中用于标识套接字所使用的协议类型。sa_family_t 的具体定义如下:
-
类型定义 :
sa_family_t通常被定义为unsigned short int(无符号短整型),占用 2 个字节。 -
作用 :该类型用于结构体(如
struct sockaddr、struct sockaddr_in、struct sockaddr_in6等)中的sa_family成员,以指定地址族的类型。 -
常见取值 :
AF_INET:表示 IPv4 协议族。AF_INET6:表示 IPv6 协议族。AF_UNIX或AF_LOCAL:表示本地域套接字(用于同一台机器上的进程间通信)。
例如,在 IPv4 套接字地址结构 struct sockaddr_in 中,sin_family 成员即为 sa_family_t 类型,用于标识该地址为 IPv4 地址。
此外,sa_family_t 的设计使得网络函数(如 bind()、connect())能够通过该字段判断传入地址结构的具体类型,并进行相应的类型转换处理。
(二)IPV4套接字数据结构

(三)sockaddr和sockaddr_in的关系
sockaddr 和 sockaddr_in 是网络编程中用于表示网络地址的两个关键结构体,它们的关系可以概括为:**sockaddr_in 是 sockaddr 的一个具体实现,专门用于 IPv4 地址。**
简单来说,sockaddr 是一个通用的地址容器,而 sockaddr_in 是为 IPv4 协议量身定制的、更具体的地址结构。
1.通用 vs. 具体
-
**
sockaddr** :这是一个通用 的套接字地址结构体,定义在<sys/socket.h>中。它的设计初衷是能容纳各种类型的网络地址(如 IPv4、IPv6、Unix 域套接字等)。它只有两个字段:sa_family(地址族,如AF_INET表示 IPv4)和sa_data(14字节的协议特定地址数据)。由于sa_data是一个不透明的字节数组,直接使用它来设置 IP 地址和端口非常不方便。 -
**
sockaddr_in** :这是一个具体 的结构体,定义在<netinet/in.h>中,专门用于表示 IPv4 地址。它包含了清晰、易用的字段:sin_family(固定为AF_INET)、sin_port(端口号)、sin_addr(IP 地址结构体)和sin_zero(填充字段)。程序员通常直接操作sockaddr_in来设置 IPv4 地址信息。
2.结构体大小
为了确保类型安全和兼容性,sockaddr_in 的设计保证了其总大小与 sockaddr 完全相同,都是 16 字节。sin_zero 字段(8字节)的存在就是为了填充,使两个结构体的大小一致。
3.使用方式
在实际编程中,你几乎总是直接使用 sockaddr_in 来构建 IPv4 地址信息,因为它字段清晰,易于理解和操作。然而,系统调用函数(如 bind(), connect(), accept())的参数类型是 struct sockaddr *。因此,你需要将 sockaddr_in 结构体的指针强制转换 为 sockaddr 指针后,再传递给这些函数。
(四)相互转换
cpp
#include <sys/socket.h>
#include <netinet/in.h>
struct sockaddr_in my_addr;
// ... 初始化 my_addr 的 sin_family, sin_port, sin_addr 等字段 ...
// 将 sockaddr_in 转换为 sockaddr,用于系统调用
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
// 反向转换(例如从 accept() 获取客户端地址时)
struct sockaddr client_addr;
socklen_t addr_len = sizeof(client_addr);
accept(sockfd, &client_addr, &addr_len);
// 将获取到的 sockaddr 转换回 sockaddr_in 以便访问
struct sockaddr_in *client_in = (struct sockaddr_in *)&client_addr;
printf("Client IP: %s\n", inet_ntoa(client_in->sin_addr));
printf("Client Port: %d\n", ntohs(client_in->sin_port));
二、套接字编程流程
(一)TCP流程


(二)UDP流程

三、socket函数介绍
socket()用于创建一个套接字。
(一)函数原型
cpp
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
(二)参数详解
1. domain (协议域/地址族)
指定套接字使用的通信协议族,决定了套接字的地址格式和通信方式。常见选项:
- AF_UNIX Unix域套接字 本地进程间通信(IPC)
- AF_INET IPv4互联网协议族 IPv4网络通信
- AF_INET6 IPv6互联网协议族 IPv6网络通信
- AF_IPX IPX协议族(已废弃) Novell网络
- AF_NETLINK 内核用户接口设备 用户与内核通信
- AF_X25 X.25协议族(已废弃) X.25网络
2. type (套接字类型)
指定套接字的通信语义类型,常见选项:
- SOCK_STREAM 面向连接的可靠字节流 TCP 双向、可靠、有序、无重复
- SOCK_DGRAM 无连接的数据报服务 UDP 不可靠、无序、可能有重复
- SOCK_RAW 原始套接字 IP 可访问底层协议头
- SOCK_SEQPACKET 面向连接的有序可靠数据报 SCTP等 类似SOCK_STREAM但保持记录边界
- SOCK_RDM 可靠交付消息 较少使用 提供可靠的消息传递
3.protocol (协议类型)
指定具体的协议,通常设置为0表示自动选择:
- IPPROTO_TCP TCP协议(通常与SOCK_STREAM配合)
- IPPROTO_UDP UDP协议(通常与SOCK_DGRAM配合)
- IPPROTO_SCTP SCTP协议
4.返回值
成功:返回一个非负整数,即套接字文件描述符(socket descriptor)
失败:返回-1,并设置errno表示具体错误,常见错误包括:
- EACCES:权限不足
- EAFNOSUPPORT:不支持指定的地址族
- EMFILE:进程打开的文件描述符过多
- ENFILE:系统全局文件描述符不足
- ENOBUFS或ENOMEM:内存不足
- EPROTONOSUPPORT:不支持指定的协议
四、TCP相关函数
(一)bind()
TCP服务端用于将用于通信的地址和端口绑定到 socket 上。函数的参数应该包含:用于通信的 socket 和服务端的 IP 地址和端口号。ip地址和端口号是放在 socketaddr_in 结构体里面的。
cpp
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
1.参数说明
- sockfd :需要绑定的socket。
- addr :存放了服务端用于通信的地址和端口。
- addrlen:表示 addr 结构体的大小
2.返回值
- 成功:返回
0 - 失败:返回
-1并设置errno
(二)listen()
cpp
#include <sys/socket.h>
int listen(int sockfd, int backlog);
1.参数说明
sockfd:
已绑定的套接字文件描述符(通过socket()创建并已调用bind())backlog:
等待连接队列的最大长度(内核为该套接字排队的最大连接数)
2.返回值
- 成功:返回
0 - 失败:返回
-1并设置errno