作者:曹元其
Linux网络服务能力非常强大,它的TCP/IP代码是最高级的。Linux的网络实现是模仿FreeBSD的,它支持FreeBSD的带有扩展的Sockets(套接字)和TCP/IP协议。它支持两个主机间的网络连接和Sockets通讯模型,实现了两种类型的Sockets:BSD Sockets和INET Sockets。它为不同的通信模型和服务质量提供了两种传输协议,即不可靠的、基于消息的UDP传输协议和可靠的、基于流的传输协议TCP,并且都是在IP网络协议上实现的。INET sockets是在以上两个协议及IP协议之上实现的。它们之间的关系见图1所示。
图1 Linux网络层
Socket在网络编程中的实现
套接字是网络通信的基本构件,它提供了不同主机间进程双向通信的端点。如同电话一样,只有当一方拨通另一方的电话时,双方才能建立对话,套接字就好比是双方的电话。通过Sockets编程,程序可以跳过复杂的网络底层协议和结构,直接编制与平台无关的应用程序。随着Internet的广泛应用,Sockets已逐渐成为网络编程的通用接口。
套接字存在于特定的通信域(即地址族)中,只有隶属于同一地址族的套接字才能建立对话。Linux支持AF_INET(IPv4协议)、AF_INET6(IPv6协议)和AF_LOCAL(Unix域协议)。
Linux支持以下的socket families或domain:
◆ Unix domain sockets;
◆ INET TneIntemet address family supports communications via;
◆ TCP/IP protocols;
◆ Amateur radio X.25;
◆ Novel IPX;
◆ Appletalk DDP;
◆ X.25。
套接口就是网络进程的ID。网络通信也是一种进程的通信,两个网络进程通信时首先要确定各自所在网络节点的网络地址(IP地址)。网络地址可以确定进程所在的计算机,一台计算机上可能同时有多个网络进程。为了区别不同的进程,套接口中还需要端口号(Port)信息。在一台计算机中,一个端口一次只能分配给一个进程。所以在一台计算机中,端口号和进程可以惟一确定整个Intemet中的一个网络进程。可以认为,套接口=网络地址+端口号。
Linux网络数据结构
在网络实际传送的数据中,有两种字节排列顺序:重要的字节在前面,或者不重要的字节在前面。前一种叫网络字节顺序(Network Byte Order,NBO),有些机器在内部是按照这个顺序储存数据的。当某数据必须按照NBO顺序时,那么要调用函数(例如htons())将它从本机字节顺序(Host Byte Order,HBO)转换过来,否则传送过去的数据将使对方机器不可读。这点对于网络数据传送来说是非常关键的。
在网络中第一个被创造的结构类型是sockaddr。这个数据结构是为许多类型的套接口储存地址信息。它的定义如下:
struct sockaddr{
unsigned short
sa_family; /*这个是地址族,通常是AF-xxxx的形式*/
char
sa_data[14];
/*14字节的地址信息*/
};
上面代码中,sa_famdly是“AF_INET”,表示它使用的是Internet地址族;sa_data用于为套接口储存目标地址和端口信息。
为了解决struct sockaddr,创造了一个并列的结构struct sockadd_in(“in”代表“Internet”),如下所示:
struct sockaddr_in{
short int
sin_family;
/*地址族信息,通常是AF-xxxx的形式*/
unsigned short
int sin_port; /*端口信息*/
struct in_addr
sin_addr;
/*网络地址*/
unsigned char
sin_zero[8];
/*补位用的0*/
}
上面这个数据结构可以轻松处理套接口地址的基本元素。需要解释的是,sin_zero被加入到这个结构中主要是为了保证struct sockaddr的数据长度和struct sockaddr_in的一样,这样在使用标准函数时,就可以使用统一的数据接口。需要注意的是,应该使用函数bzero()将sin_zero全部置零。最后,sin_port和sin_addb必须是网络字节顺序(Network Byte Order)。如果声明“inadd”是数据结构stmct sockaddr_in的实例,那么inadd.sinadd.s_addr就储存了4个字节的IP地址(网络字节顺序)。
另一个常用到的是unsigned类型。它比上面介绍的struct sockaddr_in或struct sockaddr用得更普遍。对于变量类型unsigned,可以使用的两种类型是short(两个字节)和long(四个字节)。假设想将short从本机字节顺序转换为网络字节顺序,需用“h”表示本机(host),用“to”表示进行转换,然后用“n”表示网络,用“s”表示short,那么就是h-to-n-s或者htons()(“Host to Network Short”)。
考虑到对不同机器的可移值性,这样的转换是必需的。我们对“n”、“h”、“s”和“l”这几个字母进行组合,就可以得到Linux下的全部转换函数。
IP地址在Linux网络中的处理方法
假设使用struct sockaddr_in ina,想将IP地址“164.112.175.124”储存到其中,那么所要做的是调用函数inet_addr(),转换上面“数字 + 句点”格式的IP地址到unsigned long中。这个工作可以这样来做:
ina.sin_addr.s_addr=inet_addr(”164.112.175.124”);
inet_addr()返回的地址已经是按照网络字节顺序的,不用调用htonl()。在发生错误的时候inet_addr()返回-1。调用后,需使用正确的错误检查,比如说当IP地址为255.255.255.255的时候,返回的就是(unsigned)-1。因为这是个广播地址,你的程序必需能够将这类错误捕获出来。
你现在就可以转换字符串形式的IP地址为1ong了。若有一个数据结构struct in_addr,按照“数字+句点”格式打印时,你要用函数inet_ntoa()(ntoa意思是network to ascⅡ),如下所示:
printf(“%s”,inet_ntoa(ina.sin_addr));
这样就可以打印IP地址。注意:函数inet―ntoa()的参数是struct in_addr,而不是long,它返回的是一个指向字符的指针。
在inet_ntoa内储存了字符数组,因此它每次调用inet_ntoa()的时候将覆盖以前的内容。
例如:
Char a1,
*a2;
......
a1=inet_ntoa(ina1.sin_addr); /*假设地址是;164.112.175.124*/
a2=inet_ntoa(ina2.sin_addr);/*假设地址是:202.112.58.200*/
printf(“address 1:%sn”,a1);
printf(“address 2:%sn”,a2);
上面运行结果是:
address l:202.112.58.200
address 2:202.112.58.200
如果想保存地址,那么可用strcpy()保存到自己的字符数组中。
以上介绍了Linux网络编程的基础知识和对网络IP地址处理的一些技巧。如果能够将其同Linux下众多的小工具整合在一起的话,那么所开发出来的程序的功能已经不亚于一些专业的软件了。