Linux Socket学习(四) LinuxSocket网络协议数据结构Motorola
程序员文章站
2022-05-03 22:33:02
...
格式化IPv4套接口地址
在Linux下使用最多的地址族为AF_INET。这为一个套接口指定一个IPv4套接口地址,从而使得这个套接口可以通过TCP/IP网络与其他的主机进行通信。定义了sockaddr_in结构的包含头文件是由下面的C语句来进行定义的:
#include <netinet/in.h>
下面的例子是一个用于网络地址的sockaddr_in结构。另外显示了一个in_addr结构,因为sockaddr_in结构会在他的定义中使用这个结构。
struct sockaddr_in {
sa_family_t sin_family; /* Address Family */
uint16_t sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Pad bytes */
};
struct in_addr {
uint32_ t s_ addr; /* Internet address */
};
其成员描述如下:
sin_family成员出现在与通用套接口定义中的sa_family相同的存储位置。sin_family会被初始化为AF_INET。
sin_port成员为这个套接口地址定义了TCP/IP的端口号。这个值必须为网络字节顺序。
sin_addr成员定义为in_addr结构,用于以网络字节顺序存放IP地址。如果我们检测in_addr,我们就会发现他由32位无符号整数组成。
最后,结构的剩余部分由8个字节的成员sin_zero[8]填充为16个字节。
这个地址结构的物理布局如下图所示:
从上图中我们可以看出,sin_port成员使用两个字节,而sin_addr使用4个字节。这两个成员都在其上放置了一个标志用来表明这些值必须为网络字节顺序。
理解网络字节顺序
不同的CPU体系结构对于多个字节的数据,16位,32位或者更多,会有不同的安排方式。最基本的两个字节顺序为:
大端
小端
其他的组合也是可以的,但是我们在这里并不考虑这些情况。下面这个图显示了这两种不同的字节顺序:
上个图所演示的为十进制的数4660,以十六进制表示则为0x1234.这个数值需要用两个字节来表示。从这个图我们可以看到或者我们可以首先放置最重的字节(大端),或者是我们可以首先放置最不重要的字节(小端)。这种选择是非常模糊的,而这最终涉及到CPU的设计。
我们也许已经知道,Intel CPU使用小端字节顺序。其他的CPU,例如Motorola 68000系列使用大端字节顺序。在这里我们要考虑的最重要的事情就是这两种类型的CPU都存在,而且他们要连接到同一个网络。
如果一个Motorola的CPU向网络中写入一个16位的数字,并且为一个Intel CPU接收时会发生什么呢?这些字节将会为Intel CPU进行反序解释,从而这个值看起来就是十六进制的0x3412。
为网络存在的协议,大端字节顺序将在网络上使用。只要通过网络传输的所有消息遵循这个序列,所有软件就可以顺利通信。
这就将我们带回了AF_INET地址族。TCP/IP端口号(sin_port)以及IP地址(sin_addr)必须是网络字节顺序。BSD套接口地址要求作为程序员的我们在格式化地址必须考虑到这一点。
执行端转换
有一些函数提供用来帮助我们简化端转换。需要考虑两个方向的端转换:
主机顺序到网络顺序
网络顺序到主机顺序
主机顺序是指我们的CPU所使用的字节顺序。对于Intel CPU来说是指小端字节顺序。网络字节顺序,正如我们已经了解到的,为大端字节顺序。
同时也有两类转换函数:
短整数(16位)转换
长整数(32位)转换
下面所提供的是转换函数的概要:
#include <netinet/in.h>
unsigned long htonl(unsigned long hostlong);
unsigned short htons(unsigned short hostshort);
unsigned long ntohl(unsigned long netlong);
unsigned short ntohs(unsigned short netshort);
这些函数的使用是很简单的。例如,要将一个短整数转换为网络顺序,我们可以使用下面的代码:
short host_ short = 0x1234;
short netw_short;
netw_short = htons(host_short);
netw_short值将接收转换为网络字节后的合适值。将一个网络字节顺序转换为一个主机顺序也是一样简单的:
host_short = ntohs(netw_short);
初始化一个宽网地址
现 在我们已准备好来创建一个网络地址了。在这里演示的这个例子需要这个地址必须为宽的。这经常是当我们连接到一个远程服务时完成的。这个原因是因为我们的主 机也许会有两个或是多个网卡,每一个网卡有一个不同的IP地址。而且,Linux同时也允许每一个网卡有多个IP地址。当我们指定一个宽的IP地址,我们 允许系统选择到远程服务的路由。内核会在连接建立时确定我们的最终本地套接口地址。
有时我们希望内核为我们赋一个本地端口号,这是通过将sin_port指定为0来做到的。下面的代码演示了如何使用一个宽IP地址与一个宽端口号来初始化一个AF_INET地址。
1: struct sockaddr_in adr_inet;
2: int adr_len;
3:
4: memset(&adr_inet,0,sizeof adr_inet);
5:
6: adr_inet.sin_family = AF_INET;
7: adr_inet.sin_port = ntohs(0);
8: adr_inet.sin_addr.s_addr = ntohl(INADDR_ANY);
9: adr_len = sizeof adr_inet;
描述如上:
1 使用sockaddr_in结构来定义一个adr_inet变量。
2 通过调用memset函数将adr_inet结构清0。
3 通过将AF_INET赋给adr_inet.sin_family来建立地址族。
4 在第7行指定一个宽端口号。注意ntohs函数的使用。值0指明一个宽端口号。
5 在第8行赋一个宽IP地址。注意执行端转换的ntohl函数的使用。
6 地址的尺寸简单的由adr_inet结构的尺寸来计算。
另一个常用的IP地址为127.0.0.1。这是指loopback设备。回环设备允许我们在同一个主机上与另一个进程进行通信。第8行的代码可以用下面的代码为进行替换:
adr_inet.sin_addr.s_addr = ntohl(INADDR_LOOPBACK);
这会通过回环设备来定位我们的主机。
初始化一个指定的网络地址
在前一个部分我们处理了一个简单的AF_INET地址的例子。当我们要在地址中建立一个指定的IP地址事情就会变得更为复杂。下面是一个程序示例:
/*
* af_inet.c
* Establishing a specific AF_INET
* Socket Address
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
/*
* This function reports the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
perror(on_what);
exit(1);
}
int main(int argc,char **argv,char **envp)
{
int z; /* Status return code */
int sck_inet; /* Socket */
struct sockaddr_in adr_inet; /* AF_INET */
int len_inet; /* length */
const unsigned char IPno[] = {
127,0,0,23 /* Local loopback */
};
/* Create an IPv4 Internet Socket */
sck_inet = socket(AF_INET,SOCK_STREAM,0);
if(sck_inet == -1)
bail("socket()");
/*Create an AF_INET address */
memset(&adr_inet,0,sizeof adr_inet);
adr_inet.sin_family = AF_INET;
adr_inet.sin_port = htons(9000);
memcpy(&adr_inet.sin_addr.s_addr,IPno,4);
len_inet = sizeof adr_inet;
/* Now bind the address to the socket */
z = bind(sck_inet,(struct sockaddr *)&adr_inet,len_inet);
if(z == -1)
bail("bind()");
/* Display all of our bound sockets */
system("netstat -pa --tcp 2>/dev/null | "
"sed -n '1,/^Proto/P;/af_inet/P'");
close(sck_inet);
return 0;
}
在这个程序所用的步骤是我们前面的例子程序相同。然而43到48行还需要一些解释:
1 在第30行使用sockaddr_in结构来定一个名为adr_inet的变量名。另外,在第31行将套接口地址的长度定义为一个整数len_inet。
2 在第32行与第33行定义了一个无符号字符数组IPno[4]。在这里指定了一个IP地址:127.0.0.23。
3 在第43行将adr_inet结构清0。
4 在第45行建立了AF_INET地址族。
5 在这个例子中在第46行选择TCP/IP的9000端口建立连接。在这里我们要注意在第46行htons函数的用法。
6 在第47行将字符数组IPno[4]拷贝到adr_inet.sin_addr.s_addr的位置。因为这些字节是按照网络字节顺序来定义的,所以不需要端转换函数。
7 计算地址结构的大小。
在这里我们可以看出网络地址一个确定的长度。如果我们回顾一下上一个例子,我们就可以很容易的看出来。然而,我们要记得AF_LOCAL的地址长度是变化的。对于AF_INET的地址,我们只需简单的提供sockaddr_in结构的大小。在C语言中为:
sizeof(struct sockaddr_in)
指定一个X.25地址
套接口接口允许程序员使用在Linux下可用的其他的协议。我们要处理的代码之间的一个主要区别就是套接口是如何编址的。我们已经知道如何初始化一个AF_INET或是AF_LOCAL地址。X.25地址的创建也是类似的。
用来定义X.25协议地址的结构为sockaddr_x25结构。下面的包含语句定义了这个结构:
#include <linux/x25.h>
X.25套接口地址结构如下:
struct sockaddr_x25 {
sa_family_t sx25_family; /* Must be AF_X25 */
x25_address sx25_addr; /* X.121 Address */
};
typedef struct {
char x25_addr[16];
} x25_address;
我们可以注意到有一个sx25_family成员出现在与通用套接口结构相同的前两个字节处。对于这个地址而言,必须为AF_X25。
一个X.25网络地址是由一系列的十进制数组成的。下面的af_x25.c程序用来演示如何创建一个X.25地址。
/*af_x25.c
*
* x.25 Socket Address Example:
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/x25.h>
/*
* This function reports the error and exits back to the shell:
*/
static void bail(const char *on_what)
{
perror(on_what);
exit(1);
}
int main(int argc,char **argv,char **envp)
{
int z; /* Status return code */
int sck_x25; /* Socket */
struct sockaddr_x25 adr_x25; /* AF_X25 */
int len_x25; /* length */
const char x25_host[] /* X.121 addr */
= "79400900";
/* create an AF_X25 socket */
sck_x25 = socket(AF_X25,SOCK_SEQPACKET,0);
if(sck_x25 == -1)
bail("Socket()");
/* Form an AF_X25 Address */
adr_x25.sx25_family = AF_X25;
strcpy(adr_x25.sx25_addr.x25_addr,x25_host);
len_x25 = sizeof adr_x25;
/* Bind the address to the socket */
z = bind(sck_x25,(struct sockaddr *)&adr_x25,len_x25);
if(z == -1)
bail("bind()");
puts("X.25 SOCKETS :");
system("cat /proc/net/x25");
return 0;
}
创建地址的代码包含如下的基本步骤:
1 在第29行使用sockaddr_x25结构来定义adr_x25变量。在第30行定义一个int类型的长度变量len_x25。
2 在第31行与第32行定义了一个固定的x25_host[],作为要建立了X.25地址。
3 在第41行将地址簇指定为AF_X25。
4 在第42行将主机地址号拷贝到地址结构中,并且指定了一个结束符。
5 sockaddr_x25结构的长度是在当前Linux实现下使用的正确长度。
注 意,这个程序并没有使用netstat命令。这时因为此时netstat命令并不会报告AF_X25套接口。相反,在这个例子中我们使用cat命令来将 /proc/net/x25的内容拷贝到标准输出。然而为了这个例子能够成功,我们必须将proc文件系统的支持编译进入我们的内核。
程序的运行结果如下:
$ ./af_x25
X.25 SOCKETS :
dest_addr src_addr dev lci st vs vr va t t2 t21 t22 t23 Snd-Q Rcv-Q inode
* 79400900 ??? 000 0 0 0 0 0 3 200 180 180 0 0 104172
$
在Linux下使用最多的地址族为AF_INET。这为一个套接口指定一个IPv4套接口地址,从而使得这个套接口可以通过TCP/IP网络与其他的主机进行通信。定义了sockaddr_in结构的包含头文件是由下面的C语句来进行定义的:
#include <netinet/in.h>
下面的例子是一个用于网络地址的sockaddr_in结构。另外显示了一个in_addr结构,因为sockaddr_in结构会在他的定义中使用这个结构。
struct sockaddr_in {
sa_family_t sin_family; /* Address Family */
uint16_t sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Pad bytes */
};
struct in_addr {
uint32_ t s_ addr; /* Internet address */
};
其成员描述如下:
sin_family成员出现在与通用套接口定义中的sa_family相同的存储位置。sin_family会被初始化为AF_INET。
sin_port成员为这个套接口地址定义了TCP/IP的端口号。这个值必须为网络字节顺序。
sin_addr成员定义为in_addr结构,用于以网络字节顺序存放IP地址。如果我们检测in_addr,我们就会发现他由32位无符号整数组成。
最后,结构的剩余部分由8个字节的成员sin_zero[8]填充为16个字节。
这个地址结构的物理布局如下图所示:
从上图中我们可以看出,sin_port成员使用两个字节,而sin_addr使用4个字节。这两个成员都在其上放置了一个标志用来表明这些值必须为网络字节顺序。
理解网络字节顺序
不同的CPU体系结构对于多个字节的数据,16位,32位或者更多,会有不同的安排方式。最基本的两个字节顺序为:
大端
小端
其他的组合也是可以的,但是我们在这里并不考虑这些情况。下面这个图显示了这两种不同的字节顺序:
上个图所演示的为十进制的数4660,以十六进制表示则为0x1234.这个数值需要用两个字节来表示。从这个图我们可以看到或者我们可以首先放置最重的字节(大端),或者是我们可以首先放置最不重要的字节(小端)。这种选择是非常模糊的,而这最终涉及到CPU的设计。
我们也许已经知道,Intel CPU使用小端字节顺序。其他的CPU,例如Motorola 68000系列使用大端字节顺序。在这里我们要考虑的最重要的事情就是这两种类型的CPU都存在,而且他们要连接到同一个网络。
如果一个Motorola的CPU向网络中写入一个16位的数字,并且为一个Intel CPU接收时会发生什么呢?这些字节将会为Intel CPU进行反序解释,从而这个值看起来就是十六进制的0x3412。
为网络存在的协议,大端字节顺序将在网络上使用。只要通过网络传输的所有消息遵循这个序列,所有软件就可以顺利通信。
这就将我们带回了AF_INET地址族。TCP/IP端口号(sin_port)以及IP地址(sin_addr)必须是网络字节顺序。BSD套接口地址要求作为程序员的我们在格式化地址必须考虑到这一点。
执行端转换
有一些函数提供用来帮助我们简化端转换。需要考虑两个方向的端转换:
主机顺序到网络顺序
网络顺序到主机顺序
主机顺序是指我们的CPU所使用的字节顺序。对于Intel CPU来说是指小端字节顺序。网络字节顺序,正如我们已经了解到的,为大端字节顺序。
同时也有两类转换函数:
短整数(16位)转换
长整数(32位)转换
下面所提供的是转换函数的概要:
#include <netinet/in.h>
unsigned long htonl(unsigned long hostlong);
unsigned short htons(unsigned short hostshort);
unsigned long ntohl(unsigned long netlong);
unsigned short ntohs(unsigned short netshort);
这些函数的使用是很简单的。例如,要将一个短整数转换为网络顺序,我们可以使用下面的代码:
short host_ short = 0x1234;
short netw_short;
netw_short = htons(host_short);
netw_short值将接收转换为网络字节后的合适值。将一个网络字节顺序转换为一个主机顺序也是一样简单的:
host_short = ntohs(netw_short);
初始化一个宽网地址
现 在我们已准备好来创建一个网络地址了。在这里演示的这个例子需要这个地址必须为宽的。这经常是当我们连接到一个远程服务时完成的。这个原因是因为我们的主 机也许会有两个或是多个网卡,每一个网卡有一个不同的IP地址。而且,Linux同时也允许每一个网卡有多个IP地址。当我们指定一个宽的IP地址,我们 允许系统选择到远程服务的路由。内核会在连接建立时确定我们的最终本地套接口地址。
有时我们希望内核为我们赋一个本地端口号,这是通过将sin_port指定为0来做到的。下面的代码演示了如何使用一个宽IP地址与一个宽端口号来初始化一个AF_INET地址。
1: struct sockaddr_in adr_inet;
2: int adr_len;
3:
4: memset(&adr_inet,0,sizeof adr_inet);
5:
6: adr_inet.sin_family = AF_INET;
7: adr_inet.sin_port = ntohs(0);
8: adr_inet.sin_addr.s_addr = ntohl(INADDR_ANY);
9: adr_len = sizeof adr_inet;
描述如上:
1 使用sockaddr_in结构来定义一个adr_inet变量。
2 通过调用memset函数将adr_inet结构清0。
3 通过将AF_INET赋给adr_inet.sin_family来建立地址族。
4 在第7行指定一个宽端口号。注意ntohs函数的使用。值0指明一个宽端口号。
5 在第8行赋一个宽IP地址。注意执行端转换的ntohl函数的使用。
6 地址的尺寸简单的由adr_inet结构的尺寸来计算。
另一个常用的IP地址为127.0.0.1。这是指loopback设备。回环设备允许我们在同一个主机上与另一个进程进行通信。第8行的代码可以用下面的代码为进行替换:
adr_inet.sin_addr.s_addr = ntohl(INADDR_LOOPBACK);
这会通过回环设备来定位我们的主机。
初始化一个指定的网络地址
在前一个部分我们处理了一个简单的AF_INET地址的例子。当我们要在地址中建立一个指定的IP地址事情就会变得更为复杂。下面是一个程序示例:
/*
* af_inet.c
* Establishing a specific AF_INET
* Socket Address
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
/*
* This function reports the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
perror(on_what);
exit(1);
}
int main(int argc,char **argv,char **envp)
{
int z; /* Status return code */
int sck_inet; /* Socket */
struct sockaddr_in adr_inet; /* AF_INET */
int len_inet; /* length */
const unsigned char IPno[] = {
127,0,0,23 /* Local loopback */
};
/* Create an IPv4 Internet Socket */
sck_inet = socket(AF_INET,SOCK_STREAM,0);
if(sck_inet == -1)
bail("socket()");
/*Create an AF_INET address */
memset(&adr_inet,0,sizeof adr_inet);
adr_inet.sin_family = AF_INET;
adr_inet.sin_port = htons(9000);
memcpy(&adr_inet.sin_addr.s_addr,IPno,4);
len_inet = sizeof adr_inet;
/* Now bind the address to the socket */
z = bind(sck_inet,(struct sockaddr *)&adr_inet,len_inet);
if(z == -1)
bail("bind()");
/* Display all of our bound sockets */
system("netstat -pa --tcp 2>/dev/null | "
"sed -n '1,/^Proto/P;/af_inet/P'");
close(sck_inet);
return 0;
}
在这个程序所用的步骤是我们前面的例子程序相同。然而43到48行还需要一些解释:
1 在第30行使用sockaddr_in结构来定一个名为adr_inet的变量名。另外,在第31行将套接口地址的长度定义为一个整数len_inet。
2 在第32行与第33行定义了一个无符号字符数组IPno[4]。在这里指定了一个IP地址:127.0.0.23。
3 在第43行将adr_inet结构清0。
4 在第45行建立了AF_INET地址族。
5 在这个例子中在第46行选择TCP/IP的9000端口建立连接。在这里我们要注意在第46行htons函数的用法。
6 在第47行将字符数组IPno[4]拷贝到adr_inet.sin_addr.s_addr的位置。因为这些字节是按照网络字节顺序来定义的,所以不需要端转换函数。
7 计算地址结构的大小。
在这里我们可以看出网络地址一个确定的长度。如果我们回顾一下上一个例子,我们就可以很容易的看出来。然而,我们要记得AF_LOCAL的地址长度是变化的。对于AF_INET的地址,我们只需简单的提供sockaddr_in结构的大小。在C语言中为:
sizeof(struct sockaddr_in)
指定一个X.25地址
套接口接口允许程序员使用在Linux下可用的其他的协议。我们要处理的代码之间的一个主要区别就是套接口是如何编址的。我们已经知道如何初始化一个AF_INET或是AF_LOCAL地址。X.25地址的创建也是类似的。
用来定义X.25协议地址的结构为sockaddr_x25结构。下面的包含语句定义了这个结构:
#include <linux/x25.h>
X.25套接口地址结构如下:
struct sockaddr_x25 {
sa_family_t sx25_family; /* Must be AF_X25 */
x25_address sx25_addr; /* X.121 Address */
};
typedef struct {
char x25_addr[16];
} x25_address;
我们可以注意到有一个sx25_family成员出现在与通用套接口结构相同的前两个字节处。对于这个地址而言,必须为AF_X25。
一个X.25网络地址是由一系列的十进制数组成的。下面的af_x25.c程序用来演示如何创建一个X.25地址。
/*af_x25.c
*
* x.25 Socket Address Example:
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/x25.h>
/*
* This function reports the error and exits back to the shell:
*/
static void bail(const char *on_what)
{
perror(on_what);
exit(1);
}
int main(int argc,char **argv,char **envp)
{
int z; /* Status return code */
int sck_x25; /* Socket */
struct sockaddr_x25 adr_x25; /* AF_X25 */
int len_x25; /* length */
const char x25_host[] /* X.121 addr */
= "79400900";
/* create an AF_X25 socket */
sck_x25 = socket(AF_X25,SOCK_SEQPACKET,0);
if(sck_x25 == -1)
bail("Socket()");
/* Form an AF_X25 Address */
adr_x25.sx25_family = AF_X25;
strcpy(adr_x25.sx25_addr.x25_addr,x25_host);
len_x25 = sizeof adr_x25;
/* Bind the address to the socket */
z = bind(sck_x25,(struct sockaddr *)&adr_x25,len_x25);
if(z == -1)
bail("bind()");
puts("X.25 SOCKETS :");
system("cat /proc/net/x25");
return 0;
}
创建地址的代码包含如下的基本步骤:
1 在第29行使用sockaddr_x25结构来定义adr_x25变量。在第30行定义一个int类型的长度变量len_x25。
2 在第31行与第32行定义了一个固定的x25_host[],作为要建立了X.25地址。
3 在第41行将地址簇指定为AF_X25。
4 在第42行将主机地址号拷贝到地址结构中,并且指定了一个结束符。
5 sockaddr_x25结构的长度是在当前Linux实现下使用的正确长度。
注 意,这个程序并没有使用netstat命令。这时因为此时netstat命令并不会报告AF_X25套接口。相反,在这个例子中我们使用cat命令来将 /proc/net/x25的内容拷贝到标准输出。然而为了这个例子能够成功,我们必须将proc文件系统的支持编译进入我们的内核。
程序的运行结果如下:
$ ./af_x25
X.25 SOCKETS :
dest_addr src_addr dev lci st vs vr va t t2 t21 t22 t23 Snd-Q Rcv-Q inode
* 79400900 ??? 000 0 0 0 0 0 3 200 180 180 0 0 104172
$