• 阅读源码学习erlang:erlang_websocket (1) websocket_server.erl


    最近看了几天erlang的基础语法之后还是不太明白如何运用erlang来开发,所以选择先看几个开源代码加深理解。

    今天是第一天,阅读 github.com上的erlang_websocket。  Erlang_WebSocket_Server(erlang_websocket )是由davebrysonENDOH Takanao 合作使用erlang编写的服务器侧websocket实现。

    好了,开始看代码.

    下面是 erlang_websocket 的核心部分 websocket_server.erl 代码注释.

      1 -module(websocket_server).
      2 -import(handshake, [handshake/1]).
      3 -export([start/0, start/5, default_echo_handler/0, unicast/2, broadcast/2, sendall/1]).
      4 %-compile(export_all).
      5 
      6 %%启动服务器 监听本地的,9000端口 MODULE 是一个宏
      7 start() -> start("localhost", 9000, ?MODULE, default_echo_handler, []).
      8 
      9 %% 通过参数启动服务器
     10 start(Host, Port, Module, Handler, Args) -> 
     11   %% 获得对应的IP地址
     12   {ok, IPaddress} = inet:getaddr(Host, inet), 
     13   %% 对这个地址和端口进行监听. [{ip, IPaddress}, {packet, 0}, {reuseaddr, true}, {keepalive, false}, {active, false}] 是tcp接口参数
     14   {ok, ListenSocket} = gen_tcp:listen(Port, [{ip, IPaddress}, {packet, 0}, {reuseaddr, true}, {keepalive, false}, {active, false}]),
     15   
     16   %% 将接收数据的循环放入到一个进程中运行,并且将这个进程注册名字为receiver 
     17   %% spawn 为erlang内建函数,用来创建一个轻量进程,函数原型是:spawn(Node, Mod, Func, Args) ??对这个函数原型不是很熟悉
     18   %% register为erlang内建函数,用来将一个名字注册到一个进程
     19 
     20   register(receiver, spawn(fun() -> receiver_loop() end)),
     21   %% 将发送数据的循环放入到一个进程中运行,并且将这个进程注册名字为sender
     22   register(sender, spawn(fun() -> sender_loop([]) end)),
     23   %% 将收到数据的处理操作放入到一个进程中运行,并且将这个进程注册名字为handler
     24   register(handler, spawn(Module, Handler, Args)),
     25   %% 执行循环:等待连接上来
     26   accept_connect_loop(ListenSocket).
     27 
     28 %%接受逻辑循环
     29 receiver_loop() ->
     30   %%收到消息如果是data,那么解析对应的Frame,并且获得SocketSenderPid对应的列表,将该消息转发给到handler进程,然后继续 receiver_loop
     31   %%收到消息如果是连接open。。。
     32   %%收到消息如果是连接close。。。
     33   %%收到消息如果是连接error。。
     34   %%其他。。。
     35   receive 
     36     {data, Frame, SocketSenderPid} -> 
     37       handler ! {message, decode_frame(Frame), integer_to_list(erlang:phash2(SocketSenderPid))},
     38       receiver_loop();
     39     {open, SocketSenderPid} -> 
     40       handler ! {open, integer_to_list(erlang:phash2(SocketSenderPid))},
     41       receiver_loop();
     42     {closed, SocketSenderPid} -> 
     43       handler ! {closed, integer_to_list(erlang:phash2(SocketSenderPid))},
     44       receiver_loop();
     45     {error, PosixReason, SocketSenderPid} -> 
     46       handler ! {error, PosixReason, integer_to_list(erlang:phash2(SocketSenderPid))},
     47       receiver_loop();
     48     _Any ->
     49       receiver_loop()
     50   end.
     51 
     52 %%消息处理
     53 default_echo_handler() ->
     54   receive
     55     %%处理 message ,之后继续循环
     56     {message, Data, ConnectionID} -> 
     57       unicast(Data, ConnectionID), %% 将这个数据进行 unicast(单向发送)
     58       default_echo_handler();
     59     _Any -> 
     60       default_echo_handler()
     61   end.
     62 
     63 %%将消息发送出去,具体发送处理见 sender_loop
     64 unicast(Data, ConnectionID) ->
     65   sender ! {unicast, Data, ConnectionID}.
     66 
     67 %%广播出去消息
     68 broadcast(Data, ConnectionID) ->
     69   sender ! {broadcast, Data, ConnectionID}.
     70 
     71 %%发送数据到所有人
     72 sendall(Data) ->
     73   sender ! {sendall, Data}.
     74 
     75 %%发送循环
     76 sender_loop(ConnectionIDPidList) ->
     77   receive
     78     %% 假如消息是一个连接打开,那么把这个消息的发送者id添加到连接列表
     79     {open, SocketSenderPid} ->
     80       sender_loop([{integer_to_list(erlang:phash2(SocketSenderPid)), SocketSenderPid}|ConnectionIDPidList]);
     81 
     82     %% 将消息发送给所指定的连接
     83     {unicast, Data, ConnectionID} ->
     84       {_, SocketSenderPid} = lists:keyfind(ConnectionID, 1, ConnectionIDPidList),
     85       SocketSenderPid ! {send, Data},
     86       sender_loop(ConnectionIDPidList);
     87 
     88     %%将消息广播到其他的连接 lists:map(fun(X) -> element(2, X) end, 这个操作不是很清楚???
     89     {broadcast, Data, ConnectionID} ->
     90       SocketSenderPidList = lists:map(fun(X) -> element(2, X) end, lists:keydelete(ConnectionID, 1, ConnectionIDPidList)),
     91       sendall(Data, SocketSenderPidList),
     92       sender_loop(ConnectionIDPidList);
     93 
     94     %%发一个消息给到所有的连接
     95     {sendall, Data} ->
     96       SocketSenderPidList = lists:map(fun(X) -> element(2, X) end, ConnectionIDPidList),
     97       sendall(Data, SocketSenderPidList),
     98       sender_loop(ConnectionIDPidList);
     99 
    100     %%关闭掉一个连接:从连接列表中移除
    101     {closed, SocketSenderPid} ->
    102       sender_loop(lists:keydelete(SocketSenderPid, 2,ConnectionIDPidList));
    103     _Any -> 
    104       sender_loop(ConnectionIDPidList)
    105   end.
    106 
    107 sendall(_Data, []) -> ok;
    108 
    109 %% 将消息逐个发给到列表中每个连接
    110 sendall(Data, [H|T]) ->
    111   H ! {send, Data},
    112   sendall(Data, T).
    113 
    114 %% 接收连接的循环
    115 accept_connect_loop(ListenSocket) ->
    116   {ok, Socket} = gen_tcp:accept(ListenSocket),
    117   %% 对新收到一个连接初始化
    118   spawn(fun() ->  init(Socket) end),
    119   %% 继续监听连接
    120   accept_connect_loop(ListenSocket).
    121 
    122 init(Socket) ->
    123   %% Set to http packet here to do handshake
    124   %% 设置连接的属性
    125   inet:setopts(Socket, [{packet, http}]),
    126 
    127   %% handshake 实现了消息接收的处理操作,主要是针对HTML5中websocket协议处理
    128   ok = handshake(Socket),
    129 
    130   %%设置属性 ??这个 inet:setopts 不是很熟悉???具体接口原型是?
    131 
    132   inet:setopts(Socket, [list, {packet, raw}, {active, false}]),
    133   
    134   %% 分别给这个sokect分配一个发送循环(socket_sender_loop)进程
    135   SocketSenderPid = spawn(fun() -> socket_sender_loop(Socket) end),
    136   
    137   %% 分别给这个sokect分配一个接收循环(socket_receiver_loop)进程
    138   spawn(fun() -> socket_receiver_loop(Socket, SocketSenderPid) end),
    139 
    140   %% 通知 sender,receiver 连接已经打开
    141   sender ! {open, SocketSenderPid},
    142   receiver ! {open, SocketSenderPid}.
    143 
    144 %% 将收到的消息转发到 receiver 和 sender
    145 socket_receiver_loop(Socket, SocketSenderPid) ->
    146   case gen_tcp:recv(Socket, 0) of 
    147     {ok, Frame} ->
    148       receiver ! {data, Frame, SocketSenderPid},
    149       socket_receiver_loop(Socket, SocketSenderPid);
    150     {error, closed} ->
    151       %% 收到连接关闭的消息,进行关闭进程
    152       sender ! {closed, SocketSenderPid},
    153       receiver ! {closed, SocketSenderPid},
    154       SocketSenderPid ! closed,
    155       gen_tcp:close(Socket);
    156     {error, PosixReason} ->
    157       sender ! {error, PosixReason, SocketSenderPid},
    158       receiver ! {error, PosixReason, SocketSenderPid},
    159       SocketSenderPid ! {error, PosixReason},
    160       gen_tcp:close(Socket);
    161     _Any -> 
    162       exit(normal)    
    163   end. 
    164 
    165 %%消息发送循环进程
    166 socket_sender_loop(Socket) -> 
    167   receive
    168     {send, Data} ->
    169       %% 将数据进行发送出去
    170       gen_tcp:send(Socket, [0] ++ Data ++ [255]),
    171       socket_sender_loop(Socket);
    172     closed ->
    173       %% 收到连接关闭的消息,进行关闭进程
    174       exit(normal);
    175     {error, PosixReason} ->
    176       exit(PosixReason); 
    177     _Any -> 
    178       socket_sender_loop(Socket)
    179   end.
    180 
    181 %%解析数据帧
    182 decode_frame([0|T]) -> decode_frame1(T);
    183 decode_frame(_Any) -> []. 
    184 decode_frame1([255]) -> [];
    185 decode_frame1([H|T]) -> [H|decode_frame1(T)];
    186 decode_frame1(_Any) -> [].
     
  • 相关阅读:
    jenkins+docker+rancher+zikui 部署
    利用jenkins直接构件docker镜像并发布到docker服务器
    docker+Rancher+K3S
    windows使用VSCode进行Shell开发
    v-drag 弹框拖拽的实现
    vue3兄弟组件传值
    vue3 组件传值
    Azure Computer Vision 之 Smart Crop 智能裁剪图片
    ASP.NET Core 单元测试
    ASP.NET Core Static Files
  • 原文地址:https://www.cnblogs.com/kaisne/p/2945246.html
Copyright © 2020-2023  润新知