嵌入式系统设计与开发课件-第9章_第1页
嵌入式系统设计与开发课件-第9章_第2页
嵌入式系统设计与开发课件-第9章_第3页
嵌入式系统设计与开发课件-第9章_第4页
嵌入式系统设计与开发课件-第9章_第5页
已阅读5页,还剩230页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

9.1嵌入式以太网基础知识9.2以太网接口设计9.3Linux网络编程实现练习题第9章嵌入式网络程序设计9.1嵌入式以太网基础知识

9.1.1以太网介绍及其嵌入式应用

1973年,施乐(Xerox)公司设计了第一个局域网系统,命名为Ethernet,其带宽为2.97Mb/s。1982年,DEC、Intel和Xerox联合发表了EthernetVersion2规范,将带宽提高到了10Mb/s,并正式投入商业市场。1983年,IEEE(国际电气和电子工程师学会)通过802.3CSMA/CD规范。IEEE802标准是由IEEE制定的局域网标准,IEEE802委员会有10多个分委员会。IEEE802标准分类如下:

(1) 802.1A,概述体系结构和网络互连、网络管理;

(2) 802.1B,寻址、网络管理、网间互连及高层接口;

(3) 802.2,逻辑链路控制LLC;

(4) 802.3,CSMA/CD共享总线网,即Ethernet;

(5) 802.5,令牌环网(Token-Ring);

(6) 802.11,无线局域网。

IEEE802.3的命名规则是IEEE802.3XTYPE-YNAME。其中:X表示传输介质,5指粗同轴电缆,2指细同轴电缆,T指双绞线,F指光纤;TYPE代表传输方式,Base指基带传输,Broad指宽带传输;Y与X一样表示传输介质;NAME表示局域网的名称,Ethernet为以太网,FastEthernet为快速以太网,GigaEthernet为千兆以太网。

以太网的数据链路层由以下两部分组成:

(1)逻辑链路控制子层(LogicalLinkControl,即LLC子层),为网络层定义了各种服务及接口;

(2)介质访问控制子层(MediaAccessControl,即MAC子层),定义了对各种物理传输介质的访问及控制技术。

IEEE802标准为各种局域网技术定义了统一的LLC子层,而各种局域网技术的MAC子层不尽相同,所以以太网的数据链路层主要是指以太网的MAC子层。

CSMA/CD(CarrierSenseMultipleAccess/CollisionDetect)协议的全称为带冲突检测的载波监听多路访问技术,是以太网中所使用的介质访问控制技术。这种介质访问控制技术是基于共享介质的,采用共享总线的拓扑结构,以广播的形式进行数据传送。在某一时刻,连接在共享总线上的所有站点中,只能有一个站点可以发送数据。

CSMA协议是指:

(1)总线有两个状态,即“空闲”和“忙”;

(2)每个站点在使用总线发送数据帧之前首先监听总线,查看总线是否处于“空闲”状态,如果总线“忙”,就继续等待,继续监听,一直到总线“空闲”;

(3)要发送数据帧的站点在监听到总线“空闲”时,开始发送数据帧。

CD协议是指:

(1)在使用CSMA协议时,有可能会出现两个或两个以上的站点同时监听到总线“空闲”的情况,此时这些站点将同时开始发送数据帧,出现这种情况时,总线会发生冲突,导致所有站点的发送全部失败;

(2)每个站点在发送数据后必须检测是否发生了冲突;

(3)在发生冲突的情况下,站点使用二进制指数退避算法和重发数据帧。

MAC地址是指每张网卡中包含一个独一无二的物理地址,由48位二进制构成,前24位代表设备生产商,由IEEE管理分配,后24位为各生产商内部的编号。由于CSMA/CD协议中,帧以广播的形式进行传输,因此总线上的每个站点要根据帧中包含的目的MAC和自己网卡中的MAC地址是否一致来决定是否接收该帧。以太网接口控制器主要包括MAC和PHY两部分,其中MAC层控制器作为逻辑控制比较容易集成在处理器内部。很多针对网络控制应用的嵌入式处理器都集成了MAC层控制器。以ARM处理器核为例,通常,这种在处理器内部集成片内MAC层控制器的芯片结构如图9-1所示。ASB(AdvancedSystemBus,高级系统总线)和APB(AdvancedPeripheralBus,高级外设总线)都是AMBA(AdvancedMicroprocessorBusArchitecture,高级微处理器总线结构)总线定义的类型。ASB用作处理器与高速外设之间的互连,APB则为系统的低速外部设备提供低功耗的简易互连。在图9-1所示的集成在ARM片内的MAC层控制器体系结构中,ARM核可通过APB总线访问寄存器接口,而MAC层控制器可通过DMA与内存交换数据。图9-1集成了MAC层控制器的ARM和PHY的连接

MAC层控制器和PHY的连接是通过MII(MediaIndependentInterface,媒体独立接口)、RMII(ReducedMII,精简MII)等接口实现的。MAC层负责完成数据帧的封装、解封、发送和接收功能。物理层PHY的结构随着传输速率的不同而有一定差异。MII是连接数据链路层和物理层的接口。根据协议,要求MII接口具有的功能有:数据和帧分隔符的读/写时钟同步;提供独立的读/写数据通道;为MAC层和PHY层提供相应的管理信号以及支持全双工模式。

对于10Base-T等网络,从以太网PHY芯片输出的就是传输所需的差分信号,但是还需要一个网络隔离变压器组成如图9-2所示的结构。网络隔离变压器可起到抑制共模干扰、隔离线路以及阻抗匹配等作用。图9-210Base-T网络的网络接口对于没有集成MAC控制器的嵌入式处理器,更为通用的方法就是使用以太网芯片。在嵌入式系统中通常需要的是HostBus接口的以太网芯片。其中,常见的有:

(1) RTL8019—Realtek公司的全双工10Mb/s以太网芯片。它最初是为了ISA接口的网卡设计的,支持即插即用ISA模式,兼容NE2000寄存器,支持8位和16位总线模式。其特点是:成本低廉,使用广泛,驱动程序支持好;支持跳线模式,在嵌入式处理器平台上,很容易使用固有的配置;但是,其使用的是5V逻辑电平,与3.3V逻辑连接,需要考虑总线电平的问题。

(2) CS8900—CirrusLogic公司的全双工10Mb/s以太网芯片,也是为ISA总线接口设计的以太网芯片。其特点是:16位总线宽度;成本较低,Linux等多数操作系统下都有驱动程序支持;有I/O和存储两种访问模式;3.3V供电和I/O接口很容易与多数嵌入式处理器直接连接。

(3) AX88796—ASIX公司10/100Mb/s自适应以太网芯片,支持HostBus和Motorola的68000系列处理器读/写时序。其特点是:8位或16位数据总线;兼容NE2000寄存器,驱动程序支持广泛;3.3V接口电平,容易使用;还可使用MII接口与片外的PHY芯片连接。

(4) DM9000—DAVICOM公司的10/100Mb/s自适应以太网芯片。其特点是:支持8位、16位、32位数据总线宽度;寄存器操作简单有效,有成熟的Linux驱动程序支持;3.3V接口电平,容易使用;成本低廉;还可使用MII接口与片外的PHY芯片连接。

(5) LAN91C111—SMSC公司的10/100Mb/s自适应以太网芯片。其特点是:支持8位、16位、32位数据总线宽度;支持异步和同步总线传输;有成熟的Linux驱动程序支持,但寄存器操作比较复杂;3.3V接口电平;成本偏高;也可使用MII接口与片外的PHY芯片连接。多数以太网芯片都可通过一片串行的EEPROM进行配置,主要包括网卡的工作模式、总线的地址、中断和以太网的MAC地址等信息。但是,通常对于嵌入式系统应用来说,设计者希望节省这个用于配置的存储器,而通过驱动程序来配置以太网芯片。关于芯片和处理器的连接原理图等,这里不作详细讨论,请根据需要自行设计。需要注意的问题如下:

(1)总线宽度读/写等待周期、时序是否匹配等;

(2)如果不使用配置存储器,芯片复位以后,在总线上的默认地址如何配置与保存;

(3)默认的中断号及中断触发模式,如上升沿(或高电平)、下降沿(或低电平)如何设置。9.1.2嵌入式系统中主要处理的网络协议

在互联网发展的历史中,最成功的莫过于TCP/IP协议。TCP/IP协议(TransmissionControlProtocol/InternetProtocol,传输控制协议/互联网络协议)是Internet最基本的协议,简单地说,它就是由网络层的IP协议和传输层的TCP协议组成的。广义的TCP/IP协议族主要可划分为如表9-1所示的几个层次。

TCP/IP主要定义的是网络层及网络层之上的协议标准。所有这些协议都在相应的RFC(RequestForComments,请求注解)文档中讨论及标准化。重要的协议在对应的RFC文档中均标记了状态,比如必需(required)、推荐(recommended)、可选(elective)。其他的协议还可能有试验(experimental)或历史(historic)的状态。下面对在嵌入式系统应用中几个主要协议作简要介绍。

1.ARP

ARP(AddressResolutionProtocol,地址解析协议)的功能是实现从IP地址到对应物理地址的转换。网络层用32位的IP地址来标识不同的主机,而链路层使用48位的MAC地址来标识不同的以太网接口。只知道目的主机的IP地址并不能发送数据帧给它,必须要知道目的主机网络接口的MAC地址才能发送数据帧。其工作流程可总结为:源主机发送一份包含目的主机IP地址的ARP请求数据帧给网上的每个主机(称做ARP广播),目的主机收到这份ARP广播报文后,识别出这是发送端在询问它的IP地址,于是发送一个包含目的主机IP地址及对应的MAC地址的ARP应答给源主机。每台主机上都有一个ARP缓存,存放最近的IP地址到MAC地址之间的映射记录。通常每一项的生存时间为20min。提示:在Windows或Linux中通过arp命令可查看记录在系统中的arp列表。例如Linux下:

2.ICMP

ICMP(InternetControlMessagesProtocol,网络控制报文协议)被封装于IP层数据包中,是IP层的附属协议。IP层用它来与其他主机或路由器交换错误报文及其他重要控制信息。两个实用的网络诊断工具Ping和Traceroute(Windows下是Tracert)都是利用该协议工作的。

3.IP

IP协议工作在网络层,它是TCP/IP协议族中最为核心的协议。所有的TCP、UDP、ICMP以及IGMP数据都以IP数据报格式传输。IP数据报最长可达65535字节,其中报头占20字节,包含各32位的源IP地址和目的IP地址。有时,在嵌入式应用中为了简化设计、节约资源,没有必要支持如此大(64KB)的IP数据报。例如,让IP数据报长度小于或等于数据链路层的数据长度(对于以太网来说就是1500字节),大多数IP层以上的协议仍然工作得很好。

4.TCP

TCP协议为两台主机提供基于连接的高可靠性的端到端数据通信。

发送方把应用程序交给它的数据分成合适的小块并附加信息(TCP头),包括顺序号、源、目的端口、控制、纠错信息等字段,称为TCP数据报,并将TCP数据报交给下面的网络层处理。

接收方确认接收到的TCP数据报,重组并将数据送往高层。

5.UDP

UDP(UserDatagramProtocol,用户数据报协议)是一种无连接、不可靠的传输层协议。其实现过程很简单,仅把应用程序传来的数据加上UDP头(包括端口号、段长等字段)作为UDP数据报发送出去,但并不保证它们能到达目的地。数据传输的可靠性由应用层来提供。

6.TFTP

TFTP(TrivialFileTransferProtocol,简单文件传送协议)基于UDP协议而实现。此协议设计时是进行小文件传输的,因此它不具备通常的FTP的许多功能,而只能从文件服务器上获得或写入文件,没有目录的概念,不能进行认证和权限管理。TFTP协议设计的初衷是用于引导无盘系统,因为TFTP设计得简单实用,实现也很容易。在嵌入式系统中,引导程序(BootLoader)常使用TFTP协议通过以太网下载系统内核等。

9.2以太网接口设计

9.2.1网络设备驱动程序基本结构及功能

Linux网络驱动程序位于TCP/IP网络体系结构的最底层,它主要实现两个功能:

(1)接收上层协议栈发送来的数据,将其发送给网络设备;

(2)从网络设备接收数据,后交于上层协议处理。

Linux的网络设备驱动是Linux网络子系统的一部分,它在Linux网络子系统中的层次结构如图9-3所示。网络设备驱动在硬件设备与上层协议间建立起数据通信的桥梁。图9-3Linux网络驱动体系结构上层协议栈产生的数据需由网络协议接口层的dev_queue_xmit()函数发送。dev_queue_xmit()函数通过网络设备接口net_device的hard_start_xmit()函数指针,调用驱动程序的数据发送函数,将上层协议产生的数据包发送给硬件设备。

当网络数据到达设备后会触发设备中断,设备驱动程序响应中断,从网络设备接收数据,并通过网络协议接口层的netif_rx()函数将数据发送给上层协议栈。

在Linux网络子系统中,驱动程序设计要做的工作就是根据底层具体的硬件特性定义网络设备接口structnet_device类型的结构体变量,并实现相应的操作接口函数以及中断处理,完成设备驱动功能。网络驱动程序主要完成系统的初始化、数据包的发送和接收等功能。网络设备的初始化主要由net_device数据结构中的init()函数指针所指向的初始化函数来完成,当内核启动或加载网络驱动模块的时候,就会调用这个初始化函数。在初始化函数中通过检测物理设备的硬件特征来侦测网络物理设备是否存在,然后再对设备进行资源配置,接下来构造设备的net_device数据结构,并用检测到的数据对net_device中的变量初始化,最后向Linux内核注册该设备并申请内存空间。数据包的发送和接收是实现Linux网络驱动程序中两个最关键的过程,对这两个过程处理的好坏将直接影响到驱动程序的整体运行质量。在网络设备驱动加载时,通过net_device域中的init()函数指针调用网络设备的初始化函数对设备进行初始化,如果操作成功再通过net_device域中的open()函数指针调用网络设备的打开函数打开设备,并通过net_device域中建立硬件包头函数指针hard_header来建立硬件包头信息,最后通过协议接口层函数dev_queue_xmit()(参见net/core/dev.c文件)来调用net_device域中的hard_start_xmit()函数指针完成数据包的发送。该函数把存放在套接字缓冲区中的数据发送到物理设备,该缓冲区是由数据结构sk_buff来表示的。数据包的接收是通过中断机制来完成的。当有数据到达时产生中断信号,网络设备驱动功能层调用中断处理程序(即数据包接收程序)来处理数据包的接收,随后网络协议接口层调用netif_rx()(参见net/core/dev.c文件)函数,把接收到的数据包传输到网络协议的上层进行处理。9.2.2以太网控制器CS8900A

1. CS8900A介绍

CS8900A芯片是CIRRUSLOGIC公司的一款16位的以太网控制器,芯片内嵌片内RAM、10Base-T收/发滤波器、直接ISA总线接口。该芯片的突出特点是使用灵活,其物理层接口、数据传输模式和工作模式等都能根据需要而动态调整,并可通过内部寄存器的设置来适应不同的应用环境。1)特性

(1)最大工作电流55mA;

(2)支持广泛的软件驱动;

(3) 3V供电电压;

(4)工业级温度范围;

(5)全双工通信方式;

(6)可编程发送功能;

(7)数据碰撞自动重发;

(8)自动打包及生成CRC校验码;

(9)可编程接收功能;

(10)数据流降低CPU消耗;

(11)自动切换于DMA和片内RAM;(12)提前产生中断,便于数据帧预处理;

(13)自动阻断错误包;

(14)可跳线控制EEPROM功能;

(15)启动编程支持无盘系统;

(16)边沿扫描和回环测试;

(17) LED驱动器用于指示连接状态和网络活动情况;

(18)待机和睡眠模式;

(19) TQFP-100封装。

CS8900的功能框图如图9-4所示。图9-4CS8900的功能框图

2)工作原理

CS8900A收到由主机发来的数据报(从目的地址域到数据域)后,侦听网络线路。如果线路忙,它就等到线路空闲为止,否则立即发送该数据帧。发送过程中,首先添加以太网帧头(包括先导字段和帧开始标志),然后生成CRC校验码,最后将此数据帧发送到以太网上。接收时,它将从以太网收到的数据帧在经过解码、去掉帧头和地址检验等步骤后缓存在片内。通过CRC校验后,它会根据初始化配置情况,通知主机CS8900A收到数据帧,最后用上面介绍的某种传输模式传到主机的存储区中。

3)引脚定义

CS8900A采用TQFP-100封装,引脚多达100个,但在实际使用中并不需要把它们全部引出来。CS8900A中一些比较重要的引脚定义和说明如表9-2所示。对于表9-2中没有提到的引脚,有一些具有很明确的定义。例如:XTALl、XTAL2,它们是用来连接外部20MHz的晶振;AVDD、DVDD分别是用来连接模拟和数字的电源;NC代表引脚是不连接的。

2. CS8900A的操作方法

基于对功耗和布板的要求,嵌入式系统大都采用比较简单的I/O模式。本节所介绍的CS8900A就是工作在I/O模式下,其操作方法也是基于I/O模式的。

1) CS8900A的初始化

CS8900A在上电以后,默认的操作模式是I/O模式,因此需要以I/O模式的操作方法进行初始化。

CS8900A的初始化主要由SBHE(SystemBusHighEnable)引脚参与。初始化时需在SBHE引脚上产生一个由高到低,然后由低到高的电平。完成这个操作之后,才可以对该芯片进行读/写操作。由于在正常工作时CS8900A是以字(16位)为单位寻址的,故处理器的地址线A0引脚不用于寻址。在以太网电路设计时,可把A0引脚连接到CS8900A的SBHE引脚,让处理器通过访问A0完成初始化任务。这样,在对CS8900A进行初始化时,实际上只要首先进行从奇数地址到偶数地址寻址,然后再进行一次从偶数地址到奇数地址寻址,就可以在SBHE引脚产生一个从高到低,然后由低到高的电平变化,从而实现该芯片的初始化。

2) CS8900A的I/O模式寄存器

在I/O模式下,CS8900A只有6个寄存器可以直接寻址访问,它们叫做I/O模式寄存器,如表9-3所示。对这些寄存器的访问采用直接寻址的方式。按照本章所介绍的电路图,CS8900A连接片选1的地址空间,因而地址计算公式为:地址 = 片选1的地址+I/O基址+偏移地址。

下面以计算片内寄存器指针(PacketPagePointer)寄存器地址为例,说明寄存器地址的计算。PXA255处理器的片选1的地址为0x04000000;CS8900A默认的I/O基址为0x300;根据表9-3,片内寄存器指针寄存器的偏移地址为0x00a,因此,片内寄存器指针寄存器的访问地址为:0x04000000+0x300+0x00a

=0x0400030a。

3) CS8900A片内寄存器的读/写

CS8900A片内寄存器就存在它的片内RAM中。这些片内寄存器在I/O模式下不能被直接访问,对它们的读/写须通过前述的I/O模式寄存器进行。

片内寄存器的操作与片内寄存器指针寄存器和数据端口寄存器有关,参见表9-3。

假设需要读片内寄存器A,则必须先往片内寄存器指针寄存器写入A寄存器的片内相对地址;然后寄存器就会被映射到数据端口(PacketPageData(Port0))寄存器上,也就是说,此时对数据端口寄存器的读/写就相当于对A寄存器的读/写。下面以读取产品ID寄存器为例,说明读取寄存器的方法。产品ID寄存器是个只读寄存器,片内相对地址为0x0000,该寄存器中保存芯片的产品信息。若要读取该寄存器的值,则需先向片内寄存器指针寄存器写入产品ID寄存器的片内相对地址0x0000,接着向数据端口寄存器(PacketPageData(Por0))连续读取两次数据,就可以读到CS8900A的ID信息0x0a00630e。

片内寄存器的写操作与读操作类似,也必须先往片内寄存器指针寄存器写入须访问寄存器的片内相对地址;然后再往数据端口寄存器写入指定的值。

对于介绍的CS8900A的片内寄存器的读/写操作,都须使用这种方法进行。这种方法的实质就是通过“写”来读寄存器,同样也通过“写”来写寄存器。9.2.3基于CS8900A的网络驱动程序实例

CS8900A适配器驱动程序在Linux内核中已经提供,下面以内核2.6.10版本中的代码CS8900A适配器驱动程序为分析对象,讲述网络驱动程序的开发过程。首先分析CS8900A模块的初始化。

1.初始化

CS8900A适配器的初始化主要由net_device数据结构中的init函数指针所指向的初始化函数来完成,当内核启动或加载网络驱动模块时,就会调用这个初始化函数。在初始化函数中通过检测物理设备的硬件特征来侦测网络物理设备是否存在,然后再对设备进行资源配置,接下来构造device数据结构,并用检测到的数据对net_device的变量进行初始化,最后向Linux内核注册设备并申请内存空间。首先,定义一个全局cs8900_dev,它为net_device结构的对象,并且定义cs8900_dev对象的初始化函数为cs8900_probe(),代码如下:

staticstructnet_devicecs8900_dev=

{

init:cs8900_probe

}接下来,分析cs8900_probe()函数的具体实现,它主要用于对网卡的检测,并初始化系统中的网络设备信息,用于后面的网络数据的发送和接收。它的完整代码实现如下:接下来对上述代码进行逐行分析,第3行cs8900_t结构是专门定义的一个用于描述网络设备私有信息的结构,其结构包含的成员有网络设备的统计信息结构成员(stats)、传输数据长度成员(txlen)、该网络设备所包含的字符设备个数(char_devnum)成员以及自旋锁(lock)成员。第8行用ether_setup()函数给网络设备填充以太网相关的值,如MTU值、地址长度、以太网类型等信息。第9~14行用来初始化网络设备的方法,包括open、stop、hard_start_xmit等。由于我们的程序是基于SMDK2410开发板的,因此内核中会配置CONFIG_ARCH_SMDK2410这个选项,在以下关于这个选项的条件编译代码中,都会执行到它所包含的代码。第17~22行用来定义网络设备的MAC地址,也就是硬件地址(48位长度)。第31行定义网络设备的接口介质类型为10Base-T。第32行将cs8900_t结构对象的地址赋给网络设备的私有数据成员priv。第33行以cs8900_t结构对象的自选锁成员lock为参数,传递给宏spin_lock_init,用于动态初始化自选锁。第35和36行分别定义以太网设备的I/O地址和中断号。第38~40行用来检测网络设备I/O地址空间是否可用。第41行表示如果该网络设备IYO地址可用,则注册这块区域。第42~44行用于检查EISA是否正确。第45~48行用于验证CS8900A芯片的版本号是否正确。第49行用来设置网络设备的中断号。第50~64行用来确定网络设备是否有EEPROM;由于我们的系统中没有EEPROM设备和其他的配置块,因此需要手动配置MAC地址。第66~70行用来读取MAC地址。第72行表示在一切都正确的情况下返回0,即网络设备接口初始化完成。到这里为止,已经完整讲述了网络设备接口的初始化函数cs8900_probe(),那么有人会问谁会调用cs8900_probe()函数呢?其实前面已经说过,当在内核启动或加载网络驱动模块的时候,就会调用这个初始化函数,这里我们是以模块加载形式来调用的,它的加载函数为cs8900_init(),具体实现代码如下:

staticint__intcs8900_init(void)

{

strcpy(cs8900_,“eth%d”);

return(register_netdev(&cs8900_dev));

}该加载函数实现的功能是:首先给网络设备取名,然后传递参数cs8900_dev的地址到网络设备注册函数register_netdev(),从而完成注册网络设备的任务。网络设备的卸载函数的作用与加载函数相反,其实现代码如下:该卸载函数实现的功能是:首先判断网络设备中是否定义了字符设备EEPROM,如果定义了,则首先卸载它,实际在我们的系统中没有使用EEPROM,所以就不用执行注销字符设备了;然后释放网络设备的I/O地址空间;最后,注销网络设备cs8900_dev。

2. open和stop方法

1) open方法

网络设备的open方法就是激活网络接口,使它能接收来自网络的数据并且将其传递到网络协议栈的上层,也可以将数据发送到网络上。cs8900_dev设备的open方法实现代码如下:上述代码中的第5行用于设定网络设备的中断触发类型为上升沿触发。第7~16行用于配置以太网控制寄存器,包括接收总线配置寄存器PP_RxCFG、接收控制寄存器PP_RxCTL、发送配置寄存器PP_TxCFG、缓冲配置寄存器PP_BufCFG、线控制寄存器PP_LineCTL、总线控制寄存器PP_BusCTL和测试控制寄存器PP_TestCTL。这几个寄存器的作用如下:

(1) PP_RxCFG用来确定如何传输帧到主机和哪种类型帧被触发中断;

(2) PP_RxCTL用来表示位8、C、D和E定义接收什么样的帧,位6、7、9、A和B配置目的地址过滤器;

(3) PP_TxCFG用来配置发送数据相关的中断是否可用;

(4) PP_BufCFG用来配置总线缓冲相关的中断是否可用;

(5) PP_LineCTL用来配置MAC引擎和物理接口;

(6) PP_BusCTL用来配置ISA总线接口操作;

(7) PP_TestCTL用来配置CS8900A的诊断测试模式。

上述代码中的第20~23行用于注册网络设备的中断服务程序cs8900_interrupt(后面会详细讲述这个中断服务程序)。如果中断服务程序注册成功,则将执行第45行,即用来启动一个网络接口队列,最后返回一个0,代表网络设备被正确打开。

2) stop方法

stop方法即停止网络设备,它的作用与open方法相反,具体实现代码如下:上述代码中的第4~11行用来禁止以太网控制器各相应的寄存器。第13行用于释放网络设备的中断服务程序。第15行用于停止网络设备接口队列。最后返回0,表示完成停止设备。

3.数据发送

网络设备数据发送是由net_device结构中的hard_start_xmit方法实现的,所有的网络设备驱动程序都必须有这个发送方法。在驱动程序层次中,发送和接收数据都是通过系统低层驱动对硬件的读/写来完成的。根据初始化函数cs8900_probe()定义网络设备的发送实现函数为cs8900_send_start(),其实现代码如下:上述代码中,在系统调用驱动程序的hard_start_xmit方法时,发送的数据放在一个sk_buff结构中,也就是前面讲过的套接字缓冲结构。第5行用来获得一个禁止中断的自旋锁。第6行为关闭网络接口队列。第7行和第8行为主机在发送数据前写寄存器PP_TxCMD,利用这个命令告诉CS8900A主机有一个数据帧要传输,并且告诉如何传输这个帧。此外,写PP_TxLength寄存器用来告诉将要传输数据帧的长度。第9行用来读取PP_BusST寄存器来获得当前传输操作的状态。第10~16行用于因数据帧的大小不符合要求而导致数据传输失败(比如数据帧大于1518字节)时释放自旋锁,设置数据传输长度为0,返回1。第17~22行用于因传输缓冲空间没有准备好而导致数据传输失败时释放自旋锁,设置数据传输长度为0,返回1。第23~28行用于当传输数据的状态都正常时,将skb指向的数据帧发送给CS8900A,然后释放自旋锁并且释放skb指向的内存空间,最后返回0,表示传输数据正确。

4.数据接收

数据包的接收实现方式不同于数据发送,可利用hard_start_xmit方法实现,它是通过中断机制来完成的。当有数据到达时产生中断信号,网络设备驱动功能层调用中断处理程序(即数据包接收程序)来处理数据包的接收,随后网络协议接口层调用netif_rx()函数把接收到的数据包传输到网络协议的上层进行处理。CS8900A的中断服务程序在讲述open方法时已经提到,它是利用cs8900_interrupt()函数来实现的,其具体实现代码如下:上述代码中,当触发一个中断时,首先通过读取PP_ISQ寄存器来判断产生中断的类型,这是在第13行代码实现的。第15~54行根据获得的中断类型来执行相应的处理,其中有5种中断类型,按优先级顺序分别为RxEvent、TxEvent、BufEvent、RxMISS、TxCOL。每次主控制器读ISQ(InterruptStatusQueue)寄存器,相应寄存器中产生中断的位将清零,下一个中断报告将会自动移到中断队列最前面。

数据接收的核心功能是在RxEvent中断类型的处理程序中由cs8900_receive()函数来完成的。cs8900_receive()函数实现了网络数据接收的核心功能。cs8900_receive()函数的实现代码如下:上述代码中的第4行定义了一个sk_buff(套接字)结构的指针skb,用来存放要传递的数据信息。第8~13行表示首先读取接收数据寄存器的状态是否正常,如果读取的状态不正确,则将这些错误信息赋给相应的统计变量,最后返回。第20行用于将CS8900A的数据帧读取到skb指向的内存中。第24行用于确定传输数据协议类型,比如802.3、802.2协议等。第25行用于调用netif_rx()把skb中存放的数据传送给协议层,它是网络数据接收中必须使用的函数。

9.3Linux网络编程实现

9.3.1socket基本函数

本节介绍编写网络程序时使用的基本套接字函数。因为我们编写的网络程序主要是使用TCP套接字,所以这一节主要讨论TCP套接字使用这些函数的情况。

图9-5描述了TCP客户机和服务器之间的交互过程,这是使用TCP协议进行网络通信的程序的基本模型。图9-5TCP客户机和服务器通信模型服务器程序首先进行初始化操作:调用函数socket创建一个套接字,函数bind将这个套接字与服务器的公认地址绑定在一起,函数listen将这个套接字转换成倾听套接字(1isteningsocket),然后调用函数accept来等待接收客户机的请求。过了一段时间之后,客户机启动,调用函数socket创建一个套接字,然后调用函数connect来与服务器建立连接。

连接建立之后,客户机和服务器通过读、写套接字来进行通信。

下面详细讨论这个通信过程所使用的套接字函数。

1.函数socket

函数socket用于创建一个套接字描述符。其定义如下:

#include<sys/types.h>

#include<sys/socket.h>

Intsocket(intdomain,inttype,intprotocol)

参数domain指定要创建的套接字的协议簇;参数type指定套接字类型;参数protocol指定使用哪种协议。函数socket成功执行时,返回一个正整数,称为套接字描述符,标识这个套接字;否则,返回-1,并设置全局变量errno为相应的错误类型。

Linux系统的套接字编程接口(API)是一个通用的网络编程接口,它可以访问多种通信协议,如TCP/IP协议和Unix域协议等。在创建一个套接字时需要在参数domain中指定使用哪种协议簇。参数domain可以是如下值:

(1) AF_Unix:Unix域协议簇,本机的进程间通信时使用。

(2) AF_INET:Internet协议簇(TCP/IP)。

(3) AF_ISO:ISO协议簇。参数type指定套接字类型,可以是如下值:

(1) SOCK_STREAM:流套接字,面向连接的和可靠的通信类型。

(2) SOCK_DGRAM:数据报套接字,非面向连接的和不可靠的通信类型。

(3) SOCK_RAW:原始套接字,只对Internet协议有效,可以用来直接访问端口的协议。

参数protocol通常设置为0,表示使用默认协议,如Internet协议簇的流套接字使用TCP协议,而数据报套接字使用UDP协议。当套接字是原始套接字类型时,需要指定参数protocol,因为原始套接字对多种协议有效,如ICMP和IGMP等。创建一个TCP套接字的操作一般如下:

sockfd=socket(AF_INET,SOCK_STREAM,0);

if(sockfd<0){

fprintf(stderr,“socketerror:%s\n,”,strerror(errno));

exit(1);

}

Linux系统中创建一个套接字的操作主要是:在内核中创建一个套接字数据结构,然后返回一个套接字描述符标识这个套接字数据结构。这个套接字数据结构包含连接的各种信息,如对方地址、TCP状态以及发送和接收缓冲区等。TCP协议根据这个套接字数据结构的内容来控制这条连接。

2.函数connect

函数connect与服务器建立一个连接。其定义如下:

#include<sys/types.h>

#include<sys/socket.h>

intconnect(intsockfd,structsockaddr*servaddr,intaddrlen);

参数sockfd是函数socket返回的套接字描述符;参数servaddr指定远程服务器的套接字地址,包括服务器的IP地址和端口号;参数addrlen指定这个套接字地址的长度。函数connect成功执行时,返回0;否则返回1,并设置全局变量errno为以下任何一种错误类型:EIIMEOUT、ECONNREFUSED、EHOSTUNRFACH或ENETUNREACH。在调用函数connect之前,客户机需要指定服务器进程的套接字地址。建立一个TCP连接的操作一般如下:客户机一般不用指定自己的套接字地址(IP地址和端口号),系统会自动从1024至5000的端口号范围内为它选择一个未用的端口号,然后以这个端口号和本机的IP地址填充这个套接字地址。

客户机调用函数connect来主动建立连接。这个函数将启动TCP协议的3次握手过程。在连接建立之后或发生错误时,函数返回。连接过程中可能有如下几种错误情况:

(1)如果客户机TCP协议没有接收到对它的SYN数据段的确认,则函数以错误返回,错误类型为ETIMEOUT。通常TCP协议在发送SYN数据段失败之后,会多次发送SYN数据段,在所有的发送都告失败之后,函数以错误返回。

(2)如果远程TCP协议返回一个RST数据段,则函数立即以错误返回,错误类型为ECONNREFUSED。当远程机器在SYN数据段指定的目的端口号处没有服务器进程在等待连接时,远程机器的TCP协议将发送一个RST数据段,向客户机报告这个错误。客户机的TCP协议在接收到RST数据段之后,不再继续发送SYN数据段,函数立即以错误返回。

(3)如果客户机的SYN数据段导致某个路由器产生“目的地不可到达”类型的ICMP消息,则函数以错误返回,错误类型为EHOSTUNREACH或ENETUNREACH。通常TCP协议在接收到ICMP消息之后,记录这个消息,然后继续几次发送SYN数据段,在所有的发送都宣告失败之后,TCP协议检查这个ICMP消息,函数以错误返回。在调用函数connect的过程中,当客户机TCP协议发送了SYN数据段之后,客户机的TCP状态由CLOSED状态转换成SYN_SENT状态,在接收到对SYN数据段的确认之后,TCP状态转换成ESTABLISHED状态,函数成功返回。如果调用函数connect失败,应该用函数close关闭这个套接字描述符,不能再次用这个套接字描述符来调用函数connect。

3.函数bind

函数bind将本地地址与套接字绑定在一起。其定义如下:

#include<sys/types.h>

#include<sys/socket.h>

intbind(intsockfd,structsockaddr*myaddr,intaddrlen);

参数sockfd是函数socket返回的套接字描述符;参数myaddr是本地地址;参数addrlen是套接字地址结构的长度。函数bind成功执行时,返回0;否则,返回-1,并设置全局变量errno为错误类型EADDRINUSER。服务器和客户机都可以通过调用函数bind来绑定套接字地址,但一般是服务器调用函数bind来绑定自己的公认端口号。绑定操作一般如下:

bzero(&myaddr,sizeof(myaddr));

myaddr.sin_family=AF_INET;

myaddr.sin_port=htons(PORT);

myaddr.sin_addr.s_addr=htonl(INADDR_ANY);

if(bind(sockfd,(structsockaddr*)&myaddr,sizeof(myaddr))<0){

fprintf(stderr,“Bindtoport%derror\n”,PORT);

exit(l)

}

绑定操作一般有如下几种组合方式,见表9-4。客户机和服务器的其他口地址与端口号组合方式没有什么意义,我们不予讨论。

下面详细说明表9-4中列出的5种方式。

(1)服务器指定套接字地址的公认端口号,不指定IP地址。服务器调用函数bind时,如果设置套接字的IP地址为特殊的INADDR_ANY,则表示它愿意接收来自任何网络设备接口的客户机连接。这是服务器经常使用的绑定方式。

(2)服务器指定套接字地址的公认端口号和IP地址。服务器调用函数bind时,如果设置套接字的IP地址为某个本地IP地址,则表示服务器只接收来自对应于这个IP地址的特定网络设备接口的客户机连接。当这台机器只有一个网络设备接口时,这和第一种情况是没有区别的,但当这台机器有多个网络设备接口时,我们可以用这种方式来限制服务器的接收范围。

(3)客户机指定套接字地址的连接端口号。在一般情况下,客户机不用指定自己的套接字地址的端口号,当客户机调用函数connect进行TCP连接时,系统会自动为它选择一个未用的端口号,并且用本地的IP地址来填充套接字地址中的相应项。但在有的情况下,客户机需要使用特定端口号,如Linux系统中的rlogin命令,因为rlogin命令需要使用保留端口号,而系统不会为客户机自动分配一个保留端口号,所以需要调用函数bind来和一个未用的保留端口号绑定。

(4)指定客户机的IP地址和连接端口号。在一般情况下,客户机使用指定的网络设备接口和端口号进行通信。

(5)指定客户机的IP地址。客户机使用指定的网络设备接口进行通信,系统自动为客户机选择一个未用的端口号。一般只有在主机有多个网络设备接口时使用。

我们在编写客户机程序时,一般不使用固定的客户机端口号,除非是在必须使用特定端口号的情况下。固定客户机端口号时会带来一些不便,需要考虑如下两种情况:①服务器执行主动关闭操作(如HTTP服务器)。服务器最后进入TIME_WAIT状态。当客户机再次与这个服务器进行连接时,仍使用相同的客户机端口号,于是这个连接与前次连接的套接字对完全一样,但是因为前次连接处于TIME_WAIT状态,并未消失,所以这次连接请求被拒绝,函数connect以错误返回,错误类型为ECONNREFUSED。

②客户机执行主动关闭操作(如FFP客户机)。客户机最后进入TIME_WAIT状态。当马上再次执行这个客户机程序时,客户机将继续与这个固定客户机端口号绑定,但因为前次连接处于TIME_WAlT状态,并未消失,系统会发现这个端口号仍被占用,所以这次绑定操作失败,函数bind以错误返回,错误类型为EADDRINUSE。

4.函数listen

函数listen将一个套接字转换为倾听套接字(1isteningsocket)。其定义如下:

#include<sys/socket.h>

intlisten(intsockfd,intbacklog);

其中:参数sockfd指定要转换的套接字描述符;参数backlog表示设置请求队列的最大长度。函数listen成功执行时,返回0;否则返回 -1。服务器需要调用函数listen将套接字转换成倾听套接字,以便接收客户机请求。函数listen的功能有两个:

(1)函数socket创建的套接字是主动套接字,可以用它来进行主动连接(调用函数connect),但是不能接收连接请求,而服务器的套接字必须能够接收客户机的请求。函数listen将一个尚未连接的主动套接字转换成为一个被动套接字:告诉TCP协议,这个套接字可以接收连接请求。执行函数listen之后,服务器的TCP状态由CLOSED状态转换成LISTEN状态。

(2) TCP协议将到达的连接请求排队,函数listen的第二个参数指定这个队列的最大长度。

要创建一个倾听套接字,必须首先调用函数socket创建一个主动套接字,然后调用函数bind将它与服务器套接字地址绑定在一起,最后调用函数listen进行转换。这3步操作是所有TCP服务器所必需的操作。下面讨论参数backlog的作用,这对于理解套接字建立连接的过程非常重要。TCP协议为每个倾听套接字维护两个队列,即未完成连接队列和完成连接队列。

(1)未完成连接队列。每个尚未完成3次握手操作的TCP连接在这个队列中占有一项。TCP协议在接收到一个客户机SYN数据段之后,在这个队列中创建一个新条目,然后发送对客户机SYN数据段的确认和自己的SYN数据段(ACK+SYN数据段),并等待客户机对自己的SYN数据段的确认。此时,套接字处于SYN_RCVD状态。这个条目将保存在这个队列中,直到客户机返回对SYN数据段的确认,或者连接超时。

(2)完成连接队列。每个已经完成3次握手操作,但尚未被应用程序接收(调用函数accept)的TCP连接在这个队列中占有一项。当一个在未完成连接队列中的连接接收到对SYN数据段的确认之后,完成3次握手操作,TCP协议将它从未完成连接队列移到完成连接队列中。此时,套接字处于ESTABLISHED状态。这个条目将保存在这个队列中,直到应用程序调用函数accept来接收它。图9-6描述的是一个倾听套接字的这两个队列在某一时刻的状态。图9-6倾听套接字连接队列此时,这个倾听套接字未完成连接队列中有4个未完成的连接,完成连接队列中有2个已经建立的连接。为了理解这两个队列的工作过程,我们假定新接收到一个客户机连接请求,图9-7描述了这个客户机连接3次握手的过程。图9-7客户机连接3次握手的过程服务器TCP协议在接收到客户机的SYN数据段之后,在倾听套接字的未完成队列中创建一个新的条目,然后发送对客户机SYN数据段的确认和自己的SYN数据段。经过大约一个往返时间之后,服务器接收到客户机对它的SYN数据段的确认,表示连接已经建立,于是服务器的TCP协议将这个连接从未完成队列移到完成连接队列中。当服务器程序调用函数accept接收连接时,返回完成连接队列中的一个条目。参数backlog指定这个倾听套接字的完成连接队列的最大长度,表示这个套接字能够接收的最大数目的未接收(unaccepted)连接。如果当一个客户机的SYN数据段到达时,倾听套接字的完成连接队列已经满了,那么TCP协议将忽略这个SYN数据段。对于不能接收的SYN数据段,TCP协议不发送RST数据段,原因有以下两个:

①假设TCP协议在未完成队列满时返回RST数据段,那么客户机的函数connect将马上以错误返回,不再继续发送连接请求。根据这个RST数据段,客户机无法知道,究竟是这个端口上没有服务器进程在等待连接,还是在这个端口上等待的服务器的未完成连接队列暂时没有空间。②完成队列满的情况是暂时的。经过一段时间之后,应用程序可能调用函数accept从这个完成队列中接收已经建立的连接,于是完成队列中出现新的空间。客户机TCP协议在超时之后,继续几次发送SYN数据段。如果在这几次发送过程中,完成连接队列中出现新的空间,那么TCP协议将接收这个连接请求,继续正常的3次握手操作。如果在这几次发送过程中,完成连接队列中都没有空间,则客户机将放弃发送,以错误ETIMEOUT返回。下面的这段程序显示了这个问题,我们可以用命令telnet来做客户机测试:我们执行如下命令来测试:

bash$telnetlocalhost8080&

bash$telnetlocalhost8080&

bash$telnetlocalhost8080然后用命令netstat查看,结果如下:显示结果的第2行是倾听套接字的状态,第3行和第4行标识一条已经建立的连接,第5行和第6行标识另一条已经建立的连接,最后一行对应于第三个telnet的操作,表示客户机已经发送了SYN数据段,正在等待确认。经过一段时间之后,第三个telnet命令将返回错误,错误信息是连接超时。

5.函数accept

函数accept从倾听套接字的完成连接队列中接收一个连接。如果完成连接队列为空,那么这个进程睡眠。其定义如下:

#include<sys/socket.h>

intaccept(intsockfd,structsockaddr*addr,int*addrlen);其中:参数sockfd用于指定倾听套接字描述符;参数addr为指向一个Internet套接字地址结构的指针;参数addrlen为指向一个整型变量的指针。函数accept成功执行时,返回3个结果:函数返回值为一个新的套接字描述符,标识这个接收的连接;参数addr指向的结构变量中存储客户机的地址;参数addrlen指向的整型变量中存储客户机地址的长度。如果我们对客户机的地址和长度不感兴趣,则可以将参数addr和addrlen设置为NULL。函数accept执行失败时,返回 -1。函数accept从倾听套接字的完成连接队列中接收一个已经建立起来的TCP连接,因为倾听套接字是专为接收客户机连接请求,完成3次握手操作而用的,所以TCP协议不能使用倾听套接字描述符来标识这个连接,于是TCP协议创建一个新的套接字来标识这个要接收的连接,并将它的描述符返回给应用程序。现在有两个套接字:一个是调用函数accept时使用的倾听套接字;另一个是函数accept返回的连接套接字(connectedsocket)。这两个套接字的作用是完全不同的:一个服务器进程通常只需创建一个倾听套接字,在服务器进程的整个活动期间,用它来接收所有客户机的连接请求,在服务器进程终止前关闭这个倾听套接字;而对于每个接收(accepted)的连接,TCP协议都创建一个新的连接套接字来标识这个连接,服务器使用这个连接套接字与客户机进行通信操作,当服务器处理完这个客户机请求时,关闭这个连接套接字。

当函数accept阻塞等待已经建立的连接时,如果进程捕获到信号,那么函数将以错误返回,错误类型为EINTR。对于这种错误,一般重新调用函数accept来接收连接。

6.函数close

函数close用于关闭一个套接字描述符。套接字描述符的close操作与文件描述符的close操作类似。其定义如下:

#include<unistd.h>

intclose(intsockfd);

其中,参数sockfd指定要关闭的套接字描述符。函数close成功执行时,返回0;否则,返回 -1。套接字描述符的close操作和文件描述符的close操作一样:函数close将套接字描述符的引用计数减1,如果描述符的引用计数大于0,则表示还有进程引用这个描述符,函数close正常返回;如果描述符的引用计数变为0,则表示再没有进程引用这个描述符,于是启动清除套接字描述符的操作,函数close立即正常返回。清除套接字描述符的操作是:将这个套接字描述符标记为关闭状态(不同于TCP协议状态转换图中的CLDSED状态),然后立即返回进程。调用了函数close之后,进程将不再访问这个套接字,但是这不表示TCP协议删除了这个套接字。TCP协议将继续使用这个套接字,将尚未发送的数据传递到对方,然后发送FIN数据段,执行关闭操作,一直等到这个TCP连接完全关闭之后,TCP协议才删除这个套接字。

7.函数read和write

函数read和write用于从套接字读和写数据。其定义如下:

intread(intfd,char*buf,intlen);

intwrite(intfd,char*buf,intlen);

其中:参数fd用于指定读/写操作的套接字描述符;函数read的参数buf用于指定接收数据缓冲区,函数write的参数buf用于指定发送数据缓冲区;参数len用于指定接收或发送的数据量大小。函数read成功执行时,返回读到的数据量大小;否则,返回-1。函数write成功执行时,返回写入的数据量大小;否则,返回-1。每个TCP套接字都有两个缓冲区,分别用于处理发送和接收任务,其中用于发送的缓冲区被称为套接字发送缓冲区,用于接收的缓冲区被称为套接字接收缓冲区。从网络读、写数据的操作是由TCP协议在内核中完成的:TCP协议将从网络上接收到的数据保存在相应套接字的接收缓冲区中,等待用户调用函数将它们从接收缓冲区拷贝到用户缓冲区;用户将要发送的数据拷贝到相应套接字的发送缓冲区中,然后由TCP协议按照一定的算法处理这些数据。读、写连接套接字的操作与读、写文件的操作类似,也可以使用函数read和write。函数read完成将数据从套接字接收缓冲区拷贝到用户缓冲区的任务:当套接字接收缓冲区有数据可读时,函数read读取这些数据;如果缓冲区的可读数据量大于函数read指定的值,则返回函数read参数len指定的数据量;如果缓冲区的可读数据量小于函数read指定的值,则函数read不等待请求的所有数据都到达,而是立即返回缓冲区中的所有数据,这时的返回值小于参数len的值;当套接字接收缓冲区中没有数据可读之时,函数read将阻塞不返回,等待数据到达。函数write完成将数据从用户缓冲区拷贝到套接字发送缓冲区的任务:当套接字发送缓冲区有足够拷贝所有用户数据的空间时,函数write将数据拷贝到这个缓冲区中;如果可用空间小于函数write参数len的值,则函数write将阻塞不返回,等待缓冲区有足够空间。注意,函数write返回之后,只表示用户数据已经拷贝到套接字的发送缓冲区中,并不表示数据已经发送到远方主机。当程序从一个套接字读数据时,有以下几种情况:

(1)套接字接收缓冲区接收到数据。函数read读取这些数据,返回读取的数据量大小,此时的返回值大于0。

(2) TCP协议接收到FIN数据段。FIN数据段表示对方已经发送完所有数据,函数read返回0,并且以后所有在这个套接字上的读操作均返回0。这和读普通文件中遇到文件结束符的情况一样,所以我们也将这种情况称为读到文件结束符。

(3) TCP协议接收到RST数据段。RST数据段表示连接出现了某种错误,函数read将以错误返回,错误类型为ECONNRESET。并且以后所有在这个套接字上的读操作均返回错误。

(4)进程阻塞过程中接收到信号。如果进程捕获这个信号,那么函数read将以错误返回,错误类型为EINTR。以后可以继续在这个套接字上读数据。

所以在一个套接字上的读操作一般有3种返回结果:大于0、等于0和小于0,分别对应正常返回、读到文件结束符和读错误。处理读返回值的程序片断如下:当函数read返回值大于0时,表示读到新数据,程序将这些数据写入一个文件中;当函数read返回值等于0时,表示读通道被关闭了,即读到文件结束符,程序将文件描述符和套接字描述符均关闭;当函数read返回值小于0时,表示读操作发生错误,程序需要根据错误类型进行不同的处理。如果错误类型为EINTR,则表示读函数被信号中断,套接字上并未发生错误,所以一般重新调用函数read读数据;如果错误类型为ECONNRESET,则表示套接字接收到RST数据段,对方已经重置了套接字,程序不能再在这个套接字上读数据,所以程序输出错误信息,然后终止执行或者继续其他操作。当一个程序向套接字写数据时,有以下几种情况:

(1)套接字发送缓冲区有足够空间。函数write将数据拷贝到发送缓冲区中,返回拷贝的数据量大小。

(2) TCP协议接收到RST数据段。当对方TCP已经关闭了这条连接之后,继续向这个套接字上发送数据,将导致对方TCP协议返回RST、数据段,表示这条连接已经被关闭了。TCP协议在接收到这个RST数据段之后,函数write将以错误返回,错误类型为EPIPE。并且所有以后在这个套接字上的写操作均以错误返回。

(3)进程阻塞过程中接收到信号。如果进程捕获这个信号,那么函数write将以错误返回,错误类型为EINTR。以后可以继续在这个套接字上写数据。

所以在一个套接字上读操作一般有两种返回结果:大于0和小于0,分别对应正常返回和写错误。处理写返回值的程序片断如下:当函数write返回值大于0时,表示成功写了部分或全部数据,程序继续其他操作;当函数write返回值小于0时,表示写操作出现错误,需要根据错误类型进行相应处理。如果错误类型为EINTR,则表示程序继续执行写操作;如果错误类型为EPIPE,则表示对方已经关闭了套接字,程序输出错误信息,然后终止执行。从前面的讨论知道,如果接收缓冲区中没有指定数据量的数据或者捕获到信号,读操作将不返回我们指定要读的数据量;如果写操作阻塞过程中捕获到信号,写操作也将不发送我们指定的数据量。但是这两种情况并不表示读或写操作发生了错误,如果每次编写程序时均处理这两种情况,比较麻烦,所以我们编写两个函数:一个完成读取指定数据量的读操作,当读操作被信号中断时,函数自动继续读数据;另一个完成发送指定数据量的写操作,当写操作被信号中断时,函数自动继续写数据。其定义如下:

intread_all(intfd,void*buf,intnbytes);

intwrite_all(intfd,void*buf,intnbytes);其中:函数read_all用于从参数fd指定的描述符中读取nbytes字节数据至缓冲区buf中;函数write_all用于从参数fd指定的描述符中写入参数buf的前nbytes字节数据。这两个函数的源代码如下:函数read_all调用函数read从套接字中读数据,忽略错误EINTR,如果已经读取的数据量小于指定值,则函数继续读,直到读取了指定的数据量为止。如果发生除EINTR之外的其他读错误,则函数返回 -1。函数write_all调用函数write向套接字写数据,忽略错误EINTR,如果已经发送的数据量小于指定值,则函数继续写,直到发送了指定的数据量为止。如果发生除EINTR之外的其他写错误,则函数返回 -1。

8.函数getsockname和getpeername

函数getsockname返回套接字的本地地址;函数getpeername返回套接字对应的远程地址。其定义如下:

#include<sys/socket.h>

intgetsockname(intsockfd,structsockaddr*localaddr,int*addrlen);

intgetpeername(intsockfd,structsockaddr*peeraddr,int*addrlen);其中:参数sockfd用于指定一个套接字描述符;参数localaddr和peeraddr为指向一个Internet套接字地址结构的指针;参数addrlen为指向一个整型变量的指针。函数getsockname成功执行时,返回0,并在后两个参数中返回结果,参数localaddr指向的结构中存储本地套接字地址,参数addrlen指向的整型变量中存储返回的套接字地址的长度。函数getpeername成功执行时,返回0,并在后两个参数中返回结果,参数peeraddr指向的结构中存储套接字对应的远程地址,参数addrlen指向的整型变量中存储返回的套接字地址的长度。两函数执行失败时,均返回 -l。下面这段程序显示了如何使用这两个函数:一般在以下3种情况下使用这两个函数:

(1)客户机在成功调用函数connect之后,可以用函数getsockname来获得系统在套接字地址中填充的本地IP地址和自动选择的本地端口号。

(2)

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论