网络实现架构

4.4BSD通过同时对多种通信协议的支持来提供通用的底层基础服务。4.4BSD支持四种不同的通信协议簇:

  • TCP/IP(互联网协议簇)
  • XNS(Xerox网络系统)
  • OSI协议
  • Unix域协议

    从通信协议是用来在不同的系统之间交换信息的意义上来说,它还不算是一套真正的协议,但它提供了一种进程间通信(IPC)的形式。

4.4BSD内核中的联网代码组织成三层,如下图所示

  • Socket层是一个到下面协议相关层的协议无关层所有系统调用从协议无关的Socket开始

    例如:在Socket层中的bind()系统调用的协议无关代码包含几十行代码,它们验证第一个参数是一个有效的socket描述符,并且第二个参数是一个进程中的有效指针。然后调用下层的协议相关代码,协议相关代码可能包含几百行代码。

  • 协议层包括我们提到的四种协议簇(TCP/IP,XNS,OSI和Unix域)的实现。

    每个协议簇可能包含自己的内部结构。

  • 接口层

    接口层包括同网络设备通信的设备驱动程序。

数据传递

  • Socket层中的每一个Socket都具有一个输入队列和一个输出队列
  • 协议层中的每一个协议都具有一个输入队列和输出队列
  • 接口层中的每个接口(以太网、回环、SLIP、PPP等)都有一个输入队列和输出队列

输入处理

输入处理与输出处理不同,因为输入处理是异步的。就是说,它是通过一个接收完成中断驱动以太网设备程序来接收一个输入分组,而不是通过进程的系统调用。内核处理这个设备中断,并调度设备驱动程序进入运行状态

接口层-以太网输入

以太网设备驱动程序处理这个中断。

假定它表示一个正常的接收已完成,数据从以太网设备读取到一个mbuf链表中。设备驱动程序把mbuf传给一个通用以太网输入例程,它通过以太网帧中的类型字段来确定哪个协议层接收此分组。

协议层——IP输入

IP输入是异步的,并且通过一个软中断来执行。

当接口层在系统的一个接口上收到一个IP数据报时,它就设置这个软中断。当IP输入例程执行它时,循环处理在它的输入队列中的每一个IP数据报,并在整个队列被处理完后返回。

输入层-UDP输入

IP输入历程可能会调用UDP输入例程去处理UDP数据报。

UDP输入例程验证UDP首部中的各字段(长度与可选的校验和),然后确定是否一个进程应噶接收次数据报。

UDP输入例程从一个全局变量udb开始,查看所有UDP协议控制块链表PCB,寻找一个本地端口号与接收的UDP数据报的目标端口号相匹配的协议块。(这个PCB是由我们调用socket()创建的,它的成员inp_socket指向相应socket接收,并允许接收的数据在此socket排队).

因为这个UDP数据报要传送给我们的进程,发送方的IP地址和UDP端口号放置到一个mbuf中,这个mbuf和数据被追加到此socket的接收队列中。

最后,接收进程被唤醒。如果进程处于睡眠状态等待数据的到达,进程将标志为可运行状态等待内核的调度。也可以通过select系统调用或SIGIO信号来通知进程数据的到达。

进程输入

进程可以调用socket 的输入函数将mbuf从socket的接收队列复制到我们程序的缓存中。

存储器缓存

在BSD联网代码设计中的一个基本概念就是存储器缓存,称作为一个mbuf(memory buffer),在整个联网代码中用于存储各种信息。

网络协议对内核的存储器管理能力提出了很多要求。这些要求包括能方便地操作可变长缓存,能在缓存头部和尾部添加数据(如底层封装来自高层的数据),能从缓存中移去数据(如,当数据分组向上经过协议栈时要去掉首部),并尽量减少为这些操作所做的数据复制。内核中的存储器管理调度直接关系到联网协议的性能。

mbuf的主要用途是保存在进程和网络接口间互相传递的用户数据。但mbuf也用于保存其它各种数据:源与目的地址、Socket选项等等。

  • 指针m_nextmbuf连接在一起,把一个分组形成一条mbuf链表。
  • 指针m_nextpkt把多个分组链接成一个mbuf链接成一个mbuf链表队列。在队列的每个分组可以是一个单独的mbuf,也可以是一个mbuf链表。每个分组的第一个mbuf包含一个分组首部。如果多个mbuf定义一个分组,只有第一个mbuf的成员m_nextpkt被使用——链表中其它mbuf的成员m_nextpkt全是空指针。

m_get函数

struct mbuf * m_get(int nowait,int type)
{
    struct mbuf * m;
    MGET(m,nowait,type);
    return m;
}
  • nowait的值为M_WAITM_DONTWAIT,它取决于在存储器不可用时是否要求等待。

    例如,当Socket层请求分配一个mbuf来存储sendto系统调用的目的地址时,它指定M_WAIT,因为在此阻塞是没有问题的。但是当以太网设备驱动程序请求分配一个mbuf来存储一个接收的帧时,它指定M_DONTWAIT,因为它是作为一个设备中断处理来执行的,不能进入睡眠状态来等待一个mbuf。在这种情况下,若存储器不可用,设备驱动程序丢弃这个帧比较好。

  • type 指定mbuf的类型

系统调用

所有的操作系统都提供服务访问点,程序可以通过它们请求内核中的服务。各种UNIX都提供精心定义的有限个内核入口点,即系统调用。我们不能改变系统调用,除非我们有内核的源代码。

在各种Unix系统中,每个系统调用在标准C函数库中都有一个相同名字的函数。一个应用程序用标准C的调用序列来调用此函数。这个函数再调用相应的内核服务,所使用的技术依赖于所在的系统。例如,函数可能把一个或多个C参数放到通用寄存器中,并执行几条机器指令产生一个软件中断进入内核。对我们来说,我们可以把系统调用看成C函数。

从进程到内核的受保护的环境的转换是与机器和实现相关的。

BSD内核中,每一个系统调用均被编号,当进程执行一个系统调用时,硬件被配置成仅传送控制给一个内核函数,即将CPU的使用权转给一个内核函数。将标志系统调用的整数作为参数传送给此内核函数。在i386实现中,此内核函数为syscall(),syscall()利用系统调用的编号在系统调用表中找到请求的系统调用的sysent结构.表中的每一单元均为一个sysent结构。

struct sysent{
    int sy_narg;  //参数个数
    int (*sy_call)();//系统调用的实现函数
};

表中有几个项是从sysent数据中来的,概述组是在kern/init_sysent.c中定义的:

struct sysent sysent[] = {
  {3,recvmsg},    /* 27 = recvmsg */
  {3,sendmsg},    /* 28 = sendmsg */
  {6,recvfrom},   /* 29 = recvfrom */
  {3,accept},     /* 30 = accept */
  {3,getpeername},/* 31 = getpeername */
  {3,getsockname},/* 32 = getsockname */
};

例如,recvmsg系统调用在系统调用表中的第27个项,它有2个参数,利用内核中的recvmsg函数实现。

syscall()负责将参数从调用进程复制到内核中,并且分配一个数组来保存系统调用的结果。然后,当系统调用执行完成后,syscall将结果返回给进程。syscall将控制交给鱼系统调用相对应的内核函数。

在i386实现中,调用有点像:

struct sysent * callp;
error = (*callp->syscall)(p,args,rval);

if(error){
    errno = error;
    return -1;
}else{
    return (rval);
}

这里指针callp指向相关的sysent结构;指针p指向调用系统调用的进程的进程表项;args作为参数传给系统调用,它是一个32bit长的字数组;而rval则是一个用来保存系统调用的返回结果的数组,数组有两个元素,每一个元素是一个32bit长的字。当我们用"系统调用"这个词时,我们指的是被syscall调用的内核中的函数,而是不是应用调用的进程中的函数

syscall期望系统调用函数(即sy_call指向的函数)在没有差错时返回0,否则返回非0的差错代码。如果没有差错出现,内核将rval中的值作为系统调用(应用调用的)返回值传送给进程。如果有差错,syscall忽略rval中的值,并以与机器相关的方式返回差错代码给进程,使得进程能从外部变量errno中得到差错代码。应用调用的函数则返回-1或一个空指针表示应用应该查看errno获得差错信息

下表介绍了与网络有关的系统调用





举例

socket系统调用的函数原型是:

int socket(int domain,int type,int protocol);

实现socket系统调用的内核函数原型是:

struct socket_args{
  int domain;
  int type;
  int protocl;
};

socket(struct proc * p,struct socket_args * uap,int * retvall);

当一个应用调用socket时,进程用系统调用机制将三个独立的整数传给内核。syscall将参数复制到32bit值的数组中,并将数组指针作为第二个参数传给socket的内核版。内核版的socket将第二个参数作为指向socket_args结构的指针。下图描述了上述过程:

同socket类似,(在i386实现中)每一个实现系统调用的内核函数将args说明称一个与系统调用有关的结构指针,而不是一个指向32bit的子的数组的指针

syscall在执行内核系统调用函数之前将返回值设置为0.如果没有差错出现,系统调用函数直接返回而不需要清楚*tetvall,syscall返回0给进程。

进程、描述符和插口

Unix系统中的Socket I/O遵循其"一切皆文件"的思想,因而可以使用统一的方式对Socket 进行I/O操作。

调用socket()时要求定义socket类型。Internet协议族(PF_INET)和数据报socket(SOCK_DGRAM)组合成一个UDP协议socket。

socket()的返回值是一个文件描述符,它具有其它Unix文件描述符的所有特性:可以用这个描述符调用read()write();可以用dup()复制它,在调用了fork()之后,父进程和子进程可以共享它;可以用fcntl()来改变他的属性,可以调用close()来关闭它,等的。

在每个进程的生存期内都会有一个对应的进程表项存在。

一个文件描述符是进程对应的进程表项中的一个数组的下标.这个数组项是一个指向打开文件表结构的指针。

此打开文件表结构有指向一个描述此文件的i-nodev-node结构

实现系统调用的函数的第一个参数总为p,即指向调用进程的proc结构的指针。内核利用proc结构体记录进程的有关信息。在proc结构体中,p_fd指向filedesc结构,该结构的主要功能是管理fd_ofiles指向的描述符表描述符表的大小是动态变化的,由一个指向file结构的指针数组组成每一个file结构体描述一个打开的文件,该结构体可被多个进程共享

通过p->p_fd->fd_ofiles[fd]访问到结构。在file结构中,有两个结构成员是我们感兴趣的:f_opsf_data。I/O系统调用(如read和write)的实现因描述符中的I/O对象类型的不同而不同。f_ops指向fileops结构,该结构包含一张readwriteioctlselectclose系统调用的函数指针表。显示f_ops指向一个全局的fileops结构,即socketops,该结构包含指向socket用的函数的指针。

f_data指向相关I/O对象的专用数据。对于socket而言,f_data指向与描述符相关的socket结构。最后,socket结构中的so_proto指向产生socket时选中的协议的protosw结构。回想一下,每一个protosw结构是由与该协议关联的所有socket共享的。

Socket结构

Socket代表一条通信链路的一端,存储或指向与链路有关的所有信息。这些信息包括:使用的协议协议的状态信息(包括源地址和目的地址)到达的连接队列数据缓存可选标识

struct socket{

    short so_type;//Socket类型,SOCK_STREAM、SOCK_DGRAM或SOCK_RAW
    short so_options;//Socket行为的标志
    short so_linger;
    short so_state;//Socket状态
    caddr_t so_pcb;//协议控制块(Protocol Control Block)
    struct protosw * sp_proto;//协议处理函数

    /**
     * Socket连接队列相关
     */
    struct socket * so_head;
    struct socket * so_qo;
    struct socket * so_q;
    short so_q0len;
    short so_qlen;
    short so_qlimit;
    short so_timeo;

    u_short so_error;
    pid_t so_pgid;
    u_long so_oobmasrk;

    /**
     * Socket缓存相关变量
     */
    struct sockbuf{
        struct mbuf * sb_mb;//mbuf链,用于存储用户数据

        u_long sb_cc;//缓存中的实际字节数

        u_long sb_hiwat;
        u_long sb_mbcnt;
        u_long sb_mbmax;//分配给此socket mbuf缓存的存储器数量的上限。
        long sb_lowat;
        struct selinfo sb_sel;
        short sb_flags;

        short sb_timeo;//read/write超时时间
    } so_rcv,so_snd; //Socket的输入缓存和输出缓存

    caddr_t so_tpcb;
    void (*so_upcall)(struct socket * so,caddr_t arg,int waitf);
    caddr_t so_upcallarg;
};

通用字段

so_type

so_type由产生Socket的进程来指定,它指明Socket和相关协议支持的通信语义。

pr_type 协议语义 Internet协议
SOCK_STREAM 可靠的双向字节流服务 TCP
SOCK_DGRAM 最好的传输层数据报服务 UDP
SOCK_RAW 最好的网络层数据报服务 ICMP、IGMP、原始IP
SOCK_RDM 可靠的数据报服务(未实现)

Socket实现的更多相关文章

  1. socket读写返回值的处理

    在调用socket读写函数read(),write()时,都会有返回值.如果没有正确处理返回值,就可能引入一些问题 总结了以下几点 1当read()或者write()函数返回值大于0时,表示实际从缓冲 ...

  2. Socket聊天程序——Common

    写在前面: 上一篇记录了Socket聊天程序的客户端设计,为了记录的完整性,这里还是将Socket聊天的最后一个模块--Common模块记录一下.Common的设计如下: 功能说明: Common模块 ...

  3. Socket聊天程序——客户端

    写在前面: 上周末抽点时间把自己写的一个简单Socket聊天程序的初始设计和服务端细化设计记录了一下,周二终于等来毕业前考的软考证书,然后接下来就是在加班的日子度过了,今天正好周五,打算把客户端的详细 ...

  4. Socket聊天程序——服务端

    写在前面: 昨天在博客记录自己抽空写的一个Socket聊天程序的初始设计,那是这个程序的整体设计,为了完整性,今天把服务端的设计细化记录一下,首页贴出Socket聊天程序的服务端大体设计图,如下图: ...

  5. Socket聊天程序——初始设计

    写在前面: 可能是临近期末了,各种课程设计接踵而来,最近在csdn上看到2个一样问答(问题A,问题B),那就是编写一个基于socket的聊天程序,正好最近刚用socket做了一些事,出于兴趣,自己抽了 ...

  6. Java中的Socket的用法

                                   Java中的Socket的用法 Java中的Socket分为普通的Socket和NioSocket. 普通Socket的用法 Java中的 ...

  7. Android Socket连接PC出错问题及解决

    最近测试问题:Android 通过Socket链接电脑,ip和端口都是正确的,也在同一网段,可android端就是报异常如下: 解决办法:测试电脑的防火墙可能开着,在控制面板把防火墙打开即可.

  8. Linux下的C Socket编程 -- server端的继续研究

    Linux下的C Socket编程(四) 延长server的生命周期 在前面的一个个例子中,server在处理完一个连接后便会立即结束掉自己,然而这种server并不科学啊,server应该是能够一直 ...

  9. Mono 3.2.3 Socket功能迎来一稳定的版本

    由于兴趣自己业余时间一直在搞.net下面的通讯应用,mono的存在得以让.NET程序轻松运行在Linux之下.不过经过多尝试Socket相关功能在Mono下的表现并不理想.不管性能还是吞吐能力方面离我 ...

  10. Demo源码放送:打通B/S与C/S !让HTML5 WebSocket与.NET Socket公用同一个服务端!

    随着HTML5 WebSocket技术的日益成熟与普及,我们可以借助WebSocket来更加方便地打通BS与CS -- 因为B/S中的WebSocket可以直接连接到C/S的服务端,并进行双向通信.如 ...

随机推荐

  1. poj1797 Heavy Transportation Dijkstra算法的简单应用

    题目链接:http://poj.org/problem?id=1797 题目就是求所有可达路径的其中的最小值边权的最大值 即对于每一条能够到达的路径,其必然有其最小的承载(其实也就是他们自身的最大的承 ...

  2. WPF 简易手风琴 (ListBox+Expander)

    概述 之前听说很多大神的成长之路,几乎都有个习惯--写博文,可以有效的对项目进行总结.从而提高开发的经验.所以初学WPF的我想试试,顺便提高一下小学作文的能力.O(∩_∩)O哈哈~ 读万卷书不如行万里 ...

  3. sqlmap连接Mysql实现getshell(原创)

    前言 昨天群友发了一知乎的帖子..才发现sqlmap玩了那么久有些玩意我居然没玩过...然后看着群友玩= =今天也想试试. 0x01 首先得知道这个玩意,sqlmap -help,不说大家也懂搜嘎. ...

  4. fopen的使用小记

    整理自https://msdn.microsoft.com/zh-cn/library/t3ayayh1(VS.80).aspx errno, _doserrno, _sys_errlist, and ...

  5. 基于Kubernetes的WAF集群介绍

    Kubernetes是Google开源的容器集群管理系统.它构建Docker技术之上,为容器化的应用提供资源调度.部署运行.服务发现.扩容缩容等整一套功能,可看作是基于容器技术的PaaS平台. 本文旨 ...

  6. C#反射通过类名的字符串获取生成对应的实例

    在.net core 1.1环境下 今天项目中遇到这个问题了,稍微查了一下并没有现成的样例.自己实现了. static void Main(string[] args) { TestGetAssemb ...

  7. 深入tornado中的TCPServer

    1 梳理: 应用层的下一层是传输层,而http协议一般是使用tcp的,所以实现tcp的重要性就不言而喻. 由于tornado中实现了ioloop这个反应器以及iostream这个对连接的异步读写,所以 ...

  8. Day1-模块初识

    模块,也叫库,分为标准库和第三方库.标准库,直接导入使用,比如import getpass:第三方库,需下载安装才能使用,比如paramiko: 一.sys模块 import sys print(sy ...

  9. 刨根究底字符编码之五——简体汉字编码方案(GB2312、GBK、GB18030、GB13000)以及全角、半角、CJK

    简体汉字编码方案(GB2312.GBK.GB18030.GB13000)以及全角.半角.CJK   一.概述 1. 英文字母再加一些其他标点字符之类的也不会超过256个,用一个字节来表示一个字符就足够 ...

  10. ES6核心内容精讲--快速实践ES6(三)

    Promise 是什么 Promise是异步编程的一种解决方案.Promise对象表示了异步操作的最终状态(完成或失败)和返回的结果. 其实我们在jQuery的ajax中已经见识了部分Promise的 ...