CSharp中Socket网络编程(三)自定义通信传输协议

本文介绍了如何在通信协议中添加消息类型编码,以便发送字符串、文件和实现窗口震动功能。通过在字节流前添加特定标识(0表示字符串,1表示文件,2表示震动),实现了不同数据类型的传输。发送文件时,先读取文件并附加标识符1,接收端根据标识选择保存文件。震动功能通过改变窗体位置模拟震动效果。此外,还提出了丢包重传的策略,通过报文编号确保数据完整性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一部分:整个通信过程梳理;文末附全套代码;



第二部分:重要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]是否存在。。以此类推。

类推几个看需求。有时候类推一个,有时候全部类推。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值