mqtt v3.1.1协议有规定clientid可以为空,所以当客户端clientid为空,emq会随机帮忙生成。
一、源码emq 1.1.3 -- emqttd_protocol.erl
客户端经过TCP三次握手建立连接之后,会发起mqtt connect packet,服务器接收到后开始处理这个数据包,入口函数就是如下的源文件,process函数:
https://github.com/emqx/emqx/blob/1.1.3/src/emqttd_protocol.erl
process(Packet = ?CONNECT_PACKET(Var), State0) ->
#mqtt_packet_connect{proto_ver = ProtoVer,
proto_name = ProtoName,
username = Username,
password = Password,
clean_sess = CleanSess,
keep_alive = KeepAlive,
client_id = ClientId} = Var,
State1 = State0#proto_state{proto_ver = ProtoVer,
proto_name = ProtoName,
username = Username,
client_id = ClientId,
clean_sess = CleanSess,
keepalive = KeepAlive,
will_msg = willmsg(Var),
connected_at = os:timestamp()},
trace(recv, Packet, State1),
{ReturnCode1, SessPresent, State3} =
case validate_connect(Var, State1) of
?CONNACK_ACCEPT ->
case emqttd_access_control:auth(client(State1), Password) of
ok ->
%% Generate clientId if null
State2 = maybe_set_clientid(State1),
validate_connect(Connect = #mqtt_packet_connect{}, ProtoState) ->
case validate_protocol(Connect) of
true ->
case validate_clientid(Connect, ProtoState) of
true ->
?CONNACK_ACCEPT;
false ->
?CONNACK_INVALID_ID
end;
false ->
?CONNACK_PROTO_VER
end.
%% MQTT3.1.1 allow null clientId.
validate_clientid(#mqtt_packet_connect{proto_ver =?MQTT_PROTO_V311,
client_id = ClientId}, _ProtoState)
when size(ClientId) =:= 0 ->
true;
1、validate_connect(Var, State1)
先检查客户端使用的mqtt协议版本,检查clientid是否为空,如果是mqtt v3.1.1版本,可以为空。
https://github.com/emqx/emqx/blob/1.1.3/include/emqttd_protocol.hrl 源文件有定义validate_connect函数的返回值:
%%--------------------------------------------------------------------
%% MQTT Connect Return Codes
%%--------------------------------------------------------------------
-define(CONNACK_ACCEPT, 0). %% Connection accepted
-define(CONNACK_PROTO_VER, 1). %% Unacceptable protocol version
-define(CONNACK_INVALID_ID, 2). %% Client Identifier is correct UTF-8 but not allowed by the Server
-define(CONNACK_SERVER, 3). %% Server unavailable
-define(CONNACK_CREDENTIALS, 4). %% Username or password is malformed
-define(CONNACK_AUTH, 5). %% Client is not authorized to connect
2、emqttd_access_control:auth(client(State1), Password)
再做clientid和username的合法性校验,是否在用户的内存数据库或者mysql等地方。这个步骤可有可无,完全由用户自己使能决定。在emq2.3.11版本,这个函数是通过插件来实现的,分别是emq_auth_clientid和emq_auth_username。插件是选配件。
3、%% Generate clientId if null
State2 = maybe_set_clientid(State1),
如果clientid为空,随机生成clientid。例如'emqttd_105789339469322'。
4、模块emqttd_guid:new()负责生成。
%% Generate a client if if nulll
maybe_set_clientid(State = #proto_state{client_id = NullId})
when NullId =:= undefined orelse NullId =:= <<>> ->
{_, NPid, _} = emqttd_guid:new(),
ClientId = iolist_to_binary(["emqttd_", integer_to_list(NPid)]),
State#proto_state{client_id = ClientId};
maybe_set_clientid(State) ->
State.
5、Process函数的最末尾,服务器会发送connack数据包给到客户端,里面的reruncode就是前面第1条提到的宏定义返回值。
%% Send connack
send(?CONNACK_PACKET(ReturnCode1, sp(SessPresent)), State3);
注意:如果服务器发送了一个非零返回值的CONNACK报文,那么客户端就必须关闭网络连接。
二、源码emq 2.3.11 -- emqttd_protocol.erl
https://github.com/emqx/emqx/blob/v2.3.11/src/emqttd_protocol.erl
情况类似,不赘述
%% MQTT3.1.1 allow null clientId.
validate_clientid(#mqtt_packet_connect{proto_ver =?MQTT_PROTO_V4,
client_id = ClientId}, _ProtoState)
when byte_size(ClientId) =:= 0 ->
true;
要注意的是,emq2.3.11使用了宏MQTT_PROTO_V4,其实和emq1.1.3版本的宏MQTT_PROTO_V311,数值是一样的,都代表mqtt协议3.1.1版本。
https://github.com/emqx/emqx/blob/v2.3.11/include/emqttd_protocol.hrl
%%--------------------------------------------------------------------
%% MQTT Protocol Version and Levels
%%--------------------------------------------------------------------
-define(MQTT_PROTO_V3, 3).
-define(MQTT_PROTO_V4, 4).
-define(MQTT_PROTO_V5, 5).
-define(PROTOCOL_NAMES, [
{?MQTT_PROTO_V3, <<"MQIsdp">>},
{?MQTT_PROTO_V4, <<"MQTT">>},
{?MQTT_PROTO_V5, <<"MQTT">>}]).