1. connect 系统调用 分析

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

其中的参数解释如下:
·int sockfd :套接字描述符。
·const struct sockaddr*addr :要连接的地址。
·socklen_t addrlen :要连接的地址长度。
返回值 0 表示成功, -1 表示失败。

connect 的用途是使用指定的套接字去连接指定的地址。对于面向连接的协议(套接字类型为
SOCK_STREAM ), connect 只能成功一次(当然要如此,因为真正的连接已经建立了)。如果重复调
用 connect ,会返回 -1 表示失败,同时错误码为 EISCONN 。而对于非面向连接的协议(套接字类型为
SOCK_DGRAM ),则可以执行多次 connect (因为这时的 connect 仅仅是设置了默认的目的地址)。

对于 TCP 套接字来说, connect 实际上是要真正地进行三次握手,所以其默认是一个阻塞操作。那么
是否可以写一个非阻塞的 TCP connect 代码呢?

/*
* Attempt to connect to a socket with the server address. The address
* is in user space so we verify it is OK and move it to kernel space.
*
* For 1003.1g we need to add clean support for a bind to AF_UNSPEC to
* break bindings
*
* NOTE: 1003.1g draft 6.3 is broken with respect to AX.25/NetROM and
* other SEQPACKET protocols that take time to connect() as it doesn't
* include the -EINPROGRESS status for such sockets.
*/ 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;
}
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;
}
/*
* 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;
/* 长度合法性检查*/
if (addr_len < sizeof(uaddr->sa_family))
return -EINVAL; if (uaddr->sa_family == AF_UNSPEC) {/* 如果协议族为
AF_UNSPEC ,则先执行*/
err = sk->sk_prot->disconnect(sk, flags);
/* 根据是否成功断开连接,来设置socket状态 */
sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
goto out;
} switch (sock->state) {
default:
err = -EINVAL;
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;
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。
*/ /* Error code is set above */
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; /* sk->sk_err may be not zero now, if RECVERR was ordered by user
* and error was received after socket entered established state.
* Hence, it is handled normally after connect() return successfully.
*/
/* 更新socket状态为连接已建立 */
sock->state = SS_CONNECTED;
err = 0;
out:
return err; sock_error:
err = sock_error(sk) ? : -ECONNABORTED;
sock->state = SS_UNCONNECTED;
if (sk->sk_prot->disconnect(sk, flags))
sock->state = SS_DISCONNECTING;
goto out;
}
EXPORT_SYMB
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
void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk); tcp_set_state(sk, TCP_ESTABLISHED); ------------------------
---------------------------- if (!sock_flag(sk, SOCK_DEAD)) {
sk->sk_state_change(sk);---->// 指向sock_def_wakeup
/* 如果使用了异步通知,则发送SIGIO通知进程可写 */
sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
}
} static inline void sk_wake_async(struct sock *sk, int how, int band)
{
if (sock_flag(sk, SOCK_FASYNC))
sock_wake_async(sk->sk_socket, how, band);
} static void sock_def_wakeup(struct sock *sk)
{
struct socket_wq *wq; rcu_read_lock();
wq = rcu_dereference(sk->sk_wq);
if (wq_has_sleeper(wq))
wake_up_interruptible_all(&wq->wait);
rcu_read_unlock();
} //最终调用__wake_up_common(),由于nr_exclusive为0,会把此socket上所有的等待进程都唤醒

udp_prot 是 UDP 协议中所有自定义操作函数的集合。其 connect 的实现函数为 ip4_datagram_connect 。
其主要是设置了目的 IP 、端口和路由信息

int ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
struct inet_sock *inet = inet_sk(sk);
struct sockaddr_in *usin = (struct sockaddr_in *) uaddr;
struct flowi4 *fl4;
struct rtable *rt;
__be32 saddr;
int oif;
int err; if (addr_len < sizeof(*usin))
return -EINVAL; if (usin->sin_family != AF_INET)
return -EAFNOSUPPORT;
//复位路由高速缓冲区的入口地址
sk_dst_reset(sk); lock_sock(sk);
//和套接字绑定的网络设备索引号 oif = sk->sk_bound_dev_if;
saddr = inet->inet_saddr;
//如果建立连接的地址是组传送地址,meiyou jiu 重新初始化oif和原地址
if (ipv4_is_multicast(usin->sin_addr.s_addr)) {
if (!oif)
oif = inet->mc_index;
if (!saddr)
saddr = inet->mc_addr;
}
fl4 = &inet->cork.fl.u.ip4;
/*
调用ip_route_connet寻找路由,
源路由主要根据源地址、源端口、目的地址、目的端口、输出网络设备额索引号,
如果寻找路由失败就返回错误,如果寻找的路由是广播地址路由就要是否路由在高速
缓冲区的入口并返回错误。寻找路由成功就把套接字的状态变量sk_state设置为TCP_ESTABLISHED,
并把路由保存到套接字的sk->sk_dst_cache数据域
*/
rt = ip_route_connect(fl4, usin->sin_addr.s_addr, saddr,
RT_CONN_FLAGS(sk), oif,
sk->sk_protocol,
inet->inet_sport, usin->sin_port, sk, true);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
if (err == -ENETUNREACH)
IP_INC_STATS_BH(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
goto out;
}
//寻找的路由是广播地址路由,则释放该路由在路由缓冲区的入口 if ((rt->rt_flags & RTCF_BROADCAST) && !sock_flag(sk, SOCK_BROADCAST)) {
ip_rt_put(rt);
err = -EACCES;
goto out;
}
if (!inet->inet_saddr)//从路由表中获取的信息更新udp的原地址
inet->inet_saddr = fl4->saddr; /* Update source address */
if (!inet->inet_rcv_saddr) {
inet->inet_rcv_saddr = fl4->saddr;
if (sk->sk_prot->rehash)
sk->sk_prot->rehash(sk);
}//更新目的地址和目的端口,源端口已经给定了
inet->inet_daddr = fl4->daddr;
inet->inet_dport = usin->sin_port;
sk->sk_state = TCP_ESTABLISHED;
inet->inet_id = jiffies; sk_dst_set(sk, &rt->dst);
err = 0;
out:
release_sock(sk);
return err;
}

Socket connect 等简要分析的更多相关文章

  1. Socket shutdown close简要分析

    shutdown 系统调用关闭连接的读数据通道  写数据通道 或者 读写数据通道: 关闭读通道:丢弃socket fd 读数据以及调用shutdown 后到达的数据: 关闭写通道:不同协议处理不同:t ...

  2. Socket bind系统调用简要分析

    主要查看linux kernel 源码:Socket.c 以及af_inet.c文件 1.1 bind分析 #include <sys/types.h> /* See NOTES */#i ...

  3. MapReduce启动的Map/Reduce子任务简要分析

      对于Hadoop来说,是通过在DataNode中启动Map/Reduce java进程的方式来实现分布式计算处理的,那么就从源码层简要分析一下hadoop中启动Map/Reduce任务的过程.   ...

  4. Activity源码简要分析总结

    Activity源码简要分析总结 摘自参考书籍,只列一下结论: 1. Activity的顶层View是DecorView,而我们在onCreate()方法中通过setContentView()设置的V ...

  5. Google发布SSLv3漏洞简要分析报告

    今天上午,Google发布了一份关于SSLv3漏洞的简要分析报告.根据Google的说法,该漏洞贯穿于所有的SSLv3版本中,利用该漏洞,黑客可以通过中间人攻击等类似的方式(只要劫持到的数据加密两端均 ...

  6. .NET平台下几种SOCKET模型的简要性能供参考

    转载自:http://www.cnblogs.com/asilas/archive/2006/01/05/311309.html .NET平台下几种SOCKET模型的简要性能供参考 这个内容在cnbl ...

  7. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析 系列目录 最新比较闲,为了学习下Android的开发构建ASP.NET ...

  8. C#Socket编程socket.Connect权限出错问题及解决

    最近使用Vs2010编写Socket程序,客户端在调用socket.Connect()时,总是出现: 请求“System.Net.SocketPermission, System, Version=4 ...

  9. CVE-2015-5122 简要分析(2016.4)

    CVE-2015-5122 简要分析 背景 最近在学习Flash漏洞的分析,其与IE漏洞的分析还是有诸多的不同(不便)之处,折腾了一阵子终于克服了没有符号表.Flash的超时定时器等问题.所以找到了去 ...

随机推荐

  1. 关于android和Linux的一些问题

    1.Android为什么选择java? 由于java虚拟机,实现软件层的编程与硬件无关性(无需进行特定编译或平台配置). 2.Android和Linux内核区别? Android上的应用软件运行在da ...

  2. 多测师讲解 _教师(必备)_高级讲师肖sir

    教学心得1.备课要充分,防止第二天上课会出现一些突发情况2.上课要有自己的思路,不一定要按照课件上的讲3.上课气氛比较沉闷的时候,可以适当的开下玩笑,缓解大家的学习氛围4.讲课的时候提醒学员不要做笔记 ...

  3. 【暑假集训】HZOI2019 水站 多种解法

    题目内容 已知有一个\(n\)层的水站: \(W_i\)表示未操作之前第\(i\)层的已有水量: \(L_i\)表示第\(i\)个水站能够维持或者储存的水的重量: 表示在第\(P_i\)层进行减压放水 ...

  4. C# indexof和indexofany区别(转)

    定位子串是指在一个字符串中寻找其中包含的子串或者某个字符.在String类中,常用的定位子串和字符的方法包括IndexOf/LastIndexOf及IndexOfAny/LastIndexOfAny, ...

  5. Flutter Webview添加Cookie的正确姿势

    场景 h5页面要从cookie里面取数据,所以需要在flutter webview的cookie里面塞一些数据,设置的数据多达十几条:按照网上查的使用方式来设置,通过fiddler抓包发现,只能生效一 ...

  6. 编写C语言的两种方法----Visual Studio/CodeBlocks

    1.CodeBlock(安装简单) 参考这个博客的:https://blog.csdn.net/jjjjkkjkk/article/details/80331625?utm_medium=distri ...

  7. docker 启动mysql 挂载宿主机目录

    在使用docker run 运行镜像获取容器时,有些容器会自动产生一些数据,为了这些数据会因为container (容器)的消失而消失,保证数据的安全,比如mysql 容器在运行中产生的一些表的数据, ...

  8. 1. Deep Q-Learning

    传统的强化学习算法具有很强的决策能力,但难以用于高维空间任务中,需要结合深度学习的高感知能力,因此延展出深度强化学习,最经典的就是DQN(Deep Q-Learning). DQN 2013 DQN的 ...

  9. 安卓日常开发和逆向中常用的shell命令与非shell命令

    简述shell 命令与 非shell命令区别 shell命令不用先adb shell进入界面执行 非shell命令必须要 adb shell进入界面执行 基础非shell命令 1.安装app adb ...

  10. A. Peter and Snow Blower 解析(思維、幾何)

    Codeforce 613 A. Peter and Snow Blower 解析(思維.幾何) 今天我們來看看CF613A 題目連結 題目 給你一個點\(P\)和\(n\)個點形成的多邊形(照順或逆 ...