【嵌入式Linux应用开发基础】进程间通信:套接字
在嵌入式Linux应用开发中,套接字广泛应用于网络通信和本地进程间通信。函数将套接字绑定到指定的 IP 地址和端口号,以便客户端能够连接到该服务器。函数接受连接,并返回一个新的套接字描述符,用于与该客户端进行通信。函数在服务器端和客户端之间进行数据的接收和发送。函数开始监听指定端口,等待客户端的连接请求。函数创建一个套接字,指定套接字类型和协议。:用于在已建立的连接上发送和接收数据。TCP + e
1.1. 套接字类型
套接字主要分为以下几种类型:
①流套接字(SOCK_STREAM):
- 提供可靠、有序的双向字节流通信。
- 使用传输控制协议(TCP),保证数据无差错、无重复发送,并按顺序接收。适用于对数据完整性要求高的场景(如文件传输)。
②数据报套接字(SOCK_DGRAM):
- 提供一种无连接的服务。
- 使用用户数据报协议(UDP),不保证数据传输的可靠性,数据可能在传输过程中丢失或出现重复,且无法保证顺序接收。适用于实时性要求高、允许丢包的场景(如音视频流)。
③原始套接字(SOCK_RAW):
- 允许对较低层次的协议直接访问,如IP、ICMP协议。
- 常用于检验新的协议实现或访问现有服务中配置的新设备。
1.2. 通信模型
- 客户端-服务器模型:服务器监听端口,客户端发起连接请求,建立通信通道。
- 点对点模型:适用于UDP通信,双方直接发送数据报。
二、套接字的系统调用
在Linux中,套接字的使用涉及一系列系统调用,主要包括:
①socket():
- 创建一个套接字,并返回一个套接字描述符。
- 需要指定协议族(如AF_INET用于IPv4)、套接字类型(如SOCK_STREAM或SOCK_DGRAM)和协议(通常设为0,使用默认值)。
②bind():
- 将一个地址(对于网络套接字,通常是IP地址和端口号的组合)赋给套接字。
- 服务器在启动时会调用此函数绑定一个众所周知的地址,用于提供服务。
③listen():
- 使服务器套接字进入被动监听状态,等待客户端的连接请求。
- 需要指定一个队列长度,用于存放来自客户端的连接请求。
④accept():
- 服务器调用此函数接受一个客户端的连接请求。
- 返回一个新的套接字描述符,用于与客户端进行通信。原套接字描述符继续保留,用于处理其他客户端的连接请求。
⑤connect():
- 客户端调用此函数与服务器建立连接。
- 需要指定服务器的地址(IP地址和端口号)。
⑥send()/recv()(或write()/read()):用于在已建立的连接上发送和接收数据。
⑦close():关闭套接字连接,释放资源。
三、套接字通信流程
3.1. 服务器端
①创建套接字:使用socket()函数创建一个套接字,指定套接字类型和协议。
②绑定地址:使用bind()函数将套接字绑定到指定的 IP 地址和端口号,以便客户端能够连接到该服务器。
③监听连接:使用listen()函数开始监听指定端口,等待客户端的连接请求。
④接受连接:当有客户端连接请求时,使用accept()函数接受连接,并返回一个新的套接字描述符,用于与该客户端进行通信。
⑤数据传输:使用recv()和send()函数在服务器端和客户端之间进行数据的接收和发送。
⑥关闭套接字:通信结束后,使用close()函数关闭套接字,释放资源。
3.2. 客户端
①创建套接字:与服务器端相同,使用socket()函数创建套接字。
②连接服务器:使用connect()函数连接到服务器指定的 IP 地址和端口号。
③数据传输:连接成功后,使用send()和recv()函数与服务器进行数据交互。
④关闭套接字:通信结束后,关闭套接字。
3.3. 示例代码
示例1:本地UNIX域套接字(进程间通信)
服务器端代码:
代码语言:javascript
AI代码解释
#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;
}
客户端代码:
代码语言:javascript
AI代码解释
#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网络通信(跨设备)
服务器端(嵌入式设备):
代码语言:javascript
AI代码解释
#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应用开发中的应用
在嵌入式Linux应用开发中,套接字广泛应用于网络通信和本地进程间通信。以下是一些典型应用场景:
4.1. 客户端-服务器模型
- 服务器创建一个套接字并绑定到一个众所周知的地址上,然后进入监听状态。
- 客户端创建一个套接字,并尝试连接到服务器。
- 连接建立后,双方可以通过send()/recv(或write()/read())函数进行数据传输。
4.2. 本地进程间通信
- 套接字也可以用于同一台计算机上的进程间通信。
- 本地套接字的名字是Linux文件系统中的文件名,通常放在/tmp或/usr/tmp目录中。
- 使用方式与网络通信类似,但不需要指定IP地址和端口号。
4.3. 网络编程
- 套接字是实现网络编程的基础。
- 通过套接字,可以实现不同计算机之间的数据传输和通信。
- 在嵌入式系统中,套接字常用于实现设备之间的远程控制和数据交换。
4.4. 适用场景对比
|
场景 |
推荐套接字类型 |
优点 |
|---|---|---|
|
本地进程间高速通信 |
UNIX域套接字(SOCK_STREAM) |
无需网络协议栈,低延迟 |
|
跨设备可靠数据传输 |
TCP套接字 |
数据完整,自动重传 |
|
实时音视频流 |
UDP套接字 |
低延迟,容忍丢包 |
|
多客户端并发连接 |
TCP + epoll多路复用 |
高效管理大量连接 |
五、嵌入式开发中的注意事项
5.1. 资源优化
- 减少内存占用:使用较小的接收缓冲区(通过
setsockopt调整SO_RCVBUF)。 - 轻量级协议:优先选择UDP(无连接开销)或UNIX域套接字(避免网络协议栈)。
5.2. 实时性与稳定性
- 非阻塞模式:使用
fcntl设置O_NONBLOCK标志,结合select/poll实现多路复用。 - 超时机制:通过
SO_RCVTIMEO和SO_SNDTIMEO设置套接字超时,避免永久阻塞。
5.3. 错误处理
- 检查所有系统调用返回值:尤其是
accept、connect、read/write等可能阻塞的函数。 - 处理信号中断:对
EINTR错误进行重试(如accept被信号打断时)。
5.4. 安全性
- 权限控制(UNIX域套接字):通过
bind时设置文件权限(如chmod),限制非授权进程访问。 - 网络隔离:嵌入式设备若暴露网络接口,需配置防火墙规则或禁用无用端口。
六、常见问题
6.1. 连接与通信问题
①连接失败(connect或accept错误)
- 常见原因:
- 地址未释放:服务器重启后未清理之前的UNIX域套接字文件(如
/tmp/xxx.sock),导致bind失败。 - 端口被占用:TCP端口已被其他进程占用(如未正确关闭之前的服务)。
- 权限问题:UNIX域套接字文件权限不足,或网络端口需要root权限(如绑定端口号<1024)。
- 防火墙限制:嵌入式设备防火墙阻止了端口通信(如iptables规则)。
- 地址未释放:服务器重启后未清理之前的UNIX域套接字文件(如
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)