fix ssl 心跳机制
This commit is contained in:
parent
084be2654c
commit
0f57df4726
@ -219,6 +219,22 @@ GET /event_stream?uuid=<host_uuid>&task_id=<task_id>
|
|||||||
|
|
||||||
`iot` 会为每个 `{UUID, TaskId}` 维护一个独立的任务进程,用于缓存最近的部署日志、支持多个 SSE listener,并在收到 close 事件后结束事件流。
|
`iot` 会为每个 `{UUID, TaskId}` 维护一个独立的任务进程,用于缓存最近的部署日志、支持多个 SSE listener,并在收到 close 事件后结束事件流。
|
||||||
|
|
||||||
|
### efka -> iot: ping
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
{<<"message">>, <<"ping">>}
|
||||||
|
```
|
||||||
|
|
||||||
|
用于 TLS 长连接的应用层保活。`efka` 在鉴权成功后周期发送,当前发送间隔为 30 秒。
|
||||||
|
|
||||||
|
`iot` 收到后回复:
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
{<<"message">>, <<"pong">>}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ping/pong` 只表示 TLS 连接仍可读写,不参与 host online/offline 判定。host 上下线仍由 UDP 心跳和 `iot_host` 本地连接状态共同维护。
|
||||||
|
|
||||||
### iot -> efka: pub
|
### iot -> efka: pub
|
||||||
|
|
||||||
```erlang
|
```erlang
|
||||||
@ -235,6 +251,7 @@ GET /event_stream?uuid=<host_uuid>&task_id=<task_id>
|
|||||||
|
|
||||||
- `efka` 鉴权超时时间:5 秒。
|
- `efka` 鉴权超时时间:5 秒。
|
||||||
- `iot` command inflight 超时时间:60 秒。
|
- `iot` command inflight 超时时间:60 秒。
|
||||||
|
- `iot` SSL channel 空闲超时时间:120 秒。120 秒内没有收到任何 TLS 包,包括 `ping`、业务 `message`、`request`、`command_response`,`iot` 会主动关闭该连接。
|
||||||
- `iot` 管理多个 `efka` 时,每个连接有独立 `ssl_channel` 和独立 inflight 表。
|
- `iot` 管理多个 `efka` 时,每个连接有独立 `ssl_channel` 和独立 inflight 表。
|
||||||
- command 超时后,`iot` 删除 inflight 记录;之后如果迟到的 `command_response` 到达,会被视为未预期响应。
|
- command 超时后,`iot` 删除 inflight 记录;之后如果迟到的 `command_response` 到达,会被视为未预期响应。
|
||||||
|
|
||||||
|
|||||||
@ -28,11 +28,13 @@
|
|||||||
-define(STATE_AUTH, auth).
|
-define(STATE_AUTH, auth).
|
||||||
%% 激活状态下
|
%% 激活状态下
|
||||||
-define(STATE_ACTIVATED, activated).
|
-define(STATE_ACTIVATED, activated).
|
||||||
|
-define(SSL_PING_INTERVAL, 30000).
|
||||||
|
|
||||||
-record(state, {
|
-record(state, {
|
||||||
socket :: undefined | ssl:sslsocket(),
|
socket :: undefined | ssl:sslsocket(),
|
||||||
%% 保存当前auth请求的ref,用来建立auth请求和响应的对应关系
|
%% 保存当前auth请求的ref,用来建立auth请求和响应的对应关系
|
||||||
auth_ref = undefined :: undefined | binary(),
|
auth_ref = undefined :: undefined | binary(),
|
||||||
|
ping_timer_ref = undefined :: undefined | reference(),
|
||||||
dropped_message_count = 0 :: non_neg_integer()
|
dropped_message_count = 0 :: non_neg_integer()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@ -143,6 +145,20 @@ handle_event(state_timeout, auth_timeout, ?STATE_AUTH, State = #state{socket = S
|
|||||||
schedule_reconnect(),
|
schedule_reconnect(),
|
||||||
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined}};
|
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined}};
|
||||||
|
|
||||||
|
handle_event(info, {timeout, TimerRef, ssl_ping}, ?STATE_ACTIVATED, State = #state{socket = Socket, ping_timer_ref = TimerRef}) ->
|
||||||
|
Packet = term_to_binary({<<"message">>, <<"ping">>}),
|
||||||
|
case ssl:send(Socket, Packet) of
|
||||||
|
ok ->
|
||||||
|
{keep_state, schedule_ssl_ping(State)};
|
||||||
|
{error, Reason} ->
|
||||||
|
logger:warning("[efka_client] send ssl ping failed, reason: ~p", [Reason]),
|
||||||
|
disconnect(Socket),
|
||||||
|
schedule_reconnect(),
|
||||||
|
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined, ping_timer_ref = undefined}}
|
||||||
|
end;
|
||||||
|
handle_event(info, {timeout, _TimerRef, ssl_ping}, _StateName, State) ->
|
||||||
|
{keep_state, State};
|
||||||
|
|
||||||
%% 将缓存中的数据推送到服务器端
|
%% 将缓存中的数据推送到服务器端
|
||||||
handle_event(info, flush_cache, ?STATE_ACTIVATED, State = #state{socket = Socket}) ->
|
handle_event(info, flush_cache, ?STATE_ACTIVATED, State = #state{socket = Socket}) ->
|
||||||
case efka_client_cache:fetch_next() of
|
case efka_client_cache:fetch_next() of
|
||||||
@ -165,19 +181,22 @@ handle_event(info, {ssl, Socket, PacketBin}, _, State = #state{socket = Socket})
|
|||||||
error:Error ->
|
error:Error ->
|
||||||
logger:warning("[efka_client] binary_to_term get error: ~p, packet_size: ~p", [Error, byte_size(PacketBin)]),
|
logger:warning("[efka_client] binary_to_term get error: ~p, packet_size: ~p", [Error, byte_size(PacketBin)]),
|
||||||
disconnect(Socket),
|
disconnect(Socket),
|
||||||
|
cancel_ssl_ping(State),
|
||||||
schedule_reconnect(),
|
schedule_reconnect(),
|
||||||
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined}}
|
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined, ping_timer_ref = undefined}}
|
||||||
end;
|
end;
|
||||||
handle_event(info, {ssl_error, Socket, Reason}, _, State = #state{}) ->
|
handle_event(info, {ssl_error, Socket, Reason}, _, State = #state{}) ->
|
||||||
logger:debug("[efka_client] ssl error: ~p", [Reason]),
|
logger:debug("[efka_client] ssl error: ~p", [Reason]),
|
||||||
disconnect(Socket),
|
disconnect(Socket),
|
||||||
|
cancel_ssl_ping(State),
|
||||||
schedule_reconnect(),
|
schedule_reconnect(),
|
||||||
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined}};
|
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined, ping_timer_ref = undefined}};
|
||||||
handle_event(info, {ssl_closed, Socket}, _, State = #state{}) ->
|
handle_event(info, {ssl_closed, Socket}, _, State = #state{}) ->
|
||||||
logger:debug("[efka_client] ssl closed"),
|
logger:debug("[efka_client] ssl closed"),
|
||||||
disconnect(Socket),
|
disconnect(Socket),
|
||||||
|
cancel_ssl_ping(State),
|
||||||
schedule_reconnect(),
|
schedule_reconnect(),
|
||||||
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined}};
|
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined, ping_timer_ref = undefined}};
|
||||||
|
|
||||||
%%% 处理内部消息,ssl收到的消息会先 binary_to_term,再由这里按协议结构模式匹配
|
%%% 处理内部消息,ssl收到的消息会先 binary_to_term,再由这里按协议结构模式匹配
|
||||||
|
|
||||||
@ -193,7 +212,8 @@ handle_event(internal, {<<"command">>, Ref, {<<"container">>, Request}}, _StateN
|
|||||||
%% 处理response
|
%% 处理response
|
||||||
handle_event(internal, {<<"response">>, AuthRef, {<<"auth_response">>, <<"ok">>}}, ?STATE_AUTH, State = #state{auth_ref = AuthRef}) ->
|
handle_event(internal, {<<"response">>, AuthRef, {<<"auth_response">>, <<"ok">>}}, ?STATE_AUTH, State = #state{auth_ref = AuthRef}) ->
|
||||||
logger:debug("[efka_client] auth success"),
|
logger:debug("[efka_client] auth success"),
|
||||||
{next_state, ?STATE_ACTIVATED, State#state{auth_ref = undefined}, [{next_event, info, flush_cache}]};
|
State1 = schedule_ssl_ping(State#state{auth_ref = undefined}),
|
||||||
|
{next_state, ?STATE_ACTIVATED, State1, [{next_event, info, flush_cache}]};
|
||||||
handle_event(internal, {<<"response">>, AuthRef, {<<"auth_response">>, {<<"error">>, Reason}}}, ?STATE_AUTH, State = #state{socket = Socket, auth_ref = AuthRef}) ->
|
handle_event(internal, {<<"response">>, AuthRef, {<<"auth_response">>, {<<"error">>, Reason}}}, ?STATE_AUTH, State = #state{socket = Socket, auth_ref = AuthRef}) ->
|
||||||
logger:debug("[efka_client] auth failed, reason: ~p", [Reason]),
|
logger:debug("[efka_client] auth failed, reason: ~p", [Reason]),
|
||||||
disconnect(Socket),
|
disconnect(Socket),
|
||||||
@ -207,6 +227,8 @@ handle_event(internal, {<<"command_response">>, _Ref, Reply}, StateName, State)
|
|||||||
{keep_state, State};
|
{keep_state, State};
|
||||||
|
|
||||||
%% 处理Pub/Sub机制
|
%% 处理Pub/Sub机制
|
||||||
|
handle_event(internal, {<<"message">>, <<"pong">>}, ?STATE_ACTIVATED, State) ->
|
||||||
|
{keep_state, State};
|
||||||
handle_event(internal, {<<"message">>, {<<"pub">>, #{<<"topic">> := Topic, <<"qos">> := Qos, <<"content">> := Content}}}, ?STATE_ACTIVATED, State) ->
|
handle_event(internal, {<<"message">>, {<<"pub">>, #{<<"topic">> := Topic, <<"qos">> := Qos, <<"content">> := Content}}}, ?STATE_ACTIVATED, State) ->
|
||||||
logger:debug("[efka_client] get pub topic: ~p, qos: ~p, content: ~p", [Topic, Qos, Content]),
|
logger:debug("[efka_client] get pub topic: ~p, qos: ~p, content: ~p", [Topic, Qos, Content]),
|
||||||
efka_subscription:publish(Topic, Qos, Content),
|
efka_subscription:publish(Topic, Qos, Content),
|
||||||
@ -254,7 +276,8 @@ handle_container_command(Ref, Request, Socket) ->
|
|||||||
ok.
|
ok.
|
||||||
|
|
||||||
-spec terminate(term(), atom(), #state{}) -> ok.
|
-spec terminate(term(), atom(), #state{}) -> ok.
|
||||||
terminate(Reason, _StateName, _State = #state{socket = Socket}) ->
|
terminate(Reason, _StateName, State = #state{socket = Socket}) ->
|
||||||
|
cancel_ssl_ping(State),
|
||||||
disconnect(Socket),
|
disconnect(Socket),
|
||||||
efka_client_cache:close(),
|
efka_client_cache:close(),
|
||||||
logger:notice("[efka_client] terminate with reason: ~p", [Reason]),
|
logger:notice("[efka_client] terminate with reason: ~p", [Reason]),
|
||||||
@ -305,6 +328,22 @@ disconnect(Socket) ->
|
|||||||
schedule_reconnect() ->
|
schedule_reconnect() ->
|
||||||
erlang:start_timer(5000, self(), create_transport).
|
erlang:start_timer(5000, self(), create_transport).
|
||||||
|
|
||||||
|
-spec schedule_ssl_ping(#state{}) -> #state{}.
|
||||||
|
schedule_ssl_ping(State = #state{ping_timer_ref = TimerRef}) ->
|
||||||
|
cancel_timer(TimerRef),
|
||||||
|
State#state{ping_timer_ref = erlang:start_timer(?SSL_PING_INTERVAL, self(), ssl_ping)}.
|
||||||
|
|
||||||
|
-spec cancel_ssl_ping(#state{}) -> ok.
|
||||||
|
cancel_ssl_ping(#state{ping_timer_ref = TimerRef}) ->
|
||||||
|
cancel_timer(TimerRef).
|
||||||
|
|
||||||
|
-spec cancel_timer(undefined | reference()) -> ok.
|
||||||
|
cancel_timer(undefined) ->
|
||||||
|
ok;
|
||||||
|
cancel_timer(TimerRef) ->
|
||||||
|
_ = erlang:cancel_timer(TimerRef),
|
||||||
|
ok.
|
||||||
|
|
||||||
-spec request_ref() -> binary().
|
-spec request_ref() -> binary().
|
||||||
request_ref() ->
|
request_ref() ->
|
||||||
crypto:strong_rand_bytes(16).
|
crypto:strong_rand_bytes(16).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user