websocket的介绍
WebSocket是一种在单个TCP连接上进行全双工通讯的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
上面是维基百科的介绍. 简单的讲,websocket 和http一样,都是一种网络传输协议
WebSocket 和 HTTP 的区别 http 协议是用在应用层的协议,他是基于 tcp 协议的,http 协议建立链接也必须要有三次握手才能发送信息。 http 链接分为短链接,长链接,短链接是每次请求都要三次握手才能发送自己的信息。即每一个 request 对应一个 response。长链接是在一定的期限内保持链接。保持 TCP 连接不断开。客户端与服务器通信,必须要有客户端发起然后服务器返回结果。客户端是主动的,服务器是被动的。 WebSocket 他是为了解决客户端发起多个 http 请求到服务器资源浏览器必须要经过长时间的轮训问题而生的,他实现了多路复用,他是全双工通信。在 webSocket 协议下客服端和浏览器可以同时发送信息。 建立了 WenSocket 之后服务器不必在浏览器发送 request 请求之后才能发送信息到浏览器。这时的服务器已有主动权想什么时候发就可以发送信息到服务器。而且信息当中不必在带有 head 的部分信息了与 http 的长链接通信来说,这种方式,不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。
HTTP 的长连接与 websocket 的持久连接 HTTP1.1 的连接默认使用长连接( persistent connection ), 即在一定的期限内保持链接,客户端会需要在短时间内向服务端请求大量的资源,保持 TCP 连接不断开。客户端与服务器通信,必须要有客户端发起然后服务器返回结果。客户端是主动的,服务器是被动的。 在一个 TCP 连接上可以传输多个 Request/Response 消息对,所以本质上还是 Request/Response 消息对,仍然会造成资源的浪费、实时性不强等问题。 如果不是持续连接,即短连接,那么每个资源都要建立一个新的连接,HTTP 底层使用的是 TCP,那么每次都要使用三次握手建立 TCP 连接,即每一个 request 对应一个 response,将造成极大的资源浪费。 长轮询,即客户端发送一个超时时间很长的 Request,服务器 hold 住这个连接,在有新数据到达时返回 Response websocket 的持久连接 只需建立一次 Request/Response 消息对,之后都是 TCP 连接,避免了需要多次建立 Request/Response 消息对而产生的冗余头部信息。
客户端请求
1 2 3 4 5 6 7 GET / HTTP/1.1 Upgrade: websocket Connection: Upgrade Host: example.com Origin: http://example.com Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== Sec-WebSocket-Version: 13
服务器响应
1 2 3 4 5 HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s= Sec-WebSocket-Location: ws://example.com/
和http字段不一样的地方
Connection必须设置Upgrade,表示客户端希望连接升级。
Upgrade字段必须设置Websocket,表示希望升级到Websocket协议。
Sec-WebSocket-Key是随机的字符串,服务器端会用这些数据来构造出一个SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一个特殊字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算SHA-1摘要,之后进行BASE-64编码,将结果做为“Sec-WebSocket-Accept”头的值,返回给客户端。如此操作,可以尽量避免普通HTTP请求被误认为Websocket协议。
Sec-WebSocket-Version 表示支持的Websocket版本。RFC6455要求使用的版本是13,之前草案的版本均应当弃用。
Origin字段是可选的,通常用来表示在浏览器中发起此Websocket连接所在的页面,类似于Referer。但是,与Referer不同的是,Origin只包含了协议和主机名称。
其他一些定义在HTTP协议中的字段,如Cookie等,也可以在Websocket中使用。
可以看到只是在http协议上增加了几个硬性规定,http协议的user-agent,cookie都可以在websocket握手过程中使用
抓包时候的注意事项:因为websocket只有一次握手,握手成功后就可以双方发送消息了,假如你打开网页后没有找到你要抓的数据,那么你就需要重新刷新网页,让他重新握手一次
example 简单例子 client:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import socketimport base64import randomimport timefrom urllib.parse import urlparsedef get_url_info (url: str ) -> tuple : url = urlparse(url) host = url.netloc resource = url.path or "/" return host, resource def get_key () -> str : bytes_key = bytes (random.getrandbits(8 ) for _ in range (16 )) res = base64.b64encode(bytes_key).decode() return res def get_header (url: str ) -> bytes : key = get_key() host, resource = get_url_info(url) head = { "Connection" : "Upgrade" , "Upgrade" : "websocket" , "Sec-Websocket-Key" : key, "Origin" : str (host) } headers = ["{}:{}" .format (k, item) for k, item in head.items()] headers.insert(0 , "GET {} HTTP/1.1" .format (resource)) headers.append("\r\n" ) headers = "\r\n" .join(headers) return headers.encode("utf-8" ) def shake_hands (client: socket, url: str ): header = get_header(url) client.send(header) time.sleep(3 ) message = client.recv(1024 ).decode("utf-8" ) print (message) if "Status Code:101" in message: return True return False if __name__ == '__main__' : url = "ws://127.0.0.1:9999" host, resource = get_url_info(url) client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1" , 9999 )) print (shake_hands(client, url)) client.close()
server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import socketimport tracebackdef verify (data: str ): data = data[:-4 ].split("\r\n" ) method = data.pop(0 ) headers = {} for val in data: try : print (val.split(":" )) name, value = val.split(":" ) except Exception as e: name, host, port = val.split(":" ) value = "{}:{}" .format (host, port) headers[name] = value if any (["GET" not in method, "HTTP/1.1" not in method, headers.get("Connection" ) != "Upgrade" , headers.get("Upgrade" ) != "websocket" , not headers.get("Origin" ), not headers.get("Sec-Websocket-Key" ), ]): return False return True def set_response (status ): if not status: head = {"Status Code" : 403 } return "" head = { "Status Code" : "101 Switching Protocols" , "Connection" : "Upgrade" , "Upgrade" : "websocket" , "Sec-Websocket-Accept" : "Y3nhRRZjdrBphYXwpoEouw" , } headers = ["{}:{}" .format (k, item) for k, item in head.items()] headers.append("\r\n" ) res = "\r\n" .join(headers) return res.encode("utf-8" ) def ws_main (): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind(("127.0.0.1" , 9999 )) s.listen(1 ) conn, addr = s.accept() with conn: data = conn.recv(1024 ).decode("utf-8" ) status = verify(data) response = set_response(status) conn.send(response) conn.close() if __name__ == '__main__' : ws_main()
复杂一点 client:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import asyncioimport websocketsasync def auth_system (websocket ): while True : cred_text = input ("please enter your username and password: " ) await websocket.send(cred_text) response_str = await websocket.recv() if "congratulation" in response_str: return True async def send_msg (websocket ): while True : _text = input ("please enter your context: " ) if _text == "exit" : print (f'you have enter "exit", goodbye' ) await websocket.close(reason="user exit" ) return False await websocket.send(_text) recv_text = await websocket.recv() print (f"{recv_text} " ) async def main_logic (): async with websockets.connect('ws://127.0.0.1:5678' ) as websocket: await auth_system(websocket) await send_msg(websocket) asyncio.get_event_loop().run_until_complete(main_logic())
server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import asyncioimport websocketsasync def check_permit (websocket ): while True : recv_str = await websocket.recv() cred_dict = recv_str.split(":" ) if cred_dict[0 ] == "admin" and cred_dict[1 ] == "123456" : response_str = "congratulation, you have connect with server\r\nnow, you can do something else" await websocket.send(response_str) return True else : response_str = "sorry, the username or password is wrong, please submit again" await websocket.send(response_str) async def recv_msg (websocket ): while True : recv_text = await websocket.recv() response_text = f"your submit context: {recv_text} " await websocket.send(response_text) async def main_logic (websocket, path ): await check_permit(websocket) await recv_msg(websocket) start_server = websockets.serve(main_logic, '127.0.0.1' , 5678 ) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()