zoukankan      html  css  js  c++  java
  • Linux用户态数据发送和接收

    前言:在前面的文章中介绍了协议无关层和系统调用接口层。当用户态程序调用sendto()recvfrom()来发送和接收数据时,其中的过程是怎么样的呢?又是经过了几次数据拷贝呢?这篇重点说明这两个接口,接着上篇来说明数据传输的过程。

    1. sendto()

    在上一篇中,我们知道,当在应用中调用sendto()发送函数时,就会调用到系统调用sys_sendto,在socket.c文件中,我们找到了这个系统调用的实现。

    SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,
    		unsigned, flags, struct sockaddr __user *, addr,
    		int, addr_len)
    

    首先根据文件描述符找到对应的socket结构;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    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;
    

    之后把相关的地址信息copy到内核态

    最后进行发送:

    err = sock_sendmsg(sock, &msg, len);
              ||
    	__sock_sendmsg(&iocb, sock, msg, size);
    		  ||
    		sock->ops->sendmsg(iocb, sock, msg, size);
    

    到这里,我们看到调用到了协议族注册的发送函数,如果是DGRAM类型的socket,对应的INET族的发送函数是--inet_sendmsg()。我们继续沿着这条线往下走,在INET族中,DGRAM类型对应的就是UDP协议,所以,最终会调用到udp_sendmsg()中。

    接下来看一下这个函数,在这里不打算仔细说这个函数(暂时还真说不清。。。太庞大了!)。只关心用户数据是怎么拷贝到内核空间,组装成udp报文的。

    关于函数最开始的很多工作,先跳过,直接到标签do_append_data处:
    首先确认了得到用户态数据包的处理函数,然后就开始添加数据头。

    getfrag  =  is_udplite ?  udplite_getfrag : ip_generic_getfrag;
    err = ip_append_data(sk, getfrag, msg->msg_iov, ulen,
    		sizeof(struct udphdr), &ipc, &rt,
    		corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
    

    其实,拷贝用户态的数据就在getfrag函数实现的。

    static __inline__ int udplite_getfrag(void *from, char *to, int  offset,
    				      int len, int odd, struct sk_buff *skb)
    {
    	return memcpy_fromiovecend(to, (struct iovec *) from, offset, len);
    }
    

    其他的就先不多说了,留到分析传输层UDP的时候再细说。sendto也就说到这里。

    2. recvfrom()

    第一节,我们看了发送的过程调用,下面看一下接收的过程。在无关层调用上,sendto()recvfrom()的处理过程是一样的,就是根据socket类型找到对应的处理函数,在INET域,最后就是udp_recvmsg()

    这个函数就相对简单,首先从队列中取出网卡驱动收到的数据:

    skb = __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0),
    				  &peeked, &err);
    if (!skb)
    	goto out;
    

    然后就把收到的收据拷贝到用户态中:

    if (skb_csum_unnecessary(skb))
    		err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr),
    					      msg->msg_iov, copied);
    else {
    	err = skb_copy_and_csum_datagram_iovec(skb,
    					       sizeof(struct udphdr),
    					       msg->msg_iov);
    

    最后打上时间戳和接收到的数据包的地址信息:

    sock_recv_timestamp(msg, sk, skb);
    
    /* Copy the address. */
    if (sin) {
    	sin->sin_family = AF_INET;
    	sin->sin_port = udp_hdr(skb)->source;
    	sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
    	memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
    }
    

    基本的过程就做完了,其余的都是异常处理等流程,可以针对性的看。更详细的分析,等到说传输层UDP的时候再议。

  • 相关阅读:
    oracle impdp 导入
    oracle权限的分配
    Oracle CASE WHEN 用法介绍
    Oracle自动执行任务(存储过程)
    PL/SQL注册码
    ORACLE基本的sql语句
    ORACLE导出导入问题和表空间问题
    PLSQL笔记
    JSEL 表达式
    asp.net HTTP教程一(HTTP运行期与页面执行模型 )
  • 原文地址:https://www.cnblogs.com/yhp-smarthome/p/7050928.html
Copyright © 2011-2022 走看看