第一章 概述

一:应用编程接口

        在互联网协议中两种常用的应用编程接口 ( A P I )是插口( s o c k e t )和T L I (运输层接口)。

二:系统调用和库函数

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFSIZE 150
int main()
{
    struct sockaddr_in serv;
    char buff[]BUFFSIZE;
    int sockfd, n;

    //socket函数创建了一个UDP 插口,并且给进程返回一个保存在变量sockfd中的描述符
    /*一开始调用s o c k e t,这要求定义插口类型。I n t e r n e t协议族(P F _ I N E T)和数据报插口(S O C K _ D G R A M)组合成一个U D P协议插口。s o c k e t的返回值是一个述符,它具有其他 U n i x描述符的所有特性:可以用这个描述符调用r e a d和w r i t e;可以用d u p复制它,在调用了 f o r k后,父进程和子进程可以共享它;可以调用f c n t l来改变它的属性,可以调用 c o l s e来关闭它,等等。在我们的例子中可以看到插口描述符是函数 s e n d t o和r e c v f r o m的第一个参数。当程序终止时 (通过调用e x i t ),所有打开的描述符,包括插口描述符都会被内核关闭。*/
    if((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
    {

        /*差错处理函数err_sy它接收任意数量的参数,并用vsprintf对它们格式化,将系统调用产生的errno值对应的Unix错误信息打印出来,并中断进程。*/
         err_sys("socket error");
    }
    bzero((char *) &serv,sizeof(serv));
//置字节字符串serv的前sizeof(serv)个字节为零且包括‘\0’。
    //在一个互联网插口地址结构中存放IP地址和端口号
    serv.sin_family = AF_INET;
    serv.sin_addr.s_addr = inet_addr("140.252.1.32");
//函数inet_addr将一个点分十进制表示的IP地址的ASCII字符串转换成网络字节序的32bit二进制整数。
    serv.sin_port = htons(13);//函数htons把一个主机字节序的短整数(可能是低字节在后)转换成网络字节序(高字节在后)。
    
    /*发送数据报给服务器   s e n d t o的第二个参数指示了数据缓存 ( b u ff )的开始位置,第三个参数是它的大小   第5个参数指向一个I n t e r n e t插口地址结构(叫s e r v ),第6个参数指示它 的长度(后面我们将要看到是1 6个字节)。*/
    if(sendto(sockfd, buff, BUFFSIZE, 0,
                (struct sockaddr *)&serv, sizeof(serv)) != BUFFSIZE)
        err_sys("sendto error");

    //读取从服务器返回的数据报
    if((n = recvfrom(sockfd, buff, BUFFSIZE, 0,
                        (struct sockaddr *)NULL, (int *)NULL)) < 2)
        err_sys("recvfrom error");

    /*程序通过调用r e c v f r o m来读取从服务器发回的数据报。 U n i x服务器典型地发回一个如下格式的2 6字节字符串 Sat Dec 11 11:28:05 1993\r\n,\ r是一个A S C I I回车           符,\ n是A S C I I换行符。我们的程序将回车符替换成一个空字节,然后调用p r i n t f输出结果。*/
    buff[n-2] = 0;
    printf("%s\n", buff);
    exit(0);
}

//程序示例:发送一个数据报给U D P日期/时间服务器并读取一个应答

如图1-5 是当s o c k e t被调用时(进程执行了一个系统调用),内核访问进程表结构,并且当s o c k e t被调用时返回未用描述符的最小编号3。

1) 我们的进程调用s o c k e t,最后分配了最小未用的描述符(在我们的例子中是3 )。在后面,所有针对此s o c k e t的系统调用都要用这个描述符。
2) 以下内核数据结构是一起被分配和链接起来的:一个 D T Y P E _ S O C K E T类型f i l e结构、一个s o c k e t结构和一个i n p c b结构。这些结构的很多初始化过程我们并没有说明: f i l e结构的读写标志(因为调用s o c k e t总是返回一个可读或可写的描述符 );默认的输入和输出缓存大小被设置在s o c k e t结构中,等等。
3) 我们显示了标准输入、输出和标准错误处理的非 s o c k e t描述符的目的是为了说明所有描述符最后都对应一个f i l e结构,虽然s o c k e t描述符和其他描述符之间有所不同。

三:mbuf


图1 - 7显示了1 5 0字节的数据是如何存储在两个 mbuf 中的。这种安排叫做m b u f链表。在每个m b u f中的成员m _ n e x t把链表中所有的m b u f都链接在一起。我们看到的另一个变化是链表中第一个m b u f的m b u f首部的另外两个成员:m _ p k t h d r . l e n和m _ p k t h d r . r c v i f。这两个成员组成了分组首部并且只用在链表的第一个 m b u f中。成员m _ f l a g s的值是M _ P K T H D R,指示这个m b u f包含一个分组首部。分组首部结构的成员 l e n包含了整个m b u f链表的总长度(在本例中是1 5 0 ),下一个成员r c v i f在后面我们会看到,它包含了一个指向接收分组的接收接口结构的指针。

添加I P和U D P首部


在插口层将目标插口地址结构复制到一个 m b u f中,并把数据复制到m b u f链中后,与此插口描述符(一个U D P描述符)对应的协议层被调用。明确地说, U D P输出例程被调用,指向m b u f的指针被作为一个参数传递。这个例程要在这 1 5 0字节数据的前面添加一个I P首部和一个U D P首部,然后将这些m b u f传递给I P输出例程。

I P首部和U D P首部被放置在新 m b u f的最后,这个新 m b u f就成了整个链表的首部。如果需要,它允许任何其他低层协议 (例如接口层)在I P首部前添加自己的首部,而不需要再复制I P和U D P首部。在第一个m b u f中的m _ d a t a指针指向这两个首部的起始位置, m _ l e n的值是2 8。在分组首部和 I P首部之间有 7 2字节的未用空间留给以后的首部,通过适当地修改m _ d a t a指针和m _ l e n添加在I P首部的前面。

一个进程调用 s e n d t o传输一个U D P数据报时的大致处理过程。(如图1-9)


数据从设备读到一个m b u f链表中。(如图1-10)


四:中断级别与并发

图 1 - 1 3所示的是8个优先级别的顺序,从最低级别(不阻塞中断)到最高级别(阻塞所有中断)。


不同优先级的顺序意味着高优先级中断可以抢占一个低优先级中断。看图 1 - 1 4所示的事件顺序

1)  当插口层以级别s p l 0执行时,一个以太网设备驱动程序中断发生,使接口层以级别s p l i m p执行。这个中断抢占了插口层代码的执行。这就是异步执行接口输入例程。
2) 当以太网设备驱动程序在运行时,它把一个接收的分组放置到 I P输出队列中并调度一个s p l n e t级别的软中断。软中断不会立即有效,因为内核正在一个更高的优先级 

(s p l i m p)上运行。
3) 当以太网设备驱动程序完成后,协议层以级别s p l n e t执行。这就是异步执行I P输入例程。
4) 一个终端设备中断发生 (完成一个S L I P分组),它立即被处理,抢占协议层,因为终端输入/输出(s p l t t y)优先级比图1 - 1 3中的协议层(s p l n e t)更高。
5) SLIP驱动程序把接收的分组放到I P输入队列中并为协议层调度另一个软中断。
6) 当S L I P驱动程序结束时,被抢占的协议层继续以级别 s p l n e t执行,处理完从以太网设备驱动程序收到的分组后,处理从S L I P驱动程序接收的分组。仅当没有其他输入分组要处理时,它会把控制权交还给被它抢占的进程(在本例中是插口层)。
7) 插口层从它被中断的地方继续执行。

例:

struct mbuf *m;
int s;
s = splimp ();
IF_DEQUEUE (&ipintrq, m);
splx(s);
if (m == 0)
return;

调用s p l i m p把C P U的优先级升高到网络设备驱动程序级,防止任何网络设备驱动程序中断发生。原来的优先级作为函数的返回值存储到变量 s中。然后执行宏I F _ D E Q U E U E把I P输入队列(i p i n t r q)头部的第二个分组删去,并把指向此 m b u f链表的指针放到变量m中。最后,通过调用带有参数 s (其保存着前面调用 s p l i m p的返回值)的s p l x,C P U的优先级恢复到调用s p l i m p前的级别。由于在调用s p l i m p和s p l x之间所有的网络设备驱动程序的中断被禁止,在这两个调用间的代码应尽可能的少。如果中断被禁止过长的时间,其他设备会被忽略,数据会被丢失。因此,对变量m的测试(看是否有其他分组要处理)被放在调用s p l x之后而不是之前。当以太网输出例程把一个要输出的分组放到一个接口队列,并测试接口当前是否忙时,若接口不忙则启动接口,这时例程需要调用这些 s p l调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值