【VB.NET】自写基于TCP的简单实时通讯类:HarryNet

一、简介:

本类可以做到基于TCP的服务器与客户端实时数据互传(双通道数据传输,应该不会有堵塞情况),每个套接字都有自己的独立线程进行管理,充分发挥多线程性能。对类的主要调用以事件的形式存在。

1、服务器端的主要事件:

  • NewAddClient(ByVal index As Long) '客户端接入完成的事件,index是客户端的ID序号
  • GetNetData(ByVal index As Long, ByVal data() As String) '接收到来自客户端的字符串数据,index是客户端的ID序号,data()是字符串数组(可能因为网络延迟或客户端发送的包过于紧凑,一次接收到多个包因此可能是个字符串数组)
  • ClientNetError(ByVal cid As Long, ByVal sid As Long, ByVal ex As String)  '与客户端的连接出现错误,将注销客户端(断开与客户端的所有连接并关闭相应线程),cid是客户端的ID序号,sid是发生错误套接字的ID序号,ex是错误提示信息

2、客户端的主要事件:

  • GetStringData(ByVal s() As String) '接收到来自服务器的字符串数组(可能因为网络延迟或客户端发送的包过于紧凑,一次接收到多个包因此可能是个字符串数组)
  • GetFileData(ByVal data() As Byte) '接收到来自服务器的字节数组(用于服务器向客户端传输已知大小的文件,由 HarryClient 类中的 fileGetMode 参数控制接收数据是走字符串事件还是字节事件,fileSize 参数控制接收多大的字节数组后产生事件)
  • SocketError(ByVal sockerType As Long, ByVal ex As String) '与服务器的连接套接字发生错误。sockerType是发生错误套接字的类型(1:接收数据套接字,2:发送数据套接字),ex是错误描述

利用以上六个事件做到网络数据的接收与处理。相信熟悉vb6中使用winsock进行网络编程的小伙伴已经知道如何使用了,而且更让人愉快的是这一切都是多线程下进行的……

3、服务器的主要方法:

  • StartListen(ByVal localPort As Integer) '服务器开始监听,localPort是监听的本地端口
  • SendToAll(ByVal info As String) '将字符串发送给所有客户端,info是要发送的字符串
  • SendToClient(ByVal clientId As Long, ByVal info As String) '将字符串发送给指定的客户端,clientId 是客户端ID,info是要发送的字符串消息
  • SendToClient(ByVal clientId As Long, ByVal data() As Byte) 将字节数组发送给指定的客户端,clientId 是客户端ID,data()是要发送的字节数组

4、客户端的主要方法:

  • Connect(ByVal remoteIP As String, ByVal remotePort As Integer) '连接远程服务器,remoteIP 远程服务器IP地址,remotePort 远程服务端口
  • Send(ByVal b() As Byte) '发送字节数组给服务器,b要发送的字节数组 (不建议使用(因为没有chr(5)作为包分割),除非是要传文件给服务器,但是服务器接收文件这方面哈里没有需求,所以没有写,哈哈哈哈哈哈)
  • Send(ByVal s As String) 发送字符串给服务器,s要发送的字符串

利用以上7个方法,则可以做到网络信道的建立及数据的发送。

以上就是HarryNet的简单说明,还有问题欢迎评论中反馈,好的问题哈里会在后期把问题和解答加入本文。

二、类源码:

Imports System.Threading
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Public Module HarryNetGlobal
    Public Function GetNetKey(Optional ByVal length As Long = 10) As String
        Dim key As String = "", r As New Random
        For i As Long = 1 To length
            key &= Chr(r.Next(65, 122))
        Next
        Return key
    End Function
End Module
Public Class HarryServerSocket
    Public sk As Socket
    Public sid As Long
    Public Event FirstSwitchFinish(ByVal id As Long, ByVal mode As Long, ByVal key As String)
    Public Event GetNetData(ByVal id As Long, ByVal data() As String)
    Public Event SocketError(ByVal sid As Long, ByVal ex As String)
    Public modeThread As New Thread(AddressOf GetData), sendDataTemp() As Byte, endFlag As Boolean
    Private StringDataTemp As String = ""
    Public Sub Close()
        endFlag = True
        If modeThread IsNot Nothing Then modeThread.Abort()
        sk.Close()
    End Sub
    Public Sub New(ByVal id As Long, ByVal s As Socket)
        sid = id
        sk = s
        Dim firstSwitchThread As New Thread(AddressOf FirstSwitch)
        firstSwitchThread.Start()
    End Sub
    Public Sub FirstSwitch()
        Dim b(2048) As Byte
        sk.Receive(b)
        Dim socketModeKey As String = Encoding.UTF8.GetString(b).Replace(vbNullChar, "")
        Dim socketModeKeyChr As String = Mid(socketModeKey, 1, 1)
        Select Case socketModeKeyChr
            Case "G"
                RaiseEvent FirstSwitchFinish(sid, 1, Mid(socketModeKey, 2))
            Case "S"
                RaiseEvent FirstSwitchFinish(sid, 2, "")
            Case Else
                RaiseEvent FirstSwitchFinish(sid, 0, "无效:" & socketModeKey)
        End Select
    End Sub
    Private Sub GetData()
        Dim errorFlag As Integer
        Try
            Do Until endFlag
                errorFlag = 0
                Dim b(1024) As Byte
                sk.Receive(b)
                errorFlag = 1
                Dim s As String = StringDataTemp & Encoding.UTF8.GetString(b).Replace(vbNullChar, "")
                errorFlag = 2
                If s.IndexOf(Chr(5)) <> -1 Then
                    errorFlag = 3
                    Dim sT() As String = Split(s, Chr(5))
                    StringDataTemp = sT.Last
                    ReDim Preserve sT(UBound(sT) - 1)
                    errorFlag = 4
                    RaiseEvent GetNetData(sid, sT)
                    errorFlag = 5
                Else
                    errorFlag = 6
                    StringDataTemp = s
                End If
                errorFlag = 7
            Loop
        Catch ex As Exception
            endFlag = True
            RaiseEvent SocketError(sid, "HarryServerSocket.GetData." & errorFlag & "." & ex.Message)
        End Try
    End Sub
    Public Sub Send(ByVal b() As Byte)
        sendDataTemp = b
        Dim sendThread As New Thread(AddressOf SendData)
        sendThread.Start()
    End Sub
    Public Sub Send(ByVal s As String)
        sendDataTemp = Encoding.UTF8.GetBytes(s & Chr(5))
        Dim sendThread As New Thread(AddressOf SendData)
        sendThread.Start()
    End Sub
    Private Sub SendData()
        Try
            sk.Send(sendDataTemp)
        Catch ex As Exception
            endFlag = True
            RaiseEvent SocketError(sid, "HarryServerSocket.SendData." & ex.Message)
        End Try
    End Sub
End Class
''' <summary>
''' 服务器使用的类
''' </summary>
Public Class HarryTCPServer
    Public Class HarryClientSocketsIndexType
        Public sSid As Long = -1
        Public gSid As Long = -1
        Public key As String, parent As HarryTCPServer, remotePoint As IPAddress
        Public netClose As Boolean
        Public Sub New(ByVal sid As Long, ByRef parentServer As HarryTCPServer)
            sSid = sid
            parent = parentServer
        End Sub
        Public Function Live() As Boolean
            If sSid <> -1 And gSid <> -1 Then
                Try
                    Dim sSidLive As Boolean = parent.allSockets(sSid).sk.Poll(10, SelectMode.SelectWrite)
                    Dim gSidLive As Boolean = parent.allSockets(gSid).sk.Poll(10, SelectMode.SelectWrite)
                    Return sSidLive And gSidLive
                Catch ex As Exception
                    Return False
                End Try
            End If
            Return True
        End Function
        Public Sub Close()
            parent.allSockets(sSid).Close()
            parent.allSockets(gSid).Close()
            netClose = True
        End Sub
    End Class
    Public clientSocketsIndex As List(Of HarryClientSocketsIndexType)
    Public socketToClientDic As Dictionary(Of Long, Long)
    Public keyToClientDic As Dictionary(Of String, Long)
    Public allSockets As List(Of HarryServerSocket)
    Public listenSocket As Socket
    Public listenThread As Thread
    Public liveSocketJudgeThread As Thread
    Public serverEndFlag As Boolean
    ''' <summary>
    ''' 不知道客户端ID情况下的陌生Socket(套接字)接入事件
    ''' </summary>
    ''' <param name="id">SocketID(套接字在allSockets中的序)</param>
    ''' <param name="mode">Socket发来的模式意图(接收或发送套接字)</param>
    ''' <param name="key">Socket作为发送套接字时提供的匹配字符串,用于匹配客户端的接收Socket,完成客户端创建</param>
    Public Event FristConnect(ByVal id As Long, ByVal mode As Long, ByVal key As String)
    ''' <summary>
    ''' 一个完整客户端创建完成的事件
    ''' </summary>
    ''' <param name="index">客户端在clientSocketsIndex的序</param>
    Public Event NewAddClient(ByVal index As Long)
    ''' <summary>
    ''' 接收到客户端发来的字符串数据数组(可能因为网络延迟或客户端发送的包过于紧凑,一次接收到多个包因此可能是个字符串数组)
    ''' </summary>
    ''' <param name="index">客户端在clientSocketsIndex的序</param>
    ''' <param name="data">接收到的字符串数组</param>
    Public Event GetNetData(ByVal index As Long, ByVal data() As String)
    ''' <summary>
    ''' 远程Socket接入后引发的事件
    ''' </summary>
    ''' <param name="sid">SocketID(套接字在allSockets中的序)</param>
    ''' <param name="mode">Socket发来的模式意图(接收或发送套接字)</param>
    ''' <param name="key">Socket作为发送套接字时提供的匹配字符串,用于匹配客户端的接收Socket,完成客户端创建</param>
    ''' <param name="cid">该Socket属于的客户端在clientSocketsIndex的序</param>
    Public Event SocketJion(ByVal sid As Long, ByVal mode As Long, ByVal key As String, ByVal cid As Long)
    ''' <summary>
    ''' Socket(套接字)收发或连接过程中,发生错误后的事件
    ''' </summary>
    ''' <param name="sid">SocketID(套接字在allSockets中的序)</param>
    ''' <param name="ex">错误提示</param>
    Public Event SocketError(ByVal sid As Long, ByVal ex As String)
    ''' <summary>
    ''' 客户端任意一个连接发生错误后,发生的事件(客户端将被注销)
    ''' </summary>
    ''' <param name="cid">发生错误的客户端在clientSocketsIndex的序</param>
    ''' <param name="sid">发生错误的SocketID(套接字在allSockets中的序)</param>
    ''' <param name="ex">错误提示</param>
    Public Event ClientNetError(ByVal cid As Long, ByVal sid As Long, ByVal ex As String)
    ''' <summary>
    ''' 服务器开始监听
    ''' </summary>
    ''' <param name="localPort">监听的本地端口</param>
    Public Sub StartListen(ByVal localPort As Integer)
        clientSocketsIndex = New List(Of HarryClientSocketsIndexType)
        socketToClientDic = New Dictionary(Of Long, Long)
        keyToClientDic = New Dictionary(Of String, Long)
        allSockets = New List(Of HarryServerSocket)
        listenSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        listenSocket.Bind(New IPEndPoint(IPAddress.Parse("0.0.0.0"), localPort))
        listenThread = New Thread(AddressOf Listen)
        listenThread.Start()
        liveSocketJudgeThread = New Thread(AddressOf LiveSocketJudge)
        liveSocketJudgeThread.Start()
    End Sub
    Private Sub LiveSocketJudge()
        Do Until serverEndFlag
            For i As Long = 0 To clientSocketsIndex.Count - 1
                If Not clientSocketsIndex(i).netClose Then
                    If Not clientSocketsIndex(i).Live Then
                        clientSocketsIndex(i).Close()
                        RaiseEvent ClientNetError(i, -1, "HarryTCPServer.主动检测中发现停止活动")
                    End If
                End If
            Next
            Thread.Sleep(30)
        Loop
    End Sub
    ''' <summary>
    ''' 将字符串发送给指定的客户端
    ''' </summary>
    ''' <param name="clientId">客户端ID</param>
    ''' <param name="info">要发送的字符串</param>
    Public Sub SendToClient(ByVal clientId As Long, ByVal info As String)
        allSockets(clientSocketsIndex(clientId).sSid).Send(info)
    End Sub
    ''' <summary>
    ''' 将字节数组发送给指定的客户端
    ''' </summary>
    ''' <param name="clientId">客户端ID</param>
    ''' <param name="data">要发送的字节数组</param>
    Public Sub SendToClient(ByVal clientId As Long, ByVal data() As Byte)
        allSockets(clientSocketsIndex(clientId).sSid).Send(data)
    End Sub
    ''' <summary>
    ''' 将字符串发送给所有客户端
    ''' </summary>
    ''' <param name="info">发送的字符串</param>
    Public Sub SendToAll(ByVal info As String)
        For i As Integer = 0 To clientSocketsIndex.Count - 1
            allSockets(clientSocketsIndex(i).sSid).Send(info)
        Next
    End Sub
    Private Function AddClientSocketsIndex(cSi As HarryClientSocketsIndexType) As Long
        For i As Long = 0 To clientSocketsIndex.Count - 1
            If clientSocketsIndex(i).netClose Then
                clientSocketsIndex(i) = cSi
                Return i
            End If
        Next
        clientSocketsIndex.Add(cSi)
        Return clientSocketsIndex.Count - 1
    End Function
    Public Sub SocketModeConfirm(ByVal id As Long, ByVal mode As Long, ByVal key As String)
        Dim cid As Long = -1
        RaiseEvent FristConnect(id, mode, key)
        Select Case mode
            Case 1
                If keyToClientDic.ContainsKey(key) Then
                    cid = keyToClientDic(key)
                    clientSocketsIndex(cid).gSid = id
                    If Not socketToClientDic.ContainsKey(id) Then
                        socketToClientDic.Add(id, cid)
                    Else
                        socketToClientDic(id) = cid
                    End If
                    allSockets(id).modeThread.Start()
                End If
            Case 2
                cid = AddClientSocketsIndex(New HarryClientSocketsIndexType(id, Me))
                key = GetNetKey()
                Do While keyToClientDic.ContainsKey(key)
                    key = GetNetKey()
                Loop
                keyToClientDic.Add(key, cid)
                If Not socketToClientDic.ContainsKey(id) Then
                    socketToClientDic.Add(id, cid)
                Else
                    socketToClientDic(id) = cid
                End If
                allSockets(clientSocketsIndex(cid).sSid).Send(Encoding.UTF8.GetBytes(key))
        End Select
        RaiseEvent SocketJion(id, mode, key, cid)
        If cid <> -1 Then
            With clientSocketsIndex(cid)
                If .sSid <> -1 And .gSid <> -1 Then
                    RaiseEvent NewAddClient(cid)
                End If
            End With
        End If
    End Sub

    Private Sub SocketGetNetData(ByVal id As Long, ByVal data() As String)
        Try
            RaiseEvent GetNetData(socketToClientDic(id), data)
        Catch ex As Exception
            RaiseEvent SocketError(id, "HarryTCPServer.SocketGetNetData." & ex.Message)
        End Try
    End Sub

    Private Sub GetSocketError(ByVal sid As Long, ByVal ex As String)
        RaiseEvent SocketError(sid, ex)
        If socketToClientDic.ContainsKey(sid) Then
            Dim cid As Long = socketToClientDic(sid)
            clientSocketsIndex(cid).Close()
            RaiseEvent ClientNetError(cid, sid, ex)
        End If
    End Sub
    Private Sub Listen()
        serverEndFlag = False
        listenSocket.Listen(5)
        Do Until serverEndFlag
            Dim nSk As Socket = listenSocket.Accept()
            Dim sid As Long = allSockets.Count
            allSockets.Add(New HarryServerSocket(sid, nSk))
            AddHandler allSockets(sid).FirstSwitchFinish, AddressOf SocketModeConfirm
            AddHandler allSockets(sid).GetNetData, AddressOf SocketGetNetData
            AddHandler allSockets(sid).SocketError, AddressOf GetSocketError
        Loop
        For i As Long = 0 To allSockets.Count - 1
            allSockets(i).endFlag = True
        Next
        listenSocket.Close()
    End Sub
End Class
''' <summary>
''' 客户端使用的类
''' </summary>
Public Class HarryTCPClient
    Public sendSocket As Socket, getSocket As Socket, connectState As Long, netKey As String
    Public remoteServerIP As String, remoteServerPort As Integer
    Public getNetDataThread As Thread, sendNetDataThread As Thread, clientEndFlag As Boolean
    Public sendDataList As List(Of Byte()), fileGetMode As Boolean, fileSize As Integer
    Private StringDataTemp As String = ""
    ''' <summary>
    ''' 接收到服务器的字符串数组事件。(可能因为网络延迟或客户端发送的包过于紧凑,一次接收到多个包因此可能是个字符串数组)
    ''' </summary>
    ''' <param name="s">数据数组</param>
    Public Event GetStringData(ByVal s() As String)
    ''' <summary>
    ''' 接收到服务器传来的文件。
    ''' </summary>
    ''' <param name="data">文件字节数组</param>
    Public Event GetFileData(ByVal data() As Byte)
    ''' <summary>
    ''' Socket(套接字)发生错误。
    ''' </summary>
    ''' <param name="sockerType">出现错误Socket(套接字)的类型(收还是发),出现错误后客户端将被服务器注销。</param>
    ''' <param name="ex">错误提示</param>
    Public Event SocketError(ByVal sockerType As Long, ByVal ex As String)
    ''' <summary>
    ''' 连接远程服务器
    ''' </summary>
    ''' <param name="remoteIP">远程服务器IP地址</param>
    ''' <param name="remotePort">远程服务器的服务端口</param>
    Public Sub Connect(ByVal remoteIP As String, ByVal remotePort As String)
        getSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        sendSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        sendDataList = New List(Of Byte())
        connectState = 1
        remoteServerIP = remoteIP
        remoteServerPort = CInt(remotePort)
        getSocket.Connect(remoteServerIP, remoteServerPort)
        connectState = 2
        Dim firstConnectThread As New Thread(AddressOf FirstConnect)
        firstConnectThread.Start()
    End Sub
    ''' <summary>
    ''' 连接远程服务器
    ''' </summary>
    ''' <param name="remoteIP">远程服务器IP地址</param>
    ''' <param name="remotePort">远程服务器的服务端口</param>
    Public Sub Connect(ByVal remoteIP As String, ByVal remotePort As Integer)
        getSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        sendSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        sendDataList = New List(Of Byte())
        connectState = 1
        remoteServerIP = remoteIP
        remoteServerPort = remotePort
        getSocket.Connect(remoteServerIP, remoteServerPort)
        connectState = 2
        Dim firstConnectThread As New Thread(AddressOf FirstConnect)
        firstConnectThread.Start()
    End Sub
    Public Sub FirstConnect()
        connectState = 3
        getSocket.Send(Encoding.UTF8.GetBytes("S"))
        Dim b(8192) As Byte
        getSocket.Receive(b)
        netKey = Replace(Encoding.UTF8.GetString(b), vbNullChar, "")
        connectState = 4
        sendSocket.Connect(remoteServerIP, remoteServerPort)
        connectState = 5
        sendSocket.Send(Encoding.UTF8.GetBytes("G" & netKey))
        connectState = 6
        getNetDataThread = New Thread(AddressOf GetData)
        getNetDataThread.Start()
        sendNetDataThread = New Thread(AddressOf SendData)
        sendNetDataThread.Start()
        connectState = 7
    End Sub
    Private Sub GetData()
        clientEndFlag = False
        Try
            Do Until clientEndFlag
                If fileGetMode Then
                    Dim b(fileSize) As Byte
                    getSocket.Receive(b)
                    RaiseEvent GetFileData(b)
                Else
                    Dim b(1024) As Byte
                    getSocket.Receive(b)
                    Dim s As String = StringDataTemp & Encoding.UTF8.GetString(b).Replace(vbNullChar, "")
                    If s.IndexOf(Chr(5)) <> -1 Then
                        Dim sT() As String = Split(s, Chr(5))
                        StringDataTemp = sT.Last
                        ReDim Preserve sT(UBound(sT) - 1)
                        RaiseEvent GetStringData(sT)
                    Else
                        StringDataTemp = s
                    End If
                End If
            Loop
        Catch ex As Exception
            connectState = 8
            RaiseEvent SocketError(1, "HarryTCPClient.GetData." & ex.Message)
        End Try
    End Sub
    ''' <summary>
    ''' 发送字节数组给服务器
    ''' </summary>
    ''' <param name="b">要发送的字节数组</param>
    Public Sub Send(ByVal b() As Byte)
        sendDataList.Add(b)
    End Sub
    ''' <summary>
    ''' 发送字符串给服务器
    ''' </summary>
    ''' <param name="s">要发送的字符串</param>
    Public Sub Send(ByVal s As String)
        sendDataList.Add(Encoding.UTF8.GetBytes(s & Chr(5)))
    End Sub
    Private Sub SendData()
        clientEndFlag = False
        Try
            Do Until clientEndFlag
                If sendDataList.Count > 0 Then
                    Dim b() As Byte = sendDataList(0)
                    sendDataList.RemoveAt(0)
                    sendSocket.Send(b)
                Else
                    Thread.Sleep(5)
                End If
            Loop
        Catch ex As Exception
            connectState = 8
            RaiseEvent SocketError(2, "HarryTCPClient.SendData." & ex.Message)
        End Try
    End Sub
End Class

以上,谢谢你的阅读,咱们下次再见~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
VB.NET中编写Modbus TCP通信示例,通常会使用第三方库,如`TinCanModbus`或`Honeywell.Modbus.TCP`这样的工具,它们提供了简化Modbus协议处理的功能。以下是一个简化的示例,展示了如何使用这些库进行TCP通信: ```vb.net Imports TinCanModbus Public Class ModbusTcpExample Dim client As New ModbusTcpClient("192.168.1.100", 502) ' 假设服务器地址和端口 Private Sub Connect_Click(sender As Object, e As EventArgs) Handles Connect.Click Try client.Connect() MessageBox.Show("Connected to Modbus Server") Catch ex As Exception MessageBox.Show("Failed to connect: " & ex.Message) End Try End Sub Private Sub Disconnect_Click(sender As Object, e As EventArgs) Handles Disconnect.Click If client.IsConnected Then client.Disconnect() MessageBox.Show("Disconnected from Modbus Server") Else MessageBox.Show("Not connected.") End If End Sub Private Sub Read coils_Click(sender As Object, e As EventArgs) Handles ReadCoils.Click Dim coilIds As Integer() = {0, 1} ' 选择要读取的Coil ID Dim result As ModbusResponse = client.ReadDiscreteInputs(coilIds) If result.IsSuccess Then Console.WriteLine($"Coil status: {result.ReadResult}") Else Console.WriteLine($"Error reading coils: {result.ErrorMessage}") End If End Sub End Class ``` 这个例子中,我们创建了一个`ModbusTcpClient`实例,并定义了连接、断开连接以及读取离散输入(例如Coil)的方法。实际应用中,你需要根据设备的映射调整参数,并可能需要处理响应中的数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值