1、PF_INET6:用于Ipv6网络通信PF_IPX:用于Novell IPX网络通信PF_X25:用于ITU-T X.25/ISO-8208网络通信根据套接字规范的定义,一个协议族可以支持多个地址族。但是,到目前为止的套接字实现中,全部都是一个协议族支持一个地址族。因此,在各个版本的Linux系统中,都把协议族与地址族定义为同一个值。在实际的编程过程中,考虑到将来的可移植性,在调用创建套接字的函数时,应该使用PF系列宏定义。而在类似地址绑定的系统调用中,应该使用AP系列宏定义。20.1.3 面向连接与面向无连接在套接字编程模型中,存在面向连接的服务和面向无连接的服务。面向连接的服务类似于电话系
2、统,如图20-3所示。在拨打电话的过程中,首先需要拨号,电话拨通后双方进行通话,通话完毕后挂断电话。与此相似,在面向连接的服务模式下,每一次完整的数据传输都要经过建立连接、使用连接、终止连接的过程。无连接服务则类似于电报系统,如图20-4所示。在发送电报的过程中,并不需要先与目的地建立连接,而是根据目标地址直接发送报文即可。同样,在面向无连接的服务模式下,只需要知道通信对端的信息,即可直接向目标发送数据。在上述面向连接的模型中,被叫方相当于TCP服务器,主叫方相当于客户机。首先,被叫方处于等待呼叫状态(在TCP模型中为服务器处于listen状态);由主叫方拨号,呼叫被叫方(相当于connect
3、);被叫方摘机,接受呼叫(相当于accept)过程,然后双方通话(相当于数据收发);最后完成通话后挂机(相当于close)。图20-3 电话通讯模型(面向连接)图20-4 电报通讯模型(面向无连接)在上述面向无连接模型中,发报文根据收报文的地址信息直接向收报文发送报文(sendto)。而收报方收到报文后(recvfrom),根据报文的来源向发报文返回报文(sendto),发报文则接收该响应报文(recvfrom)。20.1.4 套接字类型在创建套接字时,除需要指定协议族外,还需要指定套接字的类型。Linux系统支持多种套接字类型,主要包括流式套接字、数据报套接字和原始套接字。流式套接字(SOC
4、K_STREAM):提供面向连接、可靠的全双工数据传输服务。流式套接字通过TCP协议实现。而TCP的差错控制机制可以保证数据无差错、无重复地发送,且按发送顺序接收。另外,流式套接字有流量控制机制,可以有效避免数据流超限。Telnet、FTP等应用采用的就是流式套接字。数据报式套接字(SOCK_DGRAM):提供无连接服务。数据报套接字通过UDP协议实现。数据发送时并不经过建立连接的过程,而是直接以数据包形式被发送。另外,数据报套接字不能保证传输过程中的正确性,并且数据接收顺序可能与发送顺序不一致。传输内容的正确性和数据顺序要靠应用层编码进行控制。原始式套接字(SOCK_RAW):该接口允许对较
5、低层协议(如IP、ICMP)进行直接访问。在某些应用中,使用原始套接字可以构建自定义头部信息的IP报文。创建原始套接字需要超级用户权限。20.1.5 字节序字节序是指占内存大于一个字节的类型的数据在内存中的存储顺序,按照不同的顺序可以划分为小端字节序(Little edian)、大端字节序(Big edian)两种,统称为主机字节序。小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;大端字节序是高字节数据存放在低地址处,低字节数据存放在高地址处。字节序是与计算机硬件的种类相关的,基于Intel x86体系结构的PC机是小端字节序的,而有些平台则是大端字节序的。如图20-5
6、所示,展示了int型数据0x0001在大字节序主机和小字节序主机的内存中的存储映像。图20-5 小字节序和大字节序存储映像【示例20-1】查看当前系统的字节序的过程,实现过程如示例代码20-1所示。示例代码20-11#include /*头文件*/2#include 3int IsLittleEndian() /*定义函数,判断字节序*/45 unsigned short i=1; /*定义无符号短整型变量*/6 return (1 = *(char *)&i); /*取变量i的低8位,与1比较,如果相等,则为小字节序;如果不等,则为大字节序*/78int main() /*主函数*/910
7、if(IsLittleEndian() /*调用函数判断字节序*/11 12 printf(Little Endian.n); /*输出字节序信息*/13 14 else15 16 printf(Big Endian.n /*输出字节序信息*/17 18 return 0;19【运行结果】经过编译链接,在shell下运行上述程序,其结果如下所示。20Little Endian.【代码解析】在本例中,通过函数IsLittleEndian判断当前主机是否采用小端字节序。根据图20-1可以知道,如果是小端字节序,在将int数据强制转换为char时,将取内存低端的第1个字节的值,而这个字节的值为1表示
8、当前主机的字节序为小字节序,反之则为大字节序。源代码的各行解释如下。第12行:头文件信息。第37行:IsLittleEndian函数体。使用强制类型转换,根据地址低端存储的数据值,判断当前主机的字节序。第818行:主函数。通过调用IsLittleEndian获取主机的字节序信息。该示例的输出信息表示当前主机的字节序为小端字节序。20.1.6 套接字连接方式在面向连接的套接字编程模式下,可以根据应用的需要构建不同的连接方式。主要包括短连接和长连接两种连接方式:。短连接方式是指在每进行一次通信报文收发交易时都需要先建立连接,然后进行数据收发,收发完毕后立即断开连接。而长连接方式是指客户机与服务器建
9、立好通讯连接,然后进行报文发送和接收。报文发送与接收完毕后,连接并不断开而继续存在,以便进行下一次的数据收发,因此长连接方式可以连续进行交易报文的发送与接收。短连接与长接连的模型分别如图20-6和图20-7所示。图20-6 短连接方式图20-7 长连接方式短连接和长连接都有各自适合应用的场合。与长连接相比,短连接方式的优点是方式简单,不需要过多考虑长连接方式下的网络故障异常处理,其缺点是每次通信都要有建立连接的过程,处理效率上不如长连接。而长接连的优点是处理效率高,但是需要考虑各种网络异常的处理,程序逻辑比短连接相对要复杂。在实际应用中,具体采用何种连接方式应视应用环境确定。典型的并发服务器采
10、用的是短连接方式。如果客户机与服务器之间通信频繁,并且对传输的时间要求较高,可以考虑采用长连接方式。20.1.7 数据传输方式连接建立完成后,在数据发送与接收过程中也存在不同的方式,主要包括同步和异步两种方式。这里的同步与异步是指从程序的逻辑结构上看数据是同步发送或接收的。对于同步方式来说,报文发送和接收是同步进行的,即报文发送后,发送方等待接收方处理完成并返回应答报文。同步方式需要考虑超时问题,报文发出后发送方需要设定超时时间,超时后发送方不再继续等待,而直接返回。对于异步方式来说,发送方只负责发送数据,不需要等待接收任何返回数据;而接收方只负责接收数据。通常情况下,异步方式在客户端和服务器
11、端各有两个进程专门负责数据收发。这两个进程相互独立,互不影响。在实际网络编程过程中,建立网络通信模型需要综合考虑连接方式和数据传输方式。比较有价值的网络通信模型包括同步短连接、同步长连接和异步长连接等。异步长连接的通信模型如图20-8所示。图20-8 异步长连接通信模型从异步长连接通信模型可以看到,异步长连接包括两条连接。通常情况下,一条连接负责发送数据,另外一条连接负责接收数据。每个通信节点实际是由两个子进程组成。每个子进程负责维持一条通信链路。异步长连接是相对较为复杂的通信模型,接收和发送分别由不同的进程完成,较高的通信效率也增加了控制的复杂度。数据的接收和发送是异步完成的,这就存在数据同
12、步的问题。20.2 套接字数据结构在套接字编程接口函数中,定义了若干数据结构。这些数据结构大多为结构类型,基本上所有的套接字函数都会用到这些结构的内容。本节将对这些结构的内容进行详细阐述,读者可以结合后续章节中对接口函数的介绍等内容加深对这些结构的理解。20.2.1 套接字地址结构在套接字编程模式下,大多接口函数都需要用到套接字地址结构。套接字地址结构就是包含了套接字的地址和端口等信息的结构体。该结构在调用大多数套接字函数时是必需的。Linux操作系统支持多种地址族的套接字,如UNIX、INET、IPX、X25等,而每种套接字又有其特有的地址结构。在TCP/IP协议下进行网络编程使用的地址族是
13、Internet地址族,即INET。Internet地址族的地址结构为sockaddr_in,其中的in代表Internet地址族。该地址结构的定义位于/usr/include/netinet/in.h,数据结构如下所示。struct sockaddr_in _SOCKADDR_COMMON (sin_); in_port_t sin_port; struct in_addr sin_addr; /* Pad to size of struct sockaddr. unsigned char sin_zerosizeof (struct sockaddr) - _SOCKADDR_COMMON
14、_SIZE - sizeof (in_port_t) - sizeof (struct in_addr);参数说明如下。_SOCKADDR_COMMON (sin_):该成员是一个宏定义,指定地址族,等价于sa_family_t sin_family。 sin_port:端口号,必须为网络字节序。sin_addr:IP地址,必须为网络字节序。该成员是一个结构变量,结构中惟一的成员是IP地址,数据类型为uint32_t。sin_zero:为与通用套接字地址结构保持大小一致而填充的数据。在调用套接字编程时,往往需要将地址结构进行强制类型转换,转换为通用套接字地址结构进行传递参数。为满足这一要求,需
15、要保持两个数据结构大小的一致。该成员内容应设置为空字符“0”。20.2.2 通用套接字地址结构Linux系统支持多种不同的地址族,每种地址族的结构内容是各不相同的,例如,internet地址族的地址结构是sockaddr_in,而UNIX地址族的地址结构是sockaddr_un等。在向套接字的编程接口函数传递地址结构指针时,需要将各不相同的地址结构转换为一个通用的数据结构,这就是通用套接字地址结构。结构在头文件bits/socket.h中定义,声明如下所示。struct sockaddr _SOCKADDR_COMMON (sa_); char sa_data14;_SOCKADDR_COMM
16、ON (sa_):该成员是一个宏定义,指定地址族,等价于sa_family_t sa_family。sa_data:地址数据。对于Internet地址族来说,就是包括sin_port、sin_addr、sin_zero在内的全部地址数据。20.2.3 主机名称数据结构在进行网络编程时,通常需要用到主机名称(域名)与IP地址转换。在这种情况下,就需要使用主机名称数据结构hostent,该数据结构定义了主机名与IP地址的对应关系。在套接字编程模型中,与地址绑定相关的操作都需要使用该结构。该结构定义包括在头文件netdb.h中,其声明如下所示。struct hostent char *h_name;
17、 char *h_aliases; int h_addrtype; int h_length; char *h_addr_list;#define h_addr h_addr_list0h_name:主机名称。h_aliases:主机别名列表。主机别名可能存在多个,该成员为指向别名列表的指针。h_addrtype:主机地址类型。在Internet地址族下,该值一般为AF_INET。h_length:地址的字节长度。h_addr_list :一个以0结尾的数组,包含该主机的所有地址。h_addr:在h_addr_list中的第1个地址。用于获取该结构的接口函数有:gethostbyname、ge
18、thostbyaddr、gethostent等。其中,gethostbyname函数用于把主机名映射成IP地址,而gethostbyaddr函数的作用则相反。【示例20-2】查看当前系统的主机名称和IP地址,实现过程如示例代码20-2所示。示例代码20-221#include 22#include 23#include 24#include 25#include 26int main() /*主函数*/2728 int n; /*循环变量*/29 struct hostent *h; /*结构指针变量定义*/30 char *p; /*定义指向字符串的指针*/31 char hostnameP
19、ATH_MAX; /*定义字符串数组*/32 if(gethostname(hostname,PATH_MAX) h_name); /*输出主机名*/43 for(n=0,p=h-h_aliases; *p != NULL; p+,n+) /*循环输出所有主机别名*/44 45 fprintf(stderr,Alias name %d:,n+1,*p); /*输出别名*/46 47 for(n=0; nh_length/sizeof(int); n+) /*循环输出所有的IP地址*/48 49 fprintf(stderr,Ip address %d:,n+1,inet_ntoa(*(stru
20、ct in_addr *)(h50 -h_addr_listn); /*解析IP地址并输出*/51 52 return 0;5354Host name:Hubery.site55Alias name 1:Hubery56Ip address 1:192.168.1.8【代码解析】在本例中,首先通过系统调用gethostname得到当前主机的主机名,然后根据主机名调用gethostbyname解析/etc/hosts,得到当前主机的详细信息。第15行:其中netdb.h中包含了gethostbyname函数的原型声明。第12行:调用gethostname函数获取当前主机的主机名,返回数据存放于局
21、部变量hostname之中。第17行:调用gethostbyname函数获取主机名的详细信息,返回数据存放于struct hostent类型的结构指针之中。第2326行:循环输出主机的别名信息。第2731行:循环输出主机的IP地址信息。其中inet_ntoa函数的作用是将地址数据转换为可读性更好的点分十进制字符串。输出信息的含义具体如下。第1行:主机名是Hubery.site。第2行:主机有一个别名Hubery。第3行:主机定义了一个IP地址192.168.1.8。提示:在Linux系统中,主机名称与IP地址的对照关系是在文件/etc/hosts中定义的。系统调用gethostbyname和g
22、ethostbyaddr正是对该文件进行解析获取hostent结构的。一般情况下hosts文件的每行为一个主机,每行由三节组成,每个节之间由空格隔开。在Internet系统下,该文件还起到域名解析的作用。域名解析通过需要通过DNS服务器进行,但如果在该文件中对域名进行了定义,则将不进行DNS解析,直接使用该文件中指定的IP进行访问。20.2.4 服务名称数据结构在Linux系统中存在一个网络服务配置文件/etc/services,该文件中定义了当前主机提供的网络服务名称、对应的端口号和协议。文件的每一行定义了一种网络服务,每一行由4节组成,节间由空格分隔。其中,第1节指定了服务的名称,如Tel
23、net;第2节指定了服务的端口和协议,端口与协议用“/”分隔;第3节定义了服务的别名。这个文件的作用主要有两个:一是为Linux超级服务所使用;二是在进行网络编程使用。在进行服务器网络编程中,需要定义服务绑定的端口号。如果直接将端口号写到程序中,那么一旦端口号发生变化,则需要修改程序并重新编译。此时,可以在/etc/services文件中定义服务名称。在编程时,只需通过相应系统调用根据服务名称获取对应的端口号。在端口号需要变更时,只需要修改/etc/services文件即可。Linux系统提供了对/etc/services文件进行操作的一系列函数,包括getservbyname、getserv
24、byport等。这些函数都需要使用服务名称数据结构,即struct servent。该结构的定义在头文件netdb.h中,声明如下所示。struct servent char *s_name; char *s_aliases; int s_port; char *s_proto;s_name:服务名称,如通常的Telnet、FTP等。s_aliases:服务的别名列表。s_port:服务的端口号,如Telnet定义在23端口,而FTP定义在21端口。s_proto:协议名称,如TCP、UDP等。【示例20-3】查看当前系统中Telnet服务绑定的端口号。其实现过程如示例代码20-3所示。示例代码20-301 #include 02 #include 03 #include 04 #include 05 #include s_port); /*输出该服务所对应的端口*/15 return 0;16 【运行结果】经过编译链接,在shell下运行上述程序,结果如下所示。01 Port:23【代码解析】在本例中,首先调用getse
copyright@ 2008-2023 冰点文库 网站版权所有
经营许可证编号:鄂ICP备19020893号-2