unix网络编程第一卷——chapter 3

来源:互联网 发布:淘宝开店卖游戏币 编辑:程序博客网 时间:2024/05/30 04:32

本章的内容:套接字结构,字节排序函数,字节操作函数,地址转换函数

网络编程中的各种套接字联网API的操作对象都是套接字结构。

这些结构可以在两个方向上传播,从进程到内核,从内核到进程。

特别注意的是:从内核到进程方向的传递是值--结果参数的一个例子,即传递变量地址。比如套接字结构大小这个参数,有时候传递大小这个值,有时候传递存储这个值的指针。这是因为:当函数调用时,结构大小是一个值,告诉内核该结构的大小,这样内核在写该结构时,就不会越界;同时函数返回时,结构大小又是一个结果,内核告诉进程内核在该结构中到底存储了多少信息。当然,对于套接字地址结构是固定长度的,那么从内核返回的结果与传递的值是一样的。例如sockaddr_in的大小是16.然而对于可变长度的套接字地址结构(例如unix域的),那么返回值就可能小于该结构的最长长度。同时,我们也就明白了,为什么需要设置一个长度字段,因为有的套接字地址结构的长度是不确定的,事先需要先分配足够大小的空间,内核完成该结构后,才确定真是的长度。当然,我们在其他函数也可以碰到值--结果参数的例子,例如select,getsockopt等等(总之,socket联网API大量使用了值--结构参数)。

  总之值--结果参数的作用是:函数调用时先使用,调用完成时,作为一个结果返回。很明显这个参数是一个指针或者引用。

套接字地址结构:

套接字结构定义是有规范的。要遵循POSIX(portable operation system interface)。http://baike.baidu.com/link?url=Dwb8iIJVVjvpLtjlCKEmPjOuJMJecLMVjYKqB45evodI20O-52bX4LegJTVGIgPI(该标准定义了操作系统(主要是unix操作心系统)为应用程序提供的标准接口,希望获得源代码级别的程序可移植性)
规定套接字地址结构只需要3个字段:sin_family,sin_addr, sin_port。各种协议族的套接字地址结构可以在此基础上进行扩展。我们常用的ipv4套接字结构就进行了扩展
struct in_addr
{
in_addr_t  s_addr;
}
struct sockaddr_in
{
uint8_tsin_len;
sa_family_tsin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char   sin_zero[8];
};

对该结构肯定存在很多疑惑:

疑惑1:就是为什么struct in_addr 里面只有一个成员,却还是一个结构体,显得多此一举,这是历史原因造成的,历史上ipv4存在a/b/c三类地址,随着子网技术的发张,这种概念就消失了,那个时候问了方便访问ip地址某个字节,设计了一种联合体。后来就干脆取消了其他两种结构,留下一个网络字节序的32位无符号数。从两种第一可以看出来,后来的设计兼容了原始设计,无论系统使用哪种就够,访问sin_addr.s_addr都可以得到ip地址的32位无符号数。

structin_addr
{
    union
    {
        struct
        {
            u_char s_b1,s_b2,s_b3,s_b4;
        } S_un_b; //An IPv4 address formatted as four u_chars.
        struct
        {
            u_short s_w1,s_w2;
        } S_un_w; //An IPv4 address formatted as two u_shorts
       u_long S_addr;//An IPv4 address formatted as a u_long
    } S_un;
#define s_addr S_un.S_addr
};

疑惑2长度字段没有被初始化,只是初始化了Port和ip 和协议种类。原因是一些套接字函数用长度参数完成了套接字结构里面长度成员变量的赋值。而不是由用户自己对套接字结构进行初始化时初始化长度字段,用户只需要初始化协议字段,Port 和ip地址。除非在使用不定长套接字结构时才会初始化长度字段。套接字结构只在主机内部的进程与内核之间传输,而不会在不同主机上传输,只是传递里面的Port和ip。
疑惑3:为什么存在8个字节没有表示任何信息,并初始化为0.我的理解是为了和通用套接字地址结构大小兼容,通用套接字地址结构大小为16字节。
通用套接字结构
struct sockaddr
{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
}
疑惑4:为什么要使用通用套接字结构,套接字函数需要接受指向各种不同类型的套接字地址结构的指针,不可能为每个版本的套接字结构设计相应的函数,c提供了解决方案,可以使用(void *),但是遗憾的是;socket函数设计在ansi c之前,所以那时就设计一个通用套接字地址结构,来接受各种不同的套接字结构,这个地址的作用在程序员看来,就是在对指向其他类型套接字地址的指针进行强制转换,以便socket函数使用。

字节排序函数

另外:对于大于一个字节的成员采用的是网络字节序存储(大端字节序)。为什么要规定一个网络字节序呢。,考虑一个32位整数,由4个字节组成,那么他们就存在一个存储顺序的问题:到底是在

存储该值的起始地址存储低序字节还是高序字节。这没有统一的标准。不同的系统采用不同的顺序。我们称:在低地址存储低序字节为小端字节序,在低地址存储高序字节为大端存储,采用不同字节序的系统之间传输多字节数据会发生对同一个数据做不同的解释。所以网络协议必须指定一个网络字节序,即为大端字节序,所以主机传送数据时需要进行字节顺序准换。
这里有几个函数需要记住:htonl(); ntohl(); htons();ntohs(); 注意:h:host, n:network, l表示long int ,s short。但是不同的机器,不同的编译器对long,short的理解不一样,这里long表示32位bit,short表示16位bit。


字节处理函数:

因为要对套接字结构里的字节穿进行初始化或者赋值,所以需要一些字节处理函数,brekeley提供了一些函数(bzero(), bcopy(), bcmp()),因为自己不会用到,不介绍了。在ANSI C标准中提供了两组字符串处理函数,一类处理以‘\0’结束的字符串函数:以str开头,strlen(),strcpy(),strcmp(),strcat();但是在操作套接字地址结构的字段时,我们会遇到为0的字节,但却不是c字符串,例如ip地址。ANSI C提供了以mem(memory)开头的函数:memset(), memcpy(); memcmp();

地址转换函数

先介绍名字:Inet_aton();inet_ntoa(), inet_addr();
inet:internet, a :ascii, n :numeric;
所以:inet_aton:表示将“192.168.1.1”类的c字符串转换成网络字节序的32位bit的无符号整数。inet_ntoa:将网络字节序的二进制ipv4地址转换成点分十进制数串。
inet_addr和Inet_aton的功能一样,都是将点分十进制数串转换成网络字节序的32位二进制ipv4地址。前者采用返回值的方式返回结果,后者采用值--结果参数返回结果。而且Inet_addr()还存在一个缺陷,就是函数出错时,就会返回一个INADDR_NONE常值(通常是一个32位均为1的值),这是一个有效ip值:255.255.255.255(有限广播地址)该函数已被弃用了。
随着ipv6的出现,有两种新的函数可以转换ipv4和ipv6的地址,Inet_pton(int family const char * strptr, void *addrptr), inet_ntop(int family, void * addrptr, char * strptr,size_t len);


0 0