说明:本文的分析基于linux-2.6.0版本
1、函数原型
man connect可以看到函数原型为:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
- sockfd: 套接字的文件描述符,socket()系统调用返回的fd
- addr: 指向存放地址信息的结构体的首地址
- addrlen: 存放地址信息的结构体的大小,其值为sizof(struct sockaddr)
2、内核实现代码
asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
int err;
/* 通过文件描述符fd来获取对应的socket结构体 */
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;
/* 调用connect处理函数 */
/* 对于TCP传输协议来说,最终调用的是inet_stream_ops中的inet_stream_connect()函数 */
err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen,
sock->file->f_flags);
out_put:
sockfd_put(sock);
out:
return err;
}
//sock->ops指向的是inet_stream_ops,inet_stream_ops中绑定了inet_stream_connect以及inet_bind等函数,而inet_stream_ops声明在结构体数组inetsw_array中,该数组在初始化的时候通过和传输层协议类型protocol来进行绑定。具体的绑定过程是在inet_init()函数中通过循环数组inetsw_array来调用inet_register_protosw()函数进行绑定。下面是inetsw_array数组以及TCP传输协议对应的inet_stream_ops结构。
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot,
.ops = &inet_stream_ops,
.capability = -1,
.no_check = 0,
.flags = INET_PROTOSW_PERMANENT,
},
{
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = &udp_prot,
.ops = &inet_dgram_ops,
.capability = -1,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_PERMANENT,
},
{
.type = SOCK_RAW,
.protocol = IPPROTO_IP, /* wild card */
.prot = &raw_prot,
.ops = &inet_dgram_ops,
.capability = CAP_NET_RAW,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_REUSE,
}
};
struct proto_ops inet_stream_ops = {
.family = PF_INET,
.owner = THIS_MODULE,
.release = inet_release,
.bind = inet_bind,
.connect = inet_stream_connect,
.socketpair = sock_no_socketpair,
.accept = inet_accept,
.getname = inet_getname,
.poll = tcp_poll,
.ioctl = inet_ioctl,
.listen = inet_listen,
.shutdown = inet_shutdown,
.setsockopt = inet_setsockopt,
.getsockopt = inet_getsockopt,
.sendmsg = inet_sendmsg,
.recvmsg = inet_recvmsg,
.mmap = sock_no_mmap,
.sendpage = tcp_sendpage
};
3、inet_stream_connect函数
int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
int addr_len, int flags)
{
struct sock *sk = sock->sk;
int err;
long timeo;
lock_sock(sk);
/* 对unspec的特殊处理 */
/* sa_family参数指定调用者期望返回的套接口地址结构类型,如果指定为AF_INET,那么函数
就不能返回任何IPV6相关的地址信息,如果仅指定了AF_INET6,则不能返回任何IPV4的地址信息,AF_UNSPEC则意味着函数返回的是适用于指定主机名和服务名且适合任何协议族的地址。*/
if (uaddr->sa_family == AF_UNSPEC) {
err = sk->sk_prot->disconnect(sk, flags);
sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
goto out;
}
/* 根据socket状态对应处理 */
switch (sock->state) {
default:
err = -EINVAL;
goto out;
/* 已连接 */
case SS_CONNECTED:
err = -EISCONN;
goto out;
/* 正在连接 */
case SS_CONNECTING:
err = -EALREADY;
/* Fall out of switch with err, set for this state */
break;
/* 未连接 */
case SS_UNCONNECTED:
err = -EISCONN;
/* 需要为closed状态 */
if (sk->sk_state != TCP_CLOSE)
goto out;
/* 同样通过inetsw_array的绑定关系,最终调用的是tcp_v4_connect()主处理函数 */
err = sk->sk_prot->connect(sk, uaddr, addr_len);
if (err < 0)
goto out;
/* 标记状态为正在连接 */
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.
*/
/* 如果是非阻塞情况,则返回的错误码就是EINPROGRESS */
err = -EINPROGRESS;
break;
}
/* 阻塞情况下需要获取超时时间,非阻塞为0 */
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
/* 已发送或者已收到syn */
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
/* Error code is set above */
/* 非阻塞退出,阻塞则等待连接&#