5.1 套接字的非阻塞模式

本文详细介绍了Windows平台下套接字的非阻塞模式,包括其定义、设置方法及在Windows Sockets编程中的优缺点。

  所有Windows平台都支持套接字以阻塞模式和非阻塞模式的方式工作。本节讲述什么是套接字的非阻塞模式,如何设置套接字的非阻塞模式,以及非阻塞套接字在开发Windows Sockets程序中的不足和优势。

<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]。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值