首页 > 其他 > 详细

apue和unp的学习之旅03——套接字地址学习

时间:2014-03-24 14:57:44      阅读:419      评论:0      收藏:0      [点我收藏+]

//-----------------------------1.套接字地址结构-----------------------------

大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。每个协议都定义它自己的套接字地址结构,这些结构的名字均以sockaddr_开头,并以对应每个协议族的唯一后缀结尾。

IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在<netinnet/in.h>.

struct in_addr {

   in_addr_t s_addr;    // 无符号32位IPv4地址,保存在这个地址结构里的字节都是网络字节顺序

}

struct sockaddr_in{

    uint8_t          sin_len;      // 无符号的8位整数,unsigned short

    sa_family_t      sin_family;   // AF_INET4,套接字地址结构的地址族

    in_port_t        sin_port;     // 16位无符号整数类型,保存在这个端口字段里的字节都是网络字节顺序

    struct in_addr   sin_addr;     // 

    char             sin_zero[8];  // 保留不用

};

// tip:刚开始一看,很容易类型和成员之间会混淆,即不知道哪些是成员变量,哪些是成员类型,要记住的话也是有方法的,比如_t结尾的表示type,自然就是类型了,另外以s_开头和sin_开头的都是变量了。

套接字地址结构仅仅在给定主机上使用,虽然结构中的某些字段(ip地址和port号)用在不同主机之间的通信中,但是结构本身并不在主机之间传递。


//------------------------------2.通用套接字地址结构---------------------------------

简单地说套接字函数api,比如bind,connect等,它们可以传进多种套接字地址结构参数,本来要是在ANSI C里可以用void* 指针作为api的接口简单声明的,但是套接字api在ANSIC出现之前定义的,于是采用了个类似C++的多态的机制,抽象出一个"基类"----通用套接字地址结构sockaddr,定义在<sys/socket.h>如下:

struct sockaddr{

    uint8_t     sa_len;

    sa_family_t sa_family;

    char        sa_data[14];

};

因为C编译器本身是对结构类型敏感的,这就要求对这些函数的任何调用都必须要将指向特定于协议的套接字地址结构的指针进行类型强制转换(casting),变成通用套接字地址结构。这样子实现就实现了协议的无关性。但从内核的角度看,内核必须取调用者的指针,强制转换成struct sockaddr* 类型,然后检查其中的sa_family字段的值来确定这个结构的真实类型。要是使用void* 类型,那么就无法取到sa_family字段的值了,所以不用void*这个类型。


// ------------------------------3.IPv6套接字地址结构------------------------------

struct in6_addr {

     uint_t  s6_addr[16];     //   128位IPv6地址,保存在这个地址结构里的字节都是网络字节顺序

}

#define SIN6_LEN              // 如果系统支持套接字地址结构中的长度字段,那么SIN6_LEN常值必须定义

struct sockaddr_in6 {

     uint8_t          sin6_len;

     sa_family_t      sin6_family;      // AF_INET6

     in_port_t        sin6_port;

     uint32_t         sin6_flowinfo;

     struct  in6_addr sin6_addr;        

     uint32_t         sin6_scope_id;

};

这么多字段的顺序不是乱来的,设计者做过编排,使得如果sockaddr_in6结构本身是64位对齐的,那么128位的sin6_addr字段也是64位对齐的。在一些64位处理机上,如果64位数据存储在某个64位边界位置,那么对它的访问将得到优化。

 

// ------------------------------4.新的通用套接字地址结构------------------------------

作为IPv6套接字API的一部分而定义的新的通用套接字地址结构克服了现有struct sockaddr的一些缺点,不像struct sockaddr,新的struct sockaddr_storage足以容纳系统所支持的任何套接字地址结构,sockaddr_storage结构在<netinnet/in.h>头文件中定义。

struct sockaddr_storage

{

    uint8_t      ss_len;

    sa_family_t   ss_family;

};

特点:

1.如果系统支持的任何套接字地址结构有对齐需要,那么sockaddr_storage能够满足最苛刻的对齐要求。

2.sockaddr_storage足够大,能够容纳系统支持的任何套接字地址结构。

3.除了ss_family和ss_len外,sockaddr_storage结构中的其他字段对用户来说是透明的。sockaddr_storage 结构必须类型强制转换成或复制到适合于ss_family字段所给出地址类型的套接字地址结构中,才能访问其他字段。


//---------------------------------5.学习值-结果参数-----------------------------------

1.从进程到内核传递套接字地址结构的函数有3个:bind,connect,sendto。

struct sockaddr_in serv;

connect(sockfd, (SA*)&serv, sizeof(serv));

既然指针和指针所指的内容大小都传递给了内核,于是内核知道到底需要从进程复制到多少数据进来.

2.从内核到进程传递套接字地址结构的函数有4个:accept, recvfrom, getsockname和getpeername

struct sockaddr_un cli;      // Unix domain

socklen_t          len;


len = sizeof(cli);

getpeername(unixfd, (SA*)&cli, &len);

把套接字地址结构大小(第三个参数)从一个整数改为指向某个整数变量的指针,其原因在于:当函数被调用时,结构大小是一个值,它告诉内核该结构的大小,这样内核在写该结构时不至于越界;当函数返回时,结构大小又是一个结果,它告诉进程内核在该结构中究竟存储了多少信息。这种类型的参数称为值-结果参数。如果

套接字地址结构是固定长度的,那么从内核返回的值总是那个固定长度,例如IPv4的sockaddr_in的长度是16,IPv6的sockaddr_in6长度是28,然而对于可变长度的套接字地址结构(例如Unix域的sockaddr_un),返回值可能小于该结构的最大长度。


//-------------------------------6.大端和小端--------------------------------

考虑一个16位整数,它有2字节组成,内存保存这2个字节有2种方法:一种是将低序字节存储在起始地址,这称为小端字节,另一种是将高序字节存储在起始地址,这称为大端字节序。

举例来说,在每个TCP分解中都有16位的端口号和32位的IPv4地址,发送协议栈和接收协议栈必须就这些多字节字段各个字节的传送顺序达成一致,网际协议使用大端字节序来传送这些多字节整数。

术语"大端","小端"表示多个字节值的哪一端(小端或大端)存储在该值的起始地址。

我们把某个给定系统所用的字节序称为主机字节序。

// 测试主机使用的字节序的方法:
int main(int argc, char** argv)
{
   union{
        short s;                        // 2字节
        char  c[sizeof(short)];         // 2字节
   } un;                                // 定义一个联合,变量名为un
   un.s = 0x0102;
   printf("%s:",CPU_VENDOR_OS);         // 输出标识CPU类型,厂家和操作系统版本
   if(sizeof(short) == 2)
   {
       if(un.c[0] == 1 && un.c[1] == 2) // 低地址c[0]保存的是低序字节01,那么是小端
       {
            printf("big-endian");
       }
       else if(un.c[0] == 2 && un.c[1] == 1)// 低地址c[0]保存的是低序字节02,那么是大端
       {
            printf("little-endian");
       }
       else
       {
            printf("unknown\n");
       }
   }
   else  
   {
       ;
   }
   exit(0);
}

// -----------------------------7.字节操纵函数------------------------------

操纵多字节字段的函数有2组,它们既不对数据作解释,也不假设数据是以空字符结束的C字符串。当处理套接字地址结构时,我们需要这些类型的函数,因为我们需要操纵诸如IP地址这样的字段,这些字段可能包含值为0的字节,却并不是C字符串。以空字符结尾的C字符串是由在<string.h>头文件中定义,名字以str(表示字符串)开头的函数处理的。

名字以b(表示字节)开头的第一组函数起源于Berkeley的函数,比如bzero,bcopy,bcmp.

#include <strings.h>
void bzero(void* dest, size_t nbytes);    // 把目标字节串指定数目的字节置为0.

void bcopy(const void* src, void* dest, size_t nbytes);// 将指定数目的字节从源字节串移到目标字节串

void bcmp(const void *ptr1, const void * ptr2, size_t nbytes);// 比较任意2个字节串,相等返回0,否则<0或>0


#include <string.h>
void* memset(void* dest, int c, size_t len);     // 把目标字节串指定数目的字节置为值c, 而不一定是0
void* memcpy(void* dest, const void* src, size_t nbytes);  // 类似bcopy,不过顺序相反,dest = src;当源字节串与目标字节传重叠时,bcopy能够正确处理,但是memcpy操作结果却不可知,此时就得靠memmove函数了
void* memcpy(const void* ptr1, const void* ptr2, size_t nbytes);

http://www.cnblogs.com/kekec/archive/2011/07/22/2114107.html里有memcpy和memmove的实现上的区别。


//------------------------------8.旧的地址转换函数---------------------------------

#include<arpa/inet.h>
int inet_aton(const char* strptr, struct in_addr* addptr);
in_addr_t inet_addr(const char* strptr);
char* inet_ntoa(struct in_addr inaddr);

如今inet_addr已经被废弃,新的代码改用inet_aton函数,更好的办法使用支持IPv4和IPv6的inet_pton

inet_ntoa函数将一个32位的网络字节序二进制IPv4地址转换成点分十进制数串,由该函数返回值所指向的字符串驻留在静态内存中,这意味着该函数不可重入,另外,该函数以一个结构体作为参数,而不是结构体指针。


// -----------------------------9.新的地址转换函数---------------------------------

p代表表达式(presentation),地址表达格式通常是ASCII字符串, n代表数值(numeric),数值格式则是存放到套接字地址结构中的二进制值。

#include <arpa/inet.h>
int inet_pton(int family, const char* strptr, void* addrptr);// 若成功则返回1,输入无效表达式则为0,若出错则为-1
const char* inet_ntop(int family, const void* addrptr, char* strptr, size_t len);// 返回值若成功则为指向结果的指针,若出错则为NULL

family参数可以是AF_INET,也可以是AF_INET6,如果以不被支持的地址族作为参数,那么这2个函数返回错误,并把errno置为EAFNOSUPPORT

len参数是目标存储单元的大小,以免该函数溢出调用者的缓冲区,为有助于指定这个大小,在<netinet/in.h>头文件中有如下定义:

#define INET_ADDRSTRLRN 16

#define INET6_ADDRSTRLRN 46

如果len太小,不足以容纳表达格式的结果(包括结尾的空字符),那么返回一个空指针,并置errno为ENOSPC。

inet_ntop函数的strptr参数不可以是以空指针,调用者必须为目标存储单元分配内存并指定其大小,调用成功时,这个指针就是该函数的返回值。

IPv4上的使用方法举例:

inet_pton(AF_INET, cp, &foo.sin_addr);

char str[INET_ADDRSTRLEN];
ptr = inet_ntop(AF_INET, &foo.sin_addr, str, sizeof(str));













apue和unp的学习之旅03——套接字地址学习,布布扣,bubuko.com

apue和unp的学习之旅03——套接字地址学习

原文:http://blog.csdn.net/qxbailv15/article/details/21869095

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!