用VB.NET通过TCP SOCKET实现上位机远程数据传输程序设计

基本功能实现:

  1. 可设置心跳包的TCP SOCKET远程设备均可以连接;
  2. 远程设备数量可在限制的范围内任意增加;
  3. 可对远程设备进行什么任意频率的数据轮询;
  4. 可处理限制范围内的任意数据量;
  5. 程序使用尽量少的线程处理设备的连接、数据的处理,增加线程的繁忙度,减少内存的占用。

 

基本思路与部分代码:

  1. 创建负责侦听的线程
                '循环侦听是否有设备连接上来
                Dim threadWatch As Thread = New Thread(AddressOf WatchConnecting)
                threadWatch.IsBackground = True
                threadWatch.Start(serverSocket)

     

  2. 创建负责接收数据的线程
                '对dtu集合中每个dtu的socket进行数据的接收,接收到数据就放到数据队列中
                Dim threadReceieveData As Thread = New Thread(AddressOf receieveData)
                threadReceieveData.IsBackground = True
                threadReceieveData.Start()

     

  3. 创建负责处理数据的线程
                '对队列中的每个数据列进行读取,并处理。
                Dim threadResolvingData As Thread = New Thread(AddressOf resolvingData)
                threadResolvingData.IsBackground = True
                threadResolvingData.Start()

     

  4. 创建三个线程,分别处理侦听、数据接收、数据处理
  5. 侦听的代码
        ''' <summary>
        ''' 负责监听的代码:一个死循环接收socket连接,接收到连接后马上存放到 dtu 列表中
        ''' </summary>
        ''' <remarks></remarks>
        Sub WatchConnecting(ByVal serverSocket As Socket)
            While True  '持续不断的监听客户端的连接请求;侦听只是为了建立连接,建立完成继续侦听
                Try
                    '开始监听客户端连接请求,Accept方法会阻断当前的线程;
                    '一旦监听到一个客户端的请求,就返回一个与该客户端通信的 套接字;
                    Dim sokConnection As Socket = serverSocket.Accept()
    
                    PlcingDTUServiceEventLog.WriteEntry("侦听结果:有客户端连接成功:" & sokConnection.RemoteEndPoint.ToString())
    
                    Dim dtu As dtuClass = New dtuClass
                    dtu.socket = sokConnection
                    dtuList.Add(dtu)
    
                Catch ex As Exception
                    PlcingDTUServiceEventLog.WriteEntry("侦听客户端连接时出错:" & ex.Message)
                    restartComputer(ex.Message.ToString, "侦听客户端连接时出错")
                End Try
            End While
        End Sub

     

  6. 数据接收的代码
        ''' <summary>
        ''' 仅接收数据:在一个死循环中接收数据:接收到数据后,存放到附表中,在已经存在的线程中进行处理。
        ''' </summary>
        ''' <remarks></remarks>
        Sub receieveData()
            While True
                Thread.Sleep(100)
                Try
                    For Each dtu As dtuClass In dtuList
                        'PlcingDTUServiceEventLog.WriteEntry("dtu的信息:" & dtu.socket.RemoteEndPoint.ToString)
                        If dtu.Socket IsNot Nothing And dtu.Socket.Connected Then
                            Dim sokClient As Socket = dtu.Socket
                            'Dim dtuNumber As Integer = 0
    
                            '定义一个1024字节的缓存区;
                            Dim arrMsgRec(1024) As Byte
                            '将接受到的数据存入到输入  arrMsgRec中;
                            Dim length As Integer = -1
                            Try
                                '如果有数据,则接收,否则不处理
                                If sokClient.Available > 0 Then
                                    length = sokClient.Receive(arrMsgRec)       '接收数据,并返回数据的长度;
    
                                    '如果接收到的数据长度正确,则把数据放到消息队列中
                                    If length > 0 Then
                                        dtu.Data = byteToHexStr(arrMsgRec, length)
                                        dtu.length = length
    
                                        '接收到数据后,就把数据存放到队列中待处理
                                        dataQuene.Enqueue(dtu)
    
                                        If writeLOG Then PlcingDTUServiceEventLog.WriteEntry("接收到数据,并把数据存放到队列中待处理:" & dtu.Data & ",队列长度:" & dataQuene.Count)
                                    End If
    
                                End If
    
                            Catch se As SocketException
    
                                PlcingDTUServiceEventLog.WriteEntry("接收数据SOCKET异常:" & se.Message & vbCrLf & "数据长度:" & Str(length) & vbCrLf & "连接信息:" & sokClient.RemoteEndPoint.ToString() & vbCrLf & "  devID:" & getProjectBySocket(sokClient).gprsEquipmentID)
                                PlcingDTUServiceEventLog.WriteEntry(sokClient.RemoteEndPoint.ToString() & " : " & getProjectBySocket(sokClient).gprsEquipmentID & " 号 LQDTU SelectRead: " & sokClient.Poll(1000, SelectMode.SelectRead) & vbCrLf & "  SelectWrite: " & sokClient.Poll(1000, SelectMode.SelectWrite) & vbCrLf & "   SelectError: " & sokClient.Poll(1000, SelectMode.SelectError))
    
                                '---------异常时踢DTU下线
                                socketDissconnect(sokClient, "接收数据SOCKET异常")
    
                                '------------------------------
                                restartComputer(se.Message.ToString, "接收数据SOCKET异常")
                                Continue For
                            Catch ode As ObjectDisposedException
                                PlcingDTUServiceEventLog.WriteEntry("接收数据SOCKET异常:对已释放的对象执行操作时所引发的异常:" & ode.Message & vbCrLf & "数据长度:" & Str(length) & vbCrLf & "连接信息:" & sokClient.RemoteEndPoint.ToString() & vbCrLf & "  devID:" & getProjectBySocket(sokClient).gprsEquipmentID)
                                PlcingDTUServiceEventLog.WriteEntry(sokClient.RemoteEndPoint.ToString() & " : " & getProjectBySocket(sokClient).gprsEquipmentID & " 号 LQDTU SelectRead: " & sokClient.Poll(1000, SelectMode.SelectRead) & vbCrLf & "  SelectWrite: " & sokClient.Poll(1000, SelectMode.SelectWrite) & vbCrLf & "   SelectError: " & sokClient.Poll(1000, SelectMode.SelectError))
    
                                '---------异常时踢DTU下线
                                socketDissconnect(sokClient, "接收数据SOCKET异常:对已释放的对象执行操作时所引发的异常")
    
                                '------------------------------
                                restartComputer(ode.Message.ToString, "对已释放的对象执行操作时所引发的异常")
                                Continue For
                            Catch e As Exception
                                PlcingDTUServiceEventLog.WriteEntry("接收数据一般异常:" + e.Message & vbCrLf & "  devID:" & getProjectBySocket(sokClient).gprsEquipmentID)
    
                                '---------异常时踢DTU下线
                                socketDissconnect(sokClient, "接收数据一般异常")
                                '------------------------------
    
                                restartComputer(e.Message.ToString, "接收数据一般异常")
                                Continue For
                            End Try
                        End If
                    Next
                Catch ex As Exception
                End Try
            End While
        End Sub

     

  7. 数据处理的代码
        ''' <summary>
        ''' 单独的线程中查看消息队列中的消息,有则处理,没有则循环
        ''' </summary>
        ''' <remarks></remarks>
        Sub resolvingData()
            While True
                Thread.Sleep(100)
                '处理接收到的数据
                Dim dtu As dtuClass = Nothing
                Dim dtuNumber As Integer = 0
                If dataQuene.Count <> 0 Then
                    Try
                        dtu = dataQuene.Dequeue
                        '有时还是会出现异常,所以放在容错范围内
                        PlcingDTUServiceEventLog.WriteEntry("从队列中取出最早的一条数据进行处理:" & dtu.Data & ",项目编号:" & getProjectBySocket(dtu.Socket).gprsEquipmentID & ",队列长度:" & dataQuene.Count)
                    Catch ex As Exception
                        PlcingDTUServiceEventLog.WriteEntry("取出数据处理时出错,出错信息:" & ex.Message)
                        Continue While
                    End Try
                Else
                    Continue While
                End If
    
                Try
    
                    Dim strMsg As String = dtu.Data
    
                    '以下为数据处理的具体逻辑,不同的需求,有不同的逻辑,自己编写就好
                    '判断数据长度
                    If Len(strMsg) = 0 Or dtu.length = -1 Then Continue While
    
                    If Len(strMsg) <= 4 And Len(strMsg) >= 1 Then
                        PlcingDTUServiceEventLog.WriteEntry(Now.ToString & ":数据太短:" & dtu.Socket.RemoteEndPoint.ToString() & " 数据:" & strMsg)
                        Continue While
                    End If
    
                    If Len(strMsg) > 4 And Len(strMsg) < 2048 Then
    
                        '定义数据的类型:         '0001、3031:登录  '0003、3032:心跳包  '其他为MODBUS数据
                        Dim dataType As String = Mid(strMsg, 5, 4)
    
                        'GPRS设备登录:'01060001000CD80F
                        If (dataType = "0001" And Left(strMsg, 2) = "00") Or (dataType = "3031" And Left(strMsg, 4) = "3031") Then  ' And Left(strMsg, 2) = "00" 
                            '从登录信息中取得DTU编号
                            dtuNumber = HexStringToAsciiString(Mid(strMsg, 13, 8)) '在字符串截取后再处理就会出现invalid cast error:   从字符串“    ”到类型“Integer”的转换无效。system.InvalidCastException
                            '把设备的登录信息记录到eqState表中(设备在线情况表)
                            Dim Sql As String = "update eqstate set gprsState='1',onoffTime='" & Format(Now, "yyyy-MM-dd HH:mm") & "' where eqid='" & Right("0000" & Trim(Str(dtuNumber)), 4) & "'"
                            Call RunSqlTransaction(connectionWords, Sql)
    
                            '记录该DTU的心跳时间
                            getProjectByID(dtuNumber).heartBeatTime = Now()
                            If writeLOG Then PlcingDTUServiceEventLog.WriteEntry(Now.ToString & ":" & "处理数据结果:数据:" & strMsg & ",项目编号:" & dtuNumber & ",动作:登录")
    
                            '设备登录时,检查DTU列表中是否有DTU的信息,有就直接更换键值对应的连接信息
                            getProjectByID(dtuNumber).socket = dtu.Socket
    
                            '接收到设备心跳信息
                        ElseIf (dataType = "0003" And Left(strMsg, 2) = "00") Or (dataType = "3032" And Left(strMsg, 4) = "3032") Then    'And Left(strMsg, 2) = "00" 
    
                            '从心跳信息中取得DTU编号
                            dtuNumber = HexStringToAsciiString(Mid(strMsg, 13, 8))
                            '设备心跳时,检查DTU列表中是否有DTU信息,有就直接更换键值对应的连接信息
                            getProjectByID(dtuNumber).socket = dtu.Socket
                            '记录该DTU的心跳时间
                            getProjectByID(dtuNumber).heartBeatTime = Now()
    
                            Dim Sql As String = "update eqstate set gprsState='1',onoffTime='" & Format(Now, "yyyy-MM-dd HH:mm") & "' where eqid='" & Right("0000" & Trim(Str(dtuNumber)), 4) & "'"
                            Call RunSqlTransaction(connectionWords, Sql)
    
                            If writeLOG Then PlcingDTUServiceEventLog.WriteEntry(Now.ToString & ":" & "处理数据结果:数据:" & strMsg & ",项目编号:" & dtuNumber & ",动作:心跳")
                        Else       'MODBUS数据,用户数据
    
                            '设备有用户数据来时,检查DTU列表中是否有DTU信息,有就直接更换键值对应的连接信息
                            dtuNumber = CInt(getProjectBySocket(dtu.Socket).gprsEquipmentID)
                            getProjectByID(dtuNumber).heartBeatTime = Now()
                            getProjectByID(dtuNumber).socket = dtu.Socket
    
                            ReDim Preserve getProjectByID(dtuNumber).resolvedDataClass.Data(getProjectByID(dtuNumber).fieldNumber)
    
                            '根据功能码处理接收到的用户数据
                            Select Case Mid(strMsg, 3, 2)
                                Case "01", "02"   '01对应输出线圈,也就是Q值,02对应输入线圈,也就是I值
                                    F01_02_Object(getProjectByID(dtuNumber).modbusCommand, dtuNumber, Now(), strMsg)
                                Case "03", "04"  '03对应保持寄存器,04对应输入寄存器
                                    F03_04_Object(getProjectByID(dtuNumber).modbusCommand, dtuNumber, Now(), strMsg)
                                Case "05", "06", "10"  '这是远程控制返回的数据,需要把这些数据回写到远程控制表中,以表示远程控制的结束
                                    F05_06_10(dtuNumber, strMsg)
                            End Select
    
    
                            Dim projectTMP As projectClass = getProjectByID(dtuNumber)
    
                            '根据当前发送的指令和系统中多指令的记录及是否有结束标志,进行数据的写库操作
                            '在多指令系统中,有的数据来了,并不需要进行任何操作,所以有以下的写法
                            Select Case True
    
                                Case projectTMP.modbusCommand = projectTMP.command '单MODBUS指令时
                                    WriteToDataBase_Object(dtuNumber, projectTMP.modbusCommand, strMsg)
    
                                    '当前指令与第二指令相同 且 第二个地址有结束标志时
                                Case projectTMP.modbusCommand = projectTMP.address2Modbus And projectTMP.address2Over
                                    WriteToDataBase_Object(dtuNumber, projectTMP.modbusCommand, strMsg)
    
                                    '当前指令与第三指令相同 且 第三个地址有结束标志时
                                Case projectTMP.modbusCommand = projectTMP.address3Modbus And projectTMP.address3Over
                                    WriteToDataBase_Object(dtuNumber, projectTMP.modbusCommand, strMsg)
    
                                    '当前指令与第四指令相同 且 第四个地址有结束标志时
                                Case projectTMP.modbusCommand = projectTMP.address4Modbus And projectTMP.address4Over
                                    WriteToDataBase_Object(dtuNumber, projectTMP.modbusCommand, strMsg)
    
                                    '当前指令与第五指令相同 且 第五个地址有结束标志时
                                Case projectTMP.modbusCommand = projectTMP.address5Modbus And projectTMP.address5Over
                                    WriteToDataBase_Object(dtuNumber, projectTMP.modbusCommand, strMsg)
    
                                    '当前指令与第六指令相同 且 第六个地址有结束标志时
                                Case projectTMP.modbusCommand = projectTMP.address6Modbus And projectTMP.address6Over
                                    WriteToDataBase_Object(dtuNumber, projectTMP.modbusCommand, strMsg)
    
                                    '当前指令与第七指令相同 且 第七个地址有结束标志时
                                Case projectTMP.modbusCommand = projectTMP.address7Modbus And projectTMP.address7Over
                                    WriteToDataBase_Object(dtuNumber, projectTMP.modbusCommand, strMsg)
    
                                    '当前指令与第八指令相同 且 第八个地址有结束标志时
                                Case projectTMP.modbusCommand = projectTMP.address8Modbus And projectTMP.address8Over
                                    WriteToDataBase_Object(dtuNumber, projectTMP.modbusCommand, strMsg)
    
                                    '当前指令与第九指令相同 且 第九个地址有结束标志时
                                Case projectTMP.modbusCommand = projectTMP.address9Modbus And projectTMP.address9Over
                                    WriteToDataBase_Object(dtuNumber, projectTMP.modbusCommand, strMsg)
    
                                    '当前指令与第十指令相同 且 第十个地址有结束标志时
                                Case projectTMP.modbusCommand = projectTMP.address10Modbus And projectTMP.address10Over
                                    WriteToDataBase_Object(dtuNumber, projectTMP.modbusCommand, strMsg)
    
                            End Select
                            'PlcingDTUServiceEventLog.WriteEntry("DTUNUMBER:" & dtuNumber & ",数据:" & strMsg & ",指令:" & getProjectByID(dtuNumber).ModbusCommand)
                        End If
                    End If
    
                    If Len(strMsg) > 4000 Then                         '如果接收到的数据长度大于188,则断开这个客户端
                        PlcingDTUServiceEventLog.WriteEntry("数据太长,连接被关闭:(" & dtu.Socket.RemoteEndPoint.ToString() & "):" & strMsg)
                        '从 通信套接字 集合中删除被中断连接的通信套接字:
                        socketDissconnect(dtu.Socket, "数据太长,连接被关闭")
                    End If
    
                Catch ex As Exception
                    PlcingDTUServiceEventLog.WriteEntry("处理数据时出错,出错信息:" & ex.Message)
                    Continue While
                End Try
            End While
        End Sub

     

  8. 有了这些基本的逻辑,建立一个可以侦听远程TCPSOCKET连接的上位机,基本可以实现了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pmmiao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值