代码:
#!/usr/bin/env python
#coding:utf-8
import
os, sys, socket, struct, select, time
# From /usr/include/linux/icmp.h; your milage may vary.
ICMP_ECHO_REQUEST
=
8
# Seems to be the same on Solaris.
def
checksum(source_string):
"""
I'm not too confident that this is right but testing seems
to suggest that it gives the same answers as in_cksum in ping.c
"""
sum
=
0
countTo
=
(
len
(source_string)
/
2
)
*
2
count
=
0
while
count<countTo:
thisVal
=
ord
(source_string[count
+
1
])
*
256
+
ord
(source_string[count])
sum
=
sum
+
thisVal
sum
=
sum
&
0xffffffff
# Necessary?
count
=
count
+
2
if
countTo<
len
(source_string):
sum
=
sum
+
ord
(source_string[
len
(source_string)
-
1
])
sum
=
sum
&
0xffffffff
# Necessary?
sum
=
(
sum
>>
16
)
+
(
sum
&
0xffff
)
sum
=
sum
+
(
sum
>>
16
)
answer
=
~
sum
answer
=
answer &
0xffff
# Swap bytes. Bugger me if I know why.
answer
=
answer >>
8
| (answer <<
8
&
0xff00
)
return
answer
def
receive_one_ping(my_socket,
ID
, timeout):
"""
receive the ping from the socket.
"""
timeLeft
=
timeout
while
True
:
startedSelect
=
time.time()
whatReady
=
select.select([my_socket], [], [], timeLeft)
howLongInSelect
=
(time.time()
-
startedSelect)
if
whatReady[
0
]
=
=
[]:
# Timeout
return
timeReceived
=
time.time()
recPacket, addr
=
my_socket.recvfrom(
1024
)
icmpHeader
=
recPacket[
20
:
28
]
type
, code, checksum, packetID, sequence
=
struct.unpack(
"bbHHh"
, icmpHeader
)
if
packetID
=
=
ID
:
bytesInDouble
=
struct.calcsize(
"d"
)
timeSent
=
struct.unpack(
"d"
, recPacket[
28
:
28
+
bytesInDouble])[
0
]
return
timeReceived
-
timeSent
timeLeft
=
timeLeft
-
howLongInSelect
if
timeLeft <
=
0
:
return
def
send_one_ping(my_socket, dest_addr,
ID
):
"""
Send one ping to the given >dest_addr<.
"""
dest_addr
=
socket.gethostbyname(dest_addr)
# Header is type (8), code (8), checksum (16), id (16), sequence (16)
my_checksum
=
0
# Make a dummy heder with a 0 checksum.
header
=
struct.pack(
"bbHHh"
, ICMP_ECHO_REQUEST,
0
, my_checksum,
ID
,
1
)
#压包
#a1 = struct.unpack("bbHHh",header) #my test
bytesInDouble
=
struct.calcsize(
"d"
)
data
=
(
192
-
bytesInDouble)
*
"Q"
data
=
struct.pack(
"d"
, time.time())
+
data
# Calculate the checksum on the data and the dummy header.
my_checksum
=
checksum(header
+
data)
# Now that we have the right checksum, we put that in. It's just easier
# to make up a new header than to stuff it into the dummy.
header
=
struct.pack(
"bbHHh"
, ICMP_ECHO_REQUEST,
0
, socket.htons(my_checksum),
ID
,
1
)
packet
=
header
+
data
my_socket.sendto(packet, (dest_addr,
1
))
# Don't know about the 1
def
do_one(dest_addr, timeout):
"""
Returns either the delay (in seconds) or none on timeout.
"""
icmp
=
socket.getprotobyname(
"icmp"
)
try
:
my_socket
=
socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
except
socket.error, (errno, msg):
if
errno
=
=
1
:
# Operation not permitted
msg
=
msg
+
(
" - Note that ICMP messages can only be sent from processes"
" running as root."
)
raise
socket.error(msg)
raise
# raise the original error
my_ID
=
os.getpid() &
0xFFFF
send_one_ping(my_socket, dest_addr, my_ID)
delay
=
receive_one_ping(my_socket, my_ID, timeout)
my_socket.close()
return
delay
def
verbose_ping(dest_addr, timeout
=
2
, count
=
100
):
"""
Send >count< ping to >dest_addr< with the given >timeout< and display
the result.
"""
for
i
in
xrange
(count):
print
"ping %s..."
%
dest_addr,
try
:
delay
=
do_one(dest_addr, timeout)
except
socket.gaierror, e:
print
"failed. (socket error: '%s')"
%
e[
1
]
break
if
delay
=
=
None
:
print
"failed. (timeout within %ssec.)"
%
timeout
else
:
delay
=
delay
*
1000
print
"get ping in %0.4fms"
%
delay
if
__name__
=
=
'__main__'
:
verbose_ping(
"www.163.com"
,
2
,
1
)
用到的模块解析:
struct:
最近在学习python网络编程这一块,在写简单的socket通信代码时,遇到了struct这个模块的使用,当时不太清楚这到底有和作用,后来查阅了相关资料大概了解了,在这里做一下简单的总结。
了解c语言的人,一定会知道struct结构体在c语言中的作用,它定义了一种结构,里面包含不同类型的数据(int,char,bool等等),方便对某一结构对象进行处理。而在网络通信当中,大多传递的数据是以二进制流(binary data)存在的。当传递字符串时,不必担心太多的问题,而当传递诸如int、char之类的基本数据的时候,就需要有一种机制将某些特定的结构体类型打包成二进制流的字符串然后再网络传输,而接收端也应该可以通过某种机制进行解包还原出原始的结构体数据。python中的struct模块就提供了这样的机制,该模块的主要作用就是对python基本类型值与用python字符串格式表示的C struct类型间的转化(This module performs conversions between Python values and C structs represented as Python strings.)。stuct模块提供了很简单的几个函数,下面写几个例子。
struct提供用format specifier方式对数据进行打包和解包(Packing and Unpacking)。例如:
1 2 3 4 5 6 7 8 9 10 11 12 | import struct import binascii values = ( 1 , 'abc' , 2.7 ) s = struct.Struct( 'I3sf' ) packed_data = s.pack( * values) unpacked_data = s.unpack(packed_data) print 'Original values:' , values print 'Format string :' , s. format print 'Uses :' , s.size, 'bytes' print 'Packed Value :' , binascii.hexlify(packed_data) print 'Unpacked Type :' , type (unpacked_data), ' Value:' , unpacked_data |
输出:
Original values: (1, 'abc', 2.7)
Format string : I3sf
Uses : 12 bytes
Packed Value : 0100000061626300cdcc2c40
Unpacked Type : <type 'tuple'> Value: (1, 'abc', 2.700000047683716)
代码中,首先定义了一个元组数据,包含int、string、float三种数据类型,然后定义了struct对象,并制定了format‘I3sf’,I 表示int,3s表示三个字符长度的字符串,f 表示 float。最后通过struct的pack和unpack进行打包和解包。通过输出结果可以发现,value被pack之后,转化为了一段二进制字节串,而unpack可以把该字节串再转换回一个元组,但是值得注意的是对于float的精度发生了改变,这是由一些比如操作系统等客观因素所决定的。打包之后的数据所占用的字节数与C语言中的struct十分相似。
select 模块:
Python中的select模块专注于I/O多路复用,提供了select poll epoll三个方法(其中后两个在Linux中可用,windows仅支持select),另外也提供了kqueue方法(freeBSD系统)
select方法:
进程指定内核监听哪些文件描述符(最多监听1024个fd)的哪些事件,当没有文件描述符事件发生时,进程被阻塞;当一个或者多个文件描述符事件发生时,进程被唤醒。
当我们调用select()时:
1 上下文切换转换为内核态
2 将fd从用户空间复制到内核空间
3 内核遍历所有fd,查看其对应事件是否发生
4 如果没发生,将进程阻塞,当设备驱动产生中断或者timeout时间后,将进程唤醒,再次进行遍历
5 返回遍历后的fd
6 将fd从内核空间复制到用户空间
fd:file descriptor 文件描述符
fd_r_list, fd_w_list, fd_e_list = .秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。