INET套接字类型
SOCK_STREAM:采用TCP协议服务,提供可靠的双工顺序数据流。
SOCK_DGRAM:采用UDP协议服务,提供双工数据传送,不保证不丢失、不错序。
SOCK_RAW:未采用TCP/UDP协议,应用程序可通过该套接字与IP层交互数据。
关于套接字传输数据的流程见附页流程图。
收发数据 发送数据 调用过程 当通信双方需要想对方发送/写数据时,可以调用send()、sendto()、write()函数。
send()|write()|sendto() → sys_send() → sys_sendto() → sock_sendmsg() → __sock_sendmsg() → sock->prot->sendmsg() → sk->sk_prot->sendmsg()
如果是SOCK_STREAM套接字,则最后两步是→ inet_sendmsg() → tcp_sendmsg()
net/socket.c
sys_send() 包装了sys_sendto()函数。
sys_sendto() 向指定地址发送一个数据包:完成发送数据前的准备工作,把用户空间地址转换成内核空间地址,检查用户空间数据区是否可读,调用sock_sendmsg()函数指定具体的发送操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 asmlinkage long sys_sendto (int fd, void __user * buff, size_t len, unsigned flags, struct sockaddr __user *addr, int addr_len) { struct socket *sock ; char address[MAX_SOCK_ADDR]; int err; struct msghdr msg ; struct iovec iov ; sock = sockfd_lookup(fd, &err); 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=address; msg.msg_namelen=addr_len; } if (sock->file->f_flags & O_NONBLOCK) flags |= MSG_DONTWAIT; msg.msg_flags = flags; err = sock_sendmsg(sock, &msg, len); out_put: sockfd_put(sock); out: return err; }
sock_sendmsg() 调用__sock_sendmsg()发送数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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); if (-EIOCBQUEUED == ret) ret = wait_on_sync_kiocb(&iocb); return ret; }
自此函数起,传递的参数多了一个struct kiocb
。
在Linux内核中,每个IO请求都对应一个kiocb结构体,其ki_filp成员指向对应的file指针,通过is_sync_kiocb可以判断某Kiocb是否为同步IO请求,真——同步IO请求,非真——异步IO请求。
块设备和网络设备本身就是异步的。只有字符设备驱动必须明确指出应支持AIO.需要说明的是AIO对于大多数字符设备而言都不是必须的。只有少数才需要。
参考博客:Linux内核开发之异步通知与异步I/O
__sock_sendmsg() 调用属于某协议族的套接字发送函数——在已指定的协议族操作集中。
1 2 3 4 5 6 7 8 9 static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size) { return sock->ops->sendmsg(iocb, sock, msg, size); }
根据协议类型.type
在struct inet_protosw inetsw_array[]查询INET协议具体的操作集。如果套接字为SOCK_STREAM,则.ops=&inet_stream_ops
,在inet_stream_ops操作集中负责发送的.sendmsg=inet_sendmsg
。
因此,调用sock->ops->sendmsg(iocb, sock, msg, size);就等于调用inet_stream_ops操作集中的inet_sendmsg()函数。
关于inet_protosw inetsw_array数组,见我的博客:Linux网络内核源码分析-关于套接字 。
SOCK_STREAM套接字的sock->ops->sendmsg():inet_sendmsg()
net/ipv4/af_inet.c
1 2 3 4 5 6 7 8 9 10 11 12 int inet_sendmsg (struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size) { struct sock *sk = sock ->sk ; if (!inet_sk(sk)->num && inet_autobind(sk)) return -EAGAIN; return sk->sk_prot->sendmsg(iocb, sk, msg, size); }
传输层的发送方法sk->sk_prot->sendmsg(): tcp_sendmsg()
net/ipv4/tcp.c
sk是struct socket sock中的指针,指向传输层套接字表示结构,sk中包含传输层协议操作集.prot成员。如果是SOCK_STREAM套接字,则.prot=&tcp_prot,对应sendmsg()的函数即tcp_sendmsg()。
接收数据 调用过程 当通信双方从对方接收/读数据时,可以调用read()、recv()、recvfrom()函数。
recv()|read()|recvfrom() → sys_recv() → sys_recvfrom() → sock_recvfrom() → __sock_recvmsg() → sock_common_recvmsg() → sock->ops->recvmsg() →sk->sk_prot->recvmsg()
sys_recv() 包装了sys_recvfrom()函数。
sys_recvfrom() 从一个套接字接收一个帧,有选择地记录发送方的地址,检查缓冲区是否可写,如果有需要,就把发送方地址从内核移到用户空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 asmlinkage long sys_recvfrom (int fd, void __user * ubuf, size_t size, unsigned flags, struct sockaddr __user *addr, int __user *addr_len) { struct socket *sock ; struct iovec iov ; struct msghdr msg ; char address[MAX_SOCK_ADDR]; int err,err2; sock = sockfd_lookup(fd, &err); if (!sock) goto out; msg.msg_control=NULL ; msg.msg_controllen=0 ; msg.msg_iovlen=1 ; msg.msg_iov=&iov; iov.iov_len=size; iov.iov_base=ubuf; msg.msg_name=address; msg.msg_namelen=MAX_SOCK_ADDR; if (sock->file->f_flags & O_NONBLOCK) flags |= MSG_DONTWAIT; err=sock_recvmsg(sock, &msg, size, flags); if (err >= 0 && addr != NULL ) { err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len); if (err2<0 ) err=err2; } sockfd_put(sock); out: return err; }
sock_recvmsg() 调用__sock_recvmsg()接收数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int sock_recvmsg (struct socket *sock, struct msghdr *msg, size_t size, int flags) { struct kiocb iocb ; struct sock_iocb siocb ; int ret; init_sync_kiocb(&iocb, NULL ); iocb.private = &siocb; ret = __sock_recvmsg(&iocb, sock, msg, size, flags); if (-EIOCBQUEUED == ret) ret = wait_on_sync_kiocb(&iocb); return ret; }
__sock_recvmsg() 调用属于某协议族的套接字接收函数——在已指定的协议族操作集中。
1 2 3 4 5 6 7 8 9 static inline int __sock_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size, int flags) { return sock->ops->recvmsg(iocb, sock, msg, size, flags); }
根据协议类型.type
在struct inet_protosw inetsw_array[]查询INET协议具体的操作集。如果套接字为SOCK_STREAM,则.ops=&inet_stream_ops
,在inet_stream_ops操作集中负责接收的.recvmsg=sock_common_recvmsg
。
因此,调用sock->ops->recvmsg(iocb, sock, msg, size);就等于调用inet_stream_ops操作集中的sock_common_recvmsg()函数。
SOCK_STREAM套接字的sock->ops->recvmsg():sock_common_recvmsg() 1 2 3 4 5 6 7 8 9 10 11 12 int sock_common_recvmsg (struct kiocb *iocb, struct socket *sock,struct msghdr *msg, size_t size, int flags) { struct sock *sk = sock ->sk ; int addr_len = 0 ; int err; err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,flags & ~MSG_DONTWAIT, &addr_len); if (err >= 0 ) msg->msg_namelen = addr_len; return err; }
根据套接字采用的传输层协议,sock_common_recvmsg()调用属于不同协议的接收函数。该函数在inet_create()时指定在inetsw_array中。
当套接字为SOCK_STREAM时,有.prot=&tcp_prot,操作集中.recvmsg=tcp_recvmsg。所以sock_common_recvmsg()调用sk->sk_prot->recvmsg就等于调用tcp_recvmsg()。
传输层的接收方法sk->sk_prot->recvmsg(): tcp_recvmsg()
net/ipv4/tcp.c
监听套接字 调用过程 listen() → sys_listen()
调用listen()函数,程序触发内核的sys_listen()函数,
sys_listen()
net/socket.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 asmlinkage long sys_listen (int fd, int backlog) { struct socket *sock ; int err; if ((sock = sockfd_lookup(fd, &err)) != NULL ) { if ((unsigned ) backlog > sysctl_somaxconn) backlog = sysctl_somaxconn; err = security_socket_listen(sock, backlog); if (err) { sockfd_put(sock); return err; } err=sock->ops->listen(sock, backlog); sockfd_put(sock); } return err; }
如果是INET套接字,且为SOCK_STREAM类型,则调用sock->ops->listen()等于调用inet_listen()。
套接字连接 被动接收连接 accept() accept() → sys_accept() → sock->ops->accept()
调用accept()函数时,程序触发sys_accept(),等待接收连接请求,如果允许连接,则重新创建一个代表该连接的套接字,并返回其套接字描述符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 asmlinkage long sys_accept (int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen) { struct socket *sock , *newsock ; int err, len; char address[MAX_SOCK_ADDR]; sock = sockfd_lookup(fd, &err); if (!sock) goto out; err = -EMFILE; if (!(newsock = sock_alloc())) goto out_put; newsock->type = sock->type; newsock->ops = sock->ops; err = security_socket_accept(sock, newsock); if (err) goto out_release; __module_get(newsock->ops->owner); err = sock->ops->accept(sock, newsock, sock->file->f_flags); if (err < 0 ) goto out_release; if (upeer_sockaddr) { if (newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2 )<0 ) { err = -ECONNABORTED; goto out_release; } err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen); if (err < 0 ) goto out_release; } if ((err = sock_map_fd(newsock)) < 0 ) goto out_release; security_socket_post_accept(sock, newsock); out_put: sockfd_put(sock); out: return err; out_release: sock_release(newsock); goto out_put; }
主动连接 connect() connect() → sys_connect() → sock->ops->connect()
调用connect()发出连接请求,内核会启动函数sys_connect()。如果采用TCP协议,则将启动TCP三次握手过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 asmlinkage long sys_connect (int fd, struct sockaddr __user *uservaddr, int addrlen) { struct socket *sock ; char address[MAX_SOCK_ADDR]; int err; sock = sockfd_lookup(fd, &err); 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; err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen, sock->file->f_flags); out_put: sockfd_put(sock); out: return err; }
关闭连接 shutdown() shutdown() → sys_shutdown() → sock->ops->shutdown()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 asmlinkage long sys_shutdown (int fd, int how) { int err; struct socket *sock ; if ((sock = sockfd_lookup(fd, &err))!=NULL ) { err = security_socket_shutdown(sock, how); if (err) { sockfd_put(sock); return err; } err=sock->ops->shutdown(sock, how); sockfd_put(sock); } return err; }
绑定套接字 bind() → sys_bind()
net/socket.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 asmlinkage long sys_bind (int fd, struct sockaddr __user *umyaddr, int addrlen) { struct socket *sock ; char address[MAX_SOCK_ADDR]; int err; if ((sock = sockfd_lookup(fd,&err))!=NULL ) { if ((err=move_addr_to_kernel(umyaddr,addrlen,address))>=0 ) { err = security_socket_bind(sock, (struct sockaddr *)address, addrlen); if (err) { sockfd_put(sock); return err; } err = sock->ops->bind(sock, (struct sockaddr *)address, addrlen); } sockfd_put(sock); } return err; }
附:TCP/UDP数据传输流程