论文部分内容阅读
摘要:Linux支持多种类型的套接字地址簇并将其抽象为统一的套接字接口,这一抽象的引入是为了方便互联网应用程序的编程,也为网络应用程序之间的数据通信提供了便利。该文从socket基本概念入手,介绍了网络编程的基本模式及Linux内核对socket的支持。
关键词:Linux;嵌入式;网络编程
中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2009)15-3953-02
Applications of Network Programming in Embedded Linux Systems
JIANG Ai-zhen
(Channel 561 in SARFT,NanChang 330046,China)
Abstract: Linux supports a wide range of types of socket addresses and the abstract cluster as a single socket interface, the introduction of the abstract is for the convenience of Internet application programming, as well as web applications to provide data communications between the convenience. In this paper, starting from the basic concepts of socket, introduced the basic model of network programming and the Linux kernel support for the socket.
Key words: Linux; Embedded; Network programming
1 socket概述
在Linux中的网络编程是通过socket套接字接口来进行的,这一抽象的引入是为了方便联网应用程序的编程,在UNIX的BSD版本第一次使用了这种接口,因此他也叫做BSD套接字。应用程序可以使用这种统一接口收发网络上的数据,网络的socket数据传输是一种特殊的I/O口,socket也是一种文件描述符。套接字的设计符合Linux的习惯,在理想情况下,应将所有可读写访问的对象映射成文件,这样就可以用普通的文件读写操作来处理这些对象了,使通信中的收发可以很容易映射成读写操作。在传输协议的上下文中,由这类读写操作的对象就是通信关系的两端,他们表示成了套接字。
BSD套接字是一个通用接口,它支持不同的网络结构,同时也是一个内部进程间通信机制。当一个主机上同时有多个应用程序在运行,他们使用tcp和udp协议进行通信,则传输层协议收到数据后将根据端口和套接口区分数据是传给哪个应用程序。端口是标识传输层与应用程序的数据接口,每个端口有一个16位的标识符。套接口是IP地址与端口号的组合,用来标识全网范围内的唯一一个端口,在tep和udP协议中用来标识一个连接,网络应用程序之间通过套接口来实现通信。套接字是套接口描述字的简称,是整型数字,它与文件描述符共用一段数值空间O_65535。应用程序中使用套接字来调用套接口,套接字可认为是指向套接口的指针,就像文件描述符是指向文件的指针一样。一个套接字描述了一个链接的一个端口,一个socket端点可以用socket地址来描述,socket地址结构由正地址,端口和使用协议组成(TCPorUDP),因此两个互联的进程都要有一个描述他们之间连结的套接字。我们也可以把套接字看作为是一种特殊的管道,只是这种管道对于包含的数据量没有限制。套接字存在于特定的通信域(即地址族)中,只有隶属于同一地址族的套接字才能建立对话。Linux支持的协议族有AF_INET(IPv4协议)、AF_INET6(IPv6协议)和AF_UNIX(Unix域协议)。
Linux支持多种套接字类型,每种套接字类型对应于创建套接字的应用程序所希望的通信服务类型。同一协议簇可能提供多种服务类型,比如TCP/IP协议族提供的虚电路和数据报就是两种不同的通信服务类型。TCP/IP中常用的socket类型共有三种,一种是流式socket(SOCK_STREAM),另一种是数据报式socket(SOCK_DGRAM),还有一种是原始socket(SOCK_RAW)。流式socket是一种面向连结的socket,对应于面向连接的TCP服务应用。数据报式socket是一种无连接的socket,对应于无连接的UDP服务。原始套接字接口容许对较低层协议如IP、ICMP直接访问,常用于检验新的协议实现或访问现有服务中的新设备。
2 网络编程基本模式
2.1 客户机/服务器模式
网络编程的基本模式是Client/Serve:模式,该模式的建立基于以下两点:
1) 非对等作用;2)通信完全是异步的客户机/服务器模式在操作过程中采取的是主动请示方式,首先服务器方要先启动,并根据请示提供相应服务。Server端首先调用socket创建一个一定类型socket,然后通过bind函数将这个socket绑定到一个client知道的端口上,接着server调用Listen函数设置倾听队列的长度,为了接收来自client端的请求做准备,然后server调用accept,开始在所绑定的端口倾听来自client端的连接请求。如果socket被设置成阻塞方式, accept调用将被阻塞,进程被挂起,直到server收到来自client的请求后,accept才返回。Client端通过socket调用创建一个一定类型的socket(应当和server的socket类型相同)。然后调用connect函数向server所在的主机发出连接请求,连接时,需要指定server所在主机的IP地址和server倾听的端口号,连接的报文包含了client端的初始的序号SYN a和MSS=1460信息(最大数据段的大小)。正在倾听来自client的连接请求的server收到client的连接请求后,server从accept调用中返回(通常socket是阻塞方式工作的)。server将会向client端发送server端的初始序号SYN b和对client端的SYN a的确认ACK=a l,还有本端的最大数据MSS当client端接收到server端的回应时,将发出对server请求的ACK=b 1。然后client从connect中返回,返回值是一个打开的socket的描述符,这个描述符和文件的描述符类似,程序可以像使用文件的描述符一样使用它。稍后,在server端收到client端对其请求的回应时,server将从accept调用返回,返回值也是一个socket的描述符。
2.2 面向连接协议的字节流套接字编程
字节流socket采用的是传输控制协议TCP。TCP提供面向连接的流传输,面向连接对可靠性的保证首先是它在进行数据传输前,必须在信源端和信宿端建立连接。在面向链接传输的每一个报文都需要接收端确认,未确认的报文被认为是出错报文。字节流套接字的服务器进程和客户进程在通信前必须先建立连接,建立连接和通信的步骤如下:
1) 服务进程首先调用Socket()创建一个字节流套接字,并调用bind()将服务器地址捆扎在该套接字上,接着调用listen()监听连接请求,随后调用accept()做好与客户进程建立连接的准备,无连接请求时,服务进程被阻塞;
2) 客户进程调用Socket()创建字节流套接字,然后调用connect()向服务进程发出连接请求;
3) 当连接请求到来后,服务进程被唤醒,生成一个新的字节流套接字,并用新套接字同客户进程的套接字建立连接,而服务进程最早生成的套接字则继续用于监听网络上的服务请求;
4) 服务进程和客户进程通过调用read()和write()交换数据;
5) 服务进程和客户进程通过调用close()撤消套接字并中断连接;当选择SOCK STREAM(字节流)类型的时,sock()系统调用中的参数protocol(协议)总会选中TCP,而UDP则一直用作SOCK DGRAM类型的传输协议。
2.3 非连接协议的数据报套接字编程
数据报式socket采用的是用户数据报协议UDP,它是建立在IP协议之上的,提供无连接数据报传输,主要应用在高可靠性、低延迟的局域网上,它的优点是高效率低开销,不用建立连接和撤销连接,缺点是不可靠,报文丢失后需重发。数据套接字的服务进程客户进程通信前不必建立连接,UDP则一直用作SOCKpGRAM类型的传输协议,通信的步骤如下:
1) 服务进程首先调用Socket()创建一个数据套接字,并调用bind将服务器地址捆扎在该套接字上,然后调用recvfrom()等待客户进程发来的请求;
2) 客户进程在调用SocketQ创建一个数据报套接字后,调用bindU将客户机地址捆扎在此套接字上,接着调用sendto()向服务进程发送请求,然后调用recvfrom()等待服务进程返回该请求的处理结果;
3) 服务进程在执行客户进程所请求的任务后,调用sendto()将处理结果返回给客户进程;
4) 服务进程和客户进程通过调用close()撤消套接字;
3 Linux内核对socket的支持
确切地说,Linux内核只提供了一个与套接字有关的系统调用,应用程序的所有套接字调用都会映射到这个系统调用上。在Linux内核中的net/socket.c中定义这个函数sys_socketcall(int call,unsigned long *args)。 include/asm/unistd.h中会指派一个数字,该数字会和arch/i386/kernel/entry.s中的系统调用一起添加到表格中。通过调用中。all参数可以说明所指向的那个套接字函数,在include/linux/net.h中定义了可接受的参数SYS_SOCKET, SYS_IND,SYS_CONNECT, SYS_LISTEN等,在用户空间的函数库中,带有特定参数的sys_socketcall调用会映射成某个独立函数,在内核中若要选中希望调用的那个函数,需要在sys_socketcall函数中用到一条:witch命令如下所示,而在此之前首先要使用copy_from_user()命令将sys_ socketcall()的函数复制到一个向量中,即ensign long a中。
if copy_from user(a,args,nargs(call))
return _EFAULT;
a0=a[0];
al=a[1];
switch(call)
{
case
SYS_SOCKET:
err=sys_socket(a0,al,a[2]);
break;
SYS_BIND:
err=sys_bind(a0,al,a[2]);
break;
SYS_CONNECT:
err=sys_connect(a0,al,a[2]);
break;
为了支持BSD套接字,一个重要的数据结构就是struct socket,它的定义位于
include/linux/net.h中,其定义如下:
struct socket
{
socket statestate;
unsigned long flags;
struct proto_ops *ops;
structmode *inode;
structfasync struct *fasync list;
structfile*file;
structsock*sk;
wait queue head t wait;
shorttype;
unsigned char passcred;};
与早期的内核相比,socket结构己经稍有简化。state中存储的是套接字状态可以取值如下(include/linux/net.h):SS_FREE(不忙)、SSes UNCONNECTED(未连通)、SS_ONNECTING(目前正在连接)、SS_ONNECTED(已连通)、SS_ISCONNECTING(目前正在断开连接)。flags用以同步访问,ops指针指向了连通协议(如tcp或udp)在初始化之后的协议运作。就像Linux中的每个文件都有一个mode一样,每个BSD套接字也分派了一个mode o file中存储了一个指向该文件结构的指针,这个结构连接了套接字,因此它可以用与指向套接字。如果有进程等待着这个套接字上的事件,也可以通过fasync_list找出该进程。通过sk指针可以使用一个匹配的sock结构。不过,这个sock结构是由BSD套接字之下、特定于协议的套接字初始化的,并且连通到这个指针。字段负责根据用户空间中的同名套接字调用存储第二个参数,在Linux内核include/asm/socket.h中定义了可接受的参数。
从上面分析可以看出任何时候通过一个socket来读写数据时,都是在使用一个系统调用(system_call)这个调用(例如read或write)跨越了用户空间应用程序与内核的边界。另外,在进入内核之前,您的调用会通过C库来进入内核中的一个通用函数system_call()。从system_call()中,这个调用会进入文件系统层,内核会在这儿确定正在处理的是哪种类型的设备。最后,调用会进入socket层,数据就是在这里进行读取或进行排队从而通过socket进行传输的。
4 总结
每种网络协议都提供网络应用开发接口,TCP/IP协议的应用开发接口的事实标准是socket套接口,开发socket的目的是隐藏网络底层的复杂结构和协议,使编程人员能够简单抽象的对网络进行操作。socket面向客户机/服务器模型,针对客户机/服务器程序提供不同的socket的系统调用函数,客户端随机申请一个socket,操作系统为之分配一个随机socket号;服务器端拥有全局公认的socket号,任何客户都可以向他发送连接请求和信息请求。进程通信以前,双方必须各自创建一个端口,否则是没有办法在通信前建立联系的,而socket提供了这种进程间通信的端口。从网络编程的套接字的分析来看,选择TCP套接字和选择UDP套接字编程,在传输数据时有着速度、效率和稳定性的差别。TCP编程拥有了可靠的数据连接,UDP不具有。但是在速度方面,UDP编程确优于TCP编程,特别是对于传输短消息。基于这两种通信方式优缺点的考虑,在后续编写IDU控制应用软件时,将UDP套接字用于硬件终端对外广播本地IP地址,使局域网内客户端软件识别某台终端设备,获取其MAC地址等硬件信息。将TCP套接字用于在客户端传输用户数据,对硬件终端上的硬件设备进行初始化设置。
参考文献:
[1] Warren W Gay. Linux Socket Programming by Example. Que(R), April 2000.
[2] Jonathan Corbet,Alessandro Rubini,Greg Kroah-Hartman. Linux Device Drivers 3rd Edition. Reilly Media Inc,2005.
[3] 孙琼,嵌入式Linux应用程序开发详解[M],北京:人民邮电出版社,2006.
[4] 张斌,高波等,Linux网络编程[M],北京:清华大学出版社,2000.
[5] Linux技术丛书委员会.Linux开发者指南[M].北京:北京希望电子出版社,2008.
关键词:Linux;嵌入式;网络编程
中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2009)15-3953-02
Applications of Network Programming in Embedded Linux Systems
JIANG Ai-zhen
(Channel 561 in SARFT,NanChang 330046,China)
Abstract: Linux supports a wide range of types of socket addresses and the abstract cluster as a single socket interface, the introduction of the abstract is for the convenience of Internet application programming, as well as web applications to provide data communications between the convenience. In this paper, starting from the basic concepts of socket, introduced the basic model of network programming and the Linux kernel support for the socket.
Key words: Linux; Embedded; Network programming
1 socket概述
在Linux中的网络编程是通过socket套接字接口来进行的,这一抽象的引入是为了方便联网应用程序的编程,在UNIX的BSD版本第一次使用了这种接口,因此他也叫做BSD套接字。应用程序可以使用这种统一接口收发网络上的数据,网络的socket数据传输是一种特殊的I/O口,socket也是一种文件描述符。套接字的设计符合Linux的习惯,在理想情况下,应将所有可读写访问的对象映射成文件,这样就可以用普通的文件读写操作来处理这些对象了,使通信中的收发可以很容易映射成读写操作。在传输协议的上下文中,由这类读写操作的对象就是通信关系的两端,他们表示成了套接字。
BSD套接字是一个通用接口,它支持不同的网络结构,同时也是一个内部进程间通信机制。当一个主机上同时有多个应用程序在运行,他们使用tcp和udp协议进行通信,则传输层协议收到数据后将根据端口和套接口区分数据是传给哪个应用程序。端口是标识传输层与应用程序的数据接口,每个端口有一个16位的标识符。套接口是IP地址与端口号的组合,用来标识全网范围内的唯一一个端口,在tep和udP协议中用来标识一个连接,网络应用程序之间通过套接口来实现通信。套接字是套接口描述字的简称,是整型数字,它与文件描述符共用一段数值空间O_65535。应用程序中使用套接字来调用套接口,套接字可认为是指向套接口的指针,就像文件描述符是指向文件的指针一样。一个套接字描述了一个链接的一个端口,一个socket端点可以用socket地址来描述,socket地址结构由正地址,端口和使用协议组成(TCPorUDP),因此两个互联的进程都要有一个描述他们之间连结的套接字。我们也可以把套接字看作为是一种特殊的管道,只是这种管道对于包含的数据量没有限制。套接字存在于特定的通信域(即地址族)中,只有隶属于同一地址族的套接字才能建立对话。Linux支持的协议族有AF_INET(IPv4协议)、AF_INET6(IPv6协议)和AF_UNIX(Unix域协议)。
Linux支持多种套接字类型,每种套接字类型对应于创建套接字的应用程序所希望的通信服务类型。同一协议簇可能提供多种服务类型,比如TCP/IP协议族提供的虚电路和数据报就是两种不同的通信服务类型。TCP/IP中常用的socket类型共有三种,一种是流式socket(SOCK_STREAM),另一种是数据报式socket(SOCK_DGRAM),还有一种是原始socket(SOCK_RAW)。流式socket是一种面向连结的socket,对应于面向连接的TCP服务应用。数据报式socket是一种无连接的socket,对应于无连接的UDP服务。原始套接字接口容许对较低层协议如IP、ICMP直接访问,常用于检验新的协议实现或访问现有服务中的新设备。
2 网络编程基本模式
2.1 客户机/服务器模式
网络编程的基本模式是Client/Serve:模式,该模式的建立基于以下两点:
1) 非对等作用;2)通信完全是异步的客户机/服务器模式在操作过程中采取的是主动请示方式,首先服务器方要先启动,并根据请示提供相应服务。Server端首先调用socket创建一个一定类型socket,然后通过bind函数将这个socket绑定到一个client知道的端口上,接着server调用Listen函数设置倾听队列的长度,为了接收来自client端的请求做准备,然后server调用accept,开始在所绑定的端口倾听来自client端的连接请求。如果socket被设置成阻塞方式, accept调用将被阻塞,进程被挂起,直到server收到来自client的请求后,accept才返回。Client端通过socket调用创建一个一定类型的socket(应当和server的socket类型相同)。然后调用connect函数向server所在的主机发出连接请求,连接时,需要指定server所在主机的IP地址和server倾听的端口号,连接的报文包含了client端的初始的序号SYN a和MSS=1460信息(最大数据段的大小)。正在倾听来自client的连接请求的server收到client的连接请求后,server从accept调用中返回(通常socket是阻塞方式工作的)。server将会向client端发送server端的初始序号SYN b和对client端的SYN a的确认ACK=a l,还有本端的最大数据MSS当client端接收到server端的回应时,将发出对server请求的ACK=b 1。然后client从connect中返回,返回值是一个打开的socket的描述符,这个描述符和文件的描述符类似,程序可以像使用文件的描述符一样使用它。稍后,在server端收到client端对其请求的回应时,server将从accept调用返回,返回值也是一个socket的描述符。
2.2 面向连接协议的字节流套接字编程
字节流socket采用的是传输控制协议TCP。TCP提供面向连接的流传输,面向连接对可靠性的保证首先是它在进行数据传输前,必须在信源端和信宿端建立连接。在面向链接传输的每一个报文都需要接收端确认,未确认的报文被认为是出错报文。字节流套接字的服务器进程和客户进程在通信前必须先建立连接,建立连接和通信的步骤如下:
1) 服务进程首先调用Socket()创建一个字节流套接字,并调用bind()将服务器地址捆扎在该套接字上,接着调用listen()监听连接请求,随后调用accept()做好与客户进程建立连接的准备,无连接请求时,服务进程被阻塞;
2) 客户进程调用Socket()创建字节流套接字,然后调用connect()向服务进程发出连接请求;
3) 当连接请求到来后,服务进程被唤醒,生成一个新的字节流套接字,并用新套接字同客户进程的套接字建立连接,而服务进程最早生成的套接字则继续用于监听网络上的服务请求;
4) 服务进程和客户进程通过调用read()和write()交换数据;
5) 服务进程和客户进程通过调用close()撤消套接字并中断连接;当选择SOCK STREAM(字节流)类型的时,sock()系统调用中的参数protocol(协议)总会选中TCP,而UDP则一直用作SOCK DGRAM类型的传输协议。
2.3 非连接协议的数据报套接字编程
数据报式socket采用的是用户数据报协议UDP,它是建立在IP协议之上的,提供无连接数据报传输,主要应用在高可靠性、低延迟的局域网上,它的优点是高效率低开销,不用建立连接和撤销连接,缺点是不可靠,报文丢失后需重发。数据套接字的服务进程客户进程通信前不必建立连接,UDP则一直用作SOCKpGRAM类型的传输协议,通信的步骤如下:
1) 服务进程首先调用Socket()创建一个数据套接字,并调用bind将服务器地址捆扎在该套接字上,然后调用recvfrom()等待客户进程发来的请求;
2) 客户进程在调用SocketQ创建一个数据报套接字后,调用bindU将客户机地址捆扎在此套接字上,接着调用sendto()向服务进程发送请求,然后调用recvfrom()等待服务进程返回该请求的处理结果;
3) 服务进程在执行客户进程所请求的任务后,调用sendto()将处理结果返回给客户进程;
4) 服务进程和客户进程通过调用close()撤消套接字;
3 Linux内核对socket的支持
确切地说,Linux内核只提供了一个与套接字有关的系统调用,应用程序的所有套接字调用都会映射到这个系统调用上。在Linux内核中的net/socket.c中定义这个函数sys_socketcall(int call,unsigned long *args)。 include/asm/unistd.h中会指派一个数字,该数字会和arch/i386/kernel/entry.s中的系统调用一起添加到表格中。通过调用中。all参数可以说明所指向的那个套接字函数,在include/linux/net.h中定义了可接受的参数SYS_SOCKET, SYS_IND,SYS_CONNECT, SYS_LISTEN等,在用户空间的函数库中,带有特定参数的sys_socketcall调用会映射成某个独立函数,在内核中若要选中希望调用的那个函数,需要在sys_socketcall函数中用到一条:witch命令如下所示,而在此之前首先要使用copy_from_user()命令将sys_ socketcall()的函数复制到一个向量中,即ensign long a中。
if copy_from user(a,args,nargs(call))
return _EFAULT;
a0=a[0];
al=a[1];
switch(call)
{
case
SYS_SOCKET:
err=sys_socket(a0,al,a[2]);
break;
SYS_BIND:
err=sys_bind(a0,al,a[2]);
break;
SYS_CONNECT:
err=sys_connect(a0,al,a[2]);
break;
为了支持BSD套接字,一个重要的数据结构就是struct socket,它的定义位于
include/linux/net.h中,其定义如下:
struct socket
{
socket statestate;
unsigned long flags;
struct proto_ops *ops;
structmode *inode;
structfasync struct *fasync list;
structfile*file;
structsock*sk;
wait queue head t wait;
shorttype;
unsigned char passcred;};
与早期的内核相比,socket结构己经稍有简化。state中存储的是套接字状态可以取值如下(include/linux/net.h):SS_FREE(不忙)、SSes UNCONNECTED(未连通)、SS_ONNECTING(目前正在连接)、SS_ONNECTED(已连通)、SS_ISCONNECTING(目前正在断开连接)。flags用以同步访问,ops指针指向了连通协议(如tcp或udp)在初始化之后的协议运作。就像Linux中的每个文件都有一个mode一样,每个BSD套接字也分派了一个mode o file中存储了一个指向该文件结构的指针,这个结构连接了套接字,因此它可以用与指向套接字。如果有进程等待着这个套接字上的事件,也可以通过fasync_list找出该进程。通过sk指针可以使用一个匹配的sock结构。不过,这个sock结构是由BSD套接字之下、特定于协议的套接字初始化的,并且连通到这个指针。字段负责根据用户空间中的同名套接字调用存储第二个参数,在Linux内核include/asm/socket.h中定义了可接受的参数。
从上面分析可以看出任何时候通过一个socket来读写数据时,都是在使用一个系统调用(system_call)这个调用(例如read或write)跨越了用户空间应用程序与内核的边界。另外,在进入内核之前,您的调用会通过C库来进入内核中的一个通用函数system_call()。从system_call()中,这个调用会进入文件系统层,内核会在这儿确定正在处理的是哪种类型的设备。最后,调用会进入socket层,数据就是在这里进行读取或进行排队从而通过socket进行传输的。
4 总结
每种网络协议都提供网络应用开发接口,TCP/IP协议的应用开发接口的事实标准是socket套接口,开发socket的目的是隐藏网络底层的复杂结构和协议,使编程人员能够简单抽象的对网络进行操作。socket面向客户机/服务器模型,针对客户机/服务器程序提供不同的socket的系统调用函数,客户端随机申请一个socket,操作系统为之分配一个随机socket号;服务器端拥有全局公认的socket号,任何客户都可以向他发送连接请求和信息请求。进程通信以前,双方必须各自创建一个端口,否则是没有办法在通信前建立联系的,而socket提供了这种进程间通信的端口。从网络编程的套接字的分析来看,选择TCP套接字和选择UDP套接字编程,在传输数据时有着速度、效率和稳定性的差别。TCP编程拥有了可靠的数据连接,UDP不具有。但是在速度方面,UDP编程确优于TCP编程,特别是对于传输短消息。基于这两种通信方式优缺点的考虑,在后续编写IDU控制应用软件时,将UDP套接字用于硬件终端对外广播本地IP地址,使局域网内客户端软件识别某台终端设备,获取其MAC地址等硬件信息。将TCP套接字用于在客户端传输用户数据,对硬件终端上的硬件设备进行初始化设置。
参考文献:
[1] Warren W Gay. Linux Socket Programming by Example. Que(R), April 2000.
[2] Jonathan Corbet,Alessandro Rubini,Greg Kroah-Hartman. Linux Device Drivers 3rd Edition. Reilly Media Inc,2005.
[3] 孙琼,嵌入式Linux应用程序开发详解[M],北京:人民邮电出版社,2006.
[4] 张斌,高波等,Linux网络编程[M],北京:清华大学出版社,2000.
[5] Linux技术丛书委员会.Linux开发者指南[M].北京:北京希望电子出版社,2008.