深入理解TCP协议及其源代码——connect及bind、listen、accept背后的3次握手
一、TCP协议 理论分析
1.1 TCP数据包格式:
1.2 TCP三次握手:
- 第一次握手:A客户进程向B发出连接请求报文段,(首部的同步位SYN=1,初始序号seq=x),(SYN=1的报文段不能携带数据)但要消耗掉一个序号,此时TCP客户进程进入SYN-SENT(同步已发送)状态。
- 第二次握手:B收到连接请求报文段后,如同意建立连接,则向A发送确认,在确认报文段中(SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y),TCP服务器进程进入SYN-RCVD(同步收到)状态;
- 第三次握手:TCP客户进程收到B的确认后,要向B给出确认报文段(ACK=1,确认号ack=y+1,序号seq=x+1)(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。TCP连接已经建立,A进入ESTABLISHED(已建立连接)。
当B收到A的确认后,也进入ESTABLISHED状态。
对于服务端的流程——类似于接电话过程
socket()[找到一个可以通话的手机]
----->bind()[插入一个固定号码]
------>listen()[随时准备接听]
-------> accept
------->recv()------->send()------>close();
对于客户端的主要流程----类似于打电话过程
socket()----->connect()
------>recv/read/send------>close()
三个接口函数介绍
1. connect()函数:
是一个阻塞函数通过TCP三次握手建立连接客户端主动连接服务器,通过TCP三次握手通知Linux内核自动完成TCP 三次握手连接 如果连接成功为0 失败返回值-1,一般的情况下客户端的connect函数,默认是阻塞行为,直到三次握手阶段成功为止。
2. 服务器端的 listen() 函数:
不是一个阻塞函数: 功能:将套接字和套接字对应队列的长度告诉Linux内核。他是被动连接的一直监听来自不同客户端的请求, listen函数将socketfd 变成被动的连接监听socket 其中参数backlog作用设置内核中队列的长度 。注:listen的函数形式int listen(int sockfd, int backlog); backlog代表listen队列的长度。
3. accept() 函数阻塞:
从处于established 状态的队列中取出完成的连接,当队列中没有完成连接时候,会形成阻塞,直到取出队列中已完成连接的用户连接为止(Accept默认会阻塞进程,直到有一个客户连接建立后返回)。
【 对应关系】三个函数与TCP三次握手之间的对应关系是这样的:
服务器调用listen进行监听
客户端调用connect来发送syn报文
服务器协议栈负责三次握手的交互过程。连接建立后,往listen队列中添加一个成功的连接,直到队列的最大长度。
服务器调用accept从listen队列中取出一条成功的tcp连接,listen队列中的连接个数就少一个
二、源代码阅读&分析
在建立TCP连接过程中:
服务端执行:socket() --> bind() --> listen() --> accept()
客户端执行:socket() --> connect()
下面是逐个展示源代码:
socket()函数:
int __sys_socket(int family, int type, int protocol) { int retval; struct socket *sock; int flags; /* Check the SOCK_* constants for consistency. */ BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC); BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK); BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK); BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK); flags = type & ~SOCK_TYPE_MASK; if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) return -EINVAL; type &= SOCK_TYPE_MASK; if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK)) flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; retval = sock_create(family, type, protocol, &sock); if (retval < 0) return retval; return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)); } SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) { return __sys_socket(family, type, protocol); }
bind()函数:
int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen) { struct socket *sock; struct sockaddr_storage address; int err, fput_needed; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { err = move_addr_to_kernel(umyaddr, addrlen, &address); if (!err) { err = security_socket_bind(sock, (struct sockaddr *)&address, addrlen); if (!err) err = sock->ops->bind(sock, (struct sockaddr *) &address, addrlen); } fput_light(sock->file, fput_needed); } return err; } SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen) { return __sys_bind(fd, umyaddr, addrlen); }
listen()函数:
int __sys_listen(int fd, int backlog) { struct socket *sock; int err, fput_needed; int somaxconn; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn; if ((unsigned int)backlog > somaxconn) backlog = somaxconn; err = security_socket_listen(sock, backlog); if (!err) err = sock->ops->listen(sock, backlog); fput_light(sock->file, fput_needed); } return err; } SYSCALL_DEFINE2(listen, int, fd, int, backlog) { return __sys_listen(fd, backlog); }
accept()函数:
int __sys_accept4(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen, int flags) { struct socket *sock, *newsock; struct file *newfile; int err, len, newfd, fput_needed; struct sockaddr_storage address; if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) return -EINVAL; if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK)) flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (!sock) goto out; err = -ENFILE; newsock = sock_alloc(); if (!newsock) goto out_put; newsock->type = sock->type; newsock->ops = sock->ops; /* * We don't need try_module_get here, as the listening socket (sock) * has the protocol module (sock->ops->owner) held. */ __module_get(newsock->ops->owner); newfd = get_unused_fd_flags(flags); if (unlikely(newfd < 0)) { err = newfd; sock_release(newsock); goto out_put; } newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name); if (IS_ERR(newfile)) { err = PTR_ERR(newfile); put_unused_fd(newfd); goto out_put; } err = security_socket_accept(sock, newsock); if (err) goto out_fd; err = sock->ops->accept(sock, newsock, sock->file->f_flags, false); if (err < 0) goto out_fd; if (upeer_sockaddr) { len = newsock->ops->getname(newsock, (struct sockaddr *)&address, 2); if (len < 0) { err = -ECONNABORTED; goto out_fd; } err = move_addr_to_user(&address, len, upeer_sockaddr, upeer_addrlen); if (err < 0) goto out_fd; } /* File flags are not inherited via accept() unlike another OSes. */ fd_install(newfd, newfile); err = newfd; out_put: fput_light(sock->file, fput_needed); out: return err; out_fd: fput(newfile); put_unused_fd(newfd); goto out_put; } SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr, int __user *, upeer_addrlen, int, flags) { return __sys_accept4(fd, upeer_sockaddr, upeer_addrlen, flags); } SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr, int __user *, upeer_addrlen) { return __sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0); }
客户端调用connect去连接服务端:
SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
int, addrlen)
{
return __sys_connect(fd, uservaddr, addrlen);
}
int __sys_connect_file(struct file *file, struct sockaddr_storage *address,
int addrlen, int file_flags)
{
struct socket *sock;
int err;
//得到socket对象
sock = sock_from_file(file, &err);
if (!sock)
goto out;
err =
security_socket_connect(sock, (struct sockaddr *)address, addrlen);
if (err)
goto out;
//对于流式套接字,sock->ops为 inet_stream_ops --> inet_stream_connect
//对于数据报套接字,sock->ops为 inet_dgram_ops --> inet_dgram_connect
err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,
sock->file->f_flags | file_flags);
out:
return err;
}
int __sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
{
int ret = -EBADF;
struct fd f;
f = fdget(fd);
if (f.file) {
struct sockaddr_storage address;
//将地址对象从用户空间拷贝到内核空间
ret = move_addr_to_kernel(uservaddr, addrlen, &address);
if (!ret)
ret = __sys_connect_file(f.file, &address, addrlen, 0);
if (f.flags)
fput(f.file);
}
return ret;
}
三、gdb运行跟踪
在上面所示5个函数的地方打断点:
打了5个断点,后面每次中断后查看状况,然后用 c 就可以继续执行
服务端执行:socket() --> bind() --> listen() --> accept()
客户端执行:socket() --> connect()