惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Security Blog
Microsoft Security Blog
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
美团技术团队
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
aimingoo的专栏
aimingoo的专栏
腾讯CDC
WordPress大学
WordPress大学
Apple Machine Learning Research
Apple Machine Learning Research
F
Fortinet All Blogs
G
Google Developers Blog
MongoDB | Blog
MongoDB | Blog
Microsoft Azure Blog
Microsoft Azure Blog
小众软件
小众软件
Engineering at Meta
Engineering at Meta
博客园_首页
B
Blog RSS Feed
D
Docker
M
MIT News - Artificial intelligence
爱范儿
爱范儿
I
InfoQ

博客园 - BAsil

ubuntu下安装git server以及eclipse egit插件配置 windows下调试android源代码 Mac OS 10.6(Snow Leopard)编译Android源代码 使用Mono Runtime Bundle制作安装包让C#桌面应用程序脱离net framework c++对象成员函数返回自身引用时出现的诡异问题及解决办法 C++指针存储结构 重构一个贪吃蛇游戏(linux c++) Ubuntu 9.10 Server 配置Apache + WebDav 实现文档协同管理 javascript事件机制与jQuery.bind的补充说明 javascript 事件机制 与 jQuery.Bind Ubuntu 9.10 Server 配置基于SSL的Subversion Ubuntu 9.10 Server (Karmic) 迁移Bugzilla 域环境下配置连接sql server 的asp.net网站 注意IIS 7.0的集成管道模式下HttpApplication 请求管道事件的问题 出现Unable to launch Visual Studio's Localhost Web Server问题解决 基于TCP的网络游戏黑白棋系列(四):游戏棋盘 基于TCP的网络游戏黑白棋系列(三):游戏大厅 基于TCP的网络游戏黑白棋系列(一):建立连接 基于TCP的网络游戏黑白棋系列教程开篇
基于TCP的网络游戏黑白棋系列(二):数据传输
BAsil · 2008-09-24 · via 博客园 - BAsil

2008-09-24 14:48  BAsil  阅读(2835)  评论()    收藏  举报

上一节给大家演示了建立连接的关键代码,连接建立好后,就可以进行数据传输了。数据传输包含从服务器端到客户端和从客户端到服务器端,两者差别不大。

数据的传输,TcpClient的GetNetworkStream是关键,通过它我们可以得到NetworkStream网络流,客户端和服务器主要的工作就是对其读出和写入。关于如何构造稳定且性能好的网络应用,如何进行复杂的封包和解包,这里我们不考虑,我们使用StreamReader和StreamWriter来封装NetStream,读取和写入的代码如下

 1 TcpClient client;
 2 StreamReader sr;
 3 StreamWriter sw;
 4 this.userName = "";
 5 NetworkStream netStream = client.GetStream();
 6 sr = new StreamReader(netStream, System.Text.Encoding.UTF8);
 7 sw = new StreamWriter(netStream, System.Text.Encoding.UTF8);
 8 //读取数据
 9 string output=sr.ReadLine();
10 //写入数据
11 string input="";
12 sw.WriteLine();
13 sw.Flush();

客户端和服务器的通信一般是由客户端向服务器端发请求,服务器端接受请求并处理,再将结果重新发回客户端

发送请求和应答的协议格式可以自己定义,游戏中使用的协议格式为  命令,参数1,参数2......

先看一下客户端请求登录

 1 service.SendToServer("Login," + textBoxName.Text.Trim());
 2 class Service
 3 {
 4 ListBox listbox;
 5 StreamWriter sw;
 6 public Service(ListBox listbox, StreamWriter sw)
 7 {
 8     this.listbox = listbox;
 9     this.sw = sw;
10 }
11 public void SendToServer(string str)
12 {
13     sw.WriteLine(str);
14     sw.Flush();
15 }
16 }

服务器接受请求并处理,一般来说是根据不同的请求创建不同的线程进行处理,见本节后半部分

 1 private void ReceiveData(object obj)
 2 {
 3     //User类封装了TcpClient,StreamReader和StreamWriter
 4     User user = (User)obj;
 5     TcpClient client = user.client;
 6     string receiveString = null;
 7     receiveString = sr.ReadLine();
 8     service.SetListBox(string.Format("来自{0}:{1}", user.userName, receiveString));
 9     string[] splitString = receiveString.Split(',');
10     switch (splitString[0])
11     {
12         case "Login":
13             user.userName = string.Format("[{0}--{1}]", splitString[1], client.Client.RemoteEndPoint);
14             service.SendToOne(user, "Welcome");
15             break;
16         default:
17             break;
18     }
19 }
20 


 1 class Service
 2 {
 3         private ListBox listbox;
 4         private delegate void SetListBoxCallback(string str);
 5         private SetListBoxCallback setListBoxCallback;
 6         public Service(ListBox listbox)
 7         {
 8             this.listbox = listbox;
 9             setListBoxCallback = new SetListBoxCallback(SetListBox);
10         }
11         public void SetListBox(string str)
12         {
13             if (listbox.InvokeRequired)
14             {
15                 listbox.Invoke(setListBoxCallback, str);
16             }
17             else
18             {
19                 listbox.Items.Add(str);
20                 listbox.SelectedIndex = listbox.Items.Count - 1;
21                 listbox.ClearSelected();
22             }
23         }
24         public void SendToOne(User user, string str)
25         {
26             try
27             {
28                 user.sw.WriteLine(str);
29                 user.sw.Flush();
30                 SetListBox(string.Format("向{0}发送{1}", user.userName, str));
31             }
32             catch
33             {
34                 SetListBox(string.Format("向{0}发送信息失败", user.userName));
35             }
36         }
37  }

服务器负责监听并根据不同的用户启动不同的线程

 1 IPAddress localAddress;
 2 int port = 51888;
 3 TcpListener myListener;
 4 Service service = new Service(listbox);
 5 IPAddress[] addrIP = Dns.GetHostAddresses(Dns.GetHostName());
 6 localAddress = addrIP[0];
 7 myListener = new TcpListener(localAddress, port);
 8 myListener.Start();
 9 service.SetListBox(string.Format("开始在{0}:{1}监听客户连接", localAddress, port));
10 ThreadStart ts = new ThreadStart(ListenClientConnect);
11 Thread myThread = new Thread(ts);
12 myThread.Start();

ListenClientConnect方法

 1 while (true)
 2 {
 3     TcpClient newClient = null;
 4     try
 5     {
 6        newClient = myListener.AcceptTcpClient();
 7     }
 8     catch
 9     {
10        break;
11     }
12     User user = new User(newClient);
13     userList.Add(user);
14     service.SetListBox(string.Format("{0}进入", newClient.Client.RemoteEndPoint));
15     service.SetListBox(string.Format("当前连接用户数:{0}",userList.Count));
16     //basilwang 2008-09-06 to receive data from client
17     ParameterizedThreadStart pts = new ParameterizedThreadStart(ReceiveData);
18     Thread threadReceive = new Thread(pts);
19     threadReceive.Start(user);
20 }

此处的启用了ParameterizedThreadStart 线程处理带参数的ReceiveData方法,主要是防止同客户端传输数据的过程出现的异常不能正确处理导致线程崩溃,每一个客户端分配一个线程可以保证客户端各自的独立性;同时ParameterizedThreadStart允许传递参数。

至此我们完成了一个简单的服务器客户端应答,希望能对初学者有所帮助。

 文件下载