最近加入了一个开源的mc启动器项目开发组,另外加上本人是一mc的服主,所以想到了在启动器中加入此功能。

众所周知,几乎所有提供服务的服务器为了系统安全禁止了ping,这样多线线路(服务器有多个IP)的情况下,线路的选优全部交给了第三方的DNS系统

而本人颇不喜这种被动选优的方式,于是便想通过游戏的启动器来自动判断服务器多个地址的延迟并选择最好的线路。

这里因为Minecraft服务器走的是tcp端口,就只介绍tcp端口的方法,其实udp也差不多。

该项目只是一个在校学习学生的颇为基础的研究,请勿嘲笑。

思路:

打开端口,发送数据,开始计时然后等待服务端返回数据,一旦接受到数据就返回运行时间。

这样其实程序的流程已经大体明白了,不多说,上菜

static NetworkStream stream = null;
        static string sendstring = "2045";//发送内容 
        static byte[] senddata = <span style="font-family: 'Courier New'; line-height: 18px; white-space: pre-wrap;">Encoding.Default.GetBytes</span>(sendstring);//转换为网络传输用字节数组
        static Stopwatch time = new Stopwatch();//实例化计时
        private static TcpClient t_client = null;//实例化tcp连接
/// <summary>
        /// 获取TCP端口延迟(同步)
        /// <param name="hostname">目标IP地址或主机名</param>
        /// <param name="port">目标端口</param>
        /// </summary>
        public static long tcp(string hostname, string port)
        {

          t_client = new TcpClient();//实例化tcp

            try
            {
                t_client.Connect(hostname, Convert.ToInt32(port));//打开连接
                stream = t_client.GetStream();//获取网络流
                stream.Write(senddata, 0, senddata.Length);//同步发送数据
                time.Restart();//重置计时器并开始计时
                byte[] result = new byte[t_client.Available];
                try
                {
                    /* stream.BeginRead(result, 0, result.Length, new AsyncCallback(ReadCallback), stream);*///异步接受服务器回报的字符串
                    stream.Read(result, 0, result.Length);
                }
                catch { }
                string strResponse = Encoding.ASCII.GetString(result).Trim();//从服务器接受到的字符串
                t_client.Close();//关闭连接
                return time.ElapsedMilliseconds;//返回运行时间

            }
            catch (System.Exception ex)
            {
                return 999999;//打不开端口 发不了数据 均返回99999
            }
        }

这里使用到了官方提供的Stopwatch 来进行计时


代码非常简单 在此就不赘述了

那么现在问题来了 主要有三种情况

1.提供域名和端口要是打不开会导致线程卡住

2.打开了数据发不过去也会卡住

3.数据发过去了,目标服务如果不回复数据的会卡住

这里就需要一种“超时“的功能,简单点讲就是当执行某个操作,一定时间得不到反馈,就弹出

那么首先第一种情况,我们使用的是TcpClient打开的tcp端口,这个类其实是自带超时返还的,我们直接利用try的系统错误代码就行

第二种情况 我是没想好怎么解决,也对此概念不是很清楚,还是觉得自己懂的很少,先放弃了。

第三种情况 这里就需要我们自己写一个超时方法了

这里特别说明下,比如我在tcp的25565端口开启了一个mc服务器的服务,不是往这个端口发什么过去都会返回数据的,往往很多服务,只有你发送特定的字符串或者16进制串过去,服务才会返回内容。

那么干货

/// <summary>
        ///  获取TCP端口延迟(异步)
        /// </summary>
        /// <param name="hostname">目标IP地址或主机名</param>
        /// <param name="port">目标端口</param>
        /// <returns></returns>
        public static long Begintcp(string hostname, string port)
        {
            t_client = new TcpClient();

            try
            {
                t_client.Connect(hostname, Convert.ToInt32(port));//打开连接
                stream = t_client.GetStream();
                stream.Write(senddata, 0, senddata.Length);//同步发送数据
                i = 0;
                time.Restart();//重置计时器并开始计时
                byte[] result = new byte[t_client.Available];
                try
                {
                    stream.BeginRead(result, 0, result.Length, new AsyncCallback(ReadCallback), stream);//异步接受服务器回报的字符串

                }
                catch { }
                BackgroundWorker backWorker = new BackgroundWorker();
                backWorker.WorkerReportsProgress = true;
                backWorker.DoWork += new DoWorkEventHandler(bw_DoWork); //打开一个线程
                if (backWorker.IsBusy != true)
                {
                    backWorker.RunWorkerAsync();
                }
                else
                { }
                while(true)
                {
                    if (i == 1)
                    {
                        time.Stop();
                        break;
                    }
                    else if (i == 2)
                    {
                        return 99999;
                    }
                }
                string strResponse = Encoding.ASCII.GetString(result).Trim();//从服务器接受到的字符串

                t_client.Close();//关闭连接
                return time.ElapsedMilliseconds;


            }
            catch (System.Exception ex)
            {
                return 999999;
            }
        }
        /// <summary>
        /// 读取异步委托
        /// </summary>
        /// <param name="ar"></param>
        private static void ReadCallback(IAsyncResult ar)
        {
            NetworkStream s = (NetworkStream)ar.AsyncState;
            try
            {
                
                    i = 1;
                    s.EndRead(ar);
              
            }
            catch
            { }

        }
 /// <summary>
        /// 超时异步委托
        /// </summary>
        private static void bw_DoWork(object sender, DoWorkEventArgs e)
        {
            Thread.Sleep(5000);//设定超时五秒
            i = 2;
        }



以上,本人用的方法其实很蠢,也很简单粗暴

设定了一个静态变量 i初始值1。

然后异步接收网络流的数据,这里就开了个线程A就专门接受数据,一旦接受到数据就执行委托,委托就是把i设置成1;

在开异步接收数据的同时,再开一个线程B,线程主要作用就是等待五秒(thread.sleep方法) 然后设置i=2;

主线程在开完两个线程之后,立刻进入死循环,不停判断i的值。等于1了 就返回时间,如果5秒钟过后,还没接受到数据,线程B就会将i设定成2 就会返回99999的延迟数据

当然肯定有更好的做法,以后更。

接下来就是针对MC服务端的研究了,如上面所说,你不是随随便便发一个数据过去,mc的服务端就会返回数据的,事实是我发了个”2048“过去,毛都没有返回一个。

然后我就非常机智的从网上下了个tcp服务器,然后用mc尝试连接了下

结果如下图:

java我的世界怎么设置延时命令 我的世界 延时_tcp

然后服务端就接到了这些莫名其妙的16进制数据

然后我再开启了这个小工具的客户端模式,把这个数据发了试一下 然后:

java我的世界怎么设置延时命令 我的世界 延时_minecraft_02

你会发现服务器返还了以下数据

2016-7-8 23:55:52
*-----------------------TCP CLIENT---------------------*
CLIENT->SERVER  1200050C31302E3132322E362E3136345C0D010100
SERVER->CLIENT  .59:23565 xDD
SERVER->CLIENT  .59:23565 10.99.99"},{"modid":"Forge","version":"10.13.4.1481"},{"modid":"kimagine","version":"0.1"},{"modid":"iChunUtil","version":"4.1.2"},{"modid":"SC0_SpaceCore","version":"0.7.14"},{"modid":"recipehandler","version":"1.7.10"},{"modid":"IC2","version":"2.2.804-eѽ
SERVER->CLIENT  .59:23565 xperimental"},{"modid":"nevermine","version":"2.4.B-汉化:§6Remilia"},{"modid":"Morph","version":"0.9.1"},{"modid":"BambooMod","version":"Minecraft@MC_VERSION@ var@VERSION@"},{"modid":"shipwrecks_winslow","version":"1.6.2"},{"modid":"SC0_UsefulPets","verѽ
SERVER->CLIENT  .59:23565 sion":"1.3"},{"modid":"ropebridge","version":"1.3"},{"modid":"FLabsBF","version":"4.3"},{"modid":"levels","version":"r2.3.0"},{"modid":"Oceancraft","version":"1.4.1"},{"modid":"heart","version":"2.0.0"},{"modid":"grapplemod","version":"1.0"},{"modid":"Redsѽ
SERVER->CLIENT  .59:23565 tonePasteMod","version":"1.6.2"},{"modid":"fairylights","version":"1.4.0"},{"modid":"lootablebodies","version":"1.3.4"},{"modid":"harvestcraft","version":"1.7.10j"},{"modid":"AdvancedSolarPanel","version":"1.7.10-3.5.1"}]}}



以上字符采用UTF-8 无bom编码 其他会乱码哦

这里就很明显了 只要发过去”1200050C31302E3132322E362E3136345C0D010100“这个16进制字符串

服务器就会返回当前的mod列表

已经在1.6.4 1.7.1 1.8.4 1.9 版本的服务器上测试过了

完整类库在     

一个学生的研究日志,感谢csdn上众多的文章