Сервер на python для websockets

в 8:32, , рубрики: python, sockets, websockets, Песочница, сервер, метки: , , ,

Введение

Недавно решил открыть для себя магию, новых для меня, (Websockets.) Поскольку недавно открыл для себя чудеса python 2.7.7. решил взять его, как серверную сторону. Можно было использовать чужие библиотечки как ws4py или Twisted webscokets, но т.к. Я люблю писать сам, то возьмем просто socket.

Серверная сторона

  1. import struct #из него нам нужна функция pack() и unpack_from()
  2. import array # функция array()
  3. import socket # Сами сокеты
  4. import threading #По потоку для каждого подключения
  5. from hashlib import sha1 #Кодирование Access Key о котором будет дальше 
  6. from base64 import b64encode #Кодирование Access Key о котором будет дальше

Итак, возьмемся за дело. Для начала создаем сокет, биндим его и начинаем слушать и на каждое подключение создаем новый поток.

  1. def start_server():
  2.   s = socket.socket()
  3.   s.setsockopt(socket.SOL_SOCKETsocket.SO_REUSEADDR1)
  4.   s.bind((''9999))
  5.   s.listen(1)
  6.   while 1:
  7.       conn, addr = s.accept()
  8.       print 'Connected by', addr
  9.       threading.Thread(target = handle, args = (conn, addr)).start()
  10.  
  11. start_server()

В самом начале подключения к вебсокетам клиент посылает серверу headers (Тут WebSocket protocol standardized by the IETF as RFC 6455)image на которые сервер должен составить ответ в виде Accept, дабы подтвердить, что понимает websocket.

  1. def create_handshake(handshake):
  2. lines = handshake.splitlines() # Делим построчно
  3. for line in lines: #Итерируемся по строкам
  4. parts = line.partition(": ") # Делим по ':'
  5. if parts[0] == «Sec-WebSocket-Key»:
  6. key = parts[2] # Находим необходимый ключ
  7. """ 
  8. Тут цитирую wiki: 
  9. Details of Sec-WebSocket-Key to Sec-WebSocket-Accept :x3JJHMbDL1EzLkh9GBhXDw==258EAFA5-E914-47DA-95CA-C5AB0DC85B11 string hashed by SHA-1 gives 0x1d29ab734b0c9585240069a6e4e3e91b61da1969 hexadecimal value.Encoding the SHA-1 hash by Base64 yields HSmrc0sMlYUkAGmm5OPpG2HaGWk=, which is the Sec-WebSocket-Accept value.
  10. """
  11. key += «258EAFA5-E914-47DA-95CA-C5AB0DC85B11» 
  12. Acckey=b64encode((sha1(key)).digest())
  13. return (
  14.       «HTTP/1.1 101 Switching Protocolsrn»
  15.       «Upgrade: websocketrn»
  16.       «Connection: Upgradern»
  17.       «Sec-WebSocket-Accept: %srn»
  18.       «rn»
  19.       ) % (
  20.       Acckey)

После того, как мы установили handshake можно работать в full-duplex mode. Но тут не так все просто. Дальше пересылаемые фреймы имеют вид:
image

Эти фреймы надо «распаковывать» при получении и «запаковывать» при отправке.

Для их распаковки:

  1.  
  2. def unpack_frame(data):
  3.     frame = {}
  4.     byte1, byte2 = struct.unpack_from('!BB', data)
  5.     frame['fin'] = (byte1 >> 7) & 1
  6.     frame['opcode'] = byte1 & 0xf
  7.     masked = (byte2 >> 7) & 1
  8.     frame['masked'] = masked
  9.     mask_offset = 4 if masked else 0
  10.     payload_hint = byte2 & 0x7f
  11.     if payload_hint < 126:
  12.         payload_offset = 2
  13.         payload_length = payload_hint               
  14.     elif payload_hint == 126:
  15.         payload_offset = 4
  16.         payload_length = struct.unpack_from('!H',data,2)[0]
  17.     elif payload_hint == 127:
  18.         payload_offset = 8
  19.         payload_length = struct.unpack_from('!Q',data,2)[0]
  20.     frame['length'] = payload_length
  21.     payload = array.array('B')
  22.     payload.fromstring(data[payload_offset + mask_offset:])
  23.     if masked:
  24.         mask_bytes = struct.unpack_from('!BBBB',data,payload_offset)
  25.         for i in range(len(payload)):
  26.             payload[i] ^= mask_bytes[% 4]
  27.     frame['payload'] = payload.tostring()
  28.     return frame

Эта функция возвращает словарь типа:

  1. {'opcode':1'length':15'fin':1'masked':1'payload''WebSocket rocks' }

И для обращения к самому сообщению надо просто будет обращаться к data['payload'].

Дальше запаковки:

  1. def pack_frame(buf, opcode, base64=False):
  2.  
  3.     if base64:
  4.         buf = b64encode(buf)
  5.  
  6.     b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
  7.     payload_len = len(buf)
  8.     if payload_len <125:
  9.         header = struct.pack('>BB', b1, payload_len)
  10.     elif payload_len > 125 and payload_len < 65536:
  11.         header = struct.pack('>BBH', b1, 126, payload_len)
  12.     elif payload_len >65536:
  13.         header = struct.pack('>BBQ', b1, 127, payload_len)
  14.  
  15.     return header+buf

И будем в функции handle получать сообщение открывать его, закрывать и посылать обратно.

  1. def handle(s, addr):
  2.   data = s.recv(1024)
  3.   print data
  4.   s.send(create_handshake(data))
  5.   data = s.recv(1024)
  6.   s.send(pack_frame(unpack_frame(data)['payload'],0x1))
  7.   s.close()

Клиентская часть

В качестве клиента возьмем пример с websocket.org на 06.08.2012, предварительно изменив
var wsUri = "ws://echo.websocket.org/";
на
var wsUri = "ws://localhost:9999/";

Код клиента:

  1. <!DOCTYPE html>  <meta charset=«utf-8» />  
  2. <title>WebSocket Test</title>  
  3. <script language=«javascript» type=«text/javascript»>  
  4. var wsUri = «ws://localhost:9999/»; var output; 
  5. function init() { output = document.getElementById(«output»); testWebSocket(); }
  6. function testWebSocket() { websocket = new WebSocket(wsUri); 
  7. websocket.onopen = function(evt) { onOpen(evt) }; 
  8. websocket.onclose = function(evt) { onClose(evt) }; 
  9. websocket.onmessage = function(evt) { onMessage(evt) }; 
  10. websocket.onerror = function(evt) { onError(evt) }; }  
  11. function onOpen(evt) { writeToScreen(«CONNECTED»); doSend(«WebSocket rocks»); }  
  12. function onClose(evt) { writeToScreen(«DISCONNECTED»); }  
  13. function onMessage(evt) { writeToScreen('<span style=«color: blue;»>RESPONSE: ' + evt.data+''

websocket.close(); }   function onError(evt) { writeToScreen('<span style=«color: red;»>ERROR: ' + evt.data); }  

function doSend(message) { writeToScreen(«SENT: » + message);  websocket.send(message); }   function writeToScreen(message) { var pre = document.createElement(«p»); pre.style.wordWrap = «break-word»; pre.innerHTML = message; output.appendChild(pre); }   window.addEventListener(«load», init, false);  </script>  <h2>WebSocket Test</h2>  <div id=«output»></div>  </html> 

Заключение

Запускаем python скрипт и заходим на клиентскую страничку и видим:
image

???
Банкет!

Автор: IlyaGetReady


  1. гость:

    что не работает твой код, ты сам проверял что написал

  2. Хыиуду:

    В момент подключения клиента бросает исключение KeyboardInterrupt на строке conn, addr = s.accept()

  3. гость:

    для python 2.7
    на 3 может не работать

  4. Макс:

    Работает!

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js