参考资料
<<linux内核网络栈源代码情景分析>>
socket常用函数概述
根据socket提供的常用的库函数,socket,read,write等函数,
执行的过程
inet协议的注册流程
在上文中分析过了在设备的初始化过程中,会调用sock_init函数初始化,在该函数中又会调用proto_init函数进行初始化,在proto_init函数中会调用inet对应的初始化函数inet_proto_init,该函数如下;
void inet_proto_init(struct net_proto *pro)
{
struct inet_protocol *p;
int i;
printk("Swansea University Computer Society TCP/IP for NET3.019\n");
/*
* Tell SOCKET that we are alive...
* ÏòSOCKETÄ£¿é×¢²áÐÒé´Ø
*/
(void) sock_register(inet_proto_ops.family, &inet_proto_ops); // 注册tcp/ip协议对应的协议操作函数
seq_offset = CURRENT_TIME*250;
/*
* Add all the protocols.
*/
for(i = 0; i < SOCK_ARRAY_SIZE; i++)
{
tcp_prot.sock_array[i] = NULL;
udp_prot.sock_array[i] = NULL;
raw_prot.sock_array[i] = NULL;
}
tcp_prot.inuse = 0;
tcp_prot.highestinuse = 0;
udp_prot.inuse = 0;
udp_prot.highestinuse = 0;
raw_prot.inuse = 0;
raw_prot.highestinuse = 0;
printk("IP Protocols: ");
for(p = inet_protocol_base; p != NULL;) // 添加协议到链表尾部
{
struct inet_protocol *tmp = (struct inet_protocol *) p->next;
inet_add_protocol(p);
printk("%s%s",p->name,tmp?", ":"\n");
p = tmp;
}
/*
* Set the ARP module up
*/
arp_init(); // arp模块初始化
/*
* Set the IP module up
*/
ip_init(); // ip模块初始化
}
首先会调用sock_register函数注册协议到列表pops中,
int sock_register(int family, struct proto_ops *ops)
{
int i;
cli(); // 禁止中断
for(i = 0; i < NPROTO; i++) // 遍历最大的协议包含数量
{
if (pops[i] != NULL) // 如果该位置内容不为空则下一个
continue;
pops[i] = ops; // 找到为空的位置,讲该函数操作方法设置进去
pops[i]->family = family; // 设置该协议的family
sti(); // 打开中断并返回
return(i);
}
sti(); // 打开中断
return(-ENOMEM); // 返回出错
}
此时我们查看传入的inet_proto_ops操作集合;
static struct proto_ops inet_proto_ops = {
AF_INET,
inet_create, // 创建
inet_dup,
inet_release, // 释放
inet_bind, // 绑定
inet_connect, // 连接
inet_socketpair,
inet_accept, // 接受
inet_getname,
inet_read, // 读数据
inet_write, // 写数据
inet_select, // 检查套接字
inet_ioctl,
inet_listen, // 监听队列
inet_send, // 发送数据
inet_recv, // 接受数据
inet_sendto,
inet_recvfrom,
inet_shutdown,
inet_setsockopt, // 设置socket配置参数
inet_getsockopt, // 获取socket配置参数
inet_fcntl,
};
socket函数的调用过程
一般socket的编程过程,会出现socket服务端与客户端,python的实例代码如下;
import socket
sock = socket.socket() # 初始化
sock.bind(("127.0.0.1", 9999)) # 监听ip与端口
sock.listen(10) # 监听队列
while True:
conn, addr = sock.accept() # 等待接受请求
print(conn, addr)
data = conn.recv(1024) # 接受数据
print("recv data : ", data)
conn.send(data) # 发送数据
conn.close() # 关闭连接
服务端代码如上所示,主要初始化socket,设置监听端口,设置监听队列长度,等待接受客户端请求,接受客户端的数据与发送客户端的数据。客户端的代码如下;
sock = socket.socket() # 初始化连接
sock.connect(("127.0.0.1", 9999)) # 连接远端服务器
sock.send(b"hello world") # 发送数据
data = sock.recv(1024) # 接受数据
print("client recv data: ", data)
sock.close() # 关闭连接
客户端主要就是初始化socket,连接远端端口与ip,发送数据给服务端,然后等待接受服务端返回的数据,最后关闭连接。
sock_socket初始化函数
/*
* Perform the socket system call. we locate the appropriate
* family, then create a fresh socket.
*/
static int sock_socket(int family, int type, int protocol)
{
int i, fd;
struct socket *sock;
struct proto_ops *ops;
/* Locate the correct protocol family. */
for (i = 0; i < NPROTO; ++i)
{
if (pops[i] == NULL) continue; // 查找注册进来的协议
if (pops[i]->family == family) // 判断传入的协议号与传入的一致
break;
}
if (i == NPROTO) // 等于注册协议号的最大长度则表示没有找到对应的协议号
{
return -EINVAL; // 返回出错
}
ops = pops[i]; // 获取找到的协议号
/*
* Check that this is a type that we know how to manipulate and
* the protocol makes sense here. The family can still reject the
* protocol later.
*/
if ((type != SOCK_STREAM && type != SOCK_DGRAM &&
type != SOCK_SEQPACKET && type != SOCK_RAW &&
type != SOCK_PACKET) || protocol < 0) // 判断类型是否符合要求
return(-EINVAL);
/*
* Allocate the socket and allow the family to set things up. if
* the protocol is 0, the family is instructed to select an appropriate
* default.
*/
if (!(sock = sock_alloc())) // 申请内存
{
printk("NET: sock_socket: no more sockets\n");
return(-ENOSR); /* Was: EAGAIN, but we are out of
system resources! */ // 如果申请内存失败则报错
}
sock->type = type; // 设置类型
sock->ops = ops; // 设置协议操作的ops操作集合
if ((i = sock->ops->create(sock, protocol)) < 0) // 调用协议ops的创建sock函数
{
sock_release(sock); // 如果创建失败则释放刚刚申请的内存
return(i); // 返回报错编号
}
if ((fd = get_fd(SOCK_INODE(sock))) < 0) // 获取文件描述符
{
sock_release(sock); // 如果文件描述符获取失败则释放sock内存
return(-EINVAL); // 返回错误编码
}
return(fd); // 返回文件描述符
}
sock的初始化函数,根据传入的family参数来初始化不同的ops的函数集,如inet_proto_ops操作集合,然后再分配inode结构合socket结构并设置socket结构对应的ops字段,再通过sock->ops->create函数来创建sock结构,最后通过get_fd来分配一个文件描述符并返回。
/*
* Obtains the first available file descriptor and sets it up for use.
*/
static int get_fd(struct inode *inode)
{
int fd;
struct file *file;
/*
* Find a file descriptor suitable for return to the user.
*/
file = get_empty_filp(); // 获取一个空闲的文件结构
if (!file) // 如果没有找到则返回-1
return(-1);
for (fd = 0; fd < NR_OPEN; ++fd) // 遍历文件描述符列表
if (!current->files->fd[fd]) // 找到一个没有被使用的文件描述符
break;
if (fd == NR_OPEN)
{
file->f_count = 0;
return(-1);
}
FD_CLR(fd, ¤t->files->close_on_exec);
current->files->fd[fd] = file; // 设置当前文件描述符对应的file
/* ¶ÔsocketµÄÆÕͨÎļþ²Ù×÷¼¯ºÏ */
file->f_op = &socket_file_ops; // 设置操作函数集为socket_file_ops
file->f_mode = 3;
file->f_flags = O_RDWR; // 设置模式
file->f_count = 1;
file->f_inode = inode; // 设置inode
if (inode)
inode->i_count++;
file->f_pos = 0;
return(fd); // 返回文件描述符
}
sock->ops对应的操作集函数就是位于af_inet.c中的操作集,在af_inet.c中对应的操作集底层调用的操作集对应位于tcp.c中的tcp/ip协议的操作集。
sock_bind函数的执行过程
static int sock_bind(int fd, struct sockaddr *umyaddr, int addrlen)
{
struct socket *sock;
int i;
char address[MAX_SOCK_ADDR];
int err;
if (fd < 0 || fd >= NR_OPEN || current->files->fd[fd] == NULL) // 检查传入的文件描述符是否符合规则
return(-EBADF);
if (!(sock = sockfd_lookup(fd, NULL))) // 查找对应的文件描述符的sock
return(-ENOTSOCK);
if((err=move_addr_to_kernel(umyaddr,addrlen,address))<0) // 讲输入传入内核
return err;
if ((i = sock->ops->bind(sock, (struct sockaddr *)address, addrlen)) < 0) // 调用sock的绑定bind函数进行监听
{
return(i); // 如果出错就返回错误值
}
return(0);
}
该函数主要就是检查文件描述符是否正确,然后讲数据传入内核地址中,然后调用sock->ops->bind函数进行调用,该函数也是位于af_inet.c中,主要工作就是完成端口的检查设置监听的地址与端口。
sock_listen函数的执行过程
static int sock_listen(int fd, int backlog)
{
struct socket *sock;
if (fd < 0 || fd >= NR_OPEN || current->files->fd[fd] == NULL) // 检查文件描述符是否合法
return(-EBADF);
if (!(sock = sockfd_lookup(fd, NULL))) // 查找对应的文件描述符的sock
return(-ENOTSOCK);
/* ÒªÇósocketµÄ״̬ÊÇSS_UNCONNECTED£¬¸Õ½¨Á¢×´Ì¬ */
if (sock->state != SS_UNCONNECTED) // 检查文件描述符的状态如果不为未连接状态则报错
{
return(-EINVAL);
}
if (sock->ops && sock->ops->listen) // 检查是否有操作函数listen
sock->ops->listen(sock, backlog); // 调用该函数
sock->flags |= SO_ACCEPTCON; // sock的标志添加接受请求的标志
return(0);
}
主要检查了文件描述符的合法性,然后查找对应的文件描述符的sock,检查该sock的状态,然后调用sock->ops->listen函数,如果是tcp协议则调用了位于af_inet.c中的inet_listen函数;
/*
* Move a socket into listening state.
*/
static int inet_listen(struct socket *sock, int backlog)
{
struct sock *sk = (struct sock *) sock->data; // 获取sock结构
if(inet_autobind(sk)!=0) // 检查是否有未绑定的端口号,设置source,设置端口号
return -EAGAIN;
/* We might as well re use these. */
/*
* note that the backlog is "unsigned char", so truncate it
* somewhere. We might as well truncate it to what everybody
* else does..
*/
if ((unsigned) backlog > 128) // 如果设置的队列长度大于128则设置为128
backlog = 128;
sk->max_ack_backlog = backlog; // 设置最大队列数
if (sk->state != TCP_LISTEN) // 如果sock的状态不是TCP_LISTEN改为TCP_LISTEN
{
sk->ack_backlog = 0; // ack_backlog设置为0
sk->state = TCP_LISTEN; // 设置为TCP_LISTEN状态
}
return(0);
}
大致的监听过程如上所示,检查backlog的长度大小,检查状态并重置sk的状态。
sock_send发送数据与sock_recv接受数据
static int sock_send(int fd, void * buff, int len, unsigned flags)
{
struct socket *sock;
struct file *file;
int err;
if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL)) // 检查文件描述符是否合法
return(-EBADF);
if (!(sock = sockfd_lookup(fd, NULL))) // 查找fd对应的sock
return(-ENOTSOCK);
if(len<0)
return -EINVAL;
err=verify_area(VERIFY_READ, buff, len); // 检查发送数据的缓冲区长度
if(err)
return err;
return(sock->ops->send(sock, buff, len, (file->f_flags & O_NONBLOCK), flags)); // 发送数据并添加文件的描述符的发送状态
}
该函数检查了文件描述符后就直接调用了底层的ops的send方法将数据发送出去,该详细内容待后文详细分析。
static int sock_recv(int fd, void * buff, int len, unsigned flags)
{
struct socket *sock;
struct file *file;
int err;
if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL)) // 检查文件描述符是否合法
return(-EBADF);
if (!(sock = sockfd_lookup(fd, NULL))) // 查找对应的sock
return(-ENOTSOCK);
if(len<0)
return -EINVAL;
if(len==0)
return 0;
err=verify_area(VERIFY_WRITE, buff, len); // 检查是否合法
if(err)
return err;
return(sock->ops->recv(sock, buff, len,(file->f_flags & O_NONBLOCK), flags)); // 接受数据
}
同样是检查了文件描述符是否合法,然后获取对应的sock,调用该sock的操作集将接受数据。
sock_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.
*/
static int sock_connect(int fd, struct sockaddr *uservaddr, int addrlen)
{
struct socket *sock;
struct file *file;
int i;
char address[MAX_SOCK_ADDR];
int err;
if (fd < 0 || fd >= NR_OPEN || (file=current->files->fd[fd]) == NULL) // 检查文件描述符的合法
return(-EBADF);
if (!(sock = sockfd_lookup(fd, &file))) // 查找对应文件描述符的sock
return(-ENOTSOCK);
if((err=move_addr_to_kernel(uservaddr,addrlen,address))<0) // 拷贝数据到内核
return err;
switch(sock->state) // 检查sock的状态
{
case SS_UNCONNECTED: // 为连接则继续
/* This is ok... continue with connect */
break;
case SS_CONNECTED: // 已经连接过则检查是否为SOCK_DGRAM类型如果不是则返回
/* Socket is already connected */
if(sock->type == SOCK_DGRAM) /* Hack for now - move this all into the protocol */
break;
return -EISCONN;
case SS_CONNECTING:
/* Not yet connected... we will check this. */
/*
* FIXME: for all protocols what happens if you start
* an async connect fork and both children connect. Clean
* this up in the protocols!
*/
break;
default:
return(-EINVAL);
}
// µ÷ÓÃinet_connectº¯Êýʱ£¬socketµÄ״̬¿ÉÄÜÊÇ£º
// 1. SS_UNCONNECTED
// 2. SS_CONNECTED״̬µÄÊý¾Ý±¨Ì×½Ó×Ö(È磺udp)
// 3. SS_CONNECTING
i = sock->ops->connect(sock, (struct sockaddr *)address, addrlen, file->f_flags); // 调用操作函数集来连接
if (i < 0)
{
return(i);
}
return(0);
}
先将远端的地址从用户缓冲区复制到内核缓冲区,之后检查套接字的状态,如果有效则调用sock->ops->connect进一步操作,继续完成网络数据传输。详细的连接内容待后文分析。
总结
本文主要讲述了用户态的常见的socket的调用的方式,然后根据示例代码查看了socket.c对应的函数的处理的大致流程,并未进入详细的函数的处理过程,大部分的函数的工作都是进行参数的检查之后就调用更底层的函数进一步去处理,后续会继续分析。由于本人才疏学浅,如有错误请批评指正。