一个用python写的websocket服务端

由于要在页面上调用后台的一个shell程序,但是这个shell执行时间很长,如果异步获取shell的输出?而不必漫长的等待shell执行完毕才会一下把数据全部输出?

我们知道原生的http协议不可能完成这个要求,除非你把输出更新到一个文本里,然后用js伦询去取,这不扯淡吗

因为是内部的程序,当然有这样要求的程序基本都是内部,或者小团体使用,所以,尝试一下websocket吧


先研究一下websocket协议

首先是握手 handleshake

        GET /chat HTTP/1.1
        Host: server.example.com
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
        Origin: http://example.com
        Sec-WebSocket-Protocol: chat, superchat
        Sec-WebSocket-Version: 13
 
  
 
  
 客户端会发送这么一个头给服务器 
  

然后服务器会返回这么一个头给客户端

        HTTP/1.1 101 Switching Protocols
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
        Sec-WebSocket-Protocol: chat
 
  
 
  

其中有一个注意点

Sec-WebSocket-Key和
Sec-WebSocket-Accept
 前者是客户端,也就是客户端生成的,那么后者咋生成咧 
  

公式如下

Sec-WebSocket-Accept = base64(sha1(Sec-WebSocket-Key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11))

"+"号不算在内


具体python代码如下

# -*- coding: utf8 -*-

import socket
import time
from threading import Thread
import hashlib
import base64
class returnCrossDomain(Thread):
    def __init__(self,connection):
        Thread.__init__(self)
        self.con = connection
        self.isHandleShake = False
    def run(self):
        while True:
            if not self.isHandleShake: #握手
                clientData  = self.con.recv(1024)
                dataList = clientData.split("\r\n")
                header = {}
                print clientData
                for data in dataList:
                    if ": " in data:
                        unit = data.split(": ")
                        header[unit[0]] = unit[1]
                secKey = header['Sec-WebSocket-Key'];
                resKey = base64.encodestring(hashlib.new("sha1",secKey+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest()).replace('\n','');

                response = '''HTTP/1.1 101 Switching Protocols\r\n'''
                response += '''Upgrade: websocket\r\n'''
                response += '''Connection: Upgrade\r\n'''
                response += '''Sec-WebSocket-Accept: %s\r\n'''%(resKey,)
                response += '''Sec-WebSocket-Protocol: chat\r\n\r\n'''
                self.con.send(response)
                self.isHandleShake = True
            else:
                data = self.con.recv(1024)
                print data

def main():
    sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    sock.bind(('',88))
    sock.listen(100)
    while True:
        try:
            connection,address = sock.accept()
            returnCrossDomain(connection).start()
        except:
            time.sleep(1)

if __name__=="__main__":
    main()

注意注意,python的base64.encodestring 会自动加一个\n需要给处理掉

js代码如下

var socket = new WebSocket('ws://localhost:88');
socket.onopen = function() {
        alert(1);
}

顺利弹出1


下边开始研究数据传输 有一个问题折腾了我好久,就是因为我复制的时候没有把这个头给去掉,导致浏览器一直收不到信息,我郁闷阿

response += '''Sec-WebSocket-Protocol: chat\r\n\r\n'''


这句话的意思就是,使用chat这个协议,而不使用websocket协议,所以我按照websocket协议来,肯定怎么都收不到数据,去掉之后,果断成功


websocket的数据传输部分有点小麻烦,老版本的websocket 是很简单的,每条消息都是 一个0xFF打头,0x00结尾,很容易,但是肯定会引发安全问题


新版本的协议如下

服务端从客户端读数据

1.读1Byte字节,注意是Byte=8bits,这个要注意,奶奶的,刚看说明文档的时候,看过了,怎么也看不懂

这1个Byte分 8个bit  ,前4个bit 不去研究,后4个bit代表这个数据段的作用,比如是一条数据,一条socket关闭信息等,


获取这四个字节很简单

data_head = self.con.recv(1)
header = struct.unpack("B",data_head)[0]
opcode = header & 0b00001111

具体的opcode定义如下

      *  %x0 denotes a continuation frame

      *  %x1 denotes a text frame

      *  %x2 denotes a binary frame

      *  %x3-7 are reserved for further non-control frames

      *  %x8 denotes a connection close

      *  %x9 denotes a ping

      *  %xA denotes a pong

      *  %xB-F are reserved for further control frames

2.再读取1个byte注意是byte=8个bits

这8个bits分两端,第一bit代表是否masking,我的理解就是是否加密

后7个bits代表着真正数据的长度payloadlength,如果payloadlength<=125那么数据长度就是payloadlength,如果payloadlength=126,那么就再去2Bytes的unsiged integer,来代表数据长度,如果payloadlength = 127 那么 就再读8个字节的unsigned long long,具体代码如下

data_length = self.con.recv(1)
data_lengths= struct.unpack("B",data_length)
data_length = data_lengths[0]& 0b01111111
print bin(data_lengths[0])
masking = data_lengths[0] >> 7

if data_length<=125:
     payloadLength = data_length
elif data_length==126:
     payloadLength = struct.unpack("H",self.con.recv(2))[0]
elif data_length==127:
     payloadLength = struct.unpack("Q",self.con.recv(8))[0]
print "字符串长度是:%d"%(data_length,)
3.上一步 读取到的masking 用来判断是否加密

如果masking==1

那么就读取4个Bytes

然后按照长度读取数据,进行解密工作,具体代码和算法如下


如果masking==0

那么就直接读取刚才计算得到的数据长度的数据了

                   if masking==1:
                        print "是masking"
                        maskingKey = self.con.recv(4)
                        self.maskingKey = maskingKey
                    data = self.con.recv(payloadLength)
                    i = 0
                    true_data = ''
                    for d in data:
                        true_data += chr(ord(d) ^ ord(maskingKey[i%4]))
                        i += 1
                    self.onData(true_data)

ok读取客户端的数据完成,下边是发送数据

发送数据很简单,和接收数据一样的格式,不过我们就不用masking 加密了,所以第二个字节的第一个bit设为0

具体代码如下

def sendData(self,text) :
        print "给客户端发送信息%s"%(text,)
        #头
        self.con.send(struct.pack("!B",0x81))
        #计算长度
        length = len(text)
       # masking = 0b00000000;

        if length<=125:
            self.con.send(struct.pack("!B",length))

        elif length<=65536:
            self.con.send(struct.pack("!B",126))
            self.con.send(struct.pack("!H",length))
        else:
            self.con.send(struct.pack("!B",127))
            self.con.send(struct.pack("!Q",length))

        self.con.send(struct.pack("!%ds"%(length,),text))



具体的代码可以去我的git上下载,我已经封装好了模块,对应的js也有一个写好的类,里边有demo 欢迎大家尝试



https://github.com/suxianbaozi/pywebsocketserver






你可能感兴趣的:(python,javascript,细节研究)