Python 中如何解析 Struct sockaddr_in 这种复杂结构
lincolnrainbow
|
1#
lincolnrainbow 发表于 2008-09-19 21:37
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 |