<think>我们被要求详细介绍socket编程的核心内容,包括函数、地址结构体、网络字节序转换、非阻塞设置和错误处理。我们将结合引用内容进行说明。
## 1. Socket编程核心函数
### 1.1 创建套接字: `socket()`
```c
#include <sys/socket.h> // Linux
#include <winsock2.h> // Windows
int socket(int domain, int type, int protocol); // Linux
SOCKET socket(int af, int type, int protocol); // Windows
```
- **参数**:
- `domain/af`: 地址族,如 `AF_INET`(IPv4), `AF_INET6`(IPv6)[^1]
- `type`: 套接字类型,如 `SOCK_STREAM`(TCP), `SOCK_DGRAM`(UDP)
- `protocol`: 通常为0,表示自动选择
- **返回值**:
- Linux: 文件描述符(非负整数)
- Windows: `INVALID_SOCKET`(失败时)
### 1.2 绑定地址: `bind()`
```c
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // Linux
int bind(SOCKET s, const struct sockaddr *addr, int addrlen); // Windows
```
- 将套接字与本地IP地址和端口绑定[^2]
### 1.3 监听连接: `listen()`
```c
int listen(int sockfd, int backlog); // Linux
int listen(SOCKET s, int backlog); // Windows
```
- 将主动套接字转为被动套接字,等待连接请求
- `backlog`: 已完成连接队列的最大长度
### 1.4 接受连接: `accept()`
```c
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // Linux
SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen); // Windows
```
- 从已完成连接队列头返回一个连接
- 返回新的套接字用于数据传输
### 1.5 建立连接: `connect()`
```c
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // Linux
int connect(SOCKET s, const struct sockaddr *name, int namelen); // Windows
```
- 客户端主动连接服务器
### 1.6 数据传输
```c
// TCP
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// UDP
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
```
### 1.7 关闭套接字
```c
// Linux
close(fd);
// Windows
closesocket(s);
```
## 2. 地址结构体说明
### 2.1 通用地址结构体 `sockaddr`
```c
struct sockaddr {
sa_family_t sa_family; // 地址族
char sa_data[14]; // 地址数据
};
```
- 用于类型强制转换,统一接口[^1][^3]
### 2.2 IPv4专用结构体 `sockaddr_in`
```c
struct sockaddr_in {
short sin_family; // 地址族: AF_INET
unsigned short sin_port; // 16位端口号(网络字节序)
struct in_addr sin_addr; // 32位IP地址(网络字节序)
unsigned char sin_zero[8]; // 填充字段
};
struct in_addr {
unsigned long s_addr; // 32位IP地址
};
```
- 实际编程中主要使用此结构体[^3]
### 2.3 IPv6地址结构体 `sockaddr_in6`
```c
struct sockaddr_in6 {
sa_family_t sin6_family; // AF_INET6
in_port_t sin6_port; // 端口号(网络字节序)
uint32_t sin6_flowinfo; // IPv6流信息
struct in6_addr sin6_addr; // IPv6地址
uint32_t sin6_scope_id; // 作用域ID
};
struct in6_addr {
unsigned char s6_addr[16]; // 128位IPv6地址
};
```
## 3. 网络字节序转换
### 3.1 字节序类型
- **大端序(Big-Endian)**: 高位字节在前(网络字节序)
- **小端序(Little-Endian)**: 低位字节在前(主机字节序)
### 3.2 转换函数
```c
#include <arpa/inet.h> // Linux
#include <winsock2.h> // Windows
// 主机到网络(16位)
uint16_t htons(uint16_t hostshort);
// 主机到网络(32位)
uint32_t htonl(uint32_t hostlong);
// 网络到主机(16位)
uint16_t ntohs(uint16_t netshort);
// 网络到主机(32位)
uint32_t ntohl(uint32_t netlong);
```
### 3.3 IP地址转换
```c
// 点分十进制 -> 32位网络字节序
in_addr_t inet_addr(const char *cp); // 已弃用
// 推荐使用
int inet_pton(int af, const char *src, void *dst);
// 网络字节序 -> 点分十进制
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
```
**示例**:
```c
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); // 端口转换
inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr); // IP转换
```
## 4. 非阻塞模式设置
### 4.1 Linux设置方法
```c
#include <fcntl.h>
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
```
### 4.2 Windows设置方法
```c
#include <winsock2.h>
int set_nonblocking(SOCKET sock) {
unsigned long mode = 1;
return ioctlsocket(sock, FIONBIO, &mode);
}
```
### 4.3 跨平台封装
```c
void set_nonblocking(SOCKET sock) {
#ifdef _WIN32
unsigned long mode = 1;
ioctlsocket(sock, FIONBIO, &mode);
#else
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
}
```
## 5. 错误处理机制
### 5.1 错误码获取
```c
// Linux
#include <errno.h>
int err = errno;
// Windows
int err = WSAGetLastError();
```
### 5.2 常见错误类型
| 错误码 (Linux) | 错误码 (Windows) | 说明 |
|----------------|------------------|-----------------------|
| EAGAIN/EWOULDBLOCK | WSAEWOULDBLOCK | 非阻塞操作未完成 |
| ECONNREFUSED | WSAECONNREFUSED | 连接被拒绝 |
| ETIMEDOUT | WSAETIMEDOUT | 连接超时 |
| EINPROGRESS | WSAEINPROGRESS | 非阻塞连接进行中 |
### 5.3 跨平台错误处理示例
```c
int get_last_error() {
#ifdef _WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
const char* get_error_str(int err) {
#ifdef _WIN32
static char buffer[256];
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
buffer, sizeof(buffer), NULL);
return buffer;
#else
return strerror(err);
#endif
}
```
## 总结
Socket编程的核心要点包括:
1. 正确使用套接字函数创建和管理连接
2. 理解地址结构体的内存布局和转换关系
3. 严格遵守网络字节序转换规则
4. 合理设置非阻塞模式提升并发性能
5. 健壮的错误处理保证程序稳定性
> 注:实际开发中建议使用跨平台网络库(如libevent, Boost.Asio)简化开发复杂度[^2]。
§§相关问题§§
1. 如何实现跨平台的TCP心跳机制?
2. 在IPv6环境下socket编程需要注意哪些特殊事项?
3. 如何设计高性能的socket服务器架构?
4. socket编程中常见的安全漏洞有哪些?如何防范?
5. 如何实现UDP的可靠传输机制?</think>### Socket编程核心详解
#### 一、核心函数介绍
1. **socket()** - 创建通信端点
```c
int socket(int domain, int type, int protocol);
```
- `domain`: 协议族(`AF_INET` IPv4, `AF_INET6` IPv6)[^1]
- `type`: 通信类型(`SOCK_STREAM` TCP, `SOCK_DGRAM` UDP)
- `protocol`: 通常设为0(自动选择)
- 返回值:成功返回文件描述符(Linux)或SOCKET句柄(Windows),失败返回-1(Linux)或`INVALID_SOCKET`(Windows)
2. **bind()** - 绑定地址到套接字
```c
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
```
- 将IP地址和端口号关联到套接字
- 服务器端必须调用,客户端可选
3. **listen()** - 设置监听队列
```c
int listen(int sockfd, int backlog);
```
- `backlog`: 已完成连接队列的最大长度(建议5-10)
- 仅用于TCP服务器
4. **accept()** - 接受连接
```c
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
```
- 从已完成连接队列返回新套接字
- 阻塞调用,直到有连接到达
5. **connect()** - 发起连接
```c
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
```
- 客户端连接服务器
- TCP建立连接,UDP指定默认地址
6. **send()/recv()** - 数据传输
```c
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
```
- TCP流式数据传输
- 返回值:实际发送/接收的字节数
#### 二、地址结构体说明
1. **通用地址结构体** `sockaddr`
```c
struct sockaddr {
sa_family_t sa_family; // 地址族(AF_INET等)
char sa_data[14]; // 地址数据
};
```
- 用于类型强制转换的统一接口[^1]
2. **IPv4专用结构体** `sockaddr_in`
```c
struct sockaddr_in {
short sin_family; // 地址族(AF_INET)
unsigned short sin_port; // 16位端口号(网络字节序)
struct in_addr sin_addr; // IP地址
unsigned char sin_zero[8]; // 填充字段
};
struct in_addr {
unsigned long s_addr; // 32位IP地址(网络字节序)
};
```
- 实际编程主要使用此结构体[^3]
- 使用前需用`memset(&addr, 0, sizeof(addr))`初始化
3. **地址转换示例**
```c
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080); // 端口转换
inet_pton(AF_INET, "192.168.1.100", &serv_addr.sin_addr); // IP转换
```
#### 三、网络字节序转换
1. **转换函数**
| 函数名 | 功能描述 | 头文件 |
|---------|----------------------------|-------------------|
| `htons()` | 主机→网络(16位端口) | `<arpa/inet.h>` |
| `ntohs()` | 网络→主机(16位端口) | `<arpa/inet.h>` |
| `htonl()` | 主机→网络(32位IP地址) | `<arpa/inet.h>` |
| `ntohl()` | 网络→主机(32位IP地址) | `<arpa/inet.h>` |
2. **IP地址转换函数**
```c
// 点分十进制 → 32位网络字节序
in_addr_t inet_addr("192.168.1.1"); // 已弃用
inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr); // 推荐[^4]
// 网络字节序 → 点分十进制
char *str = inet_ntoa(addr.sin_addr); // 返回静态缓冲区
inet_ntop(AF_INET, &addr.sin_addr, buf, sizeof(buf)); // 安全版本
```
#### 四、非阻塞模式设置
1. **Linux设置方法**
```c
#include <fcntl.h>
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
```
2. **Windows设置方法**
```c
#include <winsock2.h>
unsigned long mode = 1;
ioctlsocket(sock, FIONBIO, &mode);
```
3. **行为特点**
- 非阻塞connect()立即返回`EINPROGRESS`(Linux)或`WSAEWOULDBLOCK`(Windows)
- 非阻塞recv()无数据时返回`EAGAIN`(Linux)或`WSAEWOULDBLOCK`(Windows)
- 需配合select()/poll()/epoll()等多路复用技术使用
#### 五、错误处理机制
1. **错误码获取**
```c
// Linux
#include <errno.h>
int err = errno;
// Windows
#include <winsock2.h>
int err = WSAGetLastError();
```
2. **常见错误类型**
| 错误码 (Linux) | 错误码 (Windows) | 说明 |
|----------------|--------------------|---------------------|
| EAGAIN | WSAEWOULDBLOCK | 非阻塞操作未完成 |
| ECONNREFUSED | WSAECONNREFUSED | 连接被拒绝 |
| ETIMEDOUT | WSAETIMEDOUT | 连接超时 |
| EINTR | WSAEINTR | 系统调用被中断 |
3. **跨平台错误处理示例**
```c
#ifdef _WIN32
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK) { /* 处理非阻塞连接 */ }
}
#else
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
if (errno == EINPROGRESS) { /* 处理非阻塞连接 */ }
}
#endif
```
4. **健壮性建议**
- 所有系统调用后检查返回值
- 使用`perror()`或`strerror()`输出错误信息
- TCP连接添加超时检测(使用select()设置超时)
- 资源释放:关闭套接字,Windows需调用`WSACleanup()`
> 所有网络程序都应遵循"面向失败编程"原则,假设任何操作都可能失败并做好错误处理[^2]。