TCPIP通讯程序的编写

欢迎光临Sdhjt的窝,本篇心得除参考的部分文档外属于原创,转载请注明出处,谢谢!

 

作者:Sdhjt

一、总体介绍

  网络通信最好用的还是套接字(Sockets),.NET提供了Sockets的托管实现。命名空间是System.Net.Sockets,而在这个命名空间中常用的有三个类:TcpClientTcpListener UdpClient 类。

TcpListener:监听类,一般用作服务器端监听端口使用。

TcpClient:客户端类,一般用作客户端连接服务器使用。在服务器中用作管理客户端连接。每一个TcpClient代表一个客户端的TCP连接。

UdpClientUDP客户端类,一般用作客户端连接服务器使用。在服务器中用作管理客户端连接。每一个UdpClient代表一个客户端的UDP连接。

二、流程介绍

1、典型监听代码的编写

 

    '设置/判断监听是否开始的标志

    Public IsStart As Boolean = False

    '监听类的实例,用于监听某个端口

    Private Listener As TcpListener

    '存储监听的端口

    Public ListenPort As UInteger

    '存储监听的本机IP地址

    Public LocalIP As Net.IPAddress

 

    '线程类的实例,监听工作是在单独的线程上完成的

    Private listenerThread As Threading.Thread

 

    '开始监听,当不在进行监听的时候,需要调用:StopListen

    Public Sub StartListen(ByVal pListenPort As UInteger, ByVal pLocalIP As String)

 

        If IsStart = False Then

            Try

                '将字符型的IP地址转换为Net.IPAddress类型

                LocalIP = Net.IPAddress.Parse(pLocalIP)

                ListenPort = pListenPort

 

                '开启新的线程,回调DoListen过程

                listenerThread = New Threading.Thread(AddressOf DoListen)

                '整个监听代码都在DoListen过程中完成,从而不影响主线程

                listenerThread.Start()

                UpdateStatus("监听开始。")

                IsStart = True

            Catch

                UpdateStatus("监听失败。可能是错误的本机地址。")

            End Try

        End If

    End Sub

 

    '结束监听

    Public Sub StopListen()

        If IsStart = True Then

            Listener.Stop()

            listenerThread.Abort()

            IsStart = False

        End If

End Sub

 

 

    '用于进行监听新连接的子程序,将会被独立线程执行。参数分别为:监听端口,本机IP

    Private Sub DoListen()

        Try

            '监听新的连接

            Dim ServerAddress As Net.IPAddress

            '设置要监听的本机IP

            ServerAddress = LocalIP

            '实例化监听类

            Listener = New TcpListener(ServerAddress, ListenPort)

            '正式开始监听

            Listener.Start()

            Do

                '当监听到新的连接时,TcpListener.AcceptTcpClient()返回一个TcpClient

                '代表这个新的连接。TcpListener.AcceptTcpClient()是阻塞方法,程序执行到

                ' TcpListener.AcceptTcpClient()将暂停,直到有一个新的连接传入。

                ' UserConnection类是自定义的对TcpClient进一步封装的类。

                Dim client As New UserConnection(Listener.AcceptTcpClient)

 

                '添加事件句柄,本连接的所有传入消息都由OnLineReceived处理

                AddHandler client.LineReceived, AddressOf OnLineReceived

                UpdateStatus("发现新的连接,等待对方回应。")

            Loop Until False

        Catch

        End Try

End Sub

 

2、对TcpClient的封装

监听到的每一个新的连接,都会由TcpListener.AcceptTcpClient()返回一个TcpClient,这个类的实例就代表这个连接,所有的数据发送和接收都有这个类完成。下面是一个自定义的类:UserConnection类,这个类完成对TcpClient的进一步封装。

 

 

Imports System.Net.Sockets

Imports System.Text

 

' The UserConnection class encapsulates the functionality of a TcpClient connection

' with streaming for a single user.

Public Class UserConnection

    Const READ_BUFFER_SIZE As Integer = 255

 

    ' Overload the New operator to set up a read thread.

    Public Sub New(ByVal client As TcpClient)

        Me.client = client

 

        ' This starts the asynchronous read thread.  The data will be saved into

        ' readBuffer.

        Me.client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE, AddressOf StreamReceiver, Nothing)

    End Sub

 

    Private client As TcpClient

    Private readBuffer(READ_BUFFER_SIZE) As Byte

    Private strName As String

 

    ' The Name property uniquely identifies the user connection.

    Public Property Name() As String

        Get

            Return strName

        End Get

        Set(ByVal Value As String)

            strName = Value

        End Set

    End Property

 

 

    Public Event LineReceived(ByVal sender As UserConnection, ByVal Data As String)

 

    ' This subroutine uses a StreamWriter to send a message to the user.

    Public Sub SendData(ByVal Data As String)

        ' Synclock ensure that no other threads try to use the stream at the same time.

        SyncLock client.GetStream

            Dim writer As New IO.StreamWriter(client.GetStream)

            writer.Write(Data & Chr(13) & Chr(10))

 

            ' Make sure all data is sent now.

            writer.Flush()

        End SyncLock

    End Sub

 

    ' This is the callback function for TcpClient.GetStream.Begin. It begins an

    ' asynchronous read from a stream.

    Private Sub StreamReceiver(ByVal ar As IAsyncResult)

        Dim BytesRead As Integer

        Dim strMessage As String

 

        Try

            ' Ensure that no other threads try to use the stream at the same time.

            SyncLock client.GetStream

                ' Finish asynchronous read into readBuffer and get number of bytes read.

                BytesRead = client.GetStream.EndRead(ar)

            End SyncLock

 

            ' Convert the byte array the message was saved into, minus one for the

            ' Chr(13).

            strMessage = Encoding.ASCII.GetString(readBuffer, 0, BytesRead - 1)

            RaiseEvent LineReceived(Me, strMessage)

 

            ' Ensure that no other threads try to use the stream at the same time.

            SyncLock client.GetStream

                ' Start a new asynchronous read into readBuffer.

                client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE, AddressOf StreamReceiver, Nothing)

            End SyncLock

        Catch e As Exception

        End Try

    End Sub

End Class

 

以上是原始的代码,不具备容错能力,因此出现以外的传输错误以后很容易导致程序崩溃。下面代码是我修改后的:

 

Imports System.Net.Sockets

Imports System.Text

 

'UserConnection类实现对TcpClient的进一步封装,使其满足各个用户间保持独立的要求(每一个UserConnection代表一个连接)。

Public Class UserConnection

    Const READ_BUFFER_SIZE As Integer = 1023

 

    '选手信息结构

    Public Structure UserDataStructure

        '存储用户的成绩信息

        Public Score As Integer

        '用户是否为评委,是则为真,否则为假

        Public IsReg As Boolean

        '用户是否响应消息(给客户端命令,客户端是否有回应)。0为正确,非零为出现某个错误。

        Public Response As String

    End Structure

 

    '重载New方法建立一个读取线程。

    Public Sub New(ByVal client As TcpClient)

        Me.client = client

 

        '建立一个异步的读取线程,数据将存储在readBuffer中。

        Me.client.GetStream.BeginRead(readBuffer, 0, READ_BUFFER_SIZE, AddressOf StreamReceiver, Nothing)

    End Sub

 

    Private client As TcpClient

    Private readBuffer(READ_BUFFER_SIZE) As Byte

    Private strName As String

    '存储客户端的其他信息

    Public clientInfo As UserDataStructure

 

    '在本类中约定Name是用户的唯一标识符(即Name不能有重复)。

    Public Property Name() As String

        Get

            Return strName

        End Get

        Set(ByVal Value As String)

            strName = Value

        End Set

    End Property

 

 

    '定义UserConnection的事件,此事件在收到信息后发生

    Public Event LineReceived(ByVal sender As UserConnection, ByVal Data As String)

 

    '建立一个StreamWriter向客户端发送消息。

    Public Sub SendData(ByVal Data As String)

        Try

            '用同步锁防止其它线程在此时也调用stream造成冲突。

            SyncLock client.GetStream

                Dim writer As New IO.StreamWriter(client.GetStream, Encoding.GetEncoding("GB2312"))

                writer.Write(Data & Chr(13) & Chr(10))

 

                '确保所有的数据传送完毕。此方法阻塞,等待所有数据传送完毕才返回。

                writer.Flush()

            End SyncLock

        Catch e As Exception

            '出错说明连接出问题了,断开连接。这里有可能出现的错误是,意外连接断开造成无法完成数据传输(如:突然把网线拔掉。)

            client.Close()

            '向外部传送一个断开连接的命令,这里是人为的。

            RaiseEvent LineReceived(Me, "DISCONNECT")

 

        End Try

    End Sub

 

    '此回调函数用于TcpClient.GetStream.Begin。它开始于一个异步读取流。

    Private Sub StreamReceiver(ByVal ar As IAsyncResult)

        'Dim i As Integer = 0

        Dim BytesRead As Integer

        Dim strMessage As String

 

        Try

            ' Ensure that no other threads try to use the stream at the same time.

            SyncLock client.GetStream

                ' Finish asynchronous read into readBuffer and get number of bytes read.

                '完成异步读取。异步读取的使用方法可以参阅以后的详细内容。

                BytesRead = client.GetStream.EndRead(ar)

            End SyncLock

 

            If BytesRead < 1 Then

 

                ' If no bytes were read server has close.  Disable input window.

                '已经断开连接

                '人工造成错误,捕获后即可执行断开连接指令

                Throw New System.Exception("An exception has occurred.")

            Else

                ' Convert the byte array the message was saved into, minus one for the

                ' Chr(13).

                '将字节数组转换为字符串,并减去最后一个Chr(13)字符。

                strMessage = Encoding.GetEncoding("GB2312").GetString(readBuffer, 0, BytesRead - 1)

 

                If strMessage = "DISCONNECT" Then

                    '人工造成错误,捕获后即可执行断开连接指令

                    Throw New System.Exception("An exception has occurred.")

                End If

            End If

 

            RaiseEvent LineReceived(Me, strMessage)

 

            ' Ensure that no other threads try to use the stream at the same time.

            SyncLock client.GetStream

                ' Start a new asynchronous read into readBuffer.

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值