65.9K
CodeProject 正在变化。 阅读更多。
Home

一个用于 VB 的高性能 TCP/IP Socket 服务器 COM 组件

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.50/5 (11投票s)

2002年6月16日

CPOL

6分钟阅读

viewsIcon

436737

downloadIcon

11802

尽管基于套接字(socket)并使用 IO 完成端口的 TCP/IP 服务器通常是用 C++ 编写的,但有时用 Visual Basic 编写这样的服务器也很有用。

下面的源代码是使用Visual Studio 6.0 SP5和Visual Studio .NET构建的。你需要安装一个版本的Microsoft Platform SDK

概述

尽管基于套接字并使用 IO 完成端口的 TCP/IP 服务器通常是用 C++ 编写的,但有时用 Visual Basic 编写这样的服务器也很有用。你可以为此使用 Winsock 控件,但它处理 Windows Sockets 接口的级别非常低,你需要编写大量的 VB 代码。本文介绍了一个简单的 COM 对象,它封装了我们在之前的文章中开发的高性能套接字服务器框架。该 COM 对象提供了一个简单而强大的接口,让你能够以最少的 VB 代码轻松构建高性能 TCP/IP 服务器。

本文介绍了套接字服务器 COM 对象和一个简单的 VB 服务器应用程序示例,并描述了如何使用该 COM 对象。下一篇文章将讨论 COM 对象的构建方式以及对象本身的设计决策和编码。请注意,此对象只能在 Windows NT/2000/XP 上使用——它不支持 Windows 9x。

一个高性能 TCP/IP Socket 服务器 COM 对象

套接字服务器 COM 对象提供了一个用于创建 TCP/IP 套接字服务器的工厂对象。该工厂对象被标记为 appobject,因此你无需显式引用即可使用它,如下例所示:

   Dim server As COMSOCKETSERVERLib.server
   Set server = CreateSocketServer(5001)

上述代码创建了一个将在端口 5001 上监听的服务器。如果你有一台多宿主机,那么你可以选择性地为服务器指定一个单独的 IP 地址进行监听。

   Dim server As COMSOCKETSERVERLib.server
   Set server = CreateSocketServer(5001, "192.168.0.1")

一旦你有了服务器对象,你就可以通过调用 StartListening 来开始接受连接,并通过调用 StopListening 来停止接受连接。在你停止监听之后再次调用 StartListening 是完全可以的——如果你想在暂停服务器以阻止它接受新连接后恢复服务器,你可能会这样做。服务器还有一个只读的 LocalAddress 属性,它将返回服务器的 IP 地址和端口。服务器的主要编码发生在套接字服务器触发的三个事件处理程序中,这些事件用于通知你客户端连接、客户端数据到达和客户端断开连接。这些事件如下:

 Private Sub m_server_OnConnectionClosed(ByVal socket As COMSOCKETSERVERLib.ISocket)

 End Sub

 Private Sub m_server_OnConnectionEstablished(ByVal socket As COMSOCKETSERVERLib.ISocket)

 End Sub

 Private Sub m_server_OnDataReceived( _
      ByVal socket As COMSOCKETSERVERLib.ISocket, _
      ByVal data As COMSOCKETSERVERLib.IData)
    
 End Sub

请注意,每个事件都会向你传递一个 Socket 对象。套接字对象代表连接的客户端套接字,并为你提供操作其状态的方法和属性。你可以通过两种方式向客户端写入数据;Write 方法允许你传输字节数组,WriteString 允许你发送 stringWriteString 接受一个可选的布尔标志,该标志决定 string 是否以 UNICODE string 发送。两个写入方法都接受一个可选的布尔标志,允许你指定这是你将在此套接字上执行的最后一次写入,并且在 write 完成后应该关闭套接字的发送端。你可以使用异步 RequestRead 方法从客户端读取数据——当读取完成时,将触发 OnDataReceived 事件,并将读取到的数据传递给你。当你使用完套接字时,可以使用 Shutdown 方法关闭连接。这允许你同时或独立关闭连接的读取端和写入端。你还可以使用 Close 方法强制关闭套接字,但请注意,这可能会导致部分传输的数据丢失。

Socket 对象有两个属性:RemoteAddress 属性提供对与连接客户端端点关联的地址的只读访问。UserData 属性提供了一个地方来存储你自己的“每个连接”数据,这可以是任何你喜欢的东西,但一个常见的用途是作为关联你用于存储连接会话状态的类的方式。你的用户数据存储在 Socket 中,并且由于每次数据到达和连接关闭时都会将 Socket 传递给你,因此你可以在需要时检索你的每个会话状态。

涉及的最后一个对象是当你完成读取并触发 OnDataReceived 事件时传递给你的 Data 对象。它有两个方法,Read 返回字节数组形式的数据,ReadString 返回 string 形式的数据。请记住,你不能保证客户端通过一次 RequestRead 调用和随后的 OnDataReceived 事件触发,以单个块形式发送的数据会被你完整接收。数据可能会被分割成多个数据包,你需要通过再次调用 RequestRead 并自行缓冲数据来处理重新组装。由于存在数据包碎片化的危险,ReadString 无法指定正在读取的数据已经是 UNICODE string。如果你的协议涉及发送和接收 UNICODE string,那么你必须使用 Read 方法读取数据,并自行将字节数组转换为 string。这是因为由于数据包碎片化可能导致接收不完整的 UNICODE 字符,在此级别之下进行转换本质上是不安全的。你可以在同一个 Data 对象上多次调用 ReadReadString 的组合,并且每次都会检索到相同的数据。

VB 中的一个示例服务器

作为一个简单的例子,我们将开发一个回显服务器,它存储一些每个连接的状态,并可以在回显一定数量的数据包后以各种方式可选地关闭连接。

我们的回显服务器由两个表单组成。第一个允许你指定服务器应该监听的端口,并设置一些参数。

创建服务器时,会显示第二个表单,其中包含一个列表控件并处理服务器触发的事件。创建服务器后,你可以切换回第一个表单,更改端口号并创建另一个服务器。所有服务器都可以独立运行。

第一个表单后面的代码相当简单,主要包括管理用户界面元素并将参数传递给第二个表单。创建套接字服务器本身所需的工作非常简单。

Option Explicit

Private Sub Command1_Click()

    Dim server As JBSOCKETSERVERLib.server
    Set server = CreateSocketServer(CLng(Text1.Text))
    
    Dim frm As Form2
    Set frm = New Form2
    
    frm.SetServer server
    frm.ShowDataPackets.Value = ShowDataPackets.Value
    frm.DataIsBytes.Value = DataIsBytes.Value
    frm.DataIsString.Value = DataIsString.Value
    frm.SignOnAsUnicode.Value = SignOnAsUnicode.Value
    
    If ShutdownEnabled.Value Then
    
        If ShutdownAfterWrite.Value Then
            frm.ShutdownAfterWrite = CLng(ShutdownAfter.Text)
        ElseIf ShutdownSocket.Value Then
            frm.ShutdownAfter = CLng(ShutdownAfter.Text)
        ElseIf CloseSocket.Value Then
            frm.CloseAfter = CLng(ShutdownAfter.Text)
        End If
    
    End If
    
    server.StartListening
    
    frm.Show , Me

End Sub

Private Sub ShowDataPackets_Click()

    DataIsFrame.Enabled = ShowDataPackets.Value
    DataIsBytes.Enabled = ShowDataPackets.Value
    DataIsString.Enabled = ShowDataPackets.Value

End Sub

Private Sub ShutdownEnabled_Click()

    ShutdownAfterWrite.Enabled = ShutdownEnabled.Value
    ShutdownSocket.Enabled = ShutdownEnabled.Value
    CloseSocket.Enabled = ShutdownEnabled.Value
    ShutdownAfter.Enabled = ShutdownEnabled.Value
    
End Sub

第二个表单稍微复杂一些。处理套接字服务器的代码如下:

Option Explicit

Dim WithEvents m_server As JBSOCKETSERVERLib.server
Public ShutdownAfterWrite As Integer
Public ShutdownAfter As Integer
Public CloseAfter As Integer

Private ListWidth As Integer
Private ListHeight As Integer

Public Sub SetServer(server As JBSOCKETSERVERLib.server)
 
    Set m_server = server
    
    Caption = "Socket server listening on: " & server.LocalAddress.Port
    
End Sub

Private Sub m_server_OnConnectionClosed(ByVal Socket As JBSOCKETSERVERLib.ISocket)

    If ShowDataPackets.Value Then
        AddToList "OnConnectionClosed : " & GetAddressAsString(Socket)
    End If
    
    Dim counter As Class1
    Set counter = Socket.UserData

    If ShowDataPackets.Value Then
        AddToList "User data = " & counter.GetCount()
    End If
    
    Socket.UserData = 0

End Sub

Private Sub m_server_OnConnectionEstablished(ByVal Socket As JBSOCKETSERVERLib.ISocket)

    If ShowDataPackets.Value Then
        AddToList "OnConnectionEstablished : " & GetAddressAsString(Socket)
    End If
    
    Dim counter As Class1
    Set counter = New Class1
    
    Socket.UserData = counter
    
    Socket.WriteString "Welcome to VB echo server" & vbCrLf, SignOnAsUnicode.Value

    Socket.RequestRead

End Sub

Private Sub m_server_OnDataReceived( _
    ByVal Socket As JBSOCKETSERVERLib.ISocket, _
    ByVal Data As JBSOCKETSERVERLib.IData)

    Dim counter As Class1
    Set counter = Socket.UserData
    
    counter.IncrementCount

    If DataIsBytes.Value Then
    
        OnReceivedBytes Socket, Data, counter.GetCount
    
    ElseIf DataIsString.Value Then
    
        OnReceivedString Socket, Data, counter.GetCount

    End If

    Socket.RequestRead

    If ShutdownAfter <> 0 And ShutdownAfter = counter.GetCount Then
        Socket.Shutdown ShutdownBoth
    End If
    
    If CloseAfter <> 0 And CloseAfter = counter.GetCount Then
        Socket.Close
    End If

End Sub

Private Sub OnReceivedBytes( _
    ByVal Socket As JBSOCKETSERVERLib.ISocket, _
    ByVal Data As JBSOCKETSERVERLib.IData, _
    counter As Integer)

    Dim Bytes() As Byte
    Bytes = Data.Read()

    If ShowDataPackets.Value Then
    
        Dim stringRep As String
        
        Dim i As Integer

        For i = LBound(Bytes) To UBound(Bytes)

            stringRep = stringRep & CLng(Bytes(i)) & " "

        Next i
    
        AddToList "OnDataReceived : " & GetAddressAsString(Socket) & " - " & stringRep
    
    End If
        
    Dim thenShutdown As Boolean
    thenShutdown = False
    
    If ShutdownAfterWrite <> 0 And ShutdownAfterWrite = counter Then
        thenShutdown = True
    End If
    
    Socket.Write Bytes, thenShutdown

End Sub

Private Sub OnReceivedString( _
    ByVal Socket As JBSOCKETSERVERLib.ISocket, _
    ByVal Data As JBSOCKETSERVERLib.IData, _
    counter As Integer)

    Dim theData As String
    theData = Data.ReadString
    
    If ShowDataPackets.Value Then
        AddToList "OnDataReceived : " & GetAddressAsString(Socket) & " - " & theData
    End If
    
    Dim thenShutdown As Boolean
    thenShutdown = False
    
    If ShutdownAfterWrite <> 0 And ShutdownAfterWrite = counter Then
        thenShutdown = True
    End If
    
    Socket.WriteString theData, False, thenShutdown
    
End Sub

Private Function GetAddressAsString(Socket As JBSOCKETSERVERLib.ISocket) As String

    GetAddressAsString = Socket.RemoteAddress.Address & " : " & Socket.RemoteAddress.Port

End Function

由于我们可以显示和回显数据,并以所有可能的方式关闭连接,因此代码变得更加复杂。请注意我们如何在 m_server_OnConnectionEstablished 事件中初始化用户数据并将其存储在 Socket 中。然后我们在 m_server_OnDataReceivedm_server_OnConnectionClosed 事件中检索并使用它。

修订历史

  • 2002 年 6 月 11 日 - 初次修订
  • 2002 年 6 月 26 日 - 在创建监听套接字期间删除了对 ReuseAddress() 的调用,因为它不是必需的 - 感谢 Alun Jones 指出这一点
© . All rights reserved.