Socket层实现系列 — send()类发送函数的实现
主要内容:socket发送函数的系统调用、Socket层实现。
内核版本:3.15.2
我的博客:http://blog.csdn.net/zhangskd
发送流程图
以下是send()、sendto()、sendmsg()和sendmmsg()的发送流程图,这四个函数除了在系统调用层面
上有些差别,在Socket层和TCP层的实现都是相同的。
应用层
应用层可以使用以下Socket函数来发送数据:
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int s, const void *buf, size_t len, int flags);
ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
ssize_t sendmsg(int s, const struct msghdr *msg, int flags);
int sendmmsg(int s, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags);
这些发送函数有什么区别呢?
当flags为0时,send()和write()功能相同。
send(s, buf, len, flags)和sendto(s, buf, len, flags, NULL, 0)功能相同。
write()和send()在套接字处于连接状态时可以使用,而sendto()、sendmsg()和sendmmsg()在任何时候都可用。
用户层的数据最终都是以消息头来描述的。
struct msghdr {
void *msg_name; /* optional address,目的地址 */
socklen_t msg_namelen; /* size of address,目的地址的长度 */
struct iovec *msg_iov; /* scatter/gather array,分散的数据块数组 */
size_t msg_iovlen; /* #elements in msg_iov,分散的数据块个数 */
void *msg_control; /* ancillary data, 控制数据 */
socklen_t msg_controllen; /* ancillary data buffer len,控制数据的长度 */
int msg_flags; /* flags on received message */
};
/* Structure for scatter/gather I/O. */
struct iovec {
void *iov_base; /* Pointer to data. */
size_t iov_len; /* Length of data. */
};
发送默认为阻塞发送,也可以设置为非阻塞发送。
非阻塞标志:O_NONBLOCK、MSG_DONTWAIT
When the message does not fit into the send buffer of the socket, send() normally blocks, unless the
socket has been placed in non-blocking I/O mode.
Enables non-blocking operations; if the operation would block, EAGAIN is returned (this can also be enabled
using the O_NON-BLOCK with the F_SETEL fcntl(2)).
系统调用
发送函数是由glibc提供的,声明位于include/sys/socket.h中,实现位于sysdeps/mach/hurd/connect.c中,
主要是用来从用户空间进入名为sys_socketcall的系统调用,并传递参数。sys_socketcall()实际上是所有
socket函数进入内核空间的共同入口。
SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
{
...
switch(call) {
...
case SYS_SEND:
err = sys_send(a0, (void __user *)a1, a[2], a[3]);
break; case SYS_SENDTO:
err = sys_sendto(a0, (void __user *)a1 a[2], a[3], (struct sockaddr __user *)a[4], a[5]);
break; ...
case SYS_SENDMSG:
err = sys_sendmsg(a0, (struct msghdr __user *)a1, a[2]);
break; case SYS_SENDMMSG:
err = sys_sendmmsg(a0, (struct msghdr __user *)a1, a[2], a[3]);
break;
...
}
}
send()其实是sendto()的一种特殊情况。
SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len, unsigned, flags)
{
return sys_sendto(fd, buff, len, flags, NULL, 0);
}
sendto()初始化了消息头,接着就调用sock_sendmsg()来处理。
/* Send a datagram to a given address. We move the address into kernel space
* and check the user space data area is readable before invoking the protocol.
*/ SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len, unsigned, flags,
struct sockaddr __user *, addr, int, addr_len)
{
struct socket *sock;
struct sockaddr_storage address;
int err;
struct msghdr msg;
struct iovec iov;
int fput_needed; if (len > INT_MAX)
len = INT_MAX; /* 通过文件描述符fd,找到对应的socket实例。
* 以fd为索引从当前进程的文件描述符表files_struct实例中找到对应的file实例,
* 然后从file实例的private_data成员中获取socket实例。
*/
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (! sock)
goto out; /* 初始化消息头 */
iov.iov_base = buff;
iov.iov_len = len;
msg.msg_name = NULL;
msg.msg_iov = &iov;
msg.msg_iovlen = 1; /* 只有一个数据块 */
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_namelen = 0; if (addr) {
/* 把套接字地址从用户空间拷贝到内核空间 */
err = move_addr_to_kernel(addr, addr_len, &address);
if (err < 0)
goto out_put; msg.msg_name = (struct sockaddr *)&address;
msg.msg_namelen = addr_len;
} /* 如果设置了非阻塞标志 */
if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;
msg.msg_flags = flags; /* 调用统一的发送入口函数sock_sendmsg() */
err = sock_sendmsg(sock , &msg, len); out_put:
fput_light(sock->file, fput_needed);
out:
return err;
}
struct msghdr {
void *msg_name; /* ptr to socket address structure */
int msg_namelen; /* size of socket address structure */
struct iovec *msg_iov; /* scatter/gather array,分散的数据块数组 */
__kernel_size_t msg_iovlen; /* #elements in msg_iov,分散的数据块个数 */
void *msg_control; /* ancillary data, 控制数据 */
__kernel_size_t msg_controllen; /* ancillary data buffer len,控制数据的长度 */
unsigned int msg_flags; /* flags on received message */
};
/* Structure for scatter/gather I/O. */
struct iovec {
void *iov_base; /* Pointer to data. */
__kernel_size_t iov_len; /* Length of data. */
};
/* For recvmmsg/ sendmmsg */
struct mmsghdr {
struct msghdr msg_hdr;
unsigned int msg_len;
};
sock_sendmsg()在初始化异步IO控制块后,调用__sock_sendmsg()。
int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
struct kiocb iocb;
struct sock_iocb siocb;
int ret; init_sync_kiocb(&iocb, NULL);
iocb.private = &siocb; ret = __sock_sendmsg(&iocb, sock, msg, size); /* iocb queued, will get completion event */
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&iocb); return ret;
} /* AIO控制块 */
struct kiocb {
struct file *ki_filp;
struct kioctx *ki_ctx; /* NULL for sync ops,如果是同步的则为NULL */
kiocb_cancel_fn *ki_cancel;
void *private; /* 指向sock_iocb */ union {
void __user *user;
struct task_struct *tsk; /* 执行io的进程 */
} ki_obj; __u64 ki_user_data; /* user's data for completion */
loff_t ki_pos;
size_t ki_nbytes; /* copy of iocb->aio_nbytes */ struct list_head ki_list; /* the aio core uses this for cancellation */
/* If the aio_resfd field of the userspace iocb is not zero,
* this is the underlying eventfd context to deliver events to.
*/
struct eventfd_ctx *ki_eventfd;
};
__sock_sendmsg()会调用Socket层的发送函数,如果是SOCK_STREAM,
那么接着就调用inet_sendmsg()处理。
static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size)
{
int err = security_socket_sendmsg(sock, msg, size);
return err ?: __sock_sendmsg_nosec(iocb, sock, msg, size);
} static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size)
{
struct sock_iocb *si = kiocb_to_siocb(iocb);
si->sock = sock;
si->scm = NULL;
si->msg = msg;
si->size = size; /* 调用Socket层的操作函数,如果是SOCK_STREAM,则proto_ops为inet_stream_ops,
* 函数指针指向inet_sendmsg()。
*/
return sock->ops->sendmsg(iocb, sock, msg, size);
}
sendmsg()和sendmmsg()在系统调用函数中也是拷贝用户空间的数据到内核消息头,最后调用
Socket层的发送函数inet_sendmsg()进行下一步处理,这里不再赘述。
Socket层
SOCK_STREAM套接口的socket层操作函数集实例为inet_stream_ops,其中发送函数为inet_sendmsg()。
const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
...
.sendmsg = inet_sendmsg,
...
};
inet_sendmsg()主要调用TCP层的发送函数tcp_sendmsg()来处理。
int inet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size)
{
struct sock *sk = sock->sk;
sock_rps_record_flow(sk); /* We may need to bnd the socket.
* 如果连接还没有分配本地端口,且允许自动绑定,那么给连接绑定一个本地端口。
* tcp_prot的no_autobaind为true,所以TCP是不允许自动绑定端口的。
*/
if (! inet_sk(sk)->inet_num && ! sk->sk_prot->no_autobind && inet_autobind(s))
return -EAGAIN; /* 如果传输层使用的是TCP,则sk_prot为tcp_prot,sendmsg指向tcp_sendmsg() */
return sk->sk_prot->sendmsg(iocb, sk, msg, size);
} /* Automatically bind an unbound socket. */
static int inet_autobind(struct sock *sk)
{
struct inet_sock *inet; /* We may need to bind the socket. */
lock_sock(sk); /* 如果还没有分配本地端口 */
if (! inet->inet_num) { /* SOCK_STREAM套接口的TCP操作函数集为tcp_prot,其中端口绑定函数为
* inet_csk_get_port()。
*/
if (sk->sk_prot->get_port(sk, 0)) {
release_sock(sk);
return -EAGAIN;
}
inet->inet_sport = htons(inet->inet_num);
} release_sock(sk);
return 0;
}
Socket层实现系列 — send()类发送函数的实现的更多相关文章
- Socket层实现系列 — I/O事件及其处理函数
主要内容:Socket I/O事件的定义.I/O处理函数的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd I/O事件定义 sock中定义了几个I/ ...
- Socket层实现系列 — 信号驱动的异步等待
主要内容:Socket的异步通知机制. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了几个IO事件:状态改变事件.有数据可读事 ...
- Socket层实现系列 — connect()的实现
主要内容:connect()的Socket层实现.期间进程的睡眠和唤醒. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 应用层 int connect( ...
- Socket层实现系列 — 睡眠驱动的同步等待
主要内容:Socket的同步等待机制,connect和accept等待的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了 ...
- Socket层实现系列 — bind()的实现(一)
bind()函数的使用方法很简单,但是它是怎么实现的呢? 笔者从应用层出发,沿着网络协议栈,分析了bind()的系统调用.Socket层实现,以及它的TCP层实现. 本文主要内容:bind()的系统调 ...
- Socket层实现系列 — accept()的实现(一)
本文主要介绍了accept()的系统调用.Socket层实现,以及TCP层实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int accept(int soc ...
- Socket层实现系列 — getsockname()和getpeername()的实现
本文主要介绍了getsockname()和getpeername()的内核实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int getsockname(in ...
- Socket层实现系列 — listen()的实现
本文主要分析listen()的内核实现,包括它的系统调用.Socket层实现.半连接队列,以及监听哈希表. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int l ...
- Socket层实现系列 — bind()的实现(二)
本文主要内容:bind()的TCP层实现.端口的冲突处理,以及不同内核版本的实现差异. 内核版本:3.6 Author:zhangskd @ csdn blog TCP层实现 SOCK_STREAM套 ...
随机推荐
- bzoj2500幸福的道路 树形dp+单调队列
2500: 幸福的道路 Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 434 Solved: 170[Submit][Status][Discuss ...
- 阿里云负载均衡SSL证书配置
阿里云负载均衡SSL证书 转载请注明地址:http://www.cnblogs.com/funnyzpc/p/8908461.html 好久了呢,距上篇博客的这段时间中:考试.搬家.工作赶工.业务考察 ...
- SpringBoot中跨域问题
项目中经常会遇到浏览器跨域的问题,解决方式在启动类中配置 @Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigur ...
- VMWare - Ubuntu 64 (16.04)之扩容介绍
背景 貌似是一个老生常谈的问题哈,由于自己之前也没有弄过,今天正好有时间稍微折腾了一下. 这里就选择最简单的方式来为大家呈现. VMWare 的设置 没有什么可以过多说的,完全是图形操作.这里直接上图 ...
- 从Openvswitch代码看网络包的旅程
我们知道,Openvwitch可以创建虚拟交换机,而网络包可以通过虚拟交换机进行转发,并通过流表进行处理,具体的过程如何呢? 一.内核模块Openvswitch.ko的加载 OVS是内核态和用户态配合 ...
- MySQL Innodb如何找出阻塞事务源头SQL
在MySQL数据库中出现了阻塞问题,如何快速查找定位问题根源?在实验开始前,我们先梳理一下有什么工具或命令查看MySQL的阻塞,另外,我们也要一一对比其优劣,因为有些命令可能在实际环境下可能并不适用. ...
- struct2利用相关的Aware接口
Struts 2提供了Aware接口.Aware为"感知"的意思,实现了相关Aware接口的Action能够感知相应的资源.Struts在实例化一个Action实例时,如果发现它实 ...
- PHP 实例 - AJAX 实时搜索
AJAX Live Search 在下面的实例中,我们将演示一个实时的搜索,在您键入数据的同时即可得到搜索结果. 实时的搜索与传统的搜索相比,具有很多优势: 当键入数据时,就会显示出匹配的结果 当继续 ...
- JavaScript中的事件模型
JS中的事件 1.鼠标事件 onclick ondbclick onmouseover onmouseout 2.HTML事件 onload onunload onsubmit ...
- Nodejs 模块查找机制还不错(从当前目录开始逐级向上查找node_modules)
比如 m.js是能够调用a.js的, 这样子目录就可以避免重复安装node_modules. 够用了.