<span style="font-size:18px;">在dotnet平台Net.Sockets.TcpListener和Net.Sockets.TcpClient已经为我们封装了所有Socket关于tcp部分,操作也更为简单,面向数据流。使用TcpClient的GetStream方法获取数据流后可以方便的对数据流进行读写操作,就如同本地磁盘的文件读写一样,使得程序员在设计程序时更为便捷简单。</span>
但如果你使用过这两个对象进行数据传输的时候,你会发现问题也随之而来——GetStream获取的数据流是一个永无止境的Stream,你无法获取它的具体长度。
来看一下微软MSDN关于这两个对象的例程:
Shared Sub Connect(server As [String],message String]) Try ' Create a TcpClient. ' Note,for this client to work you need to have a TcpServer ' connected to the same address as specified by the server,port ' combination. Dim port As Int32 = 13000 Dim client As New TcpClient(server,port) ' Translate the passed message into ASCII and store it as a Byte array. Dim data Byte]() = System.Text.Encoding.ASCII.GetBytes(message) ' Get a client stream for reading and writing. ' Stream stream = client.GetStream(); Dim stream As NetworkStream = client.GetStream() ' Send the message to the connected TcpServer. stream.Write(data,data.Length) Console.WriteLine("Sent: {0}",message) ' Receive the TcpServer.response. ' Buffer to store the response bytes. data = New [Byte](256) {} ' String to store the response ASCII representation. Dim responseData String] = [String].Empty ' Read the first batch of the TcpServer response bytes. Dim bytes As Int32 = stream.Read(data,data.Length) responseData = System.Text.Encoding.ASCII.GetString(data,bytes) Console.WriteLine("Received: {0}",responseData) ' Close everything. stream.Close() client.Close() Catch e As ArgumentNullException Console.WriteLine("ArgumentNullException: {0}",e) As SocketException Console.WriteLine("SocketException: {0}",sans-serif!important; color:blue">End Try Console.WriteLine(ControlChars.Cr + " Press Enter to continue...") Console.Read() Sub 'Connect你不得不去指定一个固定尺寸的缓冲区来接收数据,如果实际发送的数据超出了这个长度,你可能无法接收到全部完整的数据,而如果发送的数据少于缓冲区的大小,那么很显然你的内存会比别人消耗的更快。更为严重的时,如果你要发送一副图片,图片容量可能根据内容的不同而各有千秋,容量也不止256Byte这么小,该如何精确控制缓冲区呢?
其实我们可以很容易的解决这个问题,就像很多文件格式所做的事情一样,我们可以在Stream的前几个字节写入实际有效数据的长度,然后根据这个长度来分配内存,再读取内容,我所做的客户端与服务器之间传递屏幕的源代码如下:
'----------------客户端----------------
<pre name="code" class="vb">Public Class Form1 Private Sub Timer1_Tick(ByVal sender As System.Object,ByVal e As System.EventArgs) Handles Timer1.Tick Dim tcpc As New Net.Sockets.TcpClient Dim slens(7) As Byte Try tcpc.Connect("127.0.0.1",2099) If tcpc.Connected Then For i = 0 To 7 slens(i) = tcpc.GetStream.ReadByte Next Dim buf(BitConverter.ToUInt64(slens,0)) As Byte Me.Text = buf.Length tcpc.GetStream.Read(buf,buf.Length) Dim mem As New IO.MemoryStream(buf) Dim bmp As New Bitmap(mem) Me.PictureBox1.Image = bmp tcpc.Close() End If Catch ex As Exception Finally tcpc.Close() End Try End Sub Private Sub Form1_Load(ByVal sender As System.Object,ByVal e As System.EventArgs) Handles MyBase.Load End Sub End Class
'------------服务器----------------
Public Class Form1 Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object,ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork Dim tcps As New Net.Sockets.TcpListener(2099) tcps.Start() While True Dim slen As UInt64 Dim slens(7) As Byte Dim tcpc As Net.Sockets.TcpClient tcpc = tcps.AcceptTcpClient '创建图片 Dim bmp As New Bitmap(Screen.PrimaryScreen.Bounds.Width,Screen.PrimaryScreen.Bounds.Height) bmp.SetResolution(1,1) Dim gph As Graphics = Graphics.FromImage(bmp) gph.CopyFromScreen(New Point(0,0),New Point(0,bmp.Size) gph.Flush() '存入内存 Dim mem As New IO.MemoryStream bmp.Save(mem,Drawing.Imaging.ImageFormat.Tiff) '文件长度 slen = mem.Length slens = BitConverter.GetBytes(slen) '发送长度 For i = 0 To 7 tcpc.GetStream.WriteByte(slens(i)) Next '发送内容 tcpc.GetStream.Write(mem.ToArray,mem.Length) tcpc.Close() End While End Sub Private Sub Form1_Load(ByVal sender As System.Object,ByVal e As System.EventArgs) Handles MyBase.Load Me.BackgroundWorker1.RunWorkerAsync() End Sub End Class