recvmsg & sendmsg

recvmsg和sendmsg是套接字编程中用于替代read、readv、recvfrom等函数的高级接口。它们允许处理未连接套接口的协议地址,提供iovec结构的缓冲区数组,以及可选的辅助数据。msg_name和msg_namelen用于处理地址信息,msg_iov和msg_iovlen指定输入/输出缓冲区,msg_control和msg_controllen涉及辅助数据。recvmsg的msg_flags成员在接收过程中由内核更新,而sendmsg则直接使用flags参数。示例展示了recvmsg在接收UDP数据报时如何填充msghdr结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们可以把read,readv, recv, recvfrom 调用替换为recvmsg,各类输出函数调用也可以替换为sendmsg。

 

函数原型:

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);

struct msghdr结构体:

struct msghdr {
    void            *msg_name;           /* protocol address */
    socklen_t        msg_namelen;        /* size of protocol address */
    struct iovec    *msg_iov;            /* scatter/gather array */
    int              msg_iovlen;         /* # elements in msg_iov */
    void            *msg_control;        /* ancillary data (cmsghdr struct) */
    socklen_t        msg_controllen;     /* length of ancillary data */
    int              msg_flags;          /* flags returned by recvmsg() */
};

msg_name和msg_namelen:

这两个成员用于套接口未连接的场合(譬如未连接UDP套接口)。它们类似reacvfrom和sendto的第5和第6个参数:msg_name指向一个套接口地址结构,调用者在其中存放接收者(对于sendmsg调用)或发送者(对于recvmsg调用)的协议地址。如果无需指明协议地址(例如对于TCP套接口或已连接UDP套接口),msg_name应置为空指针。msg_namelen对于sendmsg是一个值参数,对于recvmsg却是一个值-结果参数。

msg_iov和msg_iovlen:

这两个成员指定输入或输出缓冲区数组(即iovec结构数组),类似readv和writev的第2和第3个参数。

struct iovec参考之前文章:
https://blog.youkuaiyun.com/u010034085/article/details/103588187

 

msg_control和msg_controllen:

这两个成员指定可选的辅助数据的位置和大小。

 

对于recvmsg和sendmsg,我们必须区别它们的两个标志变量:

一个是传递值的flags参数,另一个是所传递msghdr结构的msg_flags成员,它传递的是引用,因为传递给函数的是该结构的地址。

  • 只有recvmsg使用msg_flags成员。recvmsg被调用时,flags参数被拷贝到msg_flags成员,并由内核使用其值驱动接收处理过程。内核还依据recvmsg的结构更新msg_flags成员的值。
  • sendmsg忽略msg_flags成员,因为它直接使用flags参数驱动发送处理过程。这一点意味着如果想在某个sendmsg调用中设置MSG_DONTWAIT标志,那就把flags参数设置为该值;把msg_flags成员设置为该值不起作用。

 

如下图所示,展示了一个msghdr结构以及它指向的各种信息,图中假设进程即将对一个UDP套接口调用recvmsg。

  • 图中给协议地址分配了16个字节,给辅助数据分配了20个字节。
  • 为缓冲数据初始化了3个iovec结构构成的数组:第一个指定一个100字节的缓冲区,第二个指定一个60字节的缓冲区,第三个指定一个80字节的缓冲区。
  • 还假设已为这个套接口设置了IP_RECVDSTADDR套接口选项,以接收所读取UDP数报的宿IP地址。

 

假设从192.6.38.100端口2000到达一个170字节的UDP数据报,它的目的地是我们的UDP套接口,宿IP地址为206.168.112.96。如下图所示,展示了recvmsg返回时msghdr结构中的所有信息。(图中被修改过的字段标了阴影)

  • msg_name成员指向的缓冲区被填充一个网际套接字结构地址,其中有所收到的数据报源IP地址和端口号。
  • msg_namelen成员被更新为存放在msg_name所指缓冲区中的数据量。
  • 所取得数据报前100字节数据存放在第一个缓冲区,中60字节数据存放在第二个缓冲区,后10字节存放在第三个缓冲区。
  • msg_control成员指向的缓冲区被填写一个cmsghdr结构.
  • msg_controllen成员被更新为所存放辅助数据的实际数据量。

 

函数使用:

client:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "sys/select.h"
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXSIZE 100

int main(int argc, char ** argv) 
{
	int		sockfd;
	struct	sockaddr_in serv_socket;
	struct	msghdr msg;
	struct	iovec io;
	char	send[] = "hello world";

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);//UDP
	bzero(&serv_socket, sizeof(serv_socket));
	serv_socket.sin_family	= AF_INET;//ipv4
	serv_socket.sin_port	= htons(8888);
	inet_pton(AF_INET, "192.168.1.88", &serv_socket.sin_addr);//服务器IP

	msg.msg_name	= &serv_socket;
	msg.msg_namelen = sizeof(struct sockaddr_in);
	io.iov_base		= send;//"hello world"
	io.iov_len		= sizeof(send);
	msg.msg_iov		= &io;
	msg.msg_iovlen	= 1;

	ssize_t send_size = sendmsg(sockfd, &msg, 0);//发送数据到服务器

	close(sockfd);

	return;
}

 server:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "sys/wait.h"
#include "sys/select.h"
#include "sys/poll.h"
#include <sys/socket.h>
#include <netinet/in.h>

#define MAXSIZE 100

int main(int argc, char ** argv) 
{
	int		sockfd;
	struct	sockaddr_in serv_socket;
	struct	msghdr msg;
	struct	iovec io;
	struct	sockaddr_in  * client_socket = (struct sockaddr_in *) malloc (sizeof(struct sockaddr_in));
	char	buf[MAXSIZE + 1];
	char	ip[16];

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);//UDP
	bzero(&serv_socket, sizeof(serv_socket));
	serv_socket.sin_family		= AF_INET; //IPV4
	serv_socket.sin_addr.s_addr = htonl(INADDR_ANY);//本地任意ip
	serv_socket.sin_port		= htons(8888);

	bind(sockfd, (struct sockaddr *)&serv_socket, sizeof(serv_socket));	

	msg.msg_name	= client_socket;
	msg.msg_namelen = sizeof(struct sockaddr_in);
	io.iov_base		= buf;
	io.iov_len		= MAXSIZE;
	msg.msg_iov		= &io;
	msg.msg_iovlen	= 1;

	ssize_t len = recvmsg(sockfd, &msg, 0);//server 在recvmsg阻塞等待客户端数据

	//解析数据
	client_socket = (struct sockaddr_in *)msg.msg_name;
	inet_ntop(AF_INET, &(client_socket->sin_addr), ip, sizeof(ip));
	int port = ntohs(client_socket->sin_port);
	char * temp = msg.msg_iov[0].iov_base;
	temp[len] = '\0';

	printf("get message from %s[%d]: %s\n", ip, port, temp);

	close(sockfd);

	return;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值