现在的位置: 首页 > 综合 > 正文

制作一个网络通讯类(二)

2018年05月04日 ⁄ 综合 ⁄ 共 9707字 ⁄ 字号 评论关闭

http://coffecoco.bokee.com/806282.html

制作一个网络通讯类(二)- -

                                      

CTcpTalk类代码

Public Class CTcpTalk
'状态枚举
    Public Enum StateConstants
        sckClosed = 0             '已经关闭
        sckListening = 1          '正在监听
        sckConnectionPending = 2  '连接未决
        sckResolvingHost = 3      '正在解析主机
        sckHostResolved = 4       '主机解析完毕
        sckConnecting = 5         '正在连接
        sckConnected = 6          '已连接
        sckClosing = 7            '正在关闭
        sckError = 100            '错误
    End Enum

'事件
    '监听关闭时触发
    Public Event Closed()
    '建立新连接时触发
    Public Event Connect()
    '接收到数据时触发
    Public Event DataArrival(ByVal bytesTotal As Long)
    '发生错误时触发
    Public Event ErrorEvt(ByVal ex As CTcpTalkException)
    '发送完成时触发
    Public Event SendComplete()

'成员
    '收到的字节总数
    Private m_BytesReceived As Long
    '存储错误信息的对象
    Private m_Error As CTcpTalkException
    '索引号
    Private m_Index As Integer
    '本机名称
    Private m_LocalHostName As String
    '本机IP
    Private m_LocalIP As String
    '监听端口
    Private m_LocalPort As Long
    '远程主机
    Private m_RemoteHost As String
    '远程主机IP
    Private m_RemoteHostIP As String
    '远程端口
    Private m_RemotePort As Long
    '状态
    Private m_State As StateConstants
    '接收到的字符串
    Private m_DataReceived As String
    '要发送的字符串
    Private m_DataSend As String
    '监听器
    Private m_sckListen As TcpListener
    '接收外部申请的TCPClient
    Private m_sckAccept As TcpClient
    '用于申请连接的TCPClient
    Private m_sckClient As TcpClient
    '停止监听控制变量
    Private m_stopListen As Boolean
    '监听线程
    Private m_thdListen As Thread
    '发送线程
    Private m_thdSend As Thread

'属性

'已经收到的字节总数
'只读
    Public ReadOnly Property BytesReceived() As Long
        Get
            Return m_BytesReceived
        End Get
    End Property

'索引号。用于在控件数列中唯一标识控件对象
'只读
    Public ReadOnly Property Index() As Integer
        Get
            Return m_Index
        End Get
    End Property

'本机的名称
'只读
    Public ReadOnly Property LocalHostName() As String
        Get
            Return m_LocalHostName
        End Get
    End Property

'本机的IP
'只读
    Public ReadOnly Property LocalIP() As String
        Get
            Return m_LocalIP
        End Get
    End Property

'监听端口, 允许在没有连接或监听的方法下访问
'如果没有设置, 则为0
    Public Property LocalPort()
        Get
            Return m_LocalPort
        End Get
        Set(ByVal Value)
            m_LocalPort = Value
        End Set
    End Property

'远程机器
'可以是IP地址,也可以是可解析的主机名
'必须在发送数据之前进行设置
    Public Property RemoteHost() As String
        Get
            Return m_RemoteHost
        End Get
        Set(ByVal Value As String)
            m_RemoteHost = Value
        End Set
    End Property

'远程机器IP
'只有在与远方主机连接建立之后才可以读取
'只读
    Public ReadOnly Property RemoteHostIP() As String
        Get
            Return m_RemoteHostIP
        End Get
    End Property

'远程机器端口
'必须在发送数据或建立连接之前设置
    Public Property RemotePort() As Long
        Get
            Return m_RemotePort
        End Get
        Set(ByVal Value As Long)
            m_RemotePort = Value
        End Set
    End Property

'状态
    Public ReadOnly Property State() As StateConstants
        Get
            Return m_State
        End Get
    End Property

'构造函数
    Public Sub New()
        InitObj()
    End Sub

    '用端口号初始化实例
    '用于TCPListener
    Public Sub New(ByVal listenport As Long)
        InitObj()
        m_LocalPort = listenport
    End Sub

    '用远端主机名和端口号初始化实例
    '用于TCPClient
    Public Sub New(ByVal hostname As String, ByVal hostport As Long)
        InitObj()
        m_RemoteHost = hostname
        m_RemotePort = hostport
    End Sub

     '基本初始化
    Private Sub InitObj()
        '已经收到的字节总数
        m_BytesReceived = 0
        '需要传送的总字节数
        m_BytesTotal = 0
        '存储错误信息的对象
        m_Error = New CTcpTalkException
        '索引号
        m_Index = -1
        '本机名称
        m_LocalHostName = Dns.GetHostName
        m_Error = New CTcpTalkException(ex.Message)
        '本机IP
        m_LocalIP = Dns.Resolve(m_LocalHostName).AddressList(0).ToString
        '绑定的端口
        m_LocalPort = 0
        '远程主机
        m_RemoteHost = ""
        '远程主机IP
        m_RemoteHostIP = ""
        '远程端口
        m_RemotePort = 0
        '状态
        m_State = StateConstants.sckClosed
        ' 监听器
        m_sckListen = Nothing
        '接收外部申请的TCPClient
        m_sckAccept = Nothing
        '用于申请连接的TCPClient
        m_sckClient = Nothing
        '接收到的字符串
        m_DataReceived = ""
        '要发送的字符串
        m_DataSend = ""
        '停止监听
        m_stopListen = True
    End Sub

'方法
    '启动监听线程
    Public Sub Open()
        If m_thdListen Is Nothing Then
        Else  '如果正在监听则关闭当前监听
            If m_State <> StateConstants.sckClosed Then
                Close()
            End If
        End If
        m_thdListen = New Thread(AddressOf StartListen)
        m_thdListen.Start()
    End Sub

    '获取收到的数据
    Public Function GetData() As String
        Dim str As String = m_DataReceived
        m_DataReceived = ""
        Return str
    End Function

    '启动发送数据线程
    Public Sub Send(ByVal datasend As String)
        m_DataSend = datasend
        m_thdSend = New Thread(AddressOf SendMsg)
        m_thdSend.Start()
    End Sub

    '发送数据
    Private Sub SendMsg()
        '进入连接状态
        SetState(StateConstants.sckConnecting)
        '检查参数
        If m_RemotePort = 0 Then
            ErrorHandle("Send", "没有设置端口号")
            Exit Sub
        End If
        Try
            Dns.Resolve(Me.RemoteHost)
        Catch ex As Exception
            ErrorHandle("Send", ex)
            Exit Sub
        End Try
        '开始连接
        Try
            SetState(StateConstants.sckResolvingHost)
            m_sckClient = New TcpClient(RemoteHost, RemotePort)
            SetState(StateConstants.sckHostResolved)
            SetState(StateConstants.sckConnected)
            RaiseEvent Connect()
        Catch ex As Exception
            ErrorHandle("Send", ex)
            Exit Sub
        End Try
        '开始发送数据
        Try
            Dim data As Byte() = System.Text.Encoding.Unicode.GetBytes(m_DataSend)
            Dim stream As NetworkStream = m_sckClient.GetStream
            stream.Write(data, 0, data.Length)
            m_sckClient.Close()
            SetState(StateConstants.sckListening)
            RaiseEvent SendComplete()
        Catch ex As Exception
            ErrorHandle("Send", ex)
            Exit Sub
        End Try
    End Sub

'监听线程
    Private Sub StartListen()
        '参数检查
        If LocalPort = 0 Then
            ErrorHandle("StartListen", "没有设置端口号")
            Exit Sub
        End If
        '初始化监听用的套接字
        Try
            m_sckListen = New TcpListener(Dns.Resolve(LocalHostName).AddressList(0), LocalPort)
        Catch ex As SocketException
            ErrorHandle("StartListen", ex)
            Exit Sub
        End Try
        Try
            m_sckAccept = New TcpClient
            m_sckListen.Start()
        Catch ex As Exception
            ErrorHandle("StartListen", ex)
            Exit Sub
        End Try
        m_stopListen = True
        '读缓冲
        Dim bytes(5120) As [Byte]
        Dim data As String = Nothing
         '开始监听
        Try
            '进入监听循环
            While m_stopListen
                SetState(StateConstants.sckListening)
                Try
                    '接收连接请求
                    m_sckAccept = m_sckListen.AcceptTcpClient
                Catch ex As Exception
                    ErrorHandle("Listening and Accepting AcceptTcpClient", ex)
                    Exit Sub
                End Try
                SetState(StateConstants.sckConnected)
                RaiseEvent Connect()
                '开始接收数据
                data = Nothing
                m_BytesReceived = 0
                m_DataReceived = ""
                '用流对象进行读写
                Dim stream As NetworkStream = m_sckAccept.GetStream
                Dim i As Int32
                i = stream.Read(bytes, 0, bytes.Length - 1)
                m_BytesReceived = m_BytesReceived + i
                '将数据字节转换为UNICODE字符串
                data = System.Text.Encoding.Unicode.GetString(bytes, 0, i)
                m_DataReceived = m_DataReceived + data
                '循环接收客户端发来的所有数据
                While (stream.DataAvailable)
                    i = stream.Read(bytes, 0, bytes.Length - 1)
                    m_BytesReceived = m_BytesReceived + bytes.Length
                    '将数据字节转换为UNICODE字符串
                    data = System.Text.Encoding.Unicode.GetString(bytes, 0, i)
                    m_DataReceived = m_DataReceived + data
                End While
                '触发DataArrival事件
                RaiseEvent DataArrival(m_BytesReceived)
                '关闭连接
                m_sckAccept.Close()
            End While
            '关闭监听
            m_sckListen.Stop()
            SetState(StateConstants.sckClosed)
            RaiseEvent Closed()
        Catch ex As Exception
            ErrorHandle("Listening and Accepting", ex)
            Exit Sub
        End Try
    End Sub

'关闭监听
    Public Sub Close()
        '设置状态
        SetState(StateConstants.sckClosing)
        '设置监听循环终止标志
        m_stopListen = False
        '使用一个目标为本地主机的TCPClient
        m_RemotePort = m_LocalPort
        m_RemoteHost = m_LocalHostName
        '发送结束符以解除监听线程的阻塞
        Send("")
    End Sub

'错误处理
    Private Sub ErrorHandle(ByVal src As String, ByVal description As String)
        '设置状态
        SetState(StateConstants.sckError)
        '设置错误通知
        m_Error = New CTcpTalkException(src + " : " + description)
        '触发错误事件
        RaiseEvent ErrorEvt(m_Error)
    End Sub
    Private Sub ErrorHandle(ByVal src As String, ByVal ex As Exception)
        '设置状态
        SetState(StateConstants.sckError)
        '设置错误通知
        m_Error = New CTcpTalkException(src + " : " + ex.Message)
        '触发错误事件
        RaiseEvent ErrorEvt(m_Error)
    End Sub

'设置状态
    Private Sub SetState(ByVal state As StateConstants)
        m_State = state
    End Sub
End Class
用于传递错误信息的类CTcpTalkException
Public Class CTcpTalkException
    Inherits Exception
    Public Sub New()
        MyBase.New()
    End Sub
    Public Sub New(ByVal msg As String)
        MyBase.New(msg)
    End Sub
End Class

常见问题

监听线程的处理

        监听线程会在下列语句处阻塞,直到有连接请求进入。

'接收连接请求
m_sckAccept = m_sckListen.AcceptTcpClient

        在阻塞的状况下,简单的使用Abort,仅仅是将线程设置为AbortRequest状态,而没有真正的解除线程的阻塞,甚至使用Application.Exit(),也无法真正的终止线程并释放线程所占有的资源。这样在下次在同一个端口调用监听时就会有错误发生。并且在应用程序退出后,监听线程依然像孤魂野鬼一般在内存中阻塞着。

        如果在此时直接调用m_sckListen.Stop来终止监听,则会发生以下描述信息的错误:

        一个封锁操作被对 WSACancelBlockingCall 的调用中断。

        但是这样似乎并不会影响以后对此端口监听的调用,并且能够结束线程。

        彻底解决问题的一种方法是用一个终止标志作为监听循环的条件,需要终止监听时,先设置终止标志为退出监听循环,然后向自己的监听器发送一个连接请求解除监听线程的阻塞,然后就可以安全的退出监听循环,关闭监听,并结束监听线程。这样在程序结束以后也不会有线程滞留的现象。

多个连接请求

        对于多个连接请求,TcpListener将他们放入一个队列,直到到达可接收的连接的最大数,通过Accept的调用用队列中删除已经接收的连接请求。另外由于采用了在传输时动态建立连接的结构,不需要长期维护多个连接有效,使等待处理的队列非常短,所以对于不是特别频繁的多个连接请求,本例子都可以轻松的处理。但是并没有进行非常严格的极限测试,所以不保证对于大量的、并发性较强的多个连接能够有效处理。

抱歉!评论已关闭.