最近在学习计算机网络通信方便的知识,就凭自己想的编写了一个局域网的联机小游戏,游戏本身没什么意思,主要是想练习一下网络通信的编程。
为什么是局域网的呢?因为寝室里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太频繁了?但是网络通信的游戏必须保持同步啊!这部分一直没搞明白。
在编写过程中碰到了很多没有解决的问题,如果哪位牛人注意到了,能不能指点一二?在此万分感谢!