在Socket编程中,发送数据报文可供使用的API函数有send,sendto和sendmsg,下面是关于前两个系统调用的原型:
#include <sys/socket.h>
ssize_t send( int socket, const void *buffer, size_t length, int flags );
请注意它的返回值的类型ssize_t,其含义是signed size。从内核代码中,我们可以看到,在32位系统上,它是int,在64位系统上,它是long。它常用于表示在某一次操作后,缓冲区中可以被读或写的字节数量。相对应的,还有一个数据类型size_t,其含义是unsigned size。常用于表示对象本身的大小,操作sizeof的返回值就是该类型,malloc,memcpy等函数的参数中用该类型表示对象的大小,在32位系统上,它是unsigned int,在64位系统上,它是unsigned long。
send执行成功,会返回被发送出去的数据报文的字节数,如果执行失败,则会返回-1(所以不能返回size_t类型),并且可以从errno上查找到错误原因。
#include <sys/socket.h>
ssize_t sendto(int socket, const void *message, size_t length,
int flags, const struct sockaddr *dest_addr,
socklen_t dest_len);
在内核的实现中,send和sendto系统调用最终都会调用到内核函数:
asmlinkage long sys_sendto(int fd, void __user * buff, size_t len, unsigned flags,
struct sockaddr __user *addr, int addr_len)
在send系统调用中,参数addr被置为NULL,addr_len为0。sys_sendto首先根据传入的描述符fd,找到对应的struct socket结构体。然后构建内核的消息结构struct msghdr:
struct msghdr {
void * msg_name;
int msg_namelen;
struct iovec * msg_iov;
__kernel_size_t msg_iovlen;
void * msg_control;
__kernel_size_t msg_controllen;
unsigned msg_flags;
};
msg_name和msg_namelen就是数据报文要发向的对端的地址信息(即sendto系统调用中的addr和addr_len)。当使用send时,它们的值为NULL和0。msg_iov的定义如下:
struct iovec
{
void __user *iov_base;
__kernel_size_t iov_len;
};
表示存放待发送数据的一个缓冲区,iov_base是缓冲区的起始地址,指向message, iov_len是缓冲区的长度,指向length。msg_iovlen是缓冲区的数量,对于sendto和send来讲,msg_iovlen都是1。 msg_flags即为传入的参数flags,现在暂时不过多的关注flags的应用。msg_control和msg_controllen暂时不关注。
sys_sendto构建完这些后,调用sock_sendmsg继续执行发送流程,传入参数为struct msghdr和数据的长度。忽略中间的一些不重要的细节,sock_sendmsg继续调用__sock_sendmsg,__sock_sendmsg 最后调用struct socket->ops->sendmsg,即对应套接字类型的sendmsg函数,所有的套接字类型的sendmsg函数都是 inet_sendmsg,该函数首先检查本地端口是否已绑定,无绑定则执行自动绑定,而后调用具体协议的sendmsg函数。
下面再来看sendmsg系统调用:
#include <sys/socket.h>
ssize_t sendmsg(int socket, const struct msghdr *message, int flags);
可以看到,它跟send和sendto的最大区别就是struc msghdr由用户来构建完成,对应的内核处理函数是sys_sendmsg。
原文出处http://hi.baidu.com/linux_kernel/blog/item/dfae34fa638927889e51468a.html