Chinaunix首页 | 论坛 | 博客
  • 博客访问: 535811
  • 博文数量: 80
  • 博客积分: 1496
  • 博客等级: 上尉
  • 技术积分: 1292
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-18 11:24
个人简介

IT码农一个~

文章分类

全部博文(80)

文章存档

2020年(3)

2019年(7)

2017年(1)

2016年(2)

2015年(2)

2014年(26)

2013年(26)

2012年(2)

2011年(1)

2010年(1)

2008年(9)

我的朋友

分类: Python/Ruby

2008-09-19 21:37:05

目录

 

 

中如何解析 Struct sockaddr_in 结构

看了很多地方的资料,关于PythonC语言struct之间的转换问题。对于基本的c语言structPython下面直接可以利用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. PythonC数据类型对照关系

 

这里面没有关于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_inPOSIX定义

 

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语言:

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的示例:

pythonc语言中的padding

以下是转载,原文:http://borland.mblogger.cn/topcat/posts/25118.aspx

今天老鼠问我一个关于Pythonsocket传输问题。他说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产生的sizePython解码所需的不同。于是检查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;

};

 

这个structsize12,原因是既有字段间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”后缀已经没有意义了。

 

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 416909060)打包成 4 3 2 1 的顺序;
16进制表示的4 3 2 167305985)打包成 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地址转换的最终输出是不一样的,参考( /articles/article.aspx?p=169505&seqNum=4 )
()

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)))
###############################################################################
164amd+32windows 132intel+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'
但是64amd+64linux,是这样:
>>> 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
cpuos无关;
socket.ntohl/htonl
的输出结果int 32 os,处理成signed 类型,首位1被处理成负数;
64 os,则是unsigned类型;
(晕了半天,那个值才明白是位数,和cpu不相干)
###############################################################################

5mysql里面的函数是inet_aton, inet_ntoa pythonsocket不同,直接实现ip stringnetwork byte的转换,python里面只能实现ip地址到network byte2进制转换:
mysql> select inet_aton('1.2.3.4');
-> 16909060
mysql> select inet_ntoa(16909060);
-> 1.2.3.4

阅读(5010) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~