基于嵌入式Linux系统的网络编程技术Word文件下载.docx
《基于嵌入式Linux系统的网络编程技术Word文件下载.docx》由会员分享,可在线阅读,更多相关《基于嵌入式Linux系统的网络编程技术Word文件下载.docx(15页珍藏版)》请在冰点文库上搜索。
Linux2.6已经在内核主体中加入了提高中断性能和调度响应时间的改进,其中有三个最显著的改进:
采用可抢占内核、更加有效的调度算法以及同步性的提高。
在企业服务器以及嵌入式系统应用领域,Linux2.6都是一个巨大的进步。
在嵌入式领域,Linux2.6除了提高其实时性能,系统的移植,同时添加了新的体系结构和处理器类型,包括对没有硬件控制内存管理方案的MMU-less系统的支持,可以支持大容量内存模型、微控制器,同时还改善了I/O子系统,增添更多的多媒体应用功能。
1 可抢占内核
在先前的内核版本中不允许抢占以核心态运行的任务(包括通过系统调用进入内核模式的用户任务),只能等待它们自己主动释放CPU。
这样必然导致一些重要任务延时以等待系统调用结束。
一个内核任务可以被抢占,为的是让重要的用户应用程序可以继续运行。
这样做最主要的优势是极大地增强系统的用户交互性。
2.6内核并不是真正的RTOS(RealTimeOperationSystem),其在内核代码中插入了抢占点,允许调度程序中止当前进程而调用更高优先级的进程,通过对抢占点的测试避免不合理的系统调用延时。
2.6内核在一定程度上是可抢占的,比2.4内核具备更好的响应性。
但也不是所有的内核代码段都可以被抢占,可以锁定内核代码的关键部分,确保CPU的数据结构和状态始终受到保护而不被抢占。
软件需要满足最终时间限制与虚拟内存请求页面调度之间是相互矛盾的。
慢速的页错误处理将会破坏系统的实时响应性,而2.6内核可以编译无虚拟内存系统避免这个问题,这是解决问题的关键,但要求软件设计者有足够的内存来保证任务的执行。
2 有效的调度程序
2.6版本的Linux内核使用了由IngoMolnar开发的新的调度器算法,称为O
(1)算法。
它在高负载情况下执行得极其出色,并且当有很多处理器并行时也可以很好地扩展。
过去的调度程序需要查找整个readytask队列,并且计算它们的重要性以决定下一步调用的task,需要的时间随task数量而改变。
O
(1)算法则不再每次扫描所有的任务,当task就绪时被放入一个活动队列中,调度程序每次从中调度适合的task,因而每次调度都是一个固定的时间。
任务运行时分配一个时间片,当时间片结束,该任务将放弃处理器并根据其优先级转到过期队列中。
活动队列中任务全部调度结束后,两个队列指针互换,过期队列成为当前队列,调度程序继续以简单的算法调度当前队列中的任务。
这在多处理器的情况更能提高SMP的效率,平衡处理器的负载,避免进程在处理器间的跳跃。
3 同步原型与共享内存
多进程应用程序需要共享内存和外设资源,为避免竞争采用了互斥的方法保证资源在同一时刻只被一个任务访问,Linux内核用一个系统调用来决定一个线程阻塞或是继续执行来实现互斥,在线程继续执行时,这个费时的系统调用就没有必要了。
Linux2.6所支持的FastUser-SpaceMutexes可以从用户空间检测是不是需要阻塞线程,只在需要时执行系统调用终止线程。
它同样采用调度优先级来确定将要执行的进程。
多处理器嵌入式系统各处理器之间需要共享内存,对称多处理技术对内存访问采用同等优先级,在很大程度上限制了系统的可量测性和处理效率。
Linux2.6则提供了新的管理方法――NUMA(NonUniformMemoryAccess)。
NUMA根据处理器和内存的拓扑布局,在发生内存竞争时,给予不同处理器不同级别权限以解决内存抢占瓶颈,提高吞吐量。
4 POSIX线程及NPTL
新的线程模型基于一个1:
1的线程模型(一个内核线程对应一个用户线程),包括内核对新的NPTL(NativePOSIXThreadingLibrary)的支持,这是对以前内核线程方法的明显改进。
2.6内核同时还提供POSIXsignals和POSIXhigh-resolutiontimers。
POSIXsignals不会丢失,并且可以携带线程间或处理器间的通信信息。
嵌入式系统要求系统按时间表执行任务,POSIXtimer可以提供1kHz的触发器使这一切变得简单,从而可以有效地控制进度。
5 微控制器的支持
Linux2.6内核加入了多种微控制器的支持。
无MMU的处理器以前只能利用一些改进的分支版本,如uClinux,而2.6内核已经将其整合进了新的内核中,开始支持多种流行的无MMU微控制器,如Dragonball、ColdFire、HitachiH8/300。
Linux在无MMU控制器上仍旧支持多任务处理,但没有内存保护功能。
同时也加入了许多流行的控制器的支持,如S3C2410等。
6 面向应用
嵌入式应用有用户定制的特点,硬件设计都针对特定应用开发,这给系统带来对非标准化设计支持的问题(如IRQ的管理)。
为了更好地实现,可以采用部件化的操作系统。
Linux2.6采用的子系统架构将功能模块化,可以定制而对其他部分影响最小。
同时Linux2.6提供了多种新技术的支持以实现各种应用开发,如AdvancedLinuxSoundArchitecture(ALSA)和Video4Linux等,对多媒体信息处理更加方便;
对USB2.0的支持,提供更高速的传输,增加蓝牙无线接口、音频数据链接和面向链接的数据传输L2CAP,满足短距离的无线连接的需要;
而且在2.6内核中还可以配置成无输入和显示的纯粹无用户接口系统。
2.嵌入式Linux网络编程
2.1TCP/IP参考模型
TCP/IP协议模型遵循简单明确的设计思路,包括以下四层协议:
∙网络接口层:
负责将二进制流转换为数据帧,并进行数据帧的发送和接受。
数据帧是独立的网络信息传输单元。
∙网络层:
负责将数据帧封装成IP数据包,并运行必要的路由算法。
∙传输层:
负责端对端之间的通信会话连接与建立。
传输协议的选择根据数据传输方式而定。
∙应用层:
负责应用程序的网络访问,通过端口号来识别各个不同的进程。
2.2Socket概述
Linux中的网络编程是通过socket接口来进行的,它也是一种文件描述符。
通过它不仅可以在本地机器上实现进程间的通信,而且通过网络能够在不同的机器上的进程之间进行通信。
socket也有一个类似打开文件的函数调用,该函数返回一个整型的socket描述符,随后的连接建立等操作都是通过socket来实现的。
Socket类型常见有以下三种:
∙流式socket(SOCK_STREAM):
流式套接字提供可靠的、面向连接的通信流;
它使用TCP协议,保证了数据的正确性和顺序性。
∙数据报socket(SOCK_DGRAM):
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,而且不保证是可靠、无差错的。
使用数据报协议UDP。
∙原始socket:
允许对底层协议进行直接访问,功能强大但使用不便,主要用于一些协议的开发。
2.3地址及顺序处理
1)地址结构相关处理
一种常用的用于保存网络地址的数据结构sockaddr_in,其结构如下:
struct
sockaddr_in
{
short
int
sin_family;
/*地址族*/
unsigned
sin_port;
/*端口号*/
in_addr
sin_addr;
/*IP地址*/
char
sin_zero[8];
/*填充0*/
};
该结构sin_family字段可选常见值:
AF_INET:
IPv4协议
AF_INET6:
IPv6协议
AF_LOCAL:
UNIX域协议
AF_LINK:
链路地址协议
AF_KEY:
密钥套接字
2)数据存储优先顺序
计算机数据存储有两种字节优先顺序:
高位字节优先(大端模式)和低位字节优先(小段模式)。
Internet上以高位字节优先的顺序在网络传输,而PC机通常采用小端模式,因此有时候需要对两个字节存储优先顺序进行转换。
用到了4个函数:
htons()、ntohs()、htonl()和ntohl()。
h代表host,n代表network,s代表short,l代表long。
通常16位的IP端口号用s,而IP地址用l。
3)地址格式转换
IP地址通常由数字加点(192.168.0.1)的形式表示,而在structin_addr中使用的IP地址是由32位整数表示,为了转换可以使用下面三个函数:
int
inet_aton(const
*cp,struct
*inp);
*inet_ntoa(struct
in);
in_addr_t
inet_addr(const
*cp);
其中inet_aton将a.b.c.d形式的IP转换为32位的IP,存储在inp指针里面;
inet_ntoa是将32位IP转换为a.b.c.d的格式;
inet_addr将一个点分十进制的IP转换成一个长整数型数。
4)名字地址转换
通常,人们在使用过程中不愿记忆冗长的IP地址,因此,使用主机名是很好的选择。
gethostbyname()将主机名转化为IP地址,gethostbyaddr()则是逆操作,将IP地址转换为主机名。
它们都涉及到一个hostent的结构体,如下:
hostent
*h_name;
/*正式主机名*/
**h_aliases;
/*主机别名*/
h_addrtype;
/*地址类型*/
h_length;
/*地址字节长度*/
**h_addr_list;
/*指向IPv4或IPv6的地址指针数组*/
2.4基本套接字函数
1)socket()
用于建立一个socket连接,可指定socket类型等信息。
建立之后,可对sockaddr或sockaddr_in结果进行初始化,以保存所建立的socket地址信息。
Linux系统中创建一个套接字的操作主要是:
在内核中创建一个套接字数据结构,然后返回一个套接字描述符标识这个套接字数据结构。
这个套接字数据结构包含连接的各种信息,如对方地址、TCP状态以及发送和接收缓冲区等等,TCP协议根据这个套接字数据结构的内容来控制这条连接。
2)connect()
客户机调用函数connect来主动建立连接。
这个函数将启动TCP协议的3次握手过程。
在建立连接之后或发生错误时函数返回。
连接过程可能出现的错误情况有:
1 如果客户机TCP协议没有接收到对它的SYN数据段的确认,函数以错误返回,错误类型为ETIMEOUT。
通常TCP协议在发送SYN数据段失败之后,会多次发送SYN数据段,在所有的发送都高中失败之后,函数以错误返回。
注:
SYN(synchronize)位:
请求连接。
TCP用这种数据段向对方TCP协议请求建立连接。
在这个数据段中,TCP协议将它选择的初始序列号通知对方,并且与对方协议协商最大数据段大小。
SYN数据段的序列号为初始序列号,这个SYN数据段能够被确认。
当协议接收到对这个数据段的确认之后,建立TCP连接。
2 如果远程TCP协议返回一个RST数据段,函数立即以错误返回,错误类型为ECONNREFUSED。
当远程机器在SYN数据段指定的目的端口号处没有服务进程在等待连接时,远程机器的TCP协议将发送一个RST数据段,向客户机报告这个错误。
客户机的TCP协议在接收到RST数据段后不再继续发送SYN数据段,函数立即以错误返回。
RST(reset)位:
表示请求重置连接。
当TCP协议接收到一个不能处理的数据段时,向对方TCP协议发送这种数据段,表示这个数据段所标识的连接出现了某种错误,请求TCP协议将这个连接清除。
有3种情况可能导致TCP协议发送RST数据段:
(1)SYN数据段指定的目的端口处没有接收进程在等待;
(2)TCP协议想放弃一个已经存在的连接;
(3)TCP接收到一个数据段,但是这个数据段所标识的连接不存在。
接收到RST数据段的TCP协议立即将这条连接非正常地断开,并向应用程序报告错误。
3
如果客户机的SYN数据段导致某个路由器产生“目的地不可到达”类型的ICMP消息,函数以错误返回,错误类型为EHOSTUNREACH或ENETUNREACH。
通常TCP协议在接收到这个ICMP消息之后,记录这个消息,然后继续几次发送SYN数据段,在所有的发送都告失败之后,TCP协议检查这个ICMP消息,函数以错误返回。
注:
ICMP:
Internet
消息控制协议。
Internet的运行主要是由Internet的路由器来控制,路由器完成IP数据包的发送和接收,如果发送数据包时发生错误,路由器使用ICMP协议来报告这些错误。
ICMP数据包是封装在IP数据包的数据部分中进行传输的,其格式如下:
类型码
校验和数据
类型:
指出ICMP数据包的类型。
代码:
提供ICMP数据包的进一步信息。
校验和:
提供了对整个ICMP数据包内容的校验和。
ICMP数据包主要有以下类型:
1
目的地不可到达:
A、目的主机未运行;
B、目的地址不存在;
C、路由表中没有目的地址对应的条目,因而路由器无法找到去往目的主机的路由。
2 超时:
路由器将接收到的IP数据包的生存时间(TTL)域减1,如果这个域的值变为0,路由器丢弃这个IP数据包,并且发送这种ICMP消息。
3 参数出错:
当IP数据包中有无效域时发送。
4 重定向:
将一条新的路径通知主机。
5
ECHO请求、ECHO回答:
这两条消息用语测试目的主机是否可以到达。
请求者向目的主机发送ECHO请求ICMP数据包,目的主机在接收到这个ICMP数据包之后,返回ECHO回答ICMP数据包。
6
时戳请求、时戳回答:
ICMP协议使用这两种消息从其他机器处获得其时钟的当前时间。
调用函数connect的过程中,当客户机TCP协议发送了SYN数据段的确认之后,TCP状态由CLOSED状态转为SYN_SENT状态,在接收到对SYN数据段的确认之后,TCP状态转换成ESTABLISHED状态,函数成功返回。
如果调用函数connect失败,应该用close关闭这个套接字描述符,不能再次使用这个套接字描述符来调用函数connect。
3)bind()
用于将本地IP地址绑定到端口号。
服务器和客户机都可以调用函数bind来绑定套接字地址,但一般是服务器调用函数bind来绑定自己的公认端口号。
说明如下:
1 服务器指定套接字地址的公认端口号,不指定IP地址:
即服务器调用bind时,设置套接字的IP地址为特殊的INADDE-ANY,表示它愿意接收来自任何网络设备接口的客户机连接。
这是服务器最常用的绑定方式。
2 服务器指定套接字地址的公认端口号和IP地址:
服务器调用bind时,如果设置套接字的IP地址为某个本地IP地址,这表示这台机器只接收来自对应于这个IP地址的特定网络设备接口的客户机连接。
当服务器有多块网卡时,可以用这种方式来限制服务器的接收范围。
客户机指定套接字地址的连接端口号:
一般情况下,客户机调用connect函数时不用指定自己的套接字地址的端口号。
系统会自动为它选择一个未用的端口号,并且用本地的IP地址来填充套接字地址中的相应项。
但有时客户机需要使用一个特定的端口号(比如保留端口号),而系统不会未客户机自动分配一个保留端口号,所以需要调用函数bind来和一个未用的保留端口号绑定。
4 指定客户机的IP地址和连接端口号:
表示客户机使用指定的网络设备接口和端口号进行通信。
5 指定客户机的IP地址:
表示客户机使用指定的网络设备接口和端口号进行通信,系统自动为客户机选一个未用的端口号。
一般只有在主机有多个网络设备接口时使用。
我们一般不在客户机上使用固定的客户机端口号,除非是必须使用的情况。
在客户机上使用固定的端口号有以下不利:
1 服务器执行主动关闭操作:
服务器最后进入TIME_WAIT状态。
当客户机再次与这个服务器进行连接时,仍使用相同的客户机端口号,于是这个连接与前次连接的套接字对完全一样,但是一呢、为前次连接处于TIME_WAIT状态,并未消失,所以这次连接请求被拒绝,函connect以错误返回,错误类型为ECONNREFUSED。
2 客户机执行主动关闭操作:
客户机最后进入TIME_WAIT状态。
当马上再次执行这个客户机程序时,客户机将继续与这个固定客户机端口号绑定,但因为前次连接处于TIME_WAIT状态,并未消失,系统会发现这个端口号仍被占用,所以这次绑定操作失败,函数bind以错误返回,错误类型为EADDRINUSE。
4)listen()
函数listen功能有两个:
将一个尚未连接的主动套接字(函数socket创建的可以用来进行主动连接但不能接受连接请求的套接字)转换成一个被动连接套接字。
执行listen之后,服务器的TCP状态由CLOSED转为LISTEN状态。
2 TCP协议将到达的连接请求队列,函数listen的第二个参数指定这个队列的最大长度。
TCP协议为每一个征听套接字维护两个队列:
未完成连接队列:
每个尚未完成3次握手操作的TCP连接在这个队列中占有一项。
TCP希望仪在接收到一个客户机SYN数据段之后,在这个队列中创建一个新条目,然后发送对客户机SYN数据段的确认和自己的SYN数据段(ACK+SYN数据段),等待客户机对自己的SYN数据段的确认。
此时,套接字处于SYN_RCVD状态。
这个条目将保存在这个队列中,直到客户机返回对SYN数据段的确认或者连接超时。
2 完成连接队列:
每个已经完成3次握手操作,但尚未被应用程序接收(调用函数accept)的TCP连接在这个队列中占有一项。
当一个在未完成连接队列中的连接接收到对SYN数据段的确认之后,完成3次握手操作,TCP协议将它从未完成连接队列移到完成连接队列中。
此时,套接字处于ESTABLISHED状态。
这个条目将保存在这个队列中,直到应用程序调用函数accept来接收它。
如果当一个客户机的SYN数据段到达时,征听套接字的完成队列已经满了,那么TCP协议将忽略这个SYN数据段。
对于不能接收的SYN数据段,TCP协议不发送RST数据段。
5)accept()
函数accept从征听套接字的完成队列中接收一个已经建立起来的TCP连接。
如果完成连接队列为空,那么这个进程睡眠。
一个服务器通常只需创建一个征听套接字,在服务器进程的整个活动期间,用它来接收所有客户机的连接请求,在服务器进程终止前关闭这个征听套接字;
对于没一个接收的(accepted)连接,TCP协议都创建一个新的连接套接字来标识这个连接,服务器使用这个连接套接字与客户机进行通信操作,当服务器处理完这个客户机请求时,关闭这个连接套接字。
6)close()
函数close关闭一个套接字描述符。
执行成功时返回0,否则返回-1。
与操作文件描述符的close一样,函数close将套接字描述符的引用计数器减1,如果描述符的引用计数大于0,则表示还有进程引用这个描述符,函数close正常返回;
如果为0,则启动清除套接字描述符的操作,函数close立即正常返回。
调用close之后,进程将不再能够访问这个套接字,但TCP协议将继续使用这个套接字,将尚未发送的数据传递到对方,然后发送FIN数据段,执行关闭操作,一直等到这个TCP连接完全关闭之后,TCP协议才删除该套接字。
7)read()和write()
用于从套接字读写数据。
函数执行成功时,返回读或写的数据量的大小,失败时返回-1。
每个TCP套接字都有两个缓冲区:
套接字发送缓冲区、套接字接收缓冲区,分别处理发送和接收任务。
从网络读、写数据的操作是由TCP协议在内核中完成的:
TCP协议将从网络上接收到的数据保存在相应套接字的接收缓冲区中,等待用户调用函数将它们从接收缓冲区拷贝到用户缓冲区;
用户将要发送的数据拷贝到相应套接字的发送缓冲区中,然后由TCP协议按照一定的算法处理这些数据。
读写连接套接字的操作与读写文件的操作类似,也可以使用函数read和write。
函数read完成将数据从套接字接收缓冲区拷贝到用户缓冲区:
当套接字接收缓冲区有数据可读时,1:
可读数据量大于函数read指定值,返回函数参数len指定的数据量;
2:
可读数据量小于函数read指定值,函数read不等待请求的所有数据都到达,而是立即返回实际读到的数据量;
当无数据可读时,函数read将阻塞不返回,等待数据到达。
当TCP协议接收到FIN数据段,相当于给读操作一个文件结束符,此时read函数返回0,并且以后所有在这个套接字上的读操作均返回0,这和普通文件中遇到文件结束符是一样的。
当TCP协议接收到RST数据段,表示连接出现了某种错误,函数read将以错误返回,错误类型为ECONNERESET。
并且以后所有在这个套接字上的读操作均返回错误。