Socket层实现系列 — connect()的实现
主要内容:connect()的Socket层实现、期间进程的睡眠和唤醒。
内核版本:3.15.2
我的博客:http://blog.csdn.net/zhangskd
应用层
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
Connects the socket referred to by the file descriptor sockfd to the address specified by serv_addr.
服务器端的socket会使用bind()来绑定IP和端口,客户端的socket则一般让系统自动选取IP和端口。
系统调用
connect()是由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_CONNECT:
err = sys_connect(a0, (struct sockaddr __user *) a1, a[2]);
break;
...
}
return err;
}
经过了socket层的总入口sys_socketcall(),现在进入sys_connect()。
SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr, int, addrlen)
{
struct socket *sock;
struct sockaddr_storage address;
int err, fput_needed; /* 通过文件描述符fd,找到对应的socket实例。
* 以fd为索引从当前进程的文件描述符表files_struct实例中找到对应的file实例,
* 然后从file实例的private_data成员中获取socket实例。
*/
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (!sock)
goto out; /* 把套接字地址从用户空间拷贝到内核空间 */
err = move_addr_to_kernel(uservaddr, addrlen, &address);
if (err < 0)
goto out_put; err = security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
if (err)
goto out_put; /* 调用Socket层的操作函数,如果是SOCK_STREAM,则proto_ops为inet_stream_ops,
* 函数指针指向inet_stream_connect()。
*/
err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen, sock->file->f_flags); out_put:
fput_light(sock->file, fput_needed); /* 减少套接口文件的引用计数 */ out:
return err;
}
Socket层
SOCK_STREAM套接口的socket层操作函数集实例为inet_stream_ops,其中主动建立连接的函数
为inet_stream_connect()。
const struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
....
.connect = inet_stream_connect,
...
};
int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
{
int err; lock_sock(sock->sk);
err = __inet_stream_connect(sock, uaddr,addr_len, flags);
release_sock(sock->sk);
return err;
}
__inet_stream_connect()主要做了以下事情:
1. 检查socket地址长度和使用的协议族。
2. 检查socket的状态,必须是SS_UNCONNECTED或SS_CONNECTING。
3. 调用tcp_v4_connect()来发送SYN包。
4. 等待后续握手的完成:
如果socket是非阻塞的,那么就直接返回错误码-EINPROGRESS。
如果socket为阻塞的,就调用inet_wait_for_connect(),通过睡眠来等待。在以下三种情况下会被唤醒:
(1) 使用SO_SNDTIMEO选项时,睡眠时间超过设定值,返回0。connect()返回错误码-EINPROGRESS。
(2) 收到信号,返回剩余的等待时间。connect()返回错误码-ERESTARTSYS或-EINTR。
(3) 三次握手成功,sock的状态从TCP_SYN_SENT或TCP_SYN_RECV变为TCP_ESTABLISHED,
sock I/O事件的状态变化处理函数sock_def_wakeup()就会唤醒进程。connect()返回0。
/* Connect to a remote host. There is regrettably still a little TCP magic in here. */
int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
{
struct sock *sk = sock->sk;
int err;
long timeo; /* socket地址长度错误 */
if (addr_len < sizeof(uaddr->sa_family))
return -EINVAL; /* socket的协议族错误 */
if (uaddr->sa_family == AF_UNSPEC) {
/* 如果使用的是TCP,则sk_prot为tcp_prot,disconnect为tcp_disconnect() */
err = sk->sk_prot->disconnect(sk, flags); /* 根据是否成功断开连接,来设置socket状态 */
sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
goto out;
} switch(sock->state) {
default:
err = -EINVAL; /* Invalid argument */
goto out; case SS_CONNECTED: /* 此套接口已经和对端的套接口相连接了,即连接已经建立 */
err = -EISCONN; /* Transport endpoint is already connected */
goto out; case SS_CONNECTING: /* 此套接口正在尝试连接对端的套接口,即连接正在建立中 */
err = -EALREADY; /* Operation already in progress */
/* Fall out of switch with err, set for this state */
break; case SS_UNCONNECTED: /* 此套接口尚未连接对端的套接口,即连接尚未建立 */
err = -EISCONN; if (sk->sk_state != TCP_CLOSE)
goto out; /* 如果使用的是TCP,则sk_prot为tcp_prot,connect为tcp_v4_connect() */
err = sk->sk_prot->connect(sk, uaddr, addr_len); /* 发送SYN包 */
if (err < 0)
goto out; /* 发出SYN包后socket状态设为正在连接 */
sock->state = SS_CONNECTING; /* Just entered SS_CONNECTING state; the only difference
* is that return value in non-blocking case is EINPROGRESS,
* rather than EALREADY.
*/
err = -EINPROGRESS; /* Operation now in progress */
break;
} /* sock的发送超时时间,非阻塞则为0 */
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); /* 发出SYN包后,等待后续握手的完成 */
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) { int writebias = (sk->sk_protocol == IPPROTO_TCP) &&
tcp_sk(sk)->fastopen_req &&
tcp_sk(sk)->fastopen_req->data ? 1 : 0; /* 如果是非阻塞的,那么就直接返回错误码-EINPROGRESS。
* socket为阻塞时,使用inet_wait_for_connect()来等待协议栈的处理:
* 1. 使用SO_SNDTIMEO,睡眠时间超过timeo就返回0,之后返回错误码-EINPROGRESS。
* 2. 收到信号,就返回剩余的等待时间。之后会返回错误码-ERESTARTSYS或-EINTR。
* 3. 三次握手成功,被sock I/O事件处理函数唤醒,之后会返回0。
*/
if (! timeo || ! inet_wait_for_connect(sk,timeo, writebias))
goto out; err = sock_intr_errno(timeo);
/* 进程收到信号,如果err为-ERESTARTSYS,接下来库函数会重新调用connect() */
if (signal_pending(current))
goto out;
} /* Connection was closed by RST, timeout, ICMP error or
* another process disconnected us.
*/
if (sk->sk_state == TCP_CLOSE)
goto sock_error; sock->state = SS_CONNECTED; /* 更新socket状态为连接已建立 */
err = 0; /* 清除错误码 */ out:
return err; sock_error:
err = sock_error(sk) ?: -ECONNABORTED;
sock->state = SS_UNCONNECTED; /* 如果使用的是TCP,则sk_prot为tcp_prot,disconnect为tcp_disconnect() */
if (sk->sk_prot->disconnect(sk, flags)) /* 如果失败 */
sock->state == SS_DISCONNECTING; goto out;
} static inline long sock_sndtimeo(const struct sock *sk, bool noblock)
{
return noblock ? 0 : sk->sk_sndtimeo;
} static inline int sock_intr_errno(long timeo)
{
return timeo == MAX_SCHEDULE_TIMEOUT ? -ERESTARTSYS : -EINTR;
}
进程的睡眠
connect()的超时时间为sk->sk_sndtimeo,在sock_init_data()中初始化为MAX_SCHEDULE_TIMEOUT,
表示无限等待,可以通过SO_SNDTIMEO选项来修改。
static long inet_wait_for_connect(struct sock *sk, long timeo, int writebias)
{
DEFINE_WAIT(wait); /* 初始化等待任务 */ /* 把等待任务加入到socket的等待队列头部,把进程的状态设为TASK_INTERRUPTIBLE */
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
sk->sk_write_pending += writebias; /* Basic assumption: if someone sets sk->sk_err, he _must_ change state of the socket
* from TCP_SYN_*. Connect() does not allow to get error notifications without closing
* the socket.
*/ /* 完成三次握手后,状态就会变为TCP_ESTABLISHED,从而退出循环 */
while ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
release_sock(sk); /* 等下要睡觉了,先释放锁 */ /* 进入睡眠,直到超时或收到信号,或者被I/O事件处理函数唤醒。
* 1. 如果是收到信号退出的,timeo为剩余的jiffies。
* 2. 如果使用了SO_SNDTIMEO选项,超时退出后,timeo为0。
* 3. 如果没有使用SO_SNDTIMEO选项,timeo为无穷大,即MAX_SCHEDULE_TIMEOUT,
* 那么返回值也是这个,而超时时间不定。为了无限阻塞,需要上面的while循环。
*/
timeo = schedule_timeout(timeo); lock_sock(sk); /* 被唤醒后重新上锁 */ /* 如果进程有待处理的信号,或者睡眠超时了,退出循环,之后会返回错误码 */
if (signal_pending(current) || !timeo)
break; /* 继续睡眠吧 */
prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
} /* 等待结束时,把等待进程从等待队列中删除,把当前进程的状态设为TASK_RUNNING */
finish_wait(sk_sleep(sk), &wait);
sk->sk_write_pending -= writebias;
return timeo;
}
进程的唤醒
三次握手中,当客户端收到SYNACK、发出ACK后,连接就成功建立了。
此时连接的状态从TCP_SYN_SENT或TCP_SYN_RECV变为TCP_ESTABLISHED,sock的状态发生变化,
会调用sock_def_wakeup()来处理连接状态变化事件,唤醒进程,connect()就能成功返回了。
sock_def_wakeup()的函数调用路径如下:
tcp_v4_rcv
tcp_v4_do_rcv
tcp_rcv_state_process
tcp_rcv_synsent_state_process
tcp_finish_connect
sock_def_wakeup
wake_up_interruptible_all
__wake_up
__wake_up_common
void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
...
tcp_set_state(sk, TCP_ESTABLISHED); /* 在这里设置为连接已建立的状态 */
...
if (! sock_flag(sk, SOCK_DEAD)) {
sk->sk_state_change(sk); /* 指向sock_def_wakeup,会唤醒调用connect()的进程,完成连接的建立 */
sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT); /* 如果使用了异步通知,则发送SIGIO通知进程可写 */
}
}
static void sock_def_wakeup(struct sock *sk)
{
struct socket_wq *wq; /* socket的等待队列和异步通知队列 */ rcu_read_lock();
wq = rcu_dereference(sk->sk_wq); if (wq_has_sleeper(wq)) /* 有进程阻塞在此socket上 */
wake_up_interruptible_all(&wq->wait); /* 唤醒此socket上的所有睡眠进程 */ rcu_read_unlock();
}
最终调用__wake_up_common(),由于nr_exclusive为0,会把此socket上所有的等待进程都唤醒。
Socket层实现系列 — connect()的实现的更多相关文章
- Socket层实现系列 — send()类发送函数的实现
主要内容:socket发送函数的系统调用.Socket层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 发送流程图 以下是send().sendt ...
- Socket层实现系列 — 睡眠驱动的同步等待
主要内容:Socket的同步等待机制,connect和accept等待的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了 ...
- Socket层实现系列 — getsockname()和getpeername()的实现
本文主要介绍了getsockname()和getpeername()的内核实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int getsockname(in ...
- Socket层实现系列 — 信号驱动的异步等待
主要内容:Socket的异步通知机制. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了几个IO事件:状态改变事件.有数据可读事 ...
- Socket层实现系列 — accept()的实现(一)
本文主要介绍了accept()的系统调用.Socket层实现,以及TCP层实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int accept(int soc ...
- Socket层实现系列 — listen()的实现
本文主要分析listen()的内核实现,包括它的系统调用.Socket层实现.半连接队列,以及监听哈希表. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int l ...
- Socket层实现系列 — bind()的实现(一)
bind()函数的使用方法很简单,但是它是怎么实现的呢? 笔者从应用层出发,沿着网络协议栈,分析了bind()的系统调用.Socket层实现,以及它的TCP层实现. 本文主要内容:bind()的系统调 ...
- Socket层实现系列 — I/O事件及其处理函数
主要内容:Socket I/O事件的定义.I/O处理函数的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd I/O事件定义 sock中定义了几个I/ ...
- Socket层实现系列 — bind()的实现(二)
本文主要内容:bind()的TCP层实现.端口的冲突处理,以及不同内核版本的实现差异. 内核版本:3.6 Author:zhangskd @ csdn blog TCP层实现 SOCK_STREAM套 ...
随机推荐
- 将jdbc连接明文密码加密方案
最近没有及时写文章,把最近处理的几个问题集中了一下写出来.这篇文章是关于如何处理spring项目中引入数据库连接等 使用的用户名和密码的明文进行加密.防止被他人窃取利用. 我们选择的加密方式为DES加 ...
- Linux(centos7)下安装Docker
近期公司开始推Docker技术.这个系列的文章都是基于CentOS7系统下进行讲解的. Docker简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器 ...
- 解决警告: Setting property 'source' to 'org.eclipse.jst.jee.server_:' did not find a matching property.的方法
今天第一次搭建struts2框架,跟着网上的教程导入对应的jar包之后就开始写登录的jsp页面,但是运行时出现了问题, 浏览器显示"The requested resource is not ...
- jsp&servlet初体验——用户登录功能实现
数据库准备-创建db_login数据库 t_user表 1.创建web工程 2.创建用户model user.java package com.gxy.model; public class U ...
- 【转】使用virtualenv在ubuntu上搭建python 3开发环境
ubuntu 13.04默认的python版本是2.7的,想在其上做python3的开发会遇到问题.比如要使用pip安装软件包时,默认安装的就是python2的包.如果想安装python3的包,就需要 ...
- 剑指架构师系列-ActiveMQ队列的使用
安装ActiveMQ只需要下载包后解压,然后就可以启动与关闭ActiveMQ了,如下: ./activemq start ./activemq stop 访问管理页面: http://10.10.20 ...
- MongoDB 原子操作
mongodb不支持事务,所以,在你的项目中应用时,要注意这点.无论什么设计,都不要要求mongodb保证数据的完整性. 但是mongodb提供了许多原子操作,比如文档的保存,修改,删除等,都是原子操 ...
- leetcode之Find All Numbers Disappeared in an Array
问题来源:Find All Numbers Disappeared in an Array 很久没有刷题了,感觉大脑开始迟钝,所以决定重拾刷题的乐趣.一开始不要太难,选一些通过率高的题目做,然后就看到 ...
- Xcode编译错误__NSCFConstantString
__NSCFConstantString:主要错误就是数据类型造成的,然后就是检查哪个地方造成的数据类型调用错误 错误一:'-[__NSCFConstantString _imageThatSuppr ...
- 有没有最好的学习Angularjs2的视频入门体验?
Which are the best video tutorials for learning AngularJS 2? 有没有最好的学习Angularjs2的视频入门体验? 翻译来源:https:/ ...