Python 中如何解析 Struct sockaddr_in 这种复杂结构


               
v\:* {behavior:url(#default#VML);}
o\:* {behavior:url(#default#VML);}
w\:* {behavior:url(#default#VML);}
.shape {behavior:url(#default#VML);}

  Normal
  0
  
  
  
  7.8 磅
  0
  2
  
  false
  false
  false
  
  EN-US
  ZH-CN
  X-NONE
  
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
  
  MicrosoftInternetExplorer4
  
   
   
   
   
   
   
   
   
   
   
   
  

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

/* Style Definitions */
table.MsoNormalTable
        {mso-style-name:普通表格;
        mso-tstyle-rowband-size:0;
        mso-tstyle-colband-size:0;
        mso-style-noshow:yes;
        mso-style-priority:99;
        mso-style-qformat:yes;
        mso-style-parent:"";
        mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
        mso-para-margin:0cm;
        mso-para-margin-bottom:.0001pt;
        mso-pagination:widow-orphan;
        font-size:10.5pt;
        mso-bidi-font-size:11.0pt;
        font-family:"Calibri","sans-serif";
        mso-ascii-font-family:Calibri;
        mso-ascii-theme-font:minor-latin;
        mso-fareast-font-family:宋体;
        mso-fareast-theme-font:minor-fareast;
        mso-hansi-font-family:Calibri;
        mso-hansi-theme-font:minor-latin;
        mso-bidi-font-family:"Times New Roman";
        mso-bidi-theme-font:minor-bidi;
        mso-font-kerning:1.0pt;}
目录
 TOC \o "1-3" \h \z \u Python 中如何解析 Struct
sockaddr_in 结构...  PAGEREF _Toc207807283 \h 1
08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003200300037003800300037003200380033000000
问题的引出...  PAGEREF _Toc207807284 \h 1
08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003200300037003800300037003200380034000000
服务器端程序...  PAGEREF _Toc207807285 \h 2
08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003200300037003800300037003200380035000000
客户端的程序:用C实现的...  PAGEREF _Toc207807286 \h 4
08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003200300037003800300037003200380036000000
程序中遇到的问题及其解决...  PAGEREF _Toc207807287 \h 6
08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003200300037003800300037003200380037000000
附1: python和c语言中的padding.  PAGEREF _Toc207807288 \h 7
08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003200300037003800300037003200380038000000
附2: ip地址和整数间的转换...  PAGEREF _Toc207807289 \h 9
08D0C9EA79F9BACE118C8200AA004BA90B02000000080000000E0000005F0054006F0063003200300037003800300037003200380039000000


Python 中如何解析 Struct sockaddr_in 结构
问题的引出
看了很多地方的资料,关于Python与C语言struct之间的转换问题。对于基本的c语言struct在Python下面直接可以利用struct.unpack(fmt, s)便可以解析出来。但一些复杂的struct像系统的struct sockaddr_in 以及struct 里面包含struct的结构又该如何做?
首先查看python2.5的文档,关于struct的类型:

  
   
   Format
   
   
   C Type
   
   
   Python
   
   
   Notes
   
  


  
  x
  
  
  pad byte
  
  
  no value
  
  


  
  c
  
  
  char
  
  
  string of length 1
  
  


  
  b
  
  
  signed char
  
  
  integer
  
  


  
  B
  
  
  unsigned char
  
  
  integer
  
  


  
  h
  
  
  short
  
  
  integer
  
  


  
  H
  
  
  unsigned short
  
  
  integer
  
  


  
  i
  
  
  int
  
  
  integer
  
  


  
  I
  
  
  unsigned int
  
  
  long
  
  


  
  l
  
  
  long
  
  
  integer
  
  


  
  L
  
  
  unsigned long
  
  
  long
  
  


  
  q
  
  
  long long
  
  
  long
  
  
  (1)
  


  
  Q
  
  
  unsigned long long
  
  
  long
  
  
  (1)
  


  
  f
  
  
  float
  
  
  float
  
  


  
  d
  
  
  double
  
  
  float
  
  


  
  s
  
  
  char[]
  
  
  string
  
  


  
  p
  
  
  char[]
  
  
  string
  
  


  
  P
  
  
  void *
  
  
  integer
  
  


  
  
  
  

图1. Python与C数据类型对照关系

这里面没有关于struct sockaddr_in这样的结构。后来一想,c语言中的struct的存放其实就是连续存放的基本数据类型(padding暂不谈)。Struct无非就是给他们归个类而已。那么我只要知道struct
sockaddr_in的内部的具体数据类型,不就可以unpack了么?
接下来就是查找sockaddr_in的具体类型。因为我有《UNIX网络编程》第一卷这本书,所以就直接翻书。上面写的是如果的POSIX定义,具体定义在

  
  struct
  in_addr {
  in_addr_t  s_addr;            /* 32-bit IPv4 address */
                              /* network byte
  ordered */
  };
   
  struct
  sockaddr_in {
  uint8_t       sin_len;             /* length of structure (16) */
  sa_family_t    sin_family;          /* AF_INET */
  in_port_t      sin_port;           /* 16-bit TCP or UDP port number*/
                                  /* network
  byte ordered*/
  struct  in_addr  
  sin_addr;        /* 32-bit IPv4
  address */
                                 /*network  byte ordered */
  char  sin_zero[8];                /*unused*/
  };
  

            图2.    Stuct sockaddr_in的POSIX定义

OK, 这里面又出现了很多别名,好那么我全给你查出来,这个可以直接google搜索,也可以在系统include目录下找。至于哪个快点就看各自的熟悉程度了。
我找的结果如下:

  
   
  typedef   unsigned int   in_addr_t
   
  typedef   unsigned char  uint8_t
   
  typedef   unsigned short  sa_family_t
   
  typedef   unsigned short  in_port_t
  

                  图3  几个数据的别名

Ok, 各种数据类型全面搞定,开始编程:
服务器端程序
服务器端是用python写的,目的是接收c语言发过来的struct sockaddr_in结构的字段。
程序如下:

  
  from socket import *
  from time import time, ctime
   
  Host = ''
  Port = 21567
  BUFSIZ = 1024
  ADDR = (Host,
  Port)
   
  tcpSerSock =
  socket(AF_INET, SOCK_STREAM)
  tcpSerSock.bind(ADDR)
  tcpSerSock.listen(5)
   
  try:
      while 1:
          print 'waiting for connection...'
          tcpCliSock, addr =
  tcpSerSock.accept()
          print '...connected from :' ,addr
   
          while 1:
              data = tcpCliSock.recv(BUFSIZ)
              if not data: break
              print "received :", len(data)
              import struct
              ip = struct.unpack('HHIII0l', data)  #line 24
              print "ip[0] ",ip[0]
              print "ip[1] ",ip[1]
              #netip = struct.unpack('!I', ip[2])
              print "ip[2] ",ip[2]
              
              print "ip[3] ",ip[3]
              print "ip[4] ",ip[4]
              #tcpCliSock.send("hello ack")
              break
              #tcpCliSock.send('[%s] %s' %
              #           (ctime(time()),
  data))
         
            
          tcpCliSock.close()
  except EOFError:
      print 'EOFError  Occur'
  except KeyboardInterrupt:
      print 'KeyboardInterrupt
  catched'
  finally:
  tcpSerSock.close()
   
  运行结果:
  

  

                   图4  python服务器端程序
注:
这个最终的正确程序,请注意我表明的line 24那一行,最开始不是这样的,一会再细解。

客户端的程序:用C实现的

  
  C语言:
临时自用代码@代码发芽网
  01#include
  02#include
  
  03#include
  
  04#include
  
  05#include
  06#include
  
  07#include
  
  08#include
  
  09
  10
  11extern
  int errno;
  12
  13int
  main(int argc, char **argv)
  14{
  15    int   
  sockfd;
  16    struct sockaddr_in  servaddr;
  17
  18    if (argc != 3)
  19      
  fprintf(stderr, "usage: ./tcpcli
  \n");
  20
  21    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) 0) {
  22      
  fprintf(stderr, "socket create error.\n");
  23      
  exit(1);
  24    }   
  25
  26   
  bzero(&servaddr, sizeof(servaddr));
  27    servaddr.sin_family
  = AF_INET;
  28   
  servaddr.sin_port = htons(atoi(argv[2]));
  29    if ( inet_pton(AF_INET, argv[1], &(servaddr.sin_addr)) 0) {
  30        fprintf(stderr, "inet_pton error: %s\n",
  strerror(errno));
  31      
  close(sockfd);
  32      
  return -1;
  
  33    }   
  34
  35    if
  (connect(sockfd, (struct sockaddr
  *)&servaddr, sizeof(servaddr)) != 0) {
  36      
  fprintf(stderr, "connect fail: %s\n",
  strerror(errno));
  37      
  exit(1);
  38    }   
  39
  40    int len = sizeof(struct
  sockaddr_in);
  41    char send[len + 1];
  
  42
  43    memset(send, 0, len+1);
  44    memcpy(send,
  &servaddr, len);
  45   
  46     //unsigned char sin_len = servaddr.sin_len;
  47    unsigned short
  sa_family = servaddr.sin_family;
  48    unsigned short
  sin_port = servaddr.sin_port;
  49    unsigned int
  s_addr = servaddr.sin_addr.s_addr;
  50
  51    printf("%u %u %u\n", sa_family, sin_port,
  s_addr);
  52    int n=0;
  53    n =
  write(sockfd, send, len);
  54    if (n != len) {
  55        printf("write
  error\n");
  56    }
  57
  58    printf("write over\n");
  59    exit(0);
  60}
   
   
  运行结果:
  

  

                    图5. Tcp客户端程序
这里面的46-51行也是正确的。一开始是错误的。

程序中遇到的问题及其解决
最初我46行没加注释,编译的时候没通过。说servaddr没有这个字段。
我就到netinet/in.h里面找sockadd_in的定义,找到是这样的:

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

                       图6

又到linux/in.h下面发现另一个定义:

  
  179 /* Structure describing an Internet
  (IP) socket address. */
  180 #define __SOCK_SIZE__   16              /* sizeof(struct sockaddr)      */
  181 struct sockaddr_in {
  182  
  sa_family_t         
  sin_family;     /* Address
  family               */
  183  
  __be16               
  sin_port;       /* Port
  number                  */
  184  
  struct in_addr      
  sin_addr;       /* Internet
  address             */
  185
  186  
  /* Pad to size of `struct sockaddr'. */
  187  
  unsigned char         __pad[__SOCK_SIZE__
  - sizeof(short int) -
  188                         sizeof(unsigned
  short int) - sizeof(struct in_addr)];
  189 };
  190 #define sin_zero        __pad           /* for BSD UNIX comp. -FvK      */
  

                        图7
我把程序里面的include 换成结果编译报一堆错。我就又改回来了。根据里面的注释发现果然是没有sin_len这个字段。具体我不知为什么?但确实没有。
至于最后一个__pad那是一个填充用的,目的是为了和通用地址结构相同大小。
   好,我一开始是这样unpack的,struct.unpack(‘BHHIc0l’,
buf); 这是严格按照图2和图1以及图3结合对应的。并加上了对齐’0l’,后面是字母’l’表示python 中的long类型对齐。Python中的long类型是4字节的,因为c语言的struct里面进行了对齐。结果解析出来前两项是对的,第3项就相差很远了。后来根据图7重新改成 struct.unpack(‘HHIII’,s)解析正确。
其中第一个’H’对应sin_family。 第二个’H’对应sin_port, 第三个’I’对应sin_addr. 这三个按4字节对齐才8个字节。而struct sockaddr_in的长度是16字节。由于一开始后面的解析出来都是0,所以我想是填充字节,就用了两个’II’凑够16字节。结构经试验,图4和图5的打印结构一致。

至此告一段落,本文后面将附上关于padding的示例:
附1: python和c语言中的padding
以下是转载,原文:
http://borland.mblogger.cn/topcat/posts/25118.aspx
今天老鼠问我一个关于Python的socket传输问题。他说socket需要传输一个C++的struct,但是Python接受到之后却不能使用struct模块正确解开。我就帮他看了看。

该结构大致如下:

struct TestStruct
{
   
int data1;
   
char data2;
   
char data3;
};

对应的Python代码:

import struct
s = struct.unpack("icc",
buf)  #buf是从网络接收的字节流

结果却报“Unpack str size
does not match format”错。

很明显是C++ struct产生的size和Python解码所需的不同。于是检查C++的struct size:

printf ("size=%d\n",
sizeof(TestStruct));

结果得8。



struct.calcsize("icc")

结果却是6。

仔细想想,icc这种排列方式在字段间的确不会产生padding字节,也就是说,python的结果是对的。但为什么C++的结果会是8呢?原来 C++的字节对齐,除了struct内部需要字节对齐之外,struct变量本身也是需要字节对齐的,这是为了当生成一个struct数组的时候仍然能够保证所有字段的字节对齐。因此,像icc这种本身没有padding,但整体需要padding的情形,编译器会在整个struct的末尾加上 padding字节(在这里是2个字节),也正是这2个字节导致了Python的解码错误。

幸亏Python已经考虑到了这个问题,在Python struct module document的最后一段的Hint的中说,如果出现这种末尾对齐的情况,可以在格式字符串的最后加上一个“0X”,其中这个“X”可以是一个有效的格式字符,这个字符所代表的长度等于整个struct对齐的长度。在上面这种情况中,对齐长度是4字节,因此我们使用“l”字母(long, 4字节)来进行对齐:

s = struct.unpack("icc0l", buf)

问题搞定。

额外的讨论

情形1:

struct Test2
{
   
char a1;
   
int    a2;
   
char a3;
};

这个struct的size是12,原因是既有字段间padding又有整体padding。

对应的Python代码是:

s = struct.unpack("cic0l",
buf)  #cic时,Python会自己计算字节对齐。

情形2:

#pragma pack(1)
struct Test2
{
   
char a1;
   
int    a2;
   
char a3;
};
#pragma pack()

在这里,使用pack编译指令强制改变为单字节对齐,其他不变,sizeof(Test2)=6。

对应的Python代码:

s = struct.unpack("=cic",
buf)  #在单字节对齐的情况下,使用=前缀。注意这时“0X”后缀已经没有意义了。

附2: ip地址和整数间的转换
大家或许看到,我们在开始打印出来的ip只是一个很大的整数,如果我想以字符串的形式’192.168.1.235’来查看,又该怎么办? 下面就是解决方案:
http://www.cnblogs.com/thh/archive/2007/07/05/806866.html
def Ip2Int(ip):


    import struct,socket


    return struct.unpack("!I",socket.inet_aton(ip))[0]


此函数从’192.168.1.235’ 可以转换为数字’ 3120670912’,此数字为网络字节序




def Int2Ip(i):


    import socket,struct


    return socket.inet_ntoa(struct.pack("!I",i))
  此函数从网络字节序的数字’’转换为ip

另附一个详细的转换:
http://blog.chinaunix.net/u1/57278/showart_481765.html
>>>
socket.inet_ntoa(struct.pack('I',socket.htonl(16909060)))
'1.2.3.4'
>>> socket.ntohl(struct.unpack("I",socket.inet_aton('1.2.3.4'))[0])
16909060
再加几个:
>>> struct.unpack("I",socket.inet_aton('1.2.3.4'))  
(67305985L,)
>>> socket.ntohl(67305985)
16909060
>>> socket.htonl(16909060)
67305985
>>> struct.unpack('i',socket.inet_aton('1.2.3.4'))
(67305985,)
>>> struct.pack('i',16909060)  
'\x04\x03\x02\x01'
>>> struct.pack('i',67305985)
'\x01\x02\x03\x04'
>>> socket.inet_ntoa(struct.pack('I',socket.ntohl(16909060)))   
'1.2.3.4'
>>> socket.htonl(struct.unpack("I",socket.inet_aton('1.2.3.4'))[0])
16909060
这样很好玩吧~
(1). inet_aton 将ip地址的4段地址分别进行2进制转化,输出用16进制表示:
1.2.3.4 ——inet_aton——> 0000 0001,0000 0010,0000 0011,0000 0100
(2).unpack的处理是按16进制(4bit)将2进制字符,从后向前读入的,低位入,处理成:
00000100 00000011 00000010 00000001
也就是 4 3 2 1
pack也一样,从后向前读入字符,所以——
用16进制表示的1 2 3 4(16909060)打包成 4 3 2 1 的顺序;
用16进制表示的4 3 2 1(67305985)打包成 1 2 3 4 的顺序;
(3) ntohl, htonl 表示的是网络地址和主机地址之间的转换(network byte  host byte)
由于unpack/pack的解/打包的颠倒顺序,必须通过htonl 或者 ntohl 进行处理。
(4) network byte, host byte
这 2个名词折腾半天,host byte 由于处理器的方式不同
little-endian或者big-endian,将ip地址转换的最终输出是不一样的,参考(http://www.informit.com
/articles/article.aspx?p=169505&seqNum=4 )
(http://www.netrino.com/Embedded-Systems/How-To/Big-Endian-Little-Endian)
big-endian: 最高位在左边(内存存储空间的最低位)
little-endian: 最高位在右边(内存存储空间的最低位)
i386-unknown-freebsd4.8: little-endian
powerpc-apple-darwin6.6: big-endian
sparc64-unknown-freebsd5.1: big-endian
powerpc-ibm-aix5.1.0.0: big-endian
hppa1.1-hp-hpux11.11: big-endian
i586-pc-linux-gnu: little-endian
sparc-sun-solaris2.9: big-endian
而为了统一这种传输标准(打个补丁,再创造一个名词函数),又有network byte,这个其实就是标准的big-endian;
由于x86本身的处理属于little-endian,所以上述应该按照标准的network
byte 进行处理,这样可避免cpu造成的不同:
socket.htonl(struct.unpack("I",socket.inet_aton('1.2.3.4'))[0])
socket.inet_ntoa(struct.pack('I',socket.htonl(16909060)))
###############################################################################
(1个64位amd+32位windows, 1个32位intel+linux,
出来的数值是一样的:
>>>
socket.ntohl(struct.unpack("I",socket.inet_aton('220.194.61.32'))[0])
-591250144
>>> socket.inet_ntoa(struct.pack('I',socket.htonl(-591250144)))
'220.194.61.32'
但是64位amd+64位linux,是这样:
>>> socket.ntohl(struct.unpack("I",socket.inet_aton('220.194.61.32'))[0])
3703717152
>>> socket.inet_ntoa(struct.pack('I',socket.htonl(-591250144)))
'220.194.61.32'
>>>
socket.inet_ntoa(struct.pack('I',socket.htonl(3703717152)))         
'220.194.61.32'
unpack/pack和cpu,os无关;
socket.ntohl/htonl的输出结果int型
对32 os,处理成signed 类型,首位1被处理成负数;
对 64位 os,则是unsigned类型;
(晕了半天,那个值才明白是位数,和cpu不相干)
###############################################################################
(5)mysql里面的函数是inet_aton,
inet_ntoa 和python的socket不同,直接实现ip string到network byte的转换,python里面只能实现ip地址到network byte的2进制转换:
mysql> select inet_aton('1.2.3.4');
-> 16909060
mysql> select inet_ntoa(16909060);
-> 1.2.3.4