您的位置:首页 > 其它

erlang实现websocket简单示例

2014-04-17 09:16 846 查看


本示例仅支持文本消息

基于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
后端代码:

-module(local_server).
-export([start/0]).

start() ->
F = fun interact/2,
spawn(fun() -> start(F, 0) end).

start(F, State0) ->
{ok, Listen} = gen_tcp:listen(8000, [{packet,raw}, {reuseaddr,true}, {active, true}]),
par_connect(Listen, F, State0).

par_connect(Listen, F, State0) ->
case gen_tcp:accept(Listen) of
{ok, Socket} ->
spawn(fun() -> par_connect(Listen, F, State0) end),
wait(Socket, F, State0);
{error, closed} ->
{ok, Listen2} = gen_tcp:listen(8000, [{packet,raw}, {reuseaddr,true}, {active, true}]),
par_connect(Listen2, F, State0)
end.

wait(Socket, F, State0) ->
receive
{tcp, Socket, Data} ->
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"))), ": "))),
Challenge = base64:encode(crypto:sha(<< Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)),
Handshake =
["HTTP/1.1 101 Switching Protocols\r\n",
"connection: Upgrade\r\n",
"upgrade: websocket\r\n",
"sec-websocket-accept: ", Challenge, "\r\n",
"\r\n",<<>>],
gen_tcp:send(Socket, Handshake),
send_data(Socket, "Hello, my world"),
S = self(),
Pid = spawn_link(fun() -> F(S, State0) end),
loop(Socket, Pid);
_Any ->
wait(Socket, F, State0)
end.

loop(Socket, Pid) ->
receive
{tcp, Socket, Data} ->
Text = websocket_data(Data),
case Text =/= <<>> of
true ->
Pid ! {browser, self(), ["You said: ",  Text]};
false ->
ok
end,
loop(Socket, Pid);
{tcp_closed, Socket} ->
ok;
{send, Data} ->
send_data(Socket, Data),
loop(Socket, Pid);
_Any ->
loop(Socket, Pid)
end.

interact(Browser, State) ->
receive
{browser, Browser, Str} ->
Browser ! {send, Str},
interact(Browser, State)
after 1000 ->
Browser ! {send, "clock ! tick " ++ integer_to_list(State)},
interact(Browser, State+1)
end.

%% 仅处理长度为125以内的文本消息
websocket_data(Data) when is_list(Data) ->
websocket_data(list_to_binary(Data));
websocket_data(<< 1:1, 0:3, 1:4, 1:1, Len:7, MaskKey:32, Rest/bits >>) when Len < 126 ->
<<End:Len/binary, _/bits>> = Rest,
Text = websocket_unmask(End, MaskKey, <<>>),
Text;
websocket_data(_) ->
<<>>.

%% 由于Browser发过来的数据都是mask的,所以需要unmask
websocket_unmask(<<>>, _, Unmasked) ->
Unmasked;
websocket_unmask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
T = O bxor MaskKey,
websocket_unmask(Rest, MaskKey, << Acc/binary, T:32 >>);
websocket_unmask(<< O:24 >>, MaskKey, Acc) ->
<< MaskKey2:24, _:8 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:24 >>;
websocket_unmask(<< O:16 >>, MaskKey, Acc) ->
<< MaskKey2:16, _:16 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:16 >>;
websocket_unmask(<< O:8 >>, MaskKey, Acc) ->
<< MaskKey2:8, _:24 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
<< Acc/binary, T:8 >>.

%% 发送文本给Client
send_data(Socket, Payload) ->
Len = iolist_size(Payload),
BinLen = payload_length_to_binary(Len),
gen_tcp:send(Socket, [<< 1:1, 0:3, 1:4, 0:1, BinLen/bits >>, Payload]).

payload_length_to_binary(N) ->
case N of
N when N =< 125 -> << N:7 >>;
N when N =< 16#ffff -> << 126:7, N:16 >>;
N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >>
end.


前端使用js发送websocket请求

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Websocket client</title>
<script src="jquery.min.js"></script>
<script type="text/javascript">

var websocket;
$(document).ready(init);

function init() {
if(!("WebSocket" in window)){
$('#status').append('<p><span style="color: red;">websockets are not supported </span></p>');
$("#navigation").hide();
} else {
$('#status').append('<p><span style="color: green;">websockets are supported </span></p>');
connect();
};
$("#connected").hide();
$("#content").hide();
};

function connect()
{
showScreen('<span style="color: red;">CONNECTING </span>');
wsHost = $("#server").val()
websocket = new WebSocket(wsHost);
showScreen('<b>Connecting to: ' +  wsHost + '</b>');
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
};

function disconnect() {
websocket.close();
};

function toggle_connection(){
if(websocket.readyState == websocket.OPEN){
disconnect();
} else {
connect();
};
};

function sendTxt() {
if(websocket.readyState == websocket.OPEN){
txt = $("#send_txt").val();
websocket.send(txt);
showScreen('sending: ' + txt);
} else {
showScreen('websocket is not connected');
};
};

function onOpen(evt) {
showScreen('<span style="color: green;">CONNECTED </span>');
$("#connected").fadeIn('slow');
$("#content").fadeIn('slow');
};

function onClose(evt) {
showScreen('<span style="color: red;">DISCONNECTED </span>');
};

function onMessage(evt) {
showScreen('<span style="color: blue;">RESPONSE: ' + evt.data+ '</span>');
};

function onError(evt) {
showScreen('<span style="color: red;">ERROR: ' + evt.data+ '</span>');
};

function showScreen(txt) {
$('#output').prepend('<p>' + txt + '</p>');
};

function clearScreen()
{
$('#output').html("");
};
</script>
</head>

<body>
<div id="header">
<h1>Websocket client</h1>
<div id="status"></div>
</div>

<div id="navigation">

<p id="connecting">
<input type='text' id="server" value="ws://localhost:8000/websocket"></input>
<button type="button" onclick="toggle_connection()">connection</button>
</p>
<div id="connected">
<p>
<input type='text' id="send_txt" value=></input>
<button type="button" onclick="sendTxt();">send</button>
</p>
</div>

<div id="content">
<button id="clear" onclick="clearScreen()" >Clear text</button>
<div id="output"></div>
</div>

</div>
</body>
</html>


附件(内含上文代码及jquery和erlang编译后的beam文件)下载地址:
http://files.cnblogs.com/suex/websocket_demo.zip
在解压后的文件夹中启动erlang shell, 执行local_server:start()即可启动服务端,



此时打开index.html即可看到文首的截图效果
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: