通讯编程上位机软件实现(SOCKET)——第二回

这篇废话不多说,直接上代码。

首先说明,通讯过程中的异常均不进行处理(连接异常除外),由超时重发控制。

 

一、获取SOCKET连接类TimeOutSocket

 

public class TimeOutSocket

    {

        private static bool IsConnectionSuccessful = false;//连接是否成功

        private static Exception socketexception;

        private static ManualResetEvent TimeoutObject = new ManualResetEvent(false);

 

        public static Socket Connect(IPAddress ipAddress, int port, int timeoutMSec)

        {

            TimeoutObject.Reset();//将事件设为非终止状态,阻止线程

            socketexception = null;

 

            Socket temp = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //异步连接

            temp.BeginConnect(new IPEndPoint(ipAddress, port), new AsyncCallback(CallBackMethod), temp);

 

            if (TimeoutObject.WaitOne(timeoutMSec, false))

            {

                //在连接超时时间范围内等待信号量为真则返回SOCKET

                if (IsConnectionSuccessful)

                {

                    return temp;

                }

                else

                {

                    throw new TimeoutException("连接远程主机失败!请检查网络连接是否通畅。");

                }

            }

            else

            {

                //关闭连接,并释放所有相关资源

                temp.Close();

                throw new TimeoutException("连接超时!请检查网络连接是否通畅。");

            }

        }

 

        /// <summary>

        /// 异步连接成功后回调函数

        /// </summary>

        /// <param></param>

        private static void CallBackMethod(IAsyncResult asyncresult)

        {

            try

            {

                IsConnectionSuccessful = false;

                Socket client = asyncresult.AsyncState as Socket;

                //结束挂起的异步连接请求

                client.EndConnect(asyncresult);

                IsConnectionSuccessful = true;

            }

            catch (Exception ex)

            {

                IsConnectionSuccessful = false;

                socketexception = ex;

            }

            finally

            {

                //允许其它线程继续

                TimeoutObject.Set();

            }

        }

}


 

二、通讯变量(常量)

 

private const int ConnectTimeOut = 6000;//连接超时时间,以ms为单位

private byte[] data = new byte[1024];//接收字节数组

private int recvSize = 1024;//接收数据个数

private static int XH = 1;//发送的命令序号 

//下面两个类库ConvertLibrary和DealProtocolData是我封装的类库,在后续中将逐一给出

ConvertLibrary convertData = new ConvertLibrary();//对数据提供转换支持

DealProtocolData dealProtocolData = new DealProtocolData();//按要求处理协议结果  

  

 

三、委托

 

委托,实质上就是指向函数的指针,也是一个类。

        delegate void DealRecvMsgHandler(string str);//用于处理接收到的数据

 

上面一句用ILDASM反编译后如下:

 

可以看出,系统默认生成了其构造函数(.ctor是构造函数),以及成员函数BeginInvoke、EndInvoke以及Invoke。自己定义的委托继承于System.MulticastDelegate(反编译中extends看出)。

 

 

四、异步接收数据回调函数ReceivData()

 

/// <summary>

        ///异步接收数据回调

        /// </summary>

        /// <param></param>

        private void ReceivData(IAsyncResult iar)

        {

            Socket remote = (Socket)iar.AsyncState;

            int recv = 0;

            try

            {

                recv = remote.EndReceive(iar);//异常:远程主机强迫关闭了一个现有的连接

            }

            catch

            {

            }

            string tempStr = convertData.ByteToHex(data, recv);//字节数组转化成16进制

 

            if (tempStr.Length > 0)

            {         

                // DealRecvMsg数据处理函数     

                DealRecvMsgHandler handler = new DealRecvMsgHandler(DealRecvMsg);

                handler.Invoke(tempStr);//同步数据处理

            }

            else

            {

               //由于用了第三方的串口转网口模块,所以有时候会收到空的情况,这时继续接收

                    try

                    {

                        remote.BeginReceive(data, 0, recvSize, SocketFlags.None, new AsyncCallback(ReceivData), PublicCommunication.client);

                    }

                    catch { }

            }

        }


 

五、异步发送数据回调函数(SendData)

 

/// <summary>

        ///异步发送数据回调

        /// </summary>

        /// <param></param>

        private void SendData(IAsyncResult iar)

        {

            Socket remote = (Socket)iar.AsyncState;

            try

            {

                int send = remote.EndSend(iar);

 

                currentSendedOrderDate = DateTime.Now;//记录下发送时间,用于重发时使用

                if (listenerTimer.Enabled == false)// listenerTimer用于超时重发

                    listenerTimer.Enabled = true;

                currentSendedOrderEnum = GetSendedOrderType(sendMsg);//获取已发送命令类型

                currentSendedOrderXH = GetSendedOrderXH(sendMsg);//获取已发送命令序号

 

 //发送后,异步接收

                remote.BeginReceive(data, 0, recvSize, SocketFlags.None, new AsyncCallback(ReceivData), PublicCommunication.client);

            }

            catch

            {

            }

        }


 

 

 

六、接收数据处理函数(只判断命令的合法性,决定下一个命令。具体的数据需要另需委托进行处理。)

 

private void DealRecvMsg(string orgTempStr)

        {

//这里面是一个while循环,循环的条件是命令长度大于某个最小指,每次循环按命令包中命令长度取出命令,并将剩余的接收到的命令用于循环。

//由于通讯追循一定的流程,所以在这里由接收到的命令可以决定下一个命令

//大致给一下这里面的程序

 

while (orgTempStr.Length >= 12)

                    {

                        try

                        {

                            string tempStr = orgTempStr.Substring(0, int.Parse(convertData.ConvertString(orgTempStr.Substring(2, 2), 16, 10)) * 2);//取出一条命令

                            orgTempStr = orgTempStr.Substring(int.Parse(convertData.ConvertString(orgTempStr.Substring(2, 2), 16, 10)) * 2);// orgTempStr保留剩余的命令

 

                            //判断数据合法性:根据协议接收到的数据长度至少为12,而且保证接收到的数据无误(校验码), GetStrXOR()函数是获取校验码,后面给出

 

                            if (tempStr.Length >= 12

                                && dealProtocolData.GetStrXOR(tempStr.Substring(0, tempStr.Length - 2)).Equals(tempStr.Substring(tempStr.Length - 2), StringComparison.OrdinalIgnoreCase))

                            {

                                string workState = tempStr.Substring(8, 2);//获取仪器工作状态

                                string order = tempStr.Substring(4, 2);//获取命令字

 

                                //如果不是上次发送的命令则丢弃当前数据

                                if (!JudgeReceiveOrderWithSendOrder(order, currentSendedOrderEnum))

                                    continue;

                                //命令序号不同则丢弃

                                if (!(int.Parse(convertData.ConvertString(tempStr.Substring(6, 2), 16, 10)) == currentSendedOrderXH))

                                    continue;

 

                                currentSendedOrderDate = DateTime.MinValue;//收到命令后,设置命令的发送时间为DateTime的最小值

                                ReSendTimeCount = -1;//超时重发次数

 

//"00"应该定义成常量,方便使用时如果"00"改为”ff”时不用满程序中修改

                                if (workState.Equals("00", StringComparison.OrdinalIgnoreCase))//仪器正常工作

                                {

if (order.Equals(ProtocolContent.SetParamsCommandWord, StringComparison.OrdinalIgnoreCase))//设置参数命令的返回

                                    {

//决定下一个要发送的命令

sendMsg =……;

}

………..

………..

 

byte[] msg = convertData.HexToByte(sendMsg);

                                    try

                                    {

                                        //异步发送数据

                                        PublicCommunication.client.BeginSend(msg, 0, msg.Length, SocketFlags.None, new AsyncCallback(SendData), PublicCommunication.client);

 

                                        //处理发送序号,保证发送序号在1-255之间

                                        XH = (XH + 1) % 256;

                                        if (XH == 0)

                                            XH = 1;

                                    }

                                    catch

                                    {

                                    }

 

}


 

 

七、超时重发

 

/// <summary>

    /// 命令的枚举值

    /// </summary>

    public enum OrderEnum

    {

        UnKnown = 0

    }

 

private static string sendMsg = String.Empty;//记录每次发送的命令

private static OrderEnum currentSendedOrderEnum = OrderEnum.UnKnown;//记录本次发送的命令的类型

private static int currentSendedOrderXH = -1;//当前发送命令的序号

private static DateTime currentSendedOrderDate = DateTime.MinValue;//最近一次发送命令的时间

private System.Timers.Timer listenerTimer;//用于监控命令发送的定时器

private static int ReSendTimeCount = -1;//重新发送命令的次数

 

 

listenerTimer = new System.Timers.Timer(25);

listenerTimer.Elapsed += new System.Timers.ElapsedEventHandler(listenerTimer_Elapsed);

listenerTimer.Enabled = false;

 

private void listenerTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)

        {

            if (currentSendedOrderDate != DateTime.MinValue)//当没有收到数据时

            {

                double timeInterval = (DateTime.Now - currentSendedOrderDate).TotalMilliseconds;

                if (timeInterval >= 1000)//1S重发

                {

                    ReSendTimeCount++;

                    if (ReSendTimeCount >= 3)

                    {

                        //超时处理

……..

                        return;

                    }

//未超时三次,继续重发

                    SendMsgWhenTimeOut(sendMsg);

                }

            }

        }


 

八、通讯结束或者超时后的处理

 

当无通讯时,客户端关闭,服务器是检测不到的,所以我在做的时候,一次通讯结束或者超时后都会将连接关闭并置为空。

private void WhenCommunicateOver()

{

listenerTimer.Enabled = false;

try

            {

                PublicCommunication.client.Close();

            }

            catch { }

            finally

            {

                PublicCommunication.client = null;

            }

}


 

九、尝试三次连接

 

private void startCommunication()

        {

            int connectTime = 0;

            do

            {

                try

                {

                    PublicCommunication.client = TimeOutSocket.Connect(IPAddress.Parse(PublicModel.InstrumentIP), PublicModel.InstrumentPort, ConnectTimeOut);                    

                    break;

                }

                catch (Exception ec)

                {

                    connectTime++;

                    PublicCommunication.client = null;

                    if (connectTime >= 3)

                    {

                        WhenCommunicateOver();

                        lock (whetherShowErrorMsg)

                        {

                            if (!((bool)whetherShowErrorMsg))

                            {

                                MessageBox.Show(this, ec.Message, "系统提示", MessageBoxButtons.OK, MessageBoxIcon.Error);

                                whetherShowErrorMsg = true;

                            }

                        }

                        Application.DoEvents();

                        return;

                    }

                }

            }

            while (connectTime < 4);

}

posted @ 2010-04-29 22:09  Edenia  Views(2207)  Comments(4Edit  收藏  举报