Dmtp基础功能
定义
命名空间:TouchSocket.Dmtp
程序集:TouchSocket.Dmtp.dll
一、连接
连接验证可以初步保证连接客户端的安全性。框架内部默认使用一个string
类型的Token
作为验证凭证。当然也允许服务器进行其他验证。具体如下:
1.1 Token验证
在服务器或客户端的配置上,设置VerifyToken
,即可实现字符串Token
验证。
var config = new TouchSocketConfig()
.SetDmtpOption(new DmtpOption()
{
VerifyToken = "Dmtp"
});
1.2 动态验证
使用插件,实现IDmtpHandshakingPlugin插件。然后可以自行判断一些信息,例如元数据等。
如果是特定协议的Dmtp
,还可以判断一些特定信息,例如,在TcpDmtp
中,可以判断IP
地址等。
客户端,在连接时,可以设置元数据。
var config = new TouchSocketConfig()
.SetDmtpOption(new DmtpOption()
{
Metadata=new Metadata().Add("a","a")
});
服务端使用 插件验证连接信息。
internal class MyVerifyPlugin : PluginBase, IDmtpHandshakingPlugin
{
public async Task OnDmtpHandshaking(IDmtpActorObject client, DmtpVerifyEventArgs e)
{
if (e.Metadata["a"] != "a")
{
e.IsPermitOperation = false;//不允许连接
e.Message = "元数据不对";//同时返回消息
e.Handled = true;//表示该消息已在此处处理。
return;
}
if (client is ITcpDmtpSessionClient sessionClient)
{
//在特定协议的情况下,可以获取特定信息
var ip = sessionClient.IP;
}
if (e.Token == "Dmtp")
{
e.IsPermitOperation = true;
e.Handled = true;
return;
}
await e.InvokeNext();
}
}
1.4 跨语言
为使Dmtp
支持跨语言,Dmtp
在设计之初就预留了跨语言连接的便利性。诚如Dmtp描述所示,其基础数据报文为Head+Flags+Length+Data
。而框架内部的Handshake
、Ping
、Pong
、Close
等指令均是采用Json
数据格式。但是即使如此,连接时的真正数据,还与其基础协议有关。具体如下:
以连接、操作TcpDmtpService
为例。其基础协议即为tcp
,则使用常规的tcp
客户端即可模拟链接。
using var tcpClient = new TcpClient();//创建一个普通的tcp客户端。
tcpClient.Received = (client, e) =>
{
//此处接收服务器返回的消息
var head = e.ByteBlock.ToArray(0, 2);
e.ByteBlock.Seek(2, SeekOrigin.Begin);
var flags = e.ByteBlock.ReadUInt16(EndianType.Big);
var length = e.ByteBlock.ReadInt32(EndianType.Big);
var json = e.ByteBlock.Span.ToString(Encoding.UTF8);
ConsoleLogger.Default.Info($"收到响应:flags={flags},length={length},json={json.Replace("\r\n", string.Empty).Replace(" ", string.Empty)}");
return Task.CompletedTask;
};
//开始链接服务器
await tcpClient.ConnectAsync("127.0.0.1:7789");
//以json的数据方式。
//其中Token、Metadata为连接的验证数据,分别为字符串、字符串字典类型。
//Id则表示指定的默认id,字符串类型。
//Sign为本次请求的序号,一般在连接时指定一个大于0的任意数字即可。
var json = @"{""Token"":""Dmtp"",""Metadata"":{""a"":""a""},""Id"":null,""Sign"":1}";
//将json转为utf-8编码。
var jsonBytes = Encoding.UTF8.GetBytes(json);
using (var byteBlock = new ByteBlock())
{
//按照Head+Flags+Length+Data的格式。
byteBlock.Write(Encoding.ASCII.GetBytes("dm"));
byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes((ushort)1));
byteBlock.Write(TouchSocketBitConverter.BigEndian.GetBytes((int)jsonBytes.Length));
byteBlock.Write(jsonBytes);
await tcpClient.SendAsync(byteBlock.Memory);
}
await Task.Delay(2000);
接收输出:
收到的Json
字符串,会返回服务器最终修改的Token
、Metadata
。同时还包括分配或指定的Id
。Sign
会与请求时一致,表示这是同一组请求。Status
等于1
即为连接成功。其他值则可能在Message
表明连接失败的原因。
收到响应:flags=2,length=124,json={"Token":"Dmtp","Metadata":{"a":"a"},"Id":"1","Message":null,"Sign":1,"Status":1}
其他Json
请求,包括:
【请求连接】
- Token:连接令牌,字符串类型。
- Metadata:连接元数据,字典类型。
- Id:初始连接Id,字符串类型。
- Sign:请求序号,整数类型,应当每次请求递增。
- Status(仅返回时携带):连接状态,整数类型,为1时成功,其他状态可以获取Message。
- Message(仅返回时携带):连接消息。
{"Token":"Dmtp","Metadata":{"a":"a"},"Id":null,"Sign":1}
【Ping】
- Sign:请求序号,整数类型,应当每次请求递增。
- Status(仅返回时携带):连接状态,整数类型,为1时成功,其他状态可以获取Message。
- Message(仅返回时携带):连接消息。
- Route:是否路由,布尔类型,当TargetId不为空时应设为True。
- SourceId:源Id,字符串类型,当需要Ping其他客户端时,应该将该值设为自身Id,以保证返回的信息能够顺利路由回来。
- TargetId:目标Id,字符串类型,当需要Ping其他客户端时,应该将该值设为目标Id,以保证信息能够顺利送达。
{"Sign":0,"Status":0,"Message":null,"Route":false,"SourceId":null,"TargetId":null}
【Close】
Close
报文直接采用utf8编码的字符串。
"close"
DMTP基础功能是比较容易跨语言的,但这并不意味着DMTP所有的功能都支持。其扩展功能,则需要实际的跨平台设计。不过依靠简单的Head+Flags+Length+Data的格式,也能自己实现很多的功能。
二、Id同步
在Dmtp中,存在于服务器的辅助客户端(SessionClient),与远程客户端(Client)是一一对应关系,其Id也完全一致。所以在任意一方修改Id(调用ResetId),都会同时修改远程Id。所以合理使用该操作,可以完成复用Id(重置Id)的需求。
三、发送数据
Dmtp提供协议发送数据,又叫协议扩展功能,就是对现有的Dmtp进行自定义的扩展协议。
使用起来是非常简单的,每个DmtpActor,都实现了Send方法接口。
第一个参数为ushort
类型,使用者可以约定任意大于20数值。
client.SendAsync(1000,Encoding.UTF8.GetBytes("RRQM"));
Flags
不要使用小于20的,因为框架内部在使用。并且小于100的也最好不要使用,因为可能其他组件也在使用。
在接收方订阅IDmtpReceivedPlugin
,已经包含了协议参数,所以直接自行筛选即可。
internal class MyFlagsPlugin : PluginBase, IDmtpReceivedPlugin
{
public async Task OnDmtpReceived(IDmtpActorObject client, DmtpMessageEventArgs e)
{
if (e.DmtpMessage.ProtocolFlags == 1000)
{
//判断完协议以后,从 e.DmtpMessage.BodyByteBlock可以拿到实际的数据
string msg = e.DmtpMessage.BodyByteBlock.ToString();
return;
}
//flags不满足,调用下一个插件
await e.InvokeNext();
}
}