ET框架---TService和KService学习笔记

TService和KService学习笔记

请大家关注我的微博:@NormanLin_BadPixel坏像素


这里我默认大家已经了解TCP跟UDP咯。如果真有不了解的,这里提供一个传送门学习,保证学会。

因为3.0更新后把UDP变为KCP了,这里给不了解KCP的同学提供一个传送门学习,保证学会。

千万别因为变为KCP就不去了解UDP了,KCP的底层协议一般就是UDP。

TService

继承自AService

private TcpListener acceptor;

呃。。虽然我对TCP跟UDP了解了,但不代表我了解了它们对应的编程呀。对于只在大二JAVA网络编程课程中接触了下网络编程的我,看来还是得先补补基础,了解一下TcpListener这篇博文TCPListenerTCPClient都有很详细的解释。

private readonly Dictionary<long, TChannel> idChannels = new Dictionary<long, TChannel>();

之前我们看过了AChannel,现在我们看到它的具体派生类了,我们进去看看。

TChannel

我的龟龟,好长。分P吧。TChannel学习笔记

T_T,没想到,才两段代码,我们就花了大量时间去学习。我们继续回到TService

TService

/// <summary>
/// 即可做client也可做server
/// </summary>
/// <param name="host"></param>
/// <param name="port"></param>
public TService(string host, int port)
{
    this.acceptor = new TcpListener(new IPEndPoint(IPAddress.Parse(host), port));
    this.acceptor.Start();
}

这就是建立一个TcpListener的方法。

public void Add(Action action)
{
    this.actions.Enqueue(action);
}

public override AChannel GetChannel(long id)
{
    TChannel channel = null;
    this.idChannels.TryGetValue(id, out channel);
    return channel;
}

这两段代码就不用我多说了吧?我相信大家都是聪明人。

public override async Task<AChannel> AcceptChannel()
{
    if (this.acceptor == null)
    {
        throw new Exception("service construct must use host and port param");
    }
    TcpClient tcpClient = await this.acceptor.AcceptTcpClientAsync();
    TChannel channel = new TChannel(tcpClient, this);
    this.idChannels[channel.Id] = channel;
    return channel;
}

这段代码,首先通过TcpListener异步获取到TcpClient,再根据这个TcpClient创建TChannel,存起来并返回。这就是一个异步获取消息通道AChannel的方法。

public override AChannel ConnectChannel(string host, int port)
{
    TcpClient tcpClient = new TcpClient();
    TChannel channel = new TChannel(tcpClient, host, port, this);
    this.idChannels[channel.Id] = channel;

    return channel;
}

这段代码,创建了一个新的连接,并且根据新的连接获得新的TChannel,储存并返回。

我们从这里便可以了解到,一个是从已有的连接当中拉出一个新的消息通道,一个是重新创建一个新的连接,并返回一个消息通道。

Remove方法就不说了。就是根据ID从字典里移除对应的数据,并做相应的Dispose处理。

而最后的Update,是对actions这个变量的遍历处理,不过这里我们还看不出这个action具体是做什么的。(3.0版本已经删除)

接下来我们来看看KService,应该跟TService差不多。

KService

public static class KcpProtocalType
{
    public const uint SYN = 1;
    public const uint ACK = 2;
    public const uint FIN = 3;
}

这里,根据我浏览后面的代码可知,是用来标识特殊消息用的。具体什么特殊消息,往下看咯。

private readonly Dictionary<long, KChannel> idChannels = new Dictionary<long, KChannel>();

哈哈哈,这怕是一篇衍生最多的笔记了,我们去看看 ET—KChannel学习笔记

之后的变量作者已经给出了注释。我们来看一下其带参数的构造函数。

public KService(IPEndPoint ipEndPoint)
{
    this.TimeNow = (uint)TimeHelper.Now();
    this.socket = new UdpClient(ipEndPoint);

#if SERVER
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
    {
        const uint IOC_IN = 0x80000000;
        const uint IOC_VENDOR = 0x18000000;
        uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
        this.socket.Client.IOControl((int)SIO_UDP_CONNRESET, new[] { Convert.ToByte(false) }, null);
    }
#endif

    this.StartRecv();
}

这里#if SERVER … #endif是Unity宏定义的用法。我们看到,在实例化KService的时候,就调用了StartRecv方法,这个方法应该就是关键了。很长,但我也贴出来。代码一长就容易占篇幅,所以我希望大家学习的时候都能有一份源代码在边上,也方便自己扩展学习。

public async void StartRecv()
{
    while (true)
    {
        if (this.socket == null)
        {
            return;
        }

        UdpReceiveResult udpReceiveResult;
        try
        {
            udpReceiveResult = await this.socket.ReceiveAsync();
        }
        catch (Exception e)
        {
            Log.Error(e.ToString());
            continue;
        }

        int messageLength = udpReceiveResult.Buffer.Length;

        // 长度小于4,不是正常的消息
        if (messageLength < 4)
        {
            continue;
        }

        // accept
        uint conn = BitConverter.ToUInt32(udpReceiveResult.Buffer, 0);

        // conn从1000开始,如果为1,2,3则是特殊包
        switch (conn)
        {
            case KcpProtocalType.SYN:
                // 长度!=8,不是accpet消息
                if (messageLength != 8)
                {
                    break;
                }
                this.HandleAccept(udpReceiveResult);
                break;
            case KcpProtocalType.ACK:
                // 长度!=12,不是connect消息
                if (messageLength != 12)
                {
                    break;
                }
                this.HandleConnect(udpReceiveResult);
                break;
            case KcpProtocalType.FIN:
                // 长度!=12,不是DisConnect消息
                if (messageLength != 12)
                {
                    break;
                }
                this.HandleDisConnect(udpReceiveResult);
                break;
            default:
                this.HandleRecv(udpReceiveResult, conn);
                break;
        }
    }
}

async标签让我们知道了里面是可以调用异步方法的。

前面几段的作用是以异步方式获取一台远程主机返回的UDP数据报。

我们来看看KService对集中特殊消息的处理,分别是accpet消息connect消息DisConnect消息。如果不是特殊处理,则分到HandleRecv

private void HandleAccept(UdpReceiveResult udpReceiveResult)
{
    if (this.acceptTcs == null)
    {
        return;
    }

    uint requestConn = BitConverter.ToUInt32(udpReceiveResult.Buffer, 4);

    // 如果已经连接上,则重新响应请求
    KChannel kChannel;
    if (this.idChannels.TryGetValue(requestConn, out kChannel))
    {
        kChannel.HandleAccept(requestConn);
        return;
    }

    TaskCompletionSource<AChannel> t = this.acceptTcs;
    this.acceptTcs = null;
    kChannel = this.CreateAcceptChannel(udpReceiveResult.RemoteEndPoint, requestConn);
    kChannel.HandleAccept(requestConn);
    t.SetResult(kChannel);
}

之后那几个方法都能看懂,但是我们还不是很清楚它的作用。
这里我疑惑的是,为什么AcceptChannelId是用IdGenerater递增的,而Connect的则是随机的。
最后我们看一下Update方法,这里管理了该Service下所有Channel的更新。
我们之前略过的AddToUpdateAddToNextTimeUpdate方法,就是往更新队列和定时更新的队列中添加需要更新的Channel

public override void Update()
{
    this.TimeNow = (uint)TimeHelper.Now();

    while (true)
    {
        if (this.timerMap.Count <= 0)
        {
            break;
        }
        var kv = this.timerMap.First();
        if (kv.Key > TimeNow)
        {
            break;
        }
        List<long> timeOutId = kv.Value;
        foreach (long id in timeOutId)
        {
            this.updateChannels.Add(id);
        }
        this.timerMap.Remove(kv.Key);
    }
    foreach (long id in updateChannels)
    {
        KChannel kChannel;
        if (!this.idChannels.TryGetValue(id, out kChannel))
        {
            continue;
        }
        if (kChannel.Id == 0)
        {
            continue;
        }
        kChannel.Update(this.TimeNow);
    }
    this.updateChannels.Clear();

    while (true)
    {
        if (this.removedChannels.Count <= 0)
        {
            break;
        }
        long id = this.removedChannels.Dequeue();
        this.idChannels.Remove(id);
    }
}

在更新Channel前,会先检查延迟更新队列,看看里面是否有已经到时的Channel,如果有,则把Channel加入更新队列。我们知道multMap会根据键值自动排序,而timerMap的键值是时间,所以这样加入更新队列的Channel也是按时间排好序的。检查完延迟更新队列,这时候更新队列里的就是这次更新需要更新的Channel了。所以,一个简单的遍历,调用ChannelUpdate方法。然后,清空更新队列。延迟更新队列在检查的时候就进行了相应的清理。最后,则是检查Channel字典中是否有需要移除的Channel,如果有,则移除。

讲完了,大家别忘了我们来时的地方

结束语

无语。

在DELPHI FMX(FireMonkey)框架下创建一个服务,通常是指开发一个后台运行的服务进程,它可以在应用程序运行时不直接依赖于用户界面。你可以通过以下几个步骤来实现: 1. **安装必要的库**: 首先确保你已经安装了FMX库以及相关的网络和多线程支持。 2. **新建项目**: 打开DELPHI IDE,选择“File” -> “New” -> “Project”,然后选择“Mobile/FMX”类别,选择适合的服务项目模板,例如“TService”或自定义组件。 3. **设计服务组件**: 在新项目,创建一个新的“TService”组件,这是基础的服务类。如果需要更复杂的逻辑,可以继承自更高级别的抽象服务,如`TFMXService`。 4. **实现OnExecute事件**: 这是服务的核心部分,当你注册服务并启动时,这个事件会被触发。在这里编写你的业务逻辑,比如定时任务、数据处理等。 ```delphi procedure TForm1.MyServiceComponent.Execute; begin // 你的服务逻辑代码 while not Terminated do begin // 每隔一段时间执行一次任务或其他操作 Sleep(60 * 1000); // 间隔60秒 // 示例:发送数据到服务器 MyDataSender.Send(MyData); end; if Terminated then begin // 关闭连接或清理资源 Disconnect; end; end; ``` 5. **注册服务**: 在`Initialize`或`OnStart`方法,你需要注册你的服务,并设置其生命周期管理。 ```delphi procedure TForm1.FormCreate(Sender: TObject); begin RegisterMyService; // 自定义函数,注册服务 end; function RegisterMyService: Boolean; var Service: TService; begin Service := MyServiceComponent; // 将组件实例化为服务 try Service.LifecycleEvents.OnStarting.Add(self, ServiceOnStarting); Service.Starting; // 开始服务 Result := True; except on E: Exception do ShowMessage('无法启动服务: ' + E.Message); Result := False; end; end; ``` 6. **停止服务**: 在适当的时候(如关闭应用时),调用服务的`Terminate`方法停止服务。 7. **异常处理**: 记得在整个服务过程加入适当的错误处理,以防止程序崩溃。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值