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

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_in
220 {
221 __SOCKADDR_COMMON (sin_);
222 in_port_t sin_port; /* Port number. */
223 struct in_addr sin_addr; /* Internet address. */
224
225 /* 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_in6
234 {
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");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值