socket()函数
函数原型
c
复制代码
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数
| 参数 |
类型 |
说明 |
常用取值 |
| domain |
int |
协议族/地址族 |
AF_INET(IPv4), AF_INET6(IPv6), AF_UNIX(本地) |
| type |
int |
套接字类型 |
SOCK_STREAM(TCP), SOCK_DGRAM(UDP) |
| protocol |
int |
具体协议 |
通常填0,系统自动选择 |
返回值与错误处理
c
复制代码
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket创建失败");
// 常见错误:
// EACCES - 权限不足(如创建原始套接字需要root权限)
// EAFNOSUPPORT - 不支持的地址族
// ENFILE/EMFILE - 文件描述符耗尽(嵌入式系统常见)
exit(EXIT_FAILURE);
}
bind()函数
函数原型
c
复制代码
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
| 参数 |
说明 |
注意事项 |
| sockfd |
socket()返回的文件描述符 |
必须是未绑定的socket |
| addr |
地址结构体指针 |
需要转换为struct sockaddr * |
| addrlen |
地址结构体长度 |
sizeof(struct sockaddr_in) |
地址结构快速初始化
c
复制代码
#include <netinet/in.h>
#include <arpa/inet.h>
struct sockaddr_in server_addr;
// 清空结构体(重要!避免脏数据)
memset(&server_addr, 0, sizeof(server_addr));
// 设置地址族
server_addr.sin_family = AF_INET; // IPv4
// 设置端口(必须转换为网络字节序)
server_addr.sin_port = htons(8080);
// 设置IP地址(三种方式)
// 方式1:绑定到所有网络接口(服务器常用)
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 方式2:绑定到指定IP(字符串转网络字节序)
inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr);
// 方式3:绑定到指定IP(数值转网络字节序)
server_addr.sin_addr.s_addr = inet_addr("192.168.1.100");
// 执行绑定
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind失败");
// 常见错误:
// EADDRINUSE - 地址已被使用(端口被占用)
// EACCES - 权限不足(绑定1024以下端口需要root)
// EINVAL - socket已绑定
close(sockfd);
exit(EXIT_FAILURE);
}
地址重用优化
- 解决"Address already in use"问题
c
复制代码
// 开发调试时常用,允许快速重启服务器
int reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
perror("setsockopt SO_REUSEADDR");
}
// 嵌入式设备中还可设置地址重用超时
struct timeval timeout = {1, 0}; // 1秒
setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &timeout, sizeof(timeout));
listen()函数
函数原型
c
复制代码
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数详解
| 参数 |
说明 |
取值范围 |
建议值 |
| sockfd |
已绑定的socket文件描述符 |
- |
- |
| backlog |
连接请求队列的最大长度 |
1~SOMAXCONN |
嵌入式:5-20 服务器:128+ |
backlog
latex
复制代码
已完成三次握手队列(ESTABLISHED)
│
▼ 等待accept()处理的连接
┌─────────┐
│ 连接1 │ ← 客户端已完成握手,等待accept()
├─────────┤
│ 连接2 │
├─────────┤
│ 连接3 │
├─────────┤
│ ... │
├─────────┤
│ 连接N │ N = backlog
└─────────┘
▲
│
半连接队列(SYN_RCVD)
示例
c
复制代码
// 设置监听队列大小
#define BACKLOG 10 // 嵌入式设备可设为较小值
if (listen(sockfd, BACKLOG) < 0) {
perror("listen失败");
// 常见错误:
// EBADF - 无效的文件描述符
// ENOTSOCK - 不是socket文件描述符
// EOPNOTSUPP - socket不支持listen操作
close(sockfd);
exit(EXIT_FAILURE);
}
printf("服务器开始监听端口 %d...\n", port);
accept()函数
函数原型
c
复制代码
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
| 参数 |
说明 |
输入/输出 |
| sockfd |
监听socket的文件描述符 |
输入 |
| addr |
客户端地址信息结构体 |
输出(可设为NULL) |
| addrlen |
地址结构体长度 |
输入输出(需初始化) |
示例
c
复制代码
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int client_fd;
// 阻塞等待客户端连接(常用模式)
client_fd = accept(sockfd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("accept失败");
// 常见错误:
// EBADF/EINVAL - 无效参数
// ECONNABORTED - 连接已中止
// EWOULDBLOCK - 非阻塞模式无连接
return -1;
}
// 获取客户端信息
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
int client_port = ntohs(client_addr.sin_port);
printf("接受来自 %s:%d 的连接\n", client_ip, client_port);
c
复制代码
// 设置监听socket为非阻塞
fcntl(sockfd, F_SETFL, O_NONBLOCK);
// 非阻塞accept循环
while (1) {
client_fd = accept(sockfd, NULL, NULL);
if (client_fd < 0) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
// 没有新连接,继续等待
usleep(100000); // 休眠100ms
continue;
} else {
// 真正的错误
perror("accept错误");
break;
}
}
// 处理新连接
handle_client(client_fd);
}
connect()函数
函数原型
c
复制代码
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
示例
c
复制代码
// 客户端连接示例
struct sockaddr_in server_addr;
// 清空并初始化地址结构
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080); // 服务器端口
// 设置服务器地址(从命令行参数获取)
if (argc > 1) {
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
} else {
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
}
// 发起连接
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("连接服务器失败");
// 常见错误:
// ECONNREFUSED - 连接被拒绝(服务器未启动)
// ETIMEDOUT - 连接超时
// ENETUNREACH - 网络不可达
close(sockfd);
return -1;
}
printf("成功连接到服务器\n");
连接超时设置
c
复制代码
#include <sys/time.h>
// 设置socket发送超时
struct timeval timeout = {5, 0}; // 5秒超时
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
// 设置连接超时(非标准,但常用技巧)
// 方法1:使用非阻塞connect + select
fcntl(sockfd, F_SETFL, O_NONBLOCK);
connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(sockfd, &writefds);
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int ret = select(sockfd + 1, NULL, &writefds, NULL, &timeout);
if (ret <= 0) {
// 超时或错误
close(sockfd);
return -1;
}
数据传输函数
c
复制代码
#include <unistd.h>
#include <sys/socket.h>
// 通用文件I/O(可用于socket)
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
// 专用socket I/O(提供更多控制)
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
| 函数类型 |
函数原型 |
特点 |
适用场景 |
| 通用I/O |
ssize_t read(int fd, void *buf, size_t count) |
通用文件操作,可用于socket |
简单数据传输 |
| 通用I/O |
ssize_t write(int fd, const void *buf, size_t count) |
通用文件操作,可用于socket |
简单数据传输 |
| Socket专用 |
ssize_t recv(int sockfd, void *buf, size_t len, int flags) |
提供更多控制选项 |
需要特殊处理的socket通信 |
| Socket专用 |
ssize_t send(int sockfd, const void *buf, size_t len, int flags) |
提供更多控制选项 |
需要特殊处理的socket通信 |
| UDP专用 |
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) |
获取发送方地址 |
UDP通信 |
| UDP专用 |
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) |
指定接收方地址 |
UDP通信 |
send()/recv()函数
函数原型
c
复制代码
#include <sys/socket.h>
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);
参数
| 参数 |
类型 |
说明 |
| sockfd |
int |
socket文件描述符 |
| buf |
void* |
数据缓冲区指针 |
| len |
size_t |
缓冲区长度 |
| flags |
int |
控制标志位(按位或组合) |
flags常用选项
c
复制代码
// 常用flags标志位定义
#define MSG_PEEK 0x01 // 窥视数据但不从缓冲区移除
#define MSG_OOB 0x02 // 处理带外数据(紧急数据)
#define MSG_WAITALL 0x04 // 等待接收所有请求的数据
#define MSG_DONTWAIT 0x08 // 非阻塞操作
#define MSG_NOSIGNAL 0x10 // 发送失败时不产生SIGPIPE信号
#define MSG_MORE 0x20 // 有更多数据要发送(TCP_CORK相关)