一、简介:
本类可以做到基于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
以上,谢谢你的阅读,咱们下次再见~