ECHO网络程序的演变史 (1) --- Socket地址

    技术2025-05-31  15

    Andrew Huang bluedrum@163.com 转载请注明作者及联络方式.       网络Socket编程的主要技能,可能通Echo程序可以不断演进来描述网络开发的各个细节。 Echo程序是指在一个C/S模型中,当客户端通过IP网络向服务器服务器发送一个字符串,服务器再原样把字符串发送出来。此谓Echo.   在应用一级进行编程主要采用Socket库,而且Socket库的基本技能是处理Socket地址。     一.Socket地址的表示 ----------------------------------------------------------------------------    首先当年BSD Socket设计时,当时已经有多种网络并存,而且BSD Socket 也希望未来统一新的网络。 因此它设计了一个通用的网络地址结构struct sockaddr来代表所有网络的地址。       因此所有的SOCKET的库的接口的地址都采用struct sockaddr 地址的传输。 以下是sockaddr地址结构,在Linux下它定义在 /usr/include/linux/socket.h     

    struct sockaddr {      unsigned short sa_family; /* 地址族, AF_xxx */     char sa_data[14]; /* 14 字节的协议地址 */           };

           sa_family 的地址类型,参见有 AF_INET (ipv4) ,AF_INET6 (ipv6)     但是socket还是要在具体网络上运行。因为在处理具体网络地址时,还是转换成相应网络的地址结构进行处理。 ipv4的地址结构是 struct sockaddr_in,而ipv6的地址结构是 struct sockaddr_in6.这里的in是inetnet的缩写    以ipv4为例,它的在Linux下定义在 /usr/include/netinet/in.h   数据结构定义如下,其中最后的sin_zero是为了使数据结构的宽度正好与struct sockaddr 一致,特意补上去的。

    218 /* Structure describing an Internet socket address. */219 struct sockaddr_in220 {221 __SOCKADDR_COMMON (sin_);222 in_port_t sin_port; /* Port number. */223 struct in_addr sin_addr; /* Internet address. */224225 /* Pad to size of `struct sockaddr'. */226 unsigned char sin_zero[sizeof (struct sockaddr) -227 __SOCKADDR_COMMON_SIZE -228 sizeof (in_port_t) -229 sizeof (struct in_addr)];230 };

    这是sin_port是网络序的端口号.而sin_addr的数据结据 struct in_addr定义如下。它实际上一个以big-endian的整数。

    /* Type to represent a port. */typedef uint16_t in_port_t; struct in_addr {        unsigned long s_addr;};

      因此一个ipv4的地址,struct sockaddr_in正常工作,必须设三个成员值.sa_family,sin_port和sin_port.s_addr. 但所有socket接口成员函数都采用struct sockaddr * 定义,因此每一次操作都做指针转换。这是让初学者容易感到不解的地方。

    ipv6的地址定义类似,只不过成员名发生变化

    /* Ditto, for IPv6. */233 struct sockaddr_in6234 {235 __SOCKADDR_COMMON (sin6_);236 in_port_t sin6_port; /* Transport layer port # */237 uint32_t sin6_flowinfo; /* IPv6 flow information */238 struct in6_addr sin6_addr; /* IPv6 address */239 uint32_t sin6_scope_id; /* IPv6 scope-id */240 };

    二.socket地址的转换

    ---------------------------------------------------------------------

     对于ip地址,人类更为习惯的是点分法的形式 ,即"192.168.1.100"这种表示法。但是编程中需要变换成 struct sockaddr 的形式来。因此需要一些转换函数。

    1.把点分法地址转换成in_addr地址

      int inet_aton(const char *cp, struct in_addr *inp);

       cp是点分法地址字符串,inp是转换成的地址 ,成功返回0,失败返回-1

    2.把点分法地址转换成in_addr地址2

       in_addr_t inet_addr(const char *cp);

        功能同inet_aton,但是把转换后的地址直接作为返回值返回.如果失败,返回 INADDR_NONE (即-1).

    3.把in_addr地址转换成点分法地址

       char *inet_ntoa(struct in_addr in);

     把in转换成点分地址

    4.支持ipv6的地址转换

       int inet_pton(int af, const char *src, void *dst); 

      将协议点分地址转换成相应的地址结构,af是地址族的类型.AF_INET/AF_INET6

        const char *inet_ntop(int af, const void *src,                             char *dst, socklen_t cnt);

       将地址结构转换成点分地址

     5. 网络序/本机序互相转换

          网络序是big-endian数字表示,而本机序取决于CPU自身定义  

          #include <arpa/inet.h>

           uint32_t htonl(uint32_t hostlong);

           uint16_t htons(uint16_t hostshort);

           uint32_t ntohl(uint32_t netlong);

           uint16_t ntohs(uint16_t netshort);

    ipv4的socket_in地址标准设置  

    struct sockaddr_in addr;     memset(&addr,0,sizeof(addr));     //表示 127.0.0.1:2000端口     addr.sin_family = AF_INET ; /* 表示IPV4 */     addr.sin_port = htons(2000); /* 发送前转为网络序 */     addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    三.域名与ip地址的转换

    ----------------------------------------------------

      很多用户习惯于采用容易记忆的域名来访问远端机器.但Socket只采用数字形式的IP地址来访问 .因此必须一个函数来完成这一转换,以便程序界面更加友好. 如ie的地址栏,就是IE自行在内部把域名作了转换,后然后使用IP地址去联接.

      不同的操作系统采用不同函数来作域名转换.Linux/Windows采用gethostbyname()作域名转换.

       gethostbyname()首先会去检查 /etc/hosts这个文件,如果对应的ip与域名在这个文件里有记录。如果没有记录,则会发送DNS解析包给DNS服务器,请求解析。如果成功,它将把返回结果写在struct hostent结构当中。

      struct hostent *gethostbyname(const char *name);其中hostent的定义如下

    struct hostent {                      char *h_name; /* official name of host */                      char **h_aliases; /* alias list */                      int h_addrtype; /* host address type */                      int h_length; /* length of address */                      char **h_addr_list; /* list of addresses */              }              #define h_addr h_addr_list[0] /* for backward compatibility */

    h_name是主机名(域名),h_addr_type地址类型,h_addr_list是域名对应的一系列的地址的列表,注意里面采用整数数组形式,4byte为单位,它的总长度是h_lenght.

    以下是通用的解析代码,可以识别出ip和域名

     

    /* 域名处理 */int GetIpByHost(char * host_name,char * ip,int ip_len){   struct in_addr addr;   struct hostent * ent;/* 把host_name当成ip地址试着转换一次 */    if(inet_aton(host_name,&addr))     { /* 说明点分法表示的IP地址 */        snprintf(ip,ip_len,"%s",inet_ntoa(addr));         return 0;     }    //否则将其当成域名来处理    ent = gethostbyname(host_name);    if(ent == NULL)       {           fprintf(stderr,"error host name %s/n",host_name);          return -1;       }     // printf("h_name %s/n",ent->h_name);      if(ent->h_length<=0)         {             return -2;         }    {     struct sockaddr_in dest;//把h_addr_list 当成socket in地址     memcpy(&(dest.sin_addr), ent->h_addr, ent->h_length);     dest.sin_family = ent->h_addrtype;     snprintf(ip,ip_len,"%s", inet_ntoa(dest.sin_addr));      }     return 0;     } #define RESOLVE_HOST(s) {/    char buf[32];/     if(GetIpByHost(s,buf,sizeof(buf))==0) /         printf("host %s addr is %s/n",s,buf);}void test3(){  RESOLVE_HOST("192.168.0000.1");  RESOLVE_HOST("www.icbc.com.cn");  RESOLVE_HOST("www.baidu.com");  RESOLVE_HOST("ww22.asdfasfasdfasdf.com");} 

    最新回复(0)