上一节给大家演示了建立连接的关键代码,连接建立好后,就可以进行数据传输了。数据传输包含从服务器端到客户端和从客户端到服务器端,两者差别不大。
数据的传输,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允许传递参数。
至此我们完成了一个简单的服务器客户端应答,希望能对初学者有所帮助。