一个局域网联机小游戏

   最近在学习计算机网络通信方便的知识,就凭自己想的编写了一个局域网的联机小游戏,游戏本身没什么意思,主要是想练习一下网络通信的编程。

   为什么是局域网的呢?因为寝室里4个人用一个路由器上网,我不知道怎么绕过路由器通信,也没有条件用公网来测试。

   这是服务器的代码,是一个命令行程序:

   class Server : IDisposable
    {
        private PlayerSet playerList;
        private int ServerPort, udpPort;
        private byte[] buf;
        private NetworkStream netStr;
        private Socket sock;
        private String answerMsg = "ok";
        private Thread udpTh1, udpTh2;
        private UdpClient udpSendCon, udpRecCon;
        private TcpListener logLis, logoutLis;
        public Server()
        {
            buf = new byte[64];
            playerList = new PlayerSet();
            ServerPort = 1118;
            udpPort = 2000;
            udpSendCon = new UdpClient(udpPort);
            udpRecCon = new UdpClient(udpPort - 1);
            logLis = new TcpListener(ServerPort);
            logoutLis = new TcpListener(ServerPort + 1);
        }
        private void regPlayer()
        {
            try
            {
                netStr = new NetworkStream(sock);
                lock (netStr)
                {
                    //接受登陆信息
                    netStr.Read(buf, 0, buf.Length);
                    write("received : " + new String(Encoding.UTF8.GetChars(buf)).Replace("/0", ""));
                    Console.WriteLine("From " + sock.RemoteEndPoint + " in " + sock.ProtocolType);
                }
                //生成玩家数据
                buf = dataGenerator();
                lock (netStr)
                {
                    //发送确认信息和玩家数据
                    write("one player login...");
                    byte[] tmp = Encoding.UTF8.GetBytes(answerMsg.ToCharArray());
                    netStr.Write(tmp, 0, tmp.Length);
                    netStr.Write(buf, 0, buf.Length);
                    write("player data allocated");
                }
                lock (netStr)
                {
                    //注册用户信息
                    SoapFormatter soap = new SoapFormatter();
                    Player player = (Player)soap.Deserialize(netStr);
                    player.id = BitConverter.ToInt32(buf, 2);
                    playerList.playerSet.Add(player.id, player);
                    netStr.Close();
                    write("player: " + player.name + "--" + player.id + " has been registed/r/n");
                }
                write("-------------------------------------------------");
                buf = new byte[64];
            }
            catch (Exception e)
            {
                write(e.Message);
            }
        }
        private Byte[] dataGenerator()
        {
            int time = (int)DateTime.Now.Ticks;
            Int16 Px = (Int16)(time % 830+30);
            time = (int)DateTime.Now.Ticks;
            Int16 Py = (Int16)(time % 600);
            byte[] bTmp = BitConverter.GetBytes(time);
            return new byte[] { (byte)Px, (byte)Py, (byte)bTmp[0],
                (byte)bTmp[1], (byte)bTmp[2], (byte)bTmp[3] };
        }
        private void write(String msg)
        {
            Console.WriteLine(msg + " At " + DateTime.Now);
        }
        public void start()
        {
            write("Server begin to work...");
            logLis.Start();
            sock = logLis.AcceptSocket();
            Thread Logout = new Thread(new ThreadStart(logoutL));
            Logout.IsBackground = true;
            Logout.Start();
            while (true)
            {
                if (sock.Connected)
                {
                    //处理登陆
                    Thread loginTh = new Thread(new ThreadStart(regPlayer));
                    loginTh.IsBackground = true;
                    loginTh.Start();
                    //保持连接
                    if (udpTh1 == null)
                    {
                        udpTh1 = new Thread(new ThreadStart(keepSendCon));
                        udpTh1.IsBackground = true;
                        udpTh1.Start();
                    }
                    if (udpTh2 == null)
                    {
                        udpTh2 = new Thread(new ThreadStart(keepRecCon));
                        udpTh2.IsBackground = true;
                        udpTh2.Start();
                    }
                    logLis.Start();
                    sock = logLis.AcceptSocket();
                }
            }
        }
        public void logoutL()
        {
            while (true)
            {
                logoutLis.Start();
                Socket sok = logoutLis.AcceptSocket();
                NetworkStream str = new NetworkStream(sok);
                byte[] id = new byte[4];
                str.Read(id, 0, id.Length);
                lock (playerList)
                {
                    playerList.playerSet.Remove(BitConverter.ToInt32(id, 0));
                }
                write("player: " + BitConverter.ToInt32(id, 0) + " logout");
                write("-------------------------------------------------");
                sok.Close();
            }
        }
        public void keepSendCon()
        {
            byte[] soapBuf;
            MemoryStream memBuf;
            StreamReader reader;
            SoapFormatter soap = new SoapFormatter();
            while (true)
            {
                lock (playerList)
                {
                    foreach (Object ob in playerList.playerSet.Values)
                    {
                        //发送玩家数据
                        memBuf = new MemoryStream();
                        reader = new StreamReader(memBuf);
                        Player pl = (Player)ob;
                        soap.Serialize(memBuf, playerList);
                        memBuf.Position = 0;
                        soapBuf = Encoding.UTF8.GetBytes(reader.ReadToEnd());
                        udpSendCon.Send(soapBuf, soapBuf.Length, pl.ipEnd);
                        reader.Dispose();
                        memBuf.Dispose();
                    }
                }
                Thread.Sleep(50);
            }
        }
        public void keepRecCon()
        {
            IPEndPoint any = new IPEndPoint(IPAddress.Any, udpPort + 2);
            SoapFormatter soap = new SoapFormatter();
            while (true)
            {
                MemoryStream mom = new MemoryStream();
                byte[] soapTmp = udpRecCon.Receive(ref any);
                mom.Write(soapTmp, 0, soapTmp.Length);
                mom.Position = 0;
                Player play = (Player)soap.Deserialize(mom);
                lock (playerList)
                {
                    playerList.playerSet[play.id] = play;
                }
                mom.Dispose();
            }
        }
        #region IDisposable 成员

        public void Dispose()
        {
            udpTh1.Abort();
            udpTh2.Abort();
        }

        #endregion
    }

 

    流程是先监听TCP传来的登陆请求,收到请求后启动一个子进程,在子进程中生成分配给玩家的数据--所在的坐标,然后把数据回发给客户端。然后监听UDP端口的玩家移动的数据,修改玩家列表对应的数据,同时还有个一进程用来广播发送玩家列表给客户端。

    在客户端中

public void sendMove(Player pl)
        {
            byte[] soapBuf;
            SoapFormatter soap = new SoapFormatter();
            MemoryStream memBuf = new MemoryStream();
            StreamReader reader = new StreamReader(memBuf);
            soap.Serialize(memBuf, pl);
            memBuf.Position = 0;
            soapBuf = Encoding.UTF8.GetBytes(reader.ReadToEnd());
            udpNet2.Send(soapBuf, soapBuf.Length, severRecEnd);
            reader.Dispose();
            memBuf.Dispose();
        }

    用来发送本地玩家移动位置的数据给服务器,使用的是SOAP方式,过程中发现直接发送序列化之后的数据到UDP包中,然后在服务器直接使用NetWorkStream作为参数来反序列化这种方式会导致解析错误,不知道什么原因,希望有知道的人可以告诉一下,谢谢了!

    public PlayerSet getPlayersData()
        {
            MemoryStream memBuf = new MemoryStream();
            byte[] pData = udpNet1.Receive(ref severSendEnd);
            memBuf.Write(pData, 0, pData.Length);
            memBuf.Position = 0;
            SoapFormatter soap = new SoapFormatter();
            PlayerSet plTmp = (PlayerSet)soap.Deserialize(memBuf);
            memBuf.Dispose();
            return plTmp;
        }

以上这段代码是用来获取所有玩家的坐标,提供给绘图程序重绘界面的,我设定的时间是50ms一次,如下:       

public void getPlayersData()
        {
            refresh refr = new refresh(mapRf);
            while (true)
            {
                lock (playerList)
                {
                    playerList = net.getPlayersData();
                }
                try
                {
                    if (!form.IsDisposed)
                        form.Invoke(refr);
                }
                catch
                { }
                Thread.Sleep(50);
            }
        }

   由于在非界面进程中操作控件会抛出异常并且无效,我在网上找了一个使用委托之后控件回调的方法。

   最后是绘图代码:

public void paintMap(PaintEventArgs e)
        {
            //BufferedGraphicsContext curG = BufferedGraphicsManager.Current;
            //BufferedGraphics bufG = curG.Allocate(e.Graphics, e.ClipRectangle);
            //Graphics g = bufG.Graphics;
            //g.SmoothingMode = SmoothingMode.HighQuality;
            //g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
            Graphics g = e.Graphics;
            g.Clear(map.BackColor);
            lock (playerList)
            {
                foreach (Object ob in playerList.playerSet.Values)
                {
                    Player pl = (Player)ob;
                    g.DrawImage((Image)pic.ResourceManager.GetObject("header"),pl.position);
                    FontStyle style = FontStyle.Bold;
                    Font font = new Font("微软雅黑", 20,style);
                    g.DrawString(pl.name+":"+pl.profession, font, Brushes.Black,
                        new Point(pl.position.X + 20, pl.position.Y - 30));
                }
            }
            //bufG.Render(e.Graphics);
            //g.Dispose();
            //bufG.Dispose();
        }

   里面注释的部分是使用双缓冲的代码 ,我使用之后发现闪烁更加严重了。。。郁闷,所以就注释掉了,难道是我的50ms太频繁了?但是网络通信的游戏必须保持同步啊!这部分一直没搞明白。

   在编写过程中碰到了很多没有解决的问题,如果哪位牛人注意到了,能不能指点一二?在此万分感谢!

 

你可能感兴趣的:(Technical,Articles)