第一部分:整个通信过程梳理;文末附全套代码;
第二部分:重要bug完善;
本篇为第三部分:消息类型编码:自定义通信协议。
第四部分:全套代码
通信协议这事:
我们希望在通信中除了传输信息以外,还可以发送图片,还可以实现窗口震动。此时涉及发送数据的类型问题。对于接收方来说,并不知道发送方发送出来的格式,因此如果按照原来解码为字符串类型则不再适用。这事需要双方事先约定好。
发送以0开头的字节数组代表字符串类型,发送以1开头的字节数组代表文件,发送以2开头的字节数组代表震动。
0:发送消息:
我们原来的代码如此。点击按钮,直接将对话框中的文本转为字节流发送。
private void btnSend_Click(object sender, EventArgs e)
{
string str = txtMsg.Text;
byte[] buffer = Encoding.UTF8.GetBytes(str);
dicSocket[cboUsers.SelectedItem.ToString()].Send(buffer);
}
当前需要在字节之前加上一位0,表示为传输的为字符串。
数组的长度一旦声明就不可改变了。
为此需要声明一个新数组,将协议类型头数据写入,然后再接上原来的数组。实现字节编码。然后解码的时候,先解析字节数组中的协议类型头数据。
解决方案一:新数组的长度为buffer.Length+1。然后NewBuffer[0]=0。将剩余的buffer值循环,赋值给newBuffer。
解决方案二:数组的长度不可改变,但是泛型集合的长度可以改变。并且可以将数组添加到集合中。并且可以将集合转化为数组。
采用第二种方案最简单。
private void btnSend_Click(object sender, EventArgs e)
{
string str = txtMsg.Text;
byte[] buffer = Encoding.UTF8.GetBytes(str);
List<byte> list = new List<byte>();
list.Add(0);
list.AddRange(buffer);
// 将泛型集合转化为数组。
byte[] NewBuffer = list.ToArray();
//buffer = list.ToArray(); // 因为类型长度不同,无法赋值。所以新建。
// dicSocket[cboUsers.SelectedItem.ToString()].Send(buffer);
dicSocket[cboUsers.SelectedItem.ToString()].Send(NewBuffer);
}
那么相应的接受端也要先解析第一个字节数组情况。注意一点:在转码时,转码长度为r-1。
void Receive()
{
try
{
while (true)
{
byte[] buffer = new byte[1024 * 1024 * 10];
int r = socketSend.Receive(buffer);
if (r == 0)
{
break;
}
if (buffer[0]==0)
{
string str = Encoding.UTF8.GetString(buffer, 1, r-1);
ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + str);
}
else if (buffer[0]==1)
{
// 见下面文件传输
}
else if (buffer[0]==2)
{
// 见下面抖动
}
}
}
catch
{ }
}
1:发送文件
首先通过【选择文件】按钮将文件选中,然后显示在文本框中,然后点击【发送文件】按钮将文件发出。接受端(客户端)解析字节头,识别1后,打开对话框,选择保存文件路径。并保存文件。
设置按钮显示文件目录,选中文件并显示:
private void btnSelect_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.InitialDirectory = @"C:\Users\futurecloud\Desktop";
ofd.Title="请选择你要传输的文件";
ofd.Filter = "所有文件|*.*";
ofd.ShowDialog();
//this.txtPath.Text = ofd.SafeFileName;
txtPath.Text = ofd.FileName;
}
为了发送文件,首先创建一个文件流来读取文件。然后将字节流数组前加上标识符号1,发送给客户端。此操作在【发送文件】中完成。
private void btnSendFile_Click(object sender, EventArgs e)
{
string path = txtPath.Text;
using (FileStream fsRead=new FileStream(path, FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024 * 10];
int r = fsRead.Read(buffer, 0, buffer.Length);
List<byte> list = new List<byte>();
list.Add(1);
list.AddRange(buffer);
byte[] newBuffer = list.ToArray();
// send有很多重载:有限制大小的。
dicSocket[cboUsers.SelectedItem.ToString()].Send(newBuffer, 0, r+1, SocketFlags.None);
}
}
服务器端完成发送,客户端需要将解码,并打开一个保存文件对话框。
// 接续上面Recive()中的代码。
else if (buffer[0]==1)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.InitialDirectory = @"C:\Users\futurecloud\Desktop";
sfd.Title = "请选择你要保存的文件路径:";
sfd.Filter = "所有文件|*.*";
sfd.ShowDialog(this); // 不加这个this这个框不弹出来。
// 写入文件:
string path = sfd.FileName;
using (FileStream fsWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write))
{
fsWrite.Write(buffer,1,r-1);
}
//ShowMsg("写入文件成功。");
MessageBox.Show("文件写入成功");
}
至此文件发送功能完成:后续可以进一步完善的方案:在1后面继续添加一位:如果是10则为图片,如果是11则是txt,如果是12则是word。等等。依次类推。
2:震动
点击震动,只需要传输一个2过去就够了。
private void btnZD_Click(object sender, EventArgs e)
{
byte[] buffer = new byte[1];
buffer[0] = 2;
dicSocket[cboUsers.SelectedItem.ToString()].Send(buffer);
}
如何实现震动效果,说白了,就是在一定时间内,让窗体震动,或者,指定窗体震动次数。
/// <summary>
/// 实现震动效果
/// </summary>
void ZD()
{
for (int i = 0; i < 500; i++)
{
//this.Location = new Point(200, 200);
//this.Location = new Point(210, 210);
this.Location = new Point(this.Location.X, this.Location.Y);
this.Location = new Point(this.Location.X+10, this.Location.Y+10);
this.Location = new Point(this.Location.X-10, this.Location.Y-10);
}
}
else if (buffer[0]==2)
{
ZD();
}
如果有其他设置,一样可以修改字节数组来改变。
我有一个想法:(可能TCP用不到)
我们在传输的时候有丢包的担心,所以
我们给我们要发送的报文编号,如果接收方接收到的报文编号不连续,则要求重新发送某个缺失报文。
场景:每次数据更新的时候发送更新的点位数据。
[编号0]+更新数据
[编号1]+更新数据
......
[编号n]+更新数据
接收方:在接收到某个报文[n]后,先查看编号列表,如果编号[n-1]已经在了,则直接解析,如果[n-1]不存在,则要求重新发送编号[n-1]的报文,同时查看[n-2]是否存在。。以此类推。
类推几个看需求。有时候类推一个,有时候全部类推。