内核网络编程

来源:互联网 发布:笑声软件叫什么 编辑:程序博客网 时间:2024/06/06 01:20

建立监听和连接服务 

服务端建立tco监听的代码如下:

[cpp] view plaincopy
  1. struct sockaddr_in  locaddr;  
  2. struct socket      *sock;  
  3. struct socket      *newsock;  
  4. int                 rc;  
  5. int                 backlog = 0;  
  6. int                 shutdown;  
  7.   
  8. char *server_ip = "192.168.0.1";  
  9. __u32 local_ip =  (__u32)inet_addr(server_ip);  
  10. int local_port = 1123456  
  11.   
  12. /*第1步:创建套接字*/  
  13. rc = sock_create (PF_INET, SOCK_STREAM, 0, &sock);  
  14.   
  15. if (local_ip != 0 || local_port != 0)   
  16. {  
  17.     memset(&locaddr, 0, sizeof(locaddr));  
  18.     locaddr.sin_family = AF_INET;  
  19.     locaddr.sin_port = htons(local_port);  
  20.     locaddr.sin_addr.s_addr = (local_ip == 0) ?  
  21.                               INADDR_ANY : htonl(local_ip);  
  22.   
  23.     /*第2步:绑定ip或端口*/  
  24.     rc = sock->ops->bind(sock, (struct sockaddr *)&locaddr,  
  25.                                   sizeof(locaddr));  
  26.     if (rc == -EADDRINUSE)   
  27.     {  
  28.         printk( "Port %d already in use\n", local_port);  
  29.     }  
  30.   
  31.     if (rc != 0)   
  32.     {  
  33.         printk( "Error trying to bind to port %d: %d\n", local_port, rc);  
  34.   
  35.     }  
  36. }  
  37.   
  38. /*第3步:开始监听*/  
  39. rc = (*sockp)->ops->listen(*sockp, backlog);  
  40.   
  41. shutdown = rc  
  42.   
  43. /*第4步:进入循环,等待客户端连接,并且可以由全局变量shutdown控制停止服务*/  
  44. while (!shutdown) {  
  45.     wait_queue_t   wait;  
  46.     struct socket *newsock;  
  47.     struct sockaddr_in sin;  
  48.     init_waitqueue_entry(&wait, current);  
  49.     __u32          peer_ip;  
  50.     int            peer_port;  
  51.   
  52.         /*内核态套接字编程这一步很重要,在服务端是要建立客户端的socket结构*/  
  53.         rc = sock_create_lite(PF_PACKET, sock->type, IPPROTO_TCP, &newsock);  
  54.         if (rc) {  
  55.                 printk("Can't allocate socket\n");  
  56.                 return rc;  
  57.         }  
  58.   
  59.         newsock->ops = sock->ops;  
  60.   
  61.         set_current_state(TASK_INTERRUPTIBLE);/*睡眠,等待收到连接请求后被底层网络驱动程序唤醒*/  
  62.         add_wait_queue(sock->sk->sk_sleep, &wait);  
  63.   
  64.         /*第5.1步:在被调度出去之前,尝试处理是否有收到连接*/  
  65.         rc = sock->ops->accept(sock, newsock, O_NONBLOCK);  
  66.         if (rc == -EAGAIN) {  
  67.                 /* 第5.2步:开始睡眠 */  
  68.                 schedule();  
  69.                 /*第5.3步:被唤醒,尝试处理收到连接*/  
  70.                 rc = sock->ops->accept(sock, newsock, O_NONBLOCK);  
  71.         }  
  72.   
  73.         remove_wait_queue(sock->sk->sk_sleep, &wait);  
  74.         set_current_state(TASK_RUNNING);  
  75.   
  76.         /*第6步:获得客户端的ip和端口*/  
  77.         rc = newsock->ops->getname (newsock, (struct sockaddr *)&sin, &len,  
  78.                                  remote ? 2 : 0);  
  79.         peer_ip = ntohl (sin.sin_addr.s_addr);  
  80.         peer_port = ntohs (sin.sin_port);  
  81. }  

客户端与服务器建立tcp连接的代码如下:

[cpp] view plaincopy
  1. server=gethostbyname(SERVER_NAME);  
  2. sockfd=socket(AF_INET,SOCK_STREAM,0);  
  3. address.sin_family=AF_INET;  
  4. address.sin_port=htons(PORT_NUM);  
  5. memcpy(&address.sin_addr,server->h_addr,server->h_length);  
  6. connect(sockfd,&address,sizeof(address));  

连接的初始化与建立期间主要发生的事情如下:


1)sys_socket调用:调用socket_creat(),创建出一个满足传入参数family、type、和protocol的socket,调用sock_map_fd()获取一个未被使用的文件描述符,并且申请并初始化对应的file{}结构。


2)sock_creat():创建socket结构,针对每种不同的family的socket结构的初始化,就需要调用不同的create函数来完成。对应于inet类型的地址来说,在网络协议初始化时调用sock_register()函数中完成注册的定义如下:
struct net_proto_family inet_family_ops={
PF_INET;
inet_create
};
所以inet协议最后会调用inet_create函数。


3)inet_create: 初始化sock的状态设置为SS_UNCONNECTED,申请一个新的sock结构,并且初始化socket的成员ops初始化为 inet_stream_ops,而sock的成员prot初始化为tcp_prot。然后调用sock_init_data,将该socket结构的变量sock和sock类型的变量关联起来。


4)在系统初始化完毕后便是进行connect的工作,系统调用connect将一个和socket结构关联的文件描述符和一个 sockaddr{}结构的地址对应的远程机器相关联,并且调用各个协议自己对应的connect连接函数。对应于tcp类型,则 sock->ops->connect便为inet_stream_connect。


5)inet_stream_connect: 得到sk,sk=sock->sk,锁定sk,对自动获取sk的端口号存放在sk->num中,并且用htons()函数转换存放在sk-& gt;sport中。然后调用sk->prot->connect()函数指针,对tcp协议来说就是tcp_v4_connect()函数。然后将sock->state状态字设置为SS_CONNECTING,等待后面一系列的处理完成之后,就将状态改成 SS_CONNECTTED。


6) tcp_v4_connect():调用函数ip_route_connect(),寻找合适的路由存放在rt中。ip_route_connect找两次,第一次找到下一跳的ip地址,在路由缓存或fib中找到,然后第二次找到下一跳的具体邻居,到neigh_table中找到。然后申请出tcp头的空间存放在buff中。将sk中相关地址数据做一些针对路由的变动,并且初始化一个tcp连接的序列号,调用函数tcp_connect(),初始化tcp 头,并设置tcp处理需要的定时器。一次connect()建立的过程就结束了。


客户端连接的关闭主要如下:


  1)close: 一个socket文件描述符对应的file{}结构中,有一个file_operations{}结构的成员f_ops,它的初始化关闭函数为sock_close函数。


  2)sock_close:调用函数sock_release(),参数为一个socket{}结构的指针。


  3)sock_release:调用inet_release,并释放socket的指针和文件空间


  4)inet_release: 调用和该socket对应协议的关闭函数inet_release,如果是tcp协议,那么调用的是tcp_close;最后释放sk。


2 数据发送流程

各层主要函数以及位置功能说明:


1)sock_write:初始化msghdr{}结构 net/socket.c


2)sock_sendmsg:net/socket.c


3)inet_sendmsg:net/ipv4/af_net.c


4)tcp_sendmsg:申请sk_buff{}结构的空间,把msghdr{}结构中的数据填入sk_buff空间。net/ipv4/tcp.c


5)tcp_send_skb:net/ipv4/tcp_output.c


6)tcp_transmit_skb:net/ipv4/tcp_output.c


7)ip_queue_xmit:net/ipv4/ip_output.c


8)ip_queue_xmit2:net/ipv4/ip_output.c


9)ip_output:net/ipv4/ip_output.c


10)ip_finish_output:net/ipv4/ip_output.c


11)ip_finish_output2:net/ipv4/ip_output.c


12)neigh_resolve_output:net/core/neighbour.c


13)dev_queue_xmit:net/core/dev.c


3 数据接收流程



各层主要函数以及位置功能说明:


1)sock_read:初始化msghdr{}的结构类型变量msg,并且将需要接收的数据存放的地址传给msg.msg_iov->iov_base.      net/socket.c


2)sock_recvmsg: 调用函数指针sock->ops->recvmsg()完成在INET Socket层的数据接收过程.其中sock->ops被初始化为inet_stream_ops,其成员recvmsg对应的函数实现为 inet_recvmsg()函数. net/socket.c


3)sys_recv()/sys_recvfrom():分别对应着面向连接和面向无连接的协议两种情况. net/socket.c


4)inet_recvmsg:调用sk->prot->recvmsg函数完成数据接收,这个函数对于tcp协议便是tcp_recvmsg net/ipv4/af_net.c


5)tcp_recvmsg:从网络协议栈接收数据的动作,自上而下的触发动作一直到这个函数为止,出现了一次等待的过程.函数 tcp_recvmsg可能会被动地等待在sk的接收数据队列上,也就是说,系统中肯定有其他地方会去修改这个队列使得tcp_recvmsg可以进行下去.入口参数sk是这个网络连接对应的sock{}指针,msg用于存放接收到的数据.接收数据的时候会去遍历接收队列中的数据,找到序列号合适的.


但读取队列为空时tcp_recvmsg就会调用tcp_v4_do_rcv使用backlog队列填充接收队列.


6)tcp_v4_rcv:tcp_v4_rcv被ip_local_deliver函数调用,是从IP层协议向INET Socket层提交的"数据到"请求,入口参数skb存放接收到的数据,len是接收的数据的长度,这个函数首先移动skb->data指针,让它指向tcp头,然后更新tcp层的一些数据统计,然后进行tcp的一些值的校验.再从INET Socket层中已经建立的sock{}结构变量中查找正在等待当前到达数据的哪一项.可能这个sock{}结构已经建立,或者还处于监听端口、等待数据连接的状态。返回的sock结构指针存放在sk中。然后根据其他进程对sk的操作情况,将skb发送到合适的位置.调用如下:


TCP包接收器(tcp_v4_rcv)将TCP包投递到目的套接字进行接收处理. 当套接字正被用户锁定,TCP包将暂时排入该套接字的后备队列(sk_add_backlog).这时如果某一用户线程企图锁定该套接字 (lock_sock),该线程被排入套接字的后备处理等待队列(sk->lock.wq).当用户释放上锁的套接字时 (release_sock,在tcp_recvmsg中调用),后备队列中的TCP包被立即注入TCP包处理器(tcp_v4_do_rcv)进行处理,然后唤醒等待队列中最先的一个用户来获得其锁定权. 如果套接字未被上锁,当用户正在读取该套接字时, TCP包将被排入套接字的预备队列(tcp_prequeue),将其传递到该用户线程上下文中进行处理.如果添加到sk->prequeue不成功,便可以添加到 sk->receive_queue队列中(用户线程可以登记到预备队列,当预备队列中出现第一个包时就唤醒等待线程.)   /net/tcp_ipv4.c


7)ip_rcv、ip_rcv_finish:从以太网接收数据,放到skb里,作ip层的一些数据及选项检查,调用 ip_route_input()做路由处理,判断是进行ip转发还是将数据传递到高一层的协议.调用skb->dst->input函数指针,这个指针的实现可能有多种情况,如果路由得到的结果说明这个数据包应该转发到其他主机,这里的input便是ip_forward;如果数据包是给本机的,那么input指针初始化为ip_local_deliver函数./net/ipv4/ip_input.c


8)ip_local_deliver、ip_local_deliver_finish:入口参数skb存放需要传送到上层协议的数据,从 ip头中获取是否已经分拆的信息,如果已经分拆,则调用函数ip_defrag将数据包重组。然后通过调用ip_prot->handler指针调用tcp_v4_rcv(tcp)。ip_prot是inet_protocol结构指针,是用来ip层登记协议的,比如由udp,tcp,icmp等协议。 /net/ipv4/ip_input.c
0 0
原创粉丝点击