erlang内置支持ssl

This commit is contained in:
anlicheng 2026-04-19 18:15:48 +08:00
parent ae97b5374c
commit ecfbdae8f4
6 changed files with 62 additions and 82 deletions

View File

@ -7,7 +7,7 @@
{backlog, 10240} {backlog, 10240}
]}, ]},
{tcp_server, [ {ssl_server, [
{port, 18092}, {port, 18092},
{acceptors, 500}, {acceptors, 500},
{max_connections, 10240}, {max_connections, 10240},

View File

@ -7,6 +7,13 @@
{backlog, 10240} {backlog, 10240}
]}, ]},
{ssl_server, [
{port, 18092},
{acceptors, 500},
{max_connections, 10240},
{backlog, 10240}
]},
{redis_server, [ {redis_server, [
{port, 16379}, {port, 16379},
{acceptors, 500}, {acceptors, 500},

View File

@ -219,7 +219,7 @@ handle_event({call, From}, {rpc_call, ReceiverPid, RpcCall}, _, State = #state{u
case HasSession andalso is_pid(ChannelPid) of case HasSession andalso is_pid(ChannelPid) of
true -> true ->
%% websocket发送请求 %% websocket发送请求
Ref = tcp_channel:rpc_call(ChannelPid, ReceiverPid, RpcCall), Ref = ssl_channel:rpc_call(ChannelPid, ReceiverPid, RpcCall),
{keep_state, State, [{reply, From, {ok, Ref}}]}; {keep_state, State, [{reply, From, {ok, Ref}}]};
false -> false ->
logger:debug("[iot_host] uuid: ~p, invalid state: ~p", [UUID, state_map(State)]), logger:debug("[iot_host] uuid: ~p, invalid state: ~p", [UUID, state_map(State)]),
@ -229,7 +229,7 @@ handle_event({call, From}, {rpc_call, ReceiverPid, RpcCall}, _, State = #state{u
handle_event({call, From}, {container_call, ReceiverPid, Request}, _, State = #state{uuid = UUID, channel_pid = ChannelPid, has_session = HasSession}) -> handle_event({call, From}, {container_call, ReceiverPid, Request}, _, State = #state{uuid = UUID, channel_pid = ChannelPid, has_session = HasSession}) ->
case HasSession andalso is_pid(ChannelPid) of case HasSession andalso is_pid(ChannelPid) of
true -> true ->
Ref = tcp_channel:container_call(ChannelPid, ReceiverPid, Request), Ref = ssl_channel:container_call(ChannelPid, ReceiverPid, Request),
{keep_state, State, [{reply, From, {ok, Ref}}]}; {keep_state, State, [{reply, From, {ok, Ref}}]};
false -> false ->
logger:debug("[iot_host] uuid: ~p, invalid state: ~p", [UUID, state_map(State)]), logger:debug("[iot_host] uuid: ~p, invalid state: ~p", [UUID, state_map(State)]),
@ -239,7 +239,7 @@ handle_event({call, From}, {container_call, ReceiverPid, Request}, _, State = #s
handle_event({call, From}, {cancel_rpc_call, Ref}, _, State = #state{channel_pid = ChannelPid}) -> handle_event({call, From}, {cancel_rpc_call, Ref}, _, State = #state{channel_pid = ChannelPid}) ->
case is_pid(ChannelPid) of case is_pid(ChannelPid) of
true -> true ->
ok = tcp_channel:cancel_rpc_call(ChannelPid, Ref), ok = ssl_channel:cancel_rpc_call(ChannelPid, Ref),
{keep_state, State, [{reply, From, ok}]}; {keep_state, State, [{reply, From, ok}]};
false -> false ->
{keep_state, State, [{reply, From, ok}]} {keep_state, State, [{reply, From, ok}]}
@ -251,7 +251,7 @@ handle_event({call, From}, {pub, Topic, Qos, Content}, ?STATE_ACTIVATED, State =
true -> true ->
logger:debug("[iot_host] host: ~p, publish to topic: ~p, content: ~p", [UUID, Topic, Content]), logger:debug("[iot_host] host: ~p, publish to topic: ~p, content: ~p", [UUID, Topic, Content]),
%% websocket发送请求 %% websocket发送请求
tcp_channel:pub(ChannelPid, Topic, Qos, Content), ssl_channel:pub(ChannelPid, Topic, Qos, Content),
{keep_state, State, [{reply, From, ok}]}; {keep_state, State, [{reply, From, ok}]};
false -> false ->
@ -265,7 +265,7 @@ handle_event({call, From}, {command, CommandType, Command}, ?STATE_ACTIVATED, St
true -> true ->
logger:debug("[iot_host] host: ~p, command_type: ~p, command: ~p", [UUID, CommandType, Command]), logger:debug("[iot_host] host: ~p, command_type: ~p, command: ~p", [UUID, CommandType, Command]),
%% websocket发送请求 %% websocket发送请求
tcp_channel:command(ChannelPid, CommandType, Command), ssl_channel:command(ChannelPid, CommandType, Command),
{keep_state, State, [{reply, From, ok}]}; {keep_state, State, [{reply, From, ok}]};
false -> false ->
logger:debug("[iot_host] host: ~p, command_type: ~p, command: ~p, invalid state: ~p", [UUID, CommandType, Command, state_map(State)]), logger:debug("[iot_host] host: ~p, command_type: ~p, command: ~p, invalid state: ~p", [UUID, CommandType, Command, state_map(State)]),
@ -277,7 +277,7 @@ handle_event({call, From}, {activate, true}, _, State = #state{uuid = UUID, chan
case is_pid(ChannelPid) of case is_pid(ChannelPid) of
true -> true ->
logger:debug("[iot_host] uuid: ~p, activate: true", [UUID]), logger:debug("[iot_host] uuid: ~p, activate: true", [UUID]),
tcp_channel:command(ChannelPid, ?COMMAND_AUTH, <<1:8>>); ssl_channel:command(ChannelPid, ?COMMAND_AUTH, <<1:8>>);
false -> false ->
logger:debug("[iot_host] uuid: ~p, activate: true, no channel", [UUID]) logger:debug("[iot_host] uuid: ~p, activate: true, no channel", [UUID])
end, end,
@ -287,9 +287,9 @@ handle_event({call, From}, {activate, true}, _, State = #state{uuid = UUID, chan
handle_event({call, From}, {activate, false}, _, State = #state{uuid = UUID, channel_pid = ChannelPid}) -> handle_event({call, From}, {activate, false}, _, State = #state{uuid = UUID, channel_pid = ChannelPid}) ->
case is_pid(ChannelPid) of case is_pid(ChannelPid) of
true -> true ->
tcp_channel:command(ChannelPid, ?COMMAND_AUTH, <<0:8>>), ssl_channel:command(ChannelPid, ?COMMAND_AUTH, <<0:8>>),
logger:debug("[iot_host] uuid: ~p, activate: false", [UUID]), logger:debug("[iot_host] uuid: ~p, activate: false", [UUID]),
tcp_channel:stop(ChannelPid, closed); ssl_channel:stop(ChannelPid, closed);
false -> false ->
logger:debug("[iot_host] uuid: ~p, activate: false, no channel", [UUID]) logger:debug("[iot_host] uuid: ~p, activate: false, no channel", [UUID])
end, end,
@ -347,7 +347,7 @@ handle_event(info, {timeout, _, heartbeat_ticker}, _, State = #state{uuid = UUID
end, end,
%% channel %% channel
is_pid(ChannelPid) andalso tcp_channel:stop(ChannelPid, closed), is_pid(ChannelPid) andalso ssl_channel:stop(ChannelPid, closed),
erlang:start_timer(?HEARTBEAT_INTERVAL, self(), heartbeat_ticker), erlang:start_timer(?HEARTBEAT_INTERVAL, self(), heartbeat_ticker),
{keep_state, State#state{channel_pid = undefined, has_session = false, heartbeat_counter = 0}}; {keep_state, State#state{channel_pid = undefined, has_session = false, heartbeat_counter = 0}};

View File

@ -19,8 +19,8 @@ start(_StartType, _StartArgs) ->
%% http服务 %% http服务
start_http_server(), start_http_server(),
%% tcp服务 %% ssl服务
start_tcp_server(), start_ssl_server(),
iot_sup:start_link(). iot_sup:start_link().
@ -68,26 +68,31 @@ start_http_server() ->
logger:debug("[http_server] the http server start at: ~p, pid is: ~p", [Port, Pid]). logger:debug("[http_server] the http server start at: ~p, pid is: ~p", [Port, Pid]).
%% tcp服务 %% ssl服务
start_tcp_server() -> start_ssl_server() ->
{ok, Props} = application:get_env(iot, tcp_server), {ok, Props} = application:get_env(iot, ssl_server),
Acceptors = proplists:get_value(acceptors, Props, 50), Acceptors = proplists:get_value(acceptors, Props, 50),
MaxConnections = proplists:get_value(max_connections, Props, 10240), MaxConnections = proplists:get_value(max_connections, Props, 10240),
Backlog = proplists:get_value(backlog, Props, 1024), Backlog = proplists:get_value(backlog, Props, 1024),
Port = proplists:get_value(port, Props), Port = proplists:get_value(port, Props),
PrivDir = code:priv_dir(iot),
CertFile = filename:join([PrivDir, "ssl", "server.crt"]),
KeyFile = filename:join([PrivDir, "ssl", "server.key"]),
TransOpts = #{ TransOpts = #{
max_connections => MaxConnections, max_connections => MaxConnections,
num_acceptors => Acceptors, num_acceptors => Acceptors,
shutdown => brutal_kill, shutdown => brutal_kill,
socket_opts => [ socket_opts => [
{nodelay, false}, {nodelay, true},
{backlog, Backlog}, {backlog, Backlog},
{port, Port} {port, Port},
{certfile, CertFile},
{keyfile, KeyFile}
] ]
}, },
{ok, _} = ranch:start_listener(tcp_server, ranch_tcp, TransOpts, tcp_channel, []), {ok, _} = ranch:start_listener(ssl_server, ranch_ssl, TransOpts, ssl_channel, []),
logger:debug("[iot_app] the tcp server start at: ~p", [Port]). logger:debug("[iot_app] the ssl server start at: ~p", [Port]).
-spec ensure_mnesia_schema() -> any(). -spec ensure_mnesia_schema() -> any().
ensure_mnesia_schema() -> ensure_mnesia_schema() ->

View File

@ -6,7 +6,7 @@
%%% @end %%% @end
%%% Created : 11. 1 2021 12:17 %%% Created : 11. 1 2021 12:17
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(tcp_channel). -module(ssl_channel).
-author("licheng5"). -author("licheng5").
-include("protocol.hrl"). -include("protocol.hrl").
-include("message_pb.hrl"). -include("message_pb.hrl").
@ -134,20 +134,6 @@ handle_cast({request_call, ReceiverPid, Ref, Body}, State = #state{transport = T
inflight = maps:put(PacketId, RequestInfo, Inflight) inflight = maps:put(PacketId, RequestInfo, Inflight)
}}. }}.
%% auth验证
handle_info({tcp, Socket, <<?FRAME_REQUEST, FrameBin/binary>>}, State = #state{transport = Transport, socket = Socket}) ->
RequestFrame = message_pb:decode_msg(FrameBin, 'RequestFrame'),
handle_request_frame(RequestFrame, Transport, Socket, State);
handle_info({tcp, Socket, <<?FRAME_CAST, FrameBin/binary>>}, State = #state{socket = Socket, host_pid = HostPid}) ->
CastFrame = message_pb:decode_msg(FrameBin, 'CastFrame'),
handle_cast_frame(CastFrame, HostPid, State);
%%
handle_info({tcp, Socket, <<?FRAME_RESPONSE, FrameBin/binary>>}, State = #state{socket = Socket, inflight = Inflight}) ->
ResponseFrame = message_pb:decode_msg(FrameBin, 'ResponseFrame'),
handle_response_frame(ResponseFrame, Inflight, State);
handle_info({timeout, TimerRef, {request_timeout, PacketId}}, State = #state{inflight = Inflight}) -> handle_info({timeout, TimerRef, {request_timeout, PacketId}}, State = #state{inflight = Inflight}) ->
case maps:get(PacketId, Inflight, undefined) of case maps:get(PacketId, Inflight, undefined) of
#inflight_request{ref = Ref, timer_ref = TimerRef} -> #inflight_request{ref = Ref, timer_ref = TimerRef} ->
@ -157,14 +143,6 @@ handle_info({timeout, TimerRef, {request_timeout, PacketId}}, State = #state{inf
{noreply, State} {noreply, State}
end; end;
handle_info({tcp_error, Sock, Reason}, State = #state{socket = Sock}) ->
logger:notice("[sdlan_channel] tcp_error: ~p", [Reason]),
{stop, normal, State};
handle_info({tcp_closed, Sock}, State = #state{socket = Sock}) ->
logger:notice("[sdlan_channel] tcp_closed"),
{stop, normal, State};
%% %%
handle_info({stop, Reason}, State) -> handle_info({stop, Reason}, State) ->
{stop, Reason, State}; {stop, Reason, State};
@ -174,8 +152,35 @@ handle_info({'DOWN', _, process, HostPid, Reason}, State = #state{uuid = UUID, h
logger:debug("[ws_channel] uuid: ~p, channel will close because host exited with reason: ~p", [UUID, Reason]), logger:debug("[ws_channel] uuid: ~p, channel will close because host exited with reason: ~p", [UUID, Reason]),
{stop, State}; {stop, State};
handle_info({ssl, Socket, <<?FRAME_REQUEST, FrameBin/binary>>},
State = #state{transport = Transport, socket = Socket}) ->
RequestFrame = message_pb:decode_msg(FrameBin, 'RequestFrame'),
handle_request_frame(RequestFrame, Transport, Socket, State);
handle_info({ssl, Socket, <<?FRAME_CAST, FrameBin/binary>>},
State = #state{socket = Socket, host_pid = HostPid}) ->
CastFrame = message_pb:decode_msg(FrameBin, 'CastFrame'),
handle_cast_frame(CastFrame, HostPid, State);
handle_info({ssl, Socket, <<?FRAME_RESPONSE, FrameBin/binary>>},
State = #state{socket = Socket, inflight = Inflight}) ->
ResponseFrame = message_pb:decode_msg(FrameBin, 'ResponseFrame'),
handle_response_frame(ResponseFrame, Inflight, State);
handle_info({ssl_closed, Socket}, State = #state{socket = Socket}) ->
logger:notice("[sdlan_channel] ssl socket closed"),
{stop, normal, State};
handle_info({ssl_error, Socket, Reason}, State = #state{socket = Socket}) ->
logger:notice("[sdlan_channel] ssl socket error: ~p", [Reason]),
{stop, normal, State};
handle_info({ssl_passive, Socket}, State = #state{transport = Transport, socket = Socket}) ->
ok = Transport:setopts(Socket, [{active, true}]),
{noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
logger:warning("[sdlan_channel] get a unknown message: ~p, channel will closed, state: ~p", [Info, State]), logger:warning("[sdlan_channel] get a unknown message: ~p, state: ~p", [Info, State]),
{noreply, State}. {noreply, State}.
terminate(Reason, #state{}) -> terminate(Reason, #state{}) ->
@ -265,14 +270,14 @@ handle_cast_frame(#'CastFrame'{body = {event_stream, CastMessage}}, HostPid, Sta
handle_event_stream_frame(CastMessage), handle_event_stream_frame(CastMessage),
{noreply, State}; {noreply, State};
handle_cast_frame(#'CastFrame'{body = Body}, _HostPid, State) -> handle_cast_frame(#'CastFrame'{body = Body}, _HostPid, State) ->
logger:warning("[tcp_channel] unsupported cast message type: command, body: ~p", [Body]), logger:warning("[ssl_channel] unsupported cast message type: command, body: ~p", [Body]),
{noreply, State}. {noreply, State}.
-spec handle_event_stream_frame(message_pb:'TaskEventStream'()) -> any(). -spec handle_event_stream_frame(message_pb:'TaskEventStream'()) -> any().
handle_event_stream_frame(#'TaskEventStream'{task_id = TaskId, type = Type0, stream = Reason0}) when Type0 =:= <<"close">> -> handle_event_stream_frame(#'TaskEventStream'{task_id = TaskId, type = Type0, stream = Reason0}) when Type0 =:= <<"close">> ->
iot_event_stream_observer:stream_close(TaskId, iolist_to_binary(Reason0)); iot_event_stream_observer:stream_close(TaskId, iolist_to_binary(Reason0));
handle_event_stream_frame(#'TaskEventStream'{task_id = TaskId, type = Type, stream = Stream}) -> handle_event_stream_frame(#'TaskEventStream'{task_id = TaskId, type = Type, stream = Stream}) ->
logger:debug("[tcp_channel] get task_id: ~p, type: ~ts, stream: ~ts", [TaskId, Type, Stream]), logger:debug("[ssl_channel] get task_id: ~p, type: ~ts, stream: ~ts", [TaskId, Type, Stream]),
iot_event_stream_observer:stream_data(TaskId, Type, Stream). iot_event_stream_observer:stream_data(TaskId, Type, Stream).
-spec handle_response_frame(message_pb:'ResponseFrame'(), map(), #state{}) -> -spec handle_response_frame(message_pb:'ResponseFrame'(), map(), #state{}) ->

View File

@ -1,37 +0,0 @@
# 待完成
## 1. endpoint需要存储在数据库
```mysql
CREATE TABLE `endpoint` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名称,路由时基于名称',
`title` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '序列号',
`type` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '类型',
`config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '配置信息基于json格式存储',
`status` smallint NOT NULL DEFAULT '-1',
`creator` smallint NOT NULL DEFAULT '0' COMMENT '创建人',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
```
## 2. service_config微服务配置也要存在于数据库
```mysql
CREATE TABLE `service_config` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`service_id` bigint unsigned NOT NULL COMMENT '服务的id',
`host_uuid` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名称,路由时基于名称',
`config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '配置信息基于json格式存储',
`last_config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '配置信息基于json格式存储',
`creator` smallint NOT NULL DEFAULT '0' COMMENT '创建人',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_service_id` (`service_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
```