如何在服务器-客户端程序中使用sendmsg()系统调用并通过ftrace追踪流程
服务器-客户端中sendmsg的使用与ftrace追踪指南
一、sendmsg系统调用核心特点
sendmsg是比send/sendto更灵活的套接字发送接口,支持一次性发送多个缓冲区的数据,还能附带控制信息(比如文件描述符传递),适合复杂的网络数据传输场景。
二、服务器-客户端示例代码
服务器端代码(调用sendmsg发送响应)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 8080 #define BUF_SIZE 1024 int main() { int sockfd, new_fd; struct sockaddr_in serv_addr, cli_addr; socklen_t cli_len = sizeof(cli_addr); char buffer[BUF_SIZE]; // 创建TCP套接字 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket failed"); exit(EXIT_FAILURE); } // 初始化服务器地址 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(PORT); // 绑定套接字到端口 if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { perror("bind failed"); close(sockfd); exit(EXIT_FAILURE); } // 监听连接请求 if (listen(sockfd, 3) == -1) { perror("listen failed"); close(sockfd); exit(EXIT_FAILURE); } printf("服务器监听端口 %d...\n", PORT); // 接受客户端连接 if ((new_fd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len)) == -1) { perror("accept failed"); close(sockfd); exit(EXIT_FAILURE); } // 接收客户端消息 ssize_t n = recv(new_fd, buffer, BUF_SIZE-1, 0); if (n == -1) { perror("recv failed"); close(new_fd); close(sockfd); exit(EXIT_FAILURE); } buffer[n] = '\0'; printf("收到客户端消息: %s\n", buffer); // 构造sendmsg的多缓冲区数据 struct iovec iov[2]; char msg1[] = "服务器响应Part1: "; char msg2[] = "已接收你的请求"; iov[0].iov_base = msg1; iov[0].iov_len = strlen(msg1); iov[1].iov_base = msg2; iov[1].iov_len = strlen(msg2); // 初始化msghdr结构体 struct msghdr msg; memset(&msg, 0, sizeof(msg)); msg.msg_iov = iov; msg.msg_iovlen = 2; // 调用sendmsg发送数据 if (sendmsg(new_fd, &msg, 0) == -1) { perror("sendmsg failed"); close(new_fd); close(sockfd); exit(EXIT_FAILURE); } printf("sendmsg调用完成,已发送响应\n"); // 清理资源 close(new_fd); close(sockfd); return 0; }
客户端代码(触发服务器的sendmsg调用)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8080 #define BUF_SIZE 1024 int main() { int sockfd; struct sockaddr_in serv_addr; char buffer[BUF_SIZE]; // 创建TCP套接字 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket failed"); exit(EXIT_FAILURE); } // 初始化服务器地址 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); // 转换本地IP地址格式 if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { perror("无效的服务器IP"); close(sockfd); exit(EXIT_FAILURE); } // 连接服务器 if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { perror("连接服务器失败"); close(sockfd); exit(EXIT_FAILURE); } // 发送消息给服务器 char *msg = "Hello, Server!"; if (send(sockfd, msg, strlen(msg), 0) == -1) { perror("send failed"); close(sockfd); exit(EXIT_FAILURE); } printf("已发送消息到服务器: %s\n", msg); // 接收服务器响应 ssize_t n = recv(sockfd, buffer, BUF_SIZE-1, 0); if (n == -1) { perror("recv failed"); close(sockfd); exit(EXIT_FAILURE); } buffer[n] = '\0'; printf("收到服务器响应: %s\n", buffer); // 清理资源 close(sockfd); return 0; }
编译与运行
- 编译服务器:
gcc server.c -o server - 编译客户端:
gcc client.c -o client - 启动服务器:
./server - 另开终端启动客户端:
./client
三、用ftrace追踪sendmsg执行流程
1. 前置准备
确保系统已挂载debugfs(无权限则加sudo):
mount -t debugfs none /sys/kernel/debug
进入ftrace追踪目录:
cd /sys/kernel/debug/tracing
2. 配置ftrace追踪规则
- 设置追踪的核心函数:
echo "sys_sendmsg" > set_graph_function
- 启用函数调用图模式:
echo function_graph > current_tracer
- 清空历史追踪日志:
echo > trace
3. 触发sendmsg调用
保持ftrace终端开启,在另一个终端运行服务器和客户端,触发sendmsg执行。
4. 查看追踪结果
回到ftrace终端,查看完整调用链:
cat trace
四、解读sendmsg内核执行流程
从ftrace输出中,可看到sendmsg的核心调用链(不同内核版本略有差异):
sys_sendmsg:用户态系统调用入口,完成参数校验与转换__sys_sendmsg:实际处理sendmsg请求的核心函数sock_sendmsg:分发请求到对应套接字类型的处理逻辑tcp_sendmsg:TCP套接字的发送实现,处理滑动窗口、重传队列等协议逻辑__tcp_sendmsg_locked:加锁确保并发安全的TCP发送逻辑skb_queue_tail:将待发送数据封装为套接字缓冲区(skb)并加入发送队列tcp_write_xmit:尝试将发送队列中的数据推送至网络层dev_queue_xmit:将数据包交给网络设备驱动完成硬件发送
内容的提问来源于stack exchange,提问作者mittu
相关产品推荐
相关产品推荐

