erlang实现websocket简单示例
本示例仅支持文本消息
基于websocket版本13
由于joe armstrong的例子:
http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html
已经过时,不符合现在的websocket标准,于是改写了一下
还参考了cowboy的代码:
https://github.com/extend/cowboy
websocket标准参考:
http://blog.csdn.net/fenglibing/article/details/6852497
后端代码:
1 -module(local_server). 2 -export([start/0]). 3 4 start() -> 5 F = fun interact/2, 6 spawn(fun() -> start(F, 0) end). 7 8 start(F, State0) -> 9 {ok, Listen} = gen_tcp:listen(8000, [{packet,raw}, {reuseaddr,true}, {active, true}]), 10 par_connect(Listen, F, State0). 11 12 par_connect(Listen, F, State0) -> 13 case gen_tcp:accept(Listen) of 14 {ok, Socket} -> 15 spawn(fun() -> par_connect(Listen, F, State0) end), 16 wait(Socket, F, State0); 17 {error, closed} -> 18 {ok, Listen2} = gen_tcp:listen(8000, [{packet,raw}, {reuseaddr,true}, {active, true}]), 19 par_connect(Listen2, F, State0) 20 end. 21 22 wait(Socket, F, State0) -> 23 receive 24 {tcp, Socket, Data} -> 25 Key = list_to_binary(lists:last(string:tokens(hd(lists:filter(fun(S) -> lists:prefix("Sec-WebSocket-Key:", S) end, string:tokens(Data, "\r\n"))), ": "))), 26 Challenge = base64:encode(crypto:sha(<< Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)), 27 Handshake = 28 ["HTTP/1.1 101 Switching Protocols\r\n", 29 "connection: Upgrade\r\n", 30 "upgrade: websocket\r\n", 31 "sec-websocket-accept: ", Challenge, "\r\n", 32 "\r\n",<<>>], 33 gen_tcp:send(Socket, Handshake), 34 send_data(Socket, "Hello, my world"), 35 S = self(), 36 Pid = spawn_link(fun() -> F(S, State0) end), 37 loop(Socket, Pid); 38 _Any -> 39 wait(Socket, F, State0) 40 end. 41 42 loop(Socket, Pid) -> 43 receive 44 {tcp, Socket, Data} -> 45 Text = websocket_data(Data), 46 case Text =/= <<>> of 47 true -> 48 Pid ! {browser, self(), ["You said: ", Text]}; 49 false -> 50 ok 51 end, 52 loop(Socket, Pid); 53 {tcp_closed, Socket} -> 54 ok; 55 {send, Data} -> 56 send_data(Socket, Data), 57 loop(Socket, Pid); 58 _Any -> 59 loop(Socket, Pid) 60 end. 61 62 interact(Browser, State) -> 63 receive 64 {browser, Browser, Str} -> 65 Browser ! {send, Str}, 66 interact(Browser, State) 67 after 1000 -> 68 Browser ! {send, "clock ! tick " ++ integer_to_list(State)}, 69 interact(Browser, State+1) 70 end. 71 72 %% 仅处理长度为125以内的文本消息 73 websocket_data(Data) when is_list(Data) -> 74 websocket_data(list_to_binary(Data)); 75 websocket_data(<< 1:1, 0:3, 1:4, 1:1, Len:7, MaskKey:32, Rest/bits >>) when Len < 126 -> 76 <<End:Len/binary, _/bits>> = Rest, 77 Text = websocket_unmask(End, MaskKey, <<>>), 78 Text; 79 websocket_data(_) -> 80 <<>>. 81 82 %% 由于Browser发过来的数据都是mask的,所以需要unmask 83 websocket_unmask(<<>>, _, Unmasked) -> 84 Unmasked; 85 websocket_unmask(<< O:32, Rest/bits >>, MaskKey, Acc) -> 86 T = O bxor MaskKey, 87 websocket_unmask(Rest, MaskKey, << Acc/binary, T:32 >>); 88 websocket_unmask(<< O:24 >>, MaskKey, Acc) -> 89 << MaskKey2:24, _:8 >> = << MaskKey:32 >>, 90 T = O bxor MaskKey2, 91 << Acc/binary, T:24 >>; 92 websocket_unmask(<< O:16 >>, MaskKey, Acc) -> 93 << MaskKey2:16, _:16 >> = << MaskKey:32 >>, 94 T = O bxor MaskKey2, 95 << Acc/binary, T:16 >>; 96 websocket_unmask(<< O:8 >>, MaskKey, Acc) -> 97 << MaskKey2:8, _:24 >> = << MaskKey:32 >>, 98 T = O bxor MaskKey2, 99 << Acc/binary, T:8 >>. 100 101 %% 发送文本给Client 102 send_data(Socket, Payload) -> 103 Len = iolist_size(Payload), 104 BinLen = payload_length_to_binary(Len), 105 gen_tcp:send(Socket, [<< 1:1, 0:3, 1:4, 0:1, BinLen/bits >>, Payload]). 106 107 payload_length_to_binary(N) -> 108 case N of 109 N when N =< 125 -> << N:7 >>; 110 N when N =< 16#ffff -> << 126:7, N:16 >>; 111 N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >> 112 end.
前端使用js发送websocket请求
1 <html> 2 <head> 3 <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> 4 <title>Websocket client</title> 5 <script src="jquery.min.js"></script> 6 <script type="text/javascript"> 7 8 var websocket; 9 $(document).ready(init); 10 11 function init() { 12 if(!("WebSocket" in window)){ 13 $('#status').append('<p><span style="color: red;">websockets are not supported </span></p>'); 14 $("#navigation").hide(); 15 } else { 16 $('#status').append('<p><span style="color: green;">websockets are supported </span></p>'); 17 connect(); 18 }; 19 $("#connected").hide(); 20 $("#content").hide(); 21 }; 22 23 function connect() 24 { 25 showScreen('<span style="color: red;">CONNECTING </span>'); 26 wsHost = $("#server").val() 27 websocket = new WebSocket(wsHost); 28 showScreen('<b>Connecting to: ' + wsHost + '</b>'); 29 websocket.onopen = function(evt) { onOpen(evt) }; 30 websocket.onclose = function(evt) { onClose(evt) }; 31 websocket.onmessage = function(evt) { onMessage(evt) }; 32 websocket.onerror = function(evt) { onError(evt) }; 33 }; 34 35 function disconnect() { 36 websocket.close(); 37 }; 38 39 function toggle_connection(){ 40 if(websocket.readyState == websocket.OPEN){ 41 disconnect(); 42 } else { 43 connect(); 44 }; 45 }; 46 47 function sendTxt() { 48 if(websocket.readyState == websocket.OPEN){ 49 txt = $("#send_txt").val(); 50 websocket.send(txt); 51 showScreen('sending: ' + txt); 52 } else { 53 showScreen('websocket is not connected'); 54 }; 55 }; 56 57 function onOpen(evt) { 58 showScreen('<span style="color: green;">CONNECTED </span>'); 59 $("#connected").fadeIn('slow'); 60 $("#content").fadeIn('slow'); 61 }; 62 63 function onClose(evt) { 64 showScreen('<span style="color: red;">DISCONNECTED </span>'); 65 }; 66 67 function onMessage(evt) { 68 showScreen('<span style="color: blue;">RESPONSE: ' + evt.data+ '</span>'); 69 }; 70 71 function onError(evt) { 72 showScreen('<span style="color: red;">ERROR: ' + evt.data+ '</span>'); 73 }; 74 75 function showScreen(txt) { 76 $('#output').prepend('<p>' + txt + '</p>'); 77 }; 78 79 function clearScreen() 80 { 81 $('#output').html(""); 82 }; 83 </script> 84 </head> 85 86 <body> 87 <div id="header"> 88 <h1>Websocket client</h1> 89 <div id="status"></div> 90 </div> 91 92 93 <div id="navigation"> 94 95 <p id="connecting"> 96 <input type='text' id="server" value="ws://localhost:8000/websocket"></input> 97 <button type="button" onclick="toggle_connection()">connection</button> 98 </p> 99 <div id="connected"> 100 <p> 101 <input type='text' id="send_txt" value=></input> 102 <button type="button" onclick="sendTxt();">send</button> 103 </p> 104 </div> 105 106 <div id="content"> 107 <button id="clear" onclick="clearScreen()" >Clear text</button> 108 <div id="output"></div> 109 </div> 110 111 </div> 112 </body> 113 </html>
附件(内含上文代码及jquery和erlang编译后的beam文件)下载地址:
https://files.cnblogs.com/suex/websocket_demo.zip
在解压后的文件夹中启动erlang shell, 执行local_server:start()即可启动服务端,
此时打开index.html即可看到文首的截图效果