
在嵌入式Linux系统中,套接字(Socket) 是最灵活且通用的进程间通信(IPC)机制,支持跨设备、跨网络的通信。其核心是基于网络协议栈实现数据交换,适用于本地进程间通信(UNIX域套接字)或远程网络通信(TCP/UDP)。以下是套接字的核心知识点、使用场景及嵌入式开发中的优化技巧。
套接字是网络编程中实现进程间通信的关键机制,它提供了一种跨网络或在同一主机上不同进程之间进行数据交换的方式。在嵌入式 Linux 环境中,套接字常用于设备与设备之间、设备与服务器之间的通信,例如智能家居设备与云端服务器的连接、工业控制设备之间的通信等。
套接字主要分为以下几种类型:
①流套接字(SOCK_STREAM):
②数据报套接字(SOCK_DGRAM):
③原始套接字(SOCK_RAW):
在Linux中,套接字的使用涉及一系列系统调用,主要包括:
①socket():
②bind():
③listen():
④accept():
⑤connect():
⑥send()/recv()(或write()/read()):用于在已建立的连接上发送和接收数据。
⑦close():关闭套接字连接,释放资源。
①创建套接字:使用socket()函数创建一个套接字,指定套接字类型和协议。
②绑定地址:使用bind()函数将套接字绑定到指定的 IP 地址和端口号,以便客户端能够连接到该服务器。
③监听连接:使用listen()函数开始监听指定端口,等待客户端的连接请求。
④接受连接:当有客户端连接请求时,使用accept()函数接受连接,并返回一个新的套接字描述符,用于与该客户端进行通信。
⑤数据传输:使用recv()和send()函数在服务器端和客户端之间进行数据的接收和发送。
⑥关闭套接字:通信结束后,使用close()函数关闭套接字,释放资源。
①创建套接字:与服务器端相同,使用socket()函数创建套接字。
②连接服务器:使用connect()函数连接到服务器指定的 IP 地址和端口号。
③数据传输:连接成功后,使用send()和recv()函数与服务器进行数据交互。
④关闭套接字:通信结束后,关闭套接字。
示例1:本地UNIX域套接字(进程间通信)
服务器端代码:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define SOCKET_PATH "/tmp/embedded_socket"
int main() {
int server_fd, client_fd;
struct sockaddr_un addr;
char buffer[128];
// 创建UNIX域套接字
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
return 1;
}
// 绑定套接字文件
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path)-1);
unlink(SOCKET_PATH); // 确保文件不存在
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) {
perror("bind");
close(server_fd);
return 1;
}
// 监听并接受连接
listen(server_fd, 5);
printf("Server waiting for connection...\n");
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept");
close(server_fd);
return 1;
}
// 接收数据
read(client_fd, buffer, sizeof(buffer));
printf("Received: %s\n", buffer);
// 清理资源
close(client_fd);
close(server_fd);
unlink(SOCKET_PATH); // 删除套接字文件
return 0;
}客户端代码:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define SOCKET_PATH "/tmp/embedded_socket"
int main() {
int sock_fd;
struct sockaddr_un addr;
const char *msg = "Hello from client!";
sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("socket");
return 1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path)-1);
if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) {
perror("connect");
close(sock_fd);
return 1;
}
write(sock_fd, msg, strlen(msg)+1);
close(sock_fd);
return 0;
}示例2:TCP网络通信(跨设备)
服务器端(嵌入式设备):
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_in addr;
socklen_t addr_len = sizeof(addr);
char buffer[BUFFER_SIZE];
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
return 1;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) {
perror("bind");
close(server_fd);
return 1;
}
listen(server_fd, 5);
printf("TCP Server listening on port %d...\n", PORT);
client_fd = accept(server_fd, (struct sockaddr*)&addr, &addr_len);
ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE);
printf("Received: %s\n", buffer);
close(client_fd);
close(server_fd);
return 0;
}在嵌入式Linux应用开发中,套接字广泛应用于网络通信和本地进程间通信。以下是一些典型应用场景:
场景 | 推荐套接字类型 | 优点 |
|---|---|---|
本地进程间高速通信 | UNIX域套接字(SOCK_STREAM) | 无需网络协议栈,低延迟 |
跨设备可靠数据传输 | TCP套接字 | 数据完整,自动重传 |
实时音视频流 | UDP套接字 | 低延迟,容忍丢包 |
多客户端并发连接 | TCP + epoll多路复用 | 高效管理大量连接 |
setsockopt调整SO_RCVBUF)。
fcntl设置O_NONBLOCK标志,结合select/poll实现多路复用。
SO_RCVTIMEO和SO_SNDTIMEO设置套接字超时,避免永久阻塞。
accept、connect、read/write等可能阻塞的函数。
EINTR错误进行重试(如accept被信号打断时)。
bind时设置文件权限(如chmod),限制非授权进程访问。
①连接失败(connect或accept错误)
/tmp/xxx.sock),导致bind失败。
SO_REUSEADDR选项允许地址复用:
int reuse = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));netstat -tulnp检查端口占用情况,或lsof /tmp/xxx.sock查看UNIX域套接字占用。chmod 666 /tmp/xxx.sock)。
②数据收发异常(read/write阻塞或数据不完整)
read会一直阻塞。
select/poll/epoll):
fcntl(sock_fd, F_SETFL, O_NONBLOCK); // 设置为非阻塞int buf_size = 4096;
setsockopt(sock_fd, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size));①文件描述符泄漏
close遗漏),尤其在异常分支(如connect失败后未关闭)。
valgrind工具检测内存和文件描述符泄漏。
int sock_fd = socket(...);
if (connect(sock_fd, ...) == -1) {
perror("connect");
close(sock_fd); // 必须关闭!
return -1;
}②UNIX域套接字文件残留
/tmp/xxx.sock文件未被删除,导致重启时bind失败。
bind前调用unlink(SOCKET_PATH)强制删除残留文件:
unlink(SOCKET_PATH); // 确保文件不存在
bind(server_fd, ...);①高并发场景下连接数受限
accept,导致连接队列溢出(listen的backlog参数过小)。
ulimit -n值过低)。
listen的backlog参数(建议≥128):
listen(server_fd, 128); // 允许更多连接排队ulimit -n 65535 # 临时生效
# 或在/etc/security/limits.conf中永久配置②数据传输延迟或丢包(UDP场景)
setsockopt调整UDP接收缓冲区:
int rcvbuf_size = 1024 * 1024; // 1MB
setsockopt(sock_fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf_size, sizeof(rcvbuf_size));①内存不足导致套接字创建失败
②实时性要求下的阻塞问题
read/write阻塞影响关键任务。
struct timeval tv;
tv.tv_sec = 1; // 1秒超时
tv.tv_usec = 0;
setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));③跨平台通信的字节序问题
uint32_t net_num = htonl(1234); // 主机序转网络序
uint32_t host_num = ntohl(net_num); // 网络序转主机序①处理EINTR错误(系统调用被信号中断)
accept、read等函数可能被信号打断,返回EINTR。
while ((client_fd = accept(server_fd, ...)) == -1) {
if (errno != EINTR) { // 非中断错误才退出
perror("accept");
break;
}
}②使用strace跟踪套接字调用
strace -f -e trace=network ./your_program
bind、connect、sendto),定位错误源头。
③Wireshark抓包分析(网络通信)
tcpdump抓包:tcpdump -i eth0 -w capture.pcap port 8080capture.pcap文件导入Wireshark,分析TCP/UDP数据流。
套接字在嵌入式Linux中的应用需重点关注:
通过合理使用套接字,可实现设备内外的高效通信,是构建分布式嵌入式系统(如IoT网关、工业控制器)的核心技术。