上一节给大家演示了建立连接的关键代码,连接建立好后,就可以进行数据传输了。数据传输包含从服务器端到客户端和从客户端到服务器端,两者差别不大。
数据的传输,TcpClient的GetNetworkStream是关键,通过它我们可以得到NetworkStream网络流,客户端和服务器主要的工作就是对其读出和写入。关于如何构造稳定且性能好的网络应用,如何进行复杂的封包和解包,这里我们不考虑,我们使用StreamReader和StreamWriter来封装NetStream,读取和写入的代码如下
TcpClient client; StreamReader sr; StreamWriter sw; this.userName = ""; NetworkStream netStream = client.GetStream(); sr = new StreamReader(netStream, System.Text.Encoding.UTF8); sw = new StreamWriter(netStream, System.Text.Encoding.UTF8); //读取数据 string output=sr.ReadLine(); //写入数据 string input=""; sw.WriteLine(); sw.Flush(); |
客户端和服务器的通信一般是由客户端向服务器端发请求,服务器端接受请求并处理,再将结果重新发回客户端
发送请求和应答的协议格式可以自己定义,游戏中使用的协议格式为 命令,参数1,参数2……
先看一下客户端请求登录
service.SendToServer("Login," + textBoxName.Text.Trim()); class Service { ListBox listbox; StreamWriter sw; public Service(ListBox listbox, StreamWriter sw) { this.listbox = listbox; this.sw = sw; } public void SendToServer(string str) { sw.WriteLine(str); sw.Flush(); } } |
服务器接受请求并处理,一般来说是根据不同的请求创建不同的线程进行处理,见本节后半部分
private void ReceiveData(object obj) { //User类封装了TcpClient,StreamReader和StreamWriter User user = (User)obj; TcpClient client = user.client; string receiveString = null; receiveString = sr.ReadLine(); service.SetListBox(string.Format("来自{0}:{1}", user.userName, receiveString)); string[] splitString = receiveString.Split(','); switch (splitString[0]) { case "Login": user.userName = string.Format("[{0}--{1}]", splitString[1], client.Client.RemoteEndPoint); service.SendToOne(user, "Welcome"); break; default: break; } } |
class Service { private ListBox listbox; private delegate void SetListBoxCallback(string str); private SetListBoxCallback setListBoxCallback; public Service(ListBox listbox) { this.listbox = listbox; setListBoxCallback = new SetListBoxCallback(SetListBox); } public void SetListBox(string str) { if (listbox.InvokeRequired) { listbox.Invoke(setListBoxCallback, str); } else { listbox.Items.Add(str); listbox.SelectedIndex = listbox.Items.Count - 1; listbox.ClearSelected(); } } public void SendToOne(User user, string str) { try { user.sw.WriteLine(str); user.sw.Flush(); SetListBox(string.Format("向{0}发送{1}", user.userName, str)); } catch { SetListBox(string.Format("向{0}发送信息失败", user.userName)); } } } |
服务器负责监听并根据不同的用户启动不同的线程
IPAddress localAddress; int port = 51888; TcpListener myListener; Service service = new Service(listbox); IPAddress[] addrIP = Dns.GetHostAddresses(Dns.GetHostName()); localAddress = addrIP[0]; myListener = new TcpListener(localAddress, port); myListener.Start(); service.SetListBox(string.Format("开始在{0}:{1}监听客户连接", localAddress, port)); ThreadStart ts = new ThreadStart(ListenClientConnect); Thread myThread = new Thread(ts); myThread.Start(); |
ListenClientConnect方法
while (true) { TcpClient newClient = null; try { newClient = myListener.AcceptTcpClient(); } catch { break; } User user = new User(newClient); userList.Add(user); service.SetListBox(string.Format("{0}进入", newClient.Client.RemoteEndPoint)); service.SetListBox(string.Format("当前连接用户数:{0}",userList.Count)); //basilwang 2008-09-06 to receive data from client ParameterizedThreadStart pts = new ParameterizedThreadStart(ReceiveData); Thread threadReceive = new Thread(pts); threadReceive.Start(user); } |
此处的启用了ParameterizedThreadStart 线程处理带参数的ReceiveData方法,主要是防止同客户端传输数据的过程出现的异常不能正确处理导致线程崩溃,每一个客户端分配一个线程可以保证客户端各自的独立性;同时ParameterizedThreadStart允许传递参数。
至此我们完成了一个简单的服务器客户端应答,希望能对初学者有所帮助。
看过网上很多的类似系列教程(博客园包包版网络大厅的+桥牌系统),写的很深入,感觉比较复杂,初学者不宜上手。我是在学习WCF的时候,发现自己对底层的传输原理都没有搞明白,于是又回头学习网络传输的一些知识,自己写了一个简单的网络游戏黑白棋,因此也想把学习的一个过程记录下来和初学者们一块交流。我的只是小儿科,还请网友多多包涵,高手们也不要吝惜你们的砖头。
第一次写系列教程,心里没底,本来自己水平就一般,却要完成这个命题作文,难煞我了。好歹程序写的差不多了(不过还没有最终完成),这里先把完成的部分分章介绍一下,程序我在慢慢补。
本系列源代码TCP网络传输参考《C#网络应用高级编程》人民邮电出版社,马骏编,黑白棋游戏部分为本人(http://www.basilwang.net)编写。
写这个游戏只是为了我个人理解基于TCP网络游戏编程的基本思路,算法部分写的比较乱,没有优化,不过我都做了注释,方便大家阅读。
先说一下黑白棋,又叫反棋(Reversi)、奥赛罗棋(Othello),苹果棋,翻转棋。黑白棋在西方和日本很流行。但是这个游戏在中国目前还不够推广,下棋的水平还不高。黑白棋规则很简单,只要肯花点脑筋,新手也能玩得很好。因为棋盘小,下一局棋所花的时间也不多。对于黑白棋,有一种说法是:只需要几分钟学会它,却需要一生的时间去精通它(a minute to learn, a lifetime to master)。
早些年文曲星里面带的游戏就喜欢它了,我这个人脑子不开窍,很多游戏连电脑AI都打不过,好容易整了个能打败AI的游戏,还不往死里玩,嘿嘿。不过我写的这个黑白棋只是供网络对战使用,不涉及AI算法部分(我还不知道怎么做呢),斗胆发到网上,权作抛砖引玉,废话少说,进入正题。
系列的介绍打算以我学习TCP网络编程的过程为顺序,每一篇教程都能够完成功能,附上的源代码能够独立运行,我会把代码中碰到的相关知识做相应的介绍,使初学者能有直观的认识。
游戏完成的部分
1 网络大厅,可自定义桌数,人数 (完全参照《C#网络应用高级编程》,马老师应该不介意吧)
2 黑白棋游戏客户端
2.1 吃子
2.2 奇偶数统计
2.3 轮流下子
2.4 终局胜负提示
未完成部分
1 黑白棋游戏客户端
1.1 下子时间限制
1.2 甲方无子可下时,程序判定由乙方下子,甲方丢掉一次机会
1.3 判定任一方无子可下的程序(思路:需要计算盘中空子的列表,然后调用已完成的吃子程序,看能否下子,但并不真正下子)
源程序写的很简单,界面比较简陋,只是为了帮助大家更好的理解基于TCP的网络游戏传输的基本原理,还请大家多包涵。

