在现代网络应用中,白板共享服务是一种非常实用的工具,它允许多人在同一个虚拟白板上进行协作。本文将介绍一个使用C#语言编写的白板共享服务的设计与实现。该服务允许用户通过UDP协议进行数据传输,实现多人之间的白板共享。
该白板共享服务由两部分组成:服务器端和客户端。服务器端负责管理客户端列表,接收并转发UDP消息。客户端则允许用户以绘图者或猜图者的身份加入游戏。绘图者在白板上绘制图形,而猜图者则尝试猜测图形所代表的词汇。
服务的代码分为服务器端和客户端两部分。服务器端是一个控制台应用程序,它能够添加客户端到列表中,并处理来自客户端的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);
}
}