之前曾有php版的websocket封装包。见Websocket——php实战,近期使用python做一些功能,须要用到对websocket的操作,因此,參照之前的实现,实现了这个python版本号。
总体实现起来,须要在建立socket监听port。这须要用到socket标准库模块。之后。须要对对网络字节流进行操作,这个方面python有struct标准库模块。这个很好用;另外涉及到加密解密操作,还有hashlib模块和sha模块等使用。特别在此总结一下。目的主要是
1 备忘
2. 总结与思考
1 socket 操作
1 本地Socket建立
建立TCPserver的一般流程:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((addr,port))
sock.listen(10)
建立好本地socket,并绑定地址与port进行监听。
2 并发连接策略
之后,须要使用不同的策略处理多个client连接的问题,最普通的处理方式就是直接使用accept堵塞,这样server端每次仅仅能处理一个client连接。然后python标准库提供了select模块,里面有select、poll和epoll这些不同的并发连接的处理策略。当中poll和epoll仅仅能在linux下使用,并且epoll在linux 2.6之后的版本号才干使用。当然并发处理效果来看。epoll比poll性能更好。poll比select性能更优。
可是select确能够在多种平台下使用,为了兼容Windows系统。本次实现中使用的是select策略,详细例如以下:
...#接上述socket建立代码
while True:
rs, ws, es = select.select([sock], [], [])
for r in rs:
if r is sock: #r 是server端socket
cliSock,addr = r.accept()
r.connect(cliSock) #建立于client连接
else:
try:
data = r.recv(bufferLen)
... #处理client连接发送的数据
...
poll方法也是select模块内的方法。使用起来比select更简单。首先使用poll建立一个poll对象,然后使用它的register方法注冊一个文件描写叙述符,unregister方法能够移除注冊对象。之后能够调用poll方法得到(fd,event)格式的列表,fd是文件描写叙述符。event代表发生的事件。
event是一个位掩码。能够使用select模块的常量进行按位操作。
select模块中polling事件常量:
事件名
描写叙述
POLLIN
读取来自文件描写叙述符的数据
POLLPRI
读取来自文件描写叙述符的紧急数据
POLLOUT
文件描写叙述符的数据已准备好。可无堵塞写入
POLLERR
与文件描写叙述符有关的错误情况
POLLHUP
挂起,连接丢失
POLLNVAL
无效请求,连接没有打开
以下是使用poll策略的演示样例代码:
...#接上述socket建立代码
fdmap = {sock.fileno() : s}
p = select.poll()
p.register(sock)
while True:
events = p.poll()
for fd,event in events:
if fd in fdmap: #本地socket
c,addr = sock.accept()
print 'Connected from ', addr
p.register(c)
fdmap[c.fileno()] = c
elif event & select.POLLIN:
data = fdmap[fd].recv(buffer)
...#数据操作
elif event & select.POLLERR: #断开连接
p.unregister(fd)
del fdmap[fd]
......
2 Struct处理字节数据
这个标准库模块就是用来转换python的数据值与C风格的数据类型的交互,特别是二进制文件和网络的字节数据。基本的方法:
struct.pack(fmt, v1, v2…)
struct.pack_into(fmt, buffer, offset, v1, v2…) (将v1,v2等值依照fmt格式pack到buffer字符串以offset開始的之后的位置)
struct.unpack(fmt, string)
struct.unpack_from(fmt, buffer [, offset=0])
struct.calcsize(fmt) (计算fmt的长度)
上面主要是直接使用struct模块的方法,每一个fmt都须要单独进行。假设须要重用。能够使用struct提供的Struct类。使用fmt实例化Struct对象之后,调用相似方法就能够进行重用。并且这样使用对象调用的性能更好,比直接使用上述方法调用效率更高。
pack(v1,v2…)
pack_into(buffer, offset, v1, v2 …)
unpack(string)
unpack_from(buffer, offset=0)
format : 返回实例化Struct对象使用的fmt字符串
size:返回fmt字符串的长度
当中最关键的format字符串的使用。
首先是字节顺序:
Character
Byte order
Size
Alignment
@
native
native
native
=
native
standard
none
<
little-endian
standard
none
>
big-endian
standard
none
!
network (= big-endian)
standard
none
然后就是format使用特殊字符,见下表:
Format
C Type
Python type
Standard size
x
pad byte
no value
c
char
string of length 1
1
b
signed char
integer
1
B
unsigned char
integer
1
?
_Bool
bool
1
h
short
integer
2
H
unsigned short
integer
2
i
int
integer
4
I
unsigned int
integer
4
l
long
integer
4
L
unsigned long
integer
4
q
long long
integer
8
Q
unsigned long long
integer
8
f
float
float
4
d
double
float
8
s
char[]
string
p
char[]
string
P
void *
integer
3 加密解密处理
hashlib标准库模块提供了经常使用的全部加密解密hash方法。使用到的有:
hashlib.update(arg):将hash对象使用arg字符串更新。多次调用相当于将全部arg字符串连接到一起
hashlib.digest() : 返回传如到update方法的字符串的hash值
hashlib.hexdigest():返回hash值的十六进制字符串表示
hashlib.copy():返回一个hash值的副本
websocket中在握手阶段须要获取到client的key,然后使用sha1和base64进行加密处理后发送到client进行握手。
sha1Encrypt = sha1(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest()
acceptKey = base64.b64encode(sha1Encrypt)
总体来说,使用python实现这些操作很方便。与php相比更加简洁。彰显了python语言简洁的本质!