我们可以把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;
}