基于C#和UDP的多人白板共享服务设计与实现

在现代网络应用中,白板共享服务是一种非常实用的工具,它允许多人在同一个虚拟白板上进行协作。本文将介绍一个使用C#语言编写的白板共享服务的设计与实现。该服务允许用户通过UDP协议进行数据传输,实现多人之间的白板共享。

服务概述

该白板共享服务由两部分组成:服务器端和客户端。服务器端负责管理客户端列表,接收并转发UDP消息。客户端则允许用户以绘图者或猜图者的身份加入游戏。绘图者在白板上绘制图形,而猜图者则尝试猜测图形所代表的词汇。

使用C#进行编码

服务的代码分为服务器端和客户端两部分。服务器端是一个控制台应用程序,它能够添加客户端到列表中,并处理来自客户端的UDP消息。客户端由两个类组成:窗体类和面板类。

服务器端代码主要负责管理客户端连接,接收并分析UDP消息,并将消息转发给其他客户端。例如,当一个猜图者猜测一个词汇时,其他猜图者会在文本框中收到通知。此外,绘图者所绘制的词汇作为秘密词汇,不会转发给其他客户端,而是保留在服务器端以供核对猜测。

客户端包含两个类:窗体类和面板类。窗体类名为Form1,是用户与服务交互的主要界面。它包含四个方法,用于捕获界面上四个按钮的点击事件,并包含两个后台线程:read()和DrawOnPanel()。

read()方法负责从UDP连接中读取消息,并将其解码为字符串。然后,根据消息内容执行一系列if-else语句。例如,如果消息以"draw"开头,它将解析绘图坐标并开始绘图;如果消息以"guess"开头,它将显示猜测结果;如果消息以"quit"开头,则表示绘图者退出游戏。

public void read() { Thread t = new Thread(new ThreadStart(delegate { while (!abortThreads) { IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); byte[] data = {0}; try { data = client.Receive(ref remoteEP); msg = Encoding.ASCII.GetString(data, 0, data.Length); } catch (SocketException e) { abortThreads = true; } if (msg.StartsWith("draw")) { msg = msg.Substring(5); int xVal = 0, yVal = 0; for (int i = 0; msg.Length - 1 > 0; i++) { if (msg.Substring(i, 1) == "y") { xVal = Int32.Parse(msg.Substring(0, i)); msg = msg.Substring(i + 1); i = 0; } if (msg.Substring(i, 1) == "x") { yVal = Int32.Parse(msg.Substring(0, i)); msg = msg.Substring(i + 1); toDraw.Add(new Point(xVal, yVal)); i = 0; } } startDrawing = true; } else if (msg.StartsWith("accept")) { SetText(Encoding.ASCII.GetString(data, 0, data.Length)); } else if (msg.StartsWith("gues")) { if (msg.EndsWith("true")) SetText(msg.Substring(5, msg.Length - 9) + "which was RIGHT! "); if (msg.EndsWith("false")) SetText(msg.Substring(5, msg.Length - 10) + "which was WRONG! "); } else if (msg.StartsWith("quit")) { msg = "quit"; data = Encoding.ASCII.GetBytes(msg); client.Send(data, data.Length); client.Close(); SetText("The drawer quitted, the game is finished! "); abortThreads = true; Thread.CurrentThread.Abort(); } else { if (msg.Length > 5) SetText(msgList.Text + Environment.NewLine + Encoding.ASCII.GetString(data, 4, data.Length - 4)); } } })); t.IsBackground = true; t.Start(); }

DrawOnPanel()方法在收到绘图消息时进行绘图操作,该操作由read()线程中的一个布尔变量startDrawing控制。

private void DrawOnPanel() { hwnd = thePanel.Handle; Thread t0 = new Thread(new ThreadStart(delegate { using (Graphics graphics = Graphics.FromHwnd(hwnd)) { while (!abortThreads) { if (startDrawing) { if (toDraw.Count > 0) { object holder = toDraw[0]; Point now = (Point)holder, previous = (Point)holder; for (int i = 0; i < toDraw.Count; i++) { holder = toDraw[i]; now = (Point)holder; graphics.DrawLine(MyPanel.p, now, previous); previous = now; } Console.WriteLine(now + "last " + previous); startDrawing = false; toDraw.Clear(); } } } } })); t0.IsBackground = true; t0.Start(); }

面板类名为MyPanel,它覆盖了Panel类。它重写了三个鼠标事件方法:OnMouseDown()、OnMouseUp()和OnMouseMove()。

OnMouseDown()方法设置一个布尔值,表示可以开始获取绘图点。

protected override void OnMouseDown(MouseEventArgs e) { mouse_down = true; }

OnMouseMove()方法将绘图点存储在ArrayList中,以便发送。

protected override void OnMouseMove(MouseEventArgs e) { if (Form1.isDrawer) { if (last_point.Equals(Point.Empty)) last_point = new Point(e.X, e.Y); if (mouse_down) { IntPtr hwnd = this.Handle; using (Graphics graphics = Graphics.FromHwnd(hwnd)) { Point pMousePos = new Point(e.X, e.Y); graphics.DrawLine(p, pMousePos, last_point); Console.WriteLine(pMousePos + "last " + last_point); pointss.Add(pMousePos); } } last_point = new Point(e.X, e.Y); } }

OnMouseUp()方法将ArrayList中的绘图点转换为字符串,然后转换为字节并通过UDP连接发送。

protected override void OnMouseUp(MouseEventArgs e) { mouse_down = false; if (Form1.isDrawer) { string msg = "draw"; for (int i = 0; i < pointss.Count; i++) { Point poi = (Point)pointss[i]; msg += ("x" + poi.X + "y" + poi.Y); } msg += "x"; pointss.Clear(); byte[] data = Encoding.ASCII.GetBytes(msg); if (client != null) client.Send(data, data.Length); } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485