博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SerialPort串口传输文件、语音等
阅读量:6735 次
发布时间:2019-06-25

本文共 14167 字,大约阅读时间需要 47 分钟。

hot3.png

为了使用串口通信,可以发送文字、图片、声音、文件等,实现了一个小的程序。

程序的框架,决定采用window form的界面,参考QQ聊天界面和声音等,实现双工低速率4kbps的通信。界面主要实现一些按钮和必要的建立连接的装置。后面其他的代码实现相应的功能,可以说是采用了某种模型吧。

一、串口出现的问题

1、不能按照我们的目的接收bytes

解决方案是:采用有固定格式的帧,采用的帧长 LengthOfFrame=180,这个纯属自己的爱好,以后所有的帧都是这么长。然后是定义帧的形式。采用帧头长LengthOfHeader=2,剩下的部分长度LengthOfDataFrame=180-2=178,第一个数据表示该帧的类型,第二个数据表示该帧的实际长度,由于帧长是180<,buffer[0]=(byte)tag,buffer[1]=(byte)LengthOfThisFrame,考虑可能有过长的帧,我们要特殊处理一下。如果后面还有该帧的后续帧,则该帧的长度buffer[1]=0x00,设为一个标记,表示本帧的长度是LengthOfDataFrame,还有下一帧也是本帧的一部分。具体的程序,见下面的程序。

#region const parameters        const int LengthOfFrame = 180;        const int LengthOfHeaderDataFrame = 2;        const int LengthOfDataFrame = LengthOfFrame - LengthOfHeaderDataFrame;        const byte DataTag = (byte)'d';        const byte CMDTag = (byte)'c';        const byte FileTag = (byte)'f';        const byte VoiceTag = (byte)'v';        static Encoding MyEncoding = Encoding.Unicode;        // here we use utf16,that is two bytes,which can express Chinese and English        #endregion
下面是发送帧的部分程序。
public static void SendBytesBySerialPort(byte[] Buf, byte tag)        {            if (Buf == null) return;            int length = Buf.Length;            byte[] outBuf = new byte[LengthOfFrame];            int sendTimes_1 = length / LengthOfDataFrame;            int lastTimesLength = length % LengthOfDataFrame;            // if Buf is too long, then divided into dataLength            for (int i = 0; i < sendTimes_1; i++)            {                outBuf[0] = tag;                outBuf[1] = 0x00;                for (int j = 0; j < LengthOfDataFrame; j++)                {                    outBuf[2 + j] = Buf[i * LengthOfDataFrame + j];                }                if (lastTimesLength == 0 && i == sendTimes_1 - 1)                    outBuf[1] = LengthOfDataFrame;                proxySerialPort.Write(outBuf, 0, LengthOfFrame);            }            // send the last information            if (lastTimesLength > 0)            {                outBuf[0] = tag;                outBuf[1] = (byte)lastTimesLength;                for (int j = 0; j < lastTimesLength; j++)                {                    outBuf[j + 2] = Buf[sendTimes_1 * LengthOfDataFrame + j];                }                proxySerialPort.Write(outBuf, 0, LengthOfFrame);            }        }
上面的程序就算是从发送部分解决问题。

下面从数据接收部分解决问题,接收出现的最多问题是,DataReceived调用的时候,不是每次都有恰好那么多个字节,这个就需要多次接收,堆积在一起。接着出现,有数据来了,系统不调用这个DataReceived,更是无语,只好采用一直调用DataReceived的方法了。具体代码如下:

            new Thread(new ThreadStart(StartDataReceived)).Start();// start the dataReceived//delete .DataReceived+=new EventHandle(proxySerialPort_DataReceived);
        private static void StartDataReceived()        {            while (true)            {                proxySerialPort_DataReceived(null, null);            }        }
        static void proxySerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)        {            try            {                if (proxySerialPort.IsOpen)                {                    int arrivalNum = proxySerialPort.BytesToRead;                    if (arrivalNum >= LengthOfFrame)                    {                        byte[] inBuf = new byte[LengthOfFrame];                        proxySerialPort.Read(inBuf, 0, LengthOfFrame);                        //new Thread(new ParameterizedThreadStart(dealNormalFrame)).Start(inBuf);                        dealNormalFrame(inBuf);                    }                    else                    {                        System.Threading.Thread.Sleep(10);                    }                }                else                {                    System.Threading.Thread.Sleep(1000);                }            }            catch             {                System.Threading.Thread.Sleep(1000);            }        }

二、处理接收到的数据

上面的dealNormalFrame(inBuf)函数,是分类处理这些帧的,如果有错误也在这个函数里面实现。他的具体形式如下:

private static void dealNormalFrame(byte[] inBuf)        {            if (inBuf[0] == DataTag)            {                dealNormalDataFrame(inBuf);            }            else if (inBuf[0] == CMDTag)            {                dealNormalCMDFrame(inBuf);            }            else if (inBuf[0] == FileTag)            {                dealNormalFileFrame(inBuf);            }            else if (inBuf[0] == VoiceTag)            {                dealNormalVoiceFrame(inBuf);            }            else//here received error-format frame,we assume that it is data-frame with a great probablity.            {                dealErrorFormatFrame(inBuf);            }        }
上面处理异常帧,主要是处理错位的帧的方法,就是向下搜索直到搜索到一个正确的标识为止,然后跳到处理正常帧那里。代码如下:
private static void dealErrorFormatFrame(byte[] inBuf)        {            int needNumberBytes = 0;            for (; needNumberBytes < inBuf.Length; needNumberBytes++)            {                switch (inBuf[needNumberBytes])                {                    case CMDTag:                        break;                    case DataTag:                        break;                    case FileTag:                        break;                    case VoiceTag:                        break;                    default:                        continue;                }                break;            }            int currentArrivalNum = proxySerialPort.BytesToRead;            byte[] outBuf = new byte[LengthOfFrame];            if (needNumberBytes <= currentArrivalNum)            {                proxySerialPort.Read(inBuf, 0, needNumberBytes);                for (int j = needNumberBytes; j < LengthOfFrame; j++)                {                    outBuf[j - needNumberBytes] = inBuf[j];                }                for (int j = 0; j < needNumberBytes; j++)                {                    outBuf[LengthOfFrame - needNumberBytes + j] = inBuf[j];                }                dealNormalFrame(outBuf);            }        }

三、发送文件功能的实现

发送文件的大致流程是,先告诉对方我要发送某个文件,然后把该文件视作字符流,分成若干帧发送过去,然后告诉对方发送完毕,或者告诉对方我不想发送了。为了简化本程序没有做类似TCP的可靠传输,而是类似UDP的尽最大努力的发送文件,不考虑安全和丢包等。

下面的程序是发送文件请求的程序,前台的发送文件按钮。

private void buttonSendFile_Click(object sender, EventArgs e)        {            try            {                if (buttonSendFile.Text == "发送文件")                {                    if (!proxySerialDataPort.IsOpen)                    {                        buttonOpen_Click(sender, e);                    }                    Communication.SendFile();                }                else                {                    WriteTipToRichTextBox("好啊,您停止了发送文件!", "我马上就告诉对方!");                    Communication.StopSendFile();                }            }            catch { }        }
上面实现的发送文件请求和终止发送文件在另外一个类class Communication里面实现。
下面的代码是发送接收文件需要使用的全局变量
static string filePathReceived="";        static string filePathSend = "";        static Thread threadSendFile;
下面的代码是发送文件请求的代码,提供了两种方式的请求方式。
public static void SendFile(string filePath)        {            filePathSend = filePath;            string[] files = filePath.Split(new char[] { '/','\\'});            SendCMDBySerialPort(                    "requestSendFile/"                    + files[files.Length-1]);        }        public static void SendFile()        {            OpenFileDialog ofd = new OpenFileDialog();            if (ofd.ShowDialog() == DialogResult.OK)            {                SendCMDBySerialPort(                    "requestSendFile/"                    + ofd.SafeFileName);                filePathSend = ofd.FileName;            }        }

如果对方同意接收文件,则可以发送文件了。由于发送文件,占用时间比较长,所以最后采用开辟新的线程的做法。开辟新线程出现了一些问题,经过上网搜寻,但是忘记记录从哪里搜到的资料了。发送文件的程序如下。

private static void SendFileBySerialPort()        {            if (!File.Exists(filePathSend))            {                MessageBox.Show("文件:" + filePathSend + "不存在,请核实后再操作!",                    "文件错误",                    MessageBoxButtons.OK,                    MessageBoxIcon.Error);                return;            }            //Open the stream and read it back.             try            {                using (FileStream fs = File.OpenRead(filePathSend))                {                    byte[] outBuf = new byte[LengthOfFrame];                    outBuf[0] = (byte)FileTag;                    outBuf[1] = 0x00;//we assume that this is not end;                    int length = 0;                    while ((length = fs.Read(outBuf, 2, LengthOfDataFrame)) == LengthOfDataFrame)                    {                        // code from http://msdn.microsoft.com/en-us/library/system.io.filestream.position.aspx                        // show that next line is the end of file.                        if (fs.Position == fs.Length)                            break;                        proxySerialPort.Write(outBuf, 0, LengthOfFrame);                    }                    // if there are N*LengthOfDataFrame,length=0,return;                    // else we should send the last bytes;                    if (length > 0)                    {                        outBuf[1] = (byte)length;                        proxySerialPort.Write(outBuf, 0, LengthOfFrame);                    }                    filePathSend = "";                    DisplayToForm.SetAllowSendFile(false);                }            }            catch { }        }        private static void SendFileBySerialPortAsy()        {            threadSendFile = new Thread(new ThreadStart(SendFileBySerialPort));            threadSendFile.IsBackground = true;            threadSendFile.SetApartmentState(ApartmentState.STA);            threadSendFile.Start();        }
如果对方拒绝接收文件,则清空filePathSend="";如果我想终止发送文件,则如下停止发送文件。没有提供接收方终止发送文件的功能。利用关闭串口产生的异常来停止发送文件,从而实现停止发送的功能。因为使用直接停掉线程的做法没有成功,只好用这种方式实现了。下面是终止发送文件的程序。
public static void StopSendFile()        {            SendCMDBySerialPort("stopSendFile");            try            {                if (proxySerialPort.IsOpen)                {                    proxySerialPort.Close();                }            }            catch            {            }            proxySerialPort.Open();            filePathSend = "";            DisplayToForm.SetAllowSendFile(false);        }

四、接收命令的处理方式

由于本程序帧的定义是有两个字节的头部,第一个字节是标识字节,第二个字节是长度字节。对于命令帧可以任意长度,不受最长帧长的限制,所以使用起来很方便。上面发送文件或者语音通信等的握手信号都可以使用命令帧来实现。

下面的代码实现接收一个完整的命令,并解析该命令。

static string CMDString = "";        private static void dealNormalCMDFrame(byte[] inBuf)        {            if (inBuf[1] > 0 && inBuf[1] <= LengthOfDataFrame)            {                CMDString += MyEncoding.GetString(inBuf, 2, inBuf[1]);                DisplayToForm.WriteToRichTextBoxFromThat("Sound/tweet.wav", "", false);                CMD_Parse(CMDString);                CMDString = "";            }            else            {                CMDString += MyEncoding.GetString(inBuf, 2, LengthOfDataFrame);            }        }
上面CMD_Parse(string cmd)函数实现对一个完成命令的解析,并作出相应的响应。存在一个很大的问题是这里的命令没有保存到一个具体的数组里面,或者使用enum类型,容易出错,这里仅仅是一个例子,不再追求完美。具体的代码如下。
[STAThread]        private static void CMD_Parse(string CMD)        {            if (CMD == "") return;            string[] cmdLines = CMD.Split(new char[] { '/' });            switch (cmdLines[0])            {                case "requestSendFile":                    //follow reason: http://social.msdn.microsoft.com/Forums/zh-CN/winforms/thread/2411f889-8e30-4a6d-9e28-8a46e66c0fdb                    Thread t = new Thread(new ParameterizedThreadStart(WhenGotRequestSendFile));                    t.IsBackground = true;                    t.SetApartmentState(ApartmentState.STA);                    t.Start(cmdLines);                    break;                case "ackSendFile":                    DisplayToForm.SetAllowSendFile(true);                    SendFileBySerialPortAsy();                    DisplayToForm.WriteTipToRichTextBox("恭喜您", "对方同意接收文件!");                    DisplayToForm.WriteTipToRichTextBox(                    "发送文件提示",                    "正在发送\n“" + filePathSend                    + "”");                    break;                case "refuseSendFile":                    DisplayToForm.SetAllowSendFile(false);                    DisplayToForm.WriteTipToRichTextBox("很不幸", "对方拒绝接收您的文件!");                    filePathSend = "";                    break;                case "stopSendFile":                    DisplayToForm.SetAllowSendFile(false);                    DisplayToForm.WriteTipToRichTextBox("很不幸", "对方停止发送文件了!");                    filePathSend = "";                    isNewFile = true;                    break;                case "fileSendOver":                    DisplayToForm.SetAllowSendFile(false);                    DisplayToForm.WriteTipToRichTextBox("恭喜您", "文件发送完毕!");                    filePathSend = "";                    break;                case "jitter":                    DisplayToForm.WriteTipToRichTextBox("呵呵", "对方给您发送了一个窗口抖动。");                    DisplayToForm.JitterWindow();                    break;                case "requestVoice":                    WhenGotRequestVoice();                    break;                case "ackVoice":                    System.Threading.Thread.Sleep(40);                    DisplayToForm.SetAllowVoice(true);                    DisplayToForm.WriteTipToRichTextBox("恭喜你", "对方同意了您的通话请求");                    break;                case "refuseVoice":                    DisplayToForm.SetAllowVoice(false);                    DisplayToForm.WriteTipToRichTextBox("语音聊天", "对方拒绝了您的通话请求。");                    break;                case "stopVoice":                               DisplayToForm.SetAllowVoice(false);                    DisplayToForm.WriteTipToRichTextBox("语音聊天", "对方停止了通话。");                    break;                case "iSpeak":                    //if I listen other say iSpeak, i will shut up;                    VoiceSpeakAndListen.Stop();                    DisplayToForm.WriteTipToRichTextBox("消息提醒", "有人在说话,您先听着吧。");                    break;                case "iShutUp":                    //if I listen other say iSpeak, i will shut up;                    VoiceSpeakAndListen.Stop();                    DisplayToForm.WriteTipToRichTextBox("消息提醒", "现在没人说话,您可以点击说话,开始说话了。");                    break;            }        }

接下来

后面待续

转载于:https://my.oschina.net/bubifengyun/blog/98166

你可能感兴趣的文章
简单实现web的请求派发机制
查看>>
用CSS画三角形
查看>>
CISCO IOS入门知识2
查看>>
2010年7月毕业到现在
查看>>
struts2+ajax+json 基本
查看>>
编译安装apache
查看>>
Objective-C MVC
查看>>
linux视频教程之SSH
查看>>
shiro 基础
查看>>
用树莓派控制8*8led矩阵显示红心
查看>>
【译】JasperReports Library使用指南
查看>>
php添加扩展库
查看>>
zabbix邮件报警接入,微信报警接入
查看>>
Appium环境配置笔记2 -- Android SDK的路径不能有空格
查看>>
python写的系统常用命令(二)
查看>>
二、在HTML中使用JavaScript
查看>>
Oracle 用户管理与权限分配
查看>>
Linux挂载NTFS硬盘错误解决办法
查看>>
【office培训】【王佩丰】Excel2010视频教程第4讲:排序与筛选
查看>>
手电筒项目开发一闪光灯
查看>>