前言
这是一个等了2年的更新,能够真的看到并且看完这个文章的人很少了,但是我还是要发,因为这只是教程的一部分,如果你喜欢就麻烦各位跌给我点点赞,你的关注和点赞收藏是对我最大的动力,真的不吹牛啦,我的教程已经很详细了!!
本节配套视频Socket网络通信之文件传输#1_哔哩哔哩_bilibili
之前socket的一个教程基于Socket的简易聊天室(vb.net代码详细讲解,源代码下载)_vbnet socket-CSDN博客有一些bug,而且处理也不是和完善、方法运用的有所欠缺,这一次我们直接运用方法函数的特性解析文件传输,后续我们会教学文件断点续传的操作。本次教程问文件传输#1
一、客户端给服务端发送文件的流程图示意

这一次我们升级了处理流程,使用了网络stream流(Network Stream)在tcp协议下通信,好处有:不需要手动定义每一次传输的数据量,即不用Send或者Read等高危险读取方式,防止服务端因为客户端等问题而被迫下线或者数据处理出问题
为了规避问题,我们可以尝试动动小脑瓜想一想,怎么去设计这个流程,非常简单,客户端首先发送文件的名称,然后服务端接收名称,客户端等服务端接收完成后继续发送文件的长度,服务端接收,然后客户端再发送文件的具体内容,服务端按照顺序读取字节(可以定义一个块为多少比如8KB就行)
还有一个问题,是上次教程没有讲的,就是UI线程和处理客户端线程的线程管理问题,为什么卡线程呢,其实我们可以使用一个委托解决所有异常退出或者内部错误,在Socket编程中,80%的服务端闪退问题其实只要给某功能加一个委托可以解决,不要使用十年前的Socket写法了,LOW了(B站某老牌C#教程,当时就是照着那个up的教程写的,扩展性差还容易闪退,最主要一堆bug,属于Socket的一个皮毛,哎)
对了提一嘴:服务端眼里客户端就是客户端,客户端眼里服务端就是客户端,不要被名称所迷惑,他们是要互相通信的!
二、客户端:

命名空间
vbnet
Imports System.IO
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
全局变量(可Private)
vbnet
Public ServerTcp As TcpClient
Public ns As NetworkStream
Public writer As BinaryWriter
连接服务端:
vbnet
ServerTcp = New TcpClient(txtServer.Text, port.Text)
ns = ServerTcp.GetStream()
writer = New BinaryWriter(ns)
ShowMsg("连接服务器成功!")
Dim t As New Thread(Sub() ReceiveServer(ServerTcp))
t.IsBackground = True
t.Start()
发送文字:
vbnet
Function SendText(txt As String)
Dim len = txt.Length
writer.Write(SendType.Text)
'发送大小
writer.Write(len)
'发送数据
Dim buffer(8191) As Byte
Dim bytesRead As Integer
Dim txtBuffer = Encoding.UTF8.GetBytes(txt)
Using ms As New MemoryStream(txtBuffer)
While True
bytesRead = ms.Read(buffer, 0, buffer.Length)
If bytesRead <= 0 Then Exit While
ns.Write(buffer, 0, bytesRead)
End While
End Using
End Function
讲解一下:这里的流程就是(虽然这是文件传输的流程,但是如果你作为程序员,我们完全可以自定义客户端与服务端想要通信的"协议")

说到协议,可以定义一个Enum
vbnet
Enum SendType
Text = 1
File
Bitmap
Video
Emoji
Close
End Enum
简单吗,哈哈完全不需要动脑子了,简单到飞起来。。
重点:发送部分代码解析
1、第一步的bytesRead为文件实际读取到的数据长度
2、第二步为判断如果文件已经读取过末尾结束了,那么就结束发送,所有内容全部自动释放
3、第三步是给目标服务器流写入文件数据,这里数据长度取决于第一步的bytesRead的数值(如果末尾超过了8k但是不满16k呢,ms就只会完整读完有效字节,后面的无效字节是不会读取的!!)我们使用内存流可以更加方便将字符串转换成流进行处理
发送文件:
也是异曲同工,除了基础信息还有文件名
vbnet
Function SendFile(FilePath As String)
Dim fileInfo As New FileInfo(FilePath)
writer.Write(SendType.File)
' 1. 发送文件名
writer.Write(fileInfo.Name)
' 2. 发送文件大小
writer.Write(fileInfo.Length)
' 3. 发送文件数据
Using fs As New FileStream(FilePath, FileMode.Open)
Dim buffer(8191) As Byte
Dim bytesRead As Integer
While True
bytesRead = fs.Read(buffer, 0, buffer.Length)
If bytesRead <= 0 Then Exit While
ns.Write(buffer, 0, bytesRead)
End While
End Using
ShowMsg("文件发送完毕")
End Function
协议?
以此类推,我们只要掌握"协议",在客户端与服务端之间通信就有了投放目标,有了投放数据的大小和投放数据的篮子,这样比喻应该很好理解。。这真的很重要,因为在后续教程会有断点续传共功能上线。
接收文件与接收消息
(这里的过程如果太臃肿可以直接委托单个类型接收流程到外部函数就行)
vbnet
Sub ReceiveServer(server As TcpClient)
Try
Dim ID = server.Client.RemoteEndPoint.ToString
Using ns As NetworkStream = server.GetStream()
Using reader As New BinaryReader(ns)
While True
Dim SendType As SendType = reader.ReadInt32()
Select Case SendType
Case SendType.Text
Dim len As Integer = reader.ReadInt32
Using ms As New MemoryStream()
Dim buffer(8191) As Byte
Dim totalRead As Long = 0
While totalRead < len
Dim bytesRead As Integer = ns.Read(buffer, 0, buffer.Length)
ms.Write(buffer, 0, bytesRead)
totalRead += bytesRead
End While
ShowMsg(Encoding.UTF8.GetString(ms.ToArray))
End Using
Case SendType.File
Dim FileName As String = reader.ReadString()
Dim fileSize As Long = reader.ReadInt64()
Dim BasicPath = Application.StartupPath & "\FromServer\"
Directory.CreateDirectory(BasicPath)
Using fs As New FileStream(BasicPath & FileName, FileMode.Create)
Dim buffer(8191) As Byte
Dim totalRead As Long = 0
While totalRead < fileSize
Dim bytesRead As Integer = ns.Read(buffer, 0, buffer.Length)
fs.Write(buffer, 0, bytesRead)
totalRead += bytesRead
End While
ShowMsg(ID & "发送了一个文件")
AddFileReceiveInfo(FileName, ID, fileSize)
End Using
Case SendType.Close
server.Close()
ShowMsg("已被服务器踢出连接!")
End Select
End While
End Using
End Using
Catch ex As Exception
'MessageBox.Show("错误: " & ex.Message)
End Try
End Sub
注意FileStream的文件存在性问题,如果没有文件夹需要新建文件夹!以上为示意,例如如果要实现Emoji可以在上面追加Case Emoji ,RichTextBox里面显示位图就行,可以传输gif也可以是图片
Log消息的方法(委托,一定要用委托!!)
vbnet
Function ShowMsg(s As String)
Me.Invoke(Sub()
Txtlog.AppendText(s & vbCrLf)
Txtlog.ScrollToCaret()
End Sub)
End Function
文件接收管理器

vbnet
Sub AddFileReceiveInfo(filename As String, id As String, size As Long)
Me.Invoke(Sub()
Dim FileItem As New ListViewItem({filename, id, Format(Date.Now, "yyyy/MM/dd hh:mm:ss"), size})
FileReceiveManager.ReceiveView.Items.Add(FileItem)
FileReceiveManager.Show()
End Sub)
End Sub
三、服务端:

设计有:基础文本聊天框、错误输出、服务端全局数据信息打印,支持v6与v4还有本地连接
命名空间(不要引入其他不知道什么玩意的命名空间!!!)
vbnet
Imports System.IO
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
全局变量
vbnet
Dim listener As TcpListener
Dim ClientList As New List(Of TcpClient)
开始监听:
监听该服务器所有IP
vbnet
Dim thread As New Thread(AddressOf StartServer)
thread.IsBackground = True
thread.Start()
PostLog("服务器开启监听")
''''
Private Sub StartServer()
listener = New TcpListener(IPAddress.IPv6Any, port.Text)
listener.Server.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, False)'兼容v4v6连接
listener.Start()
ShowMsg("服务端开始监听")
While True
Dim client As TcpClient = listener.AcceptTcpClient()'阻塞等待
ClientList.Add(client)
Dim t As New Thread(Sub() ReceiveClient(client))
t.IsBackground = True
t.Start()
ShowMsg(client.Client.RemoteEndPoint.ToString & "连接服务器")
RefreshView()
End While
End Sub
发送与接收:与客户端相似,把开头的大流程反过来就行,这就是为什么客户端眼里,服务端就是客户端的客户端。。
发送文字:
vbnet
Function SendText(txt As String, client As TcpClient)
Dim ns As NetworkStream
ns = client.GetStream()
Dim writer As BinaryWriter
writer = New BinaryWriter(ns)
Dim len = txt.Length
writer.Write(SendType.Text)
'发送大小
writer.Write(len)
'发送数据
Dim buffer(8191) As Byte
Dim bytesRead As Integer
Dim txtBuffer = Encoding.UTF8.GetBytes(txt)
Using ms As New MemoryStream(txtBuffer)
While True
bytesRead = ms.Read(buffer, 0, buffer.Length)
If bytesRead <= 0 Then Exit While
ns.Write(buffer, 0, bytesRead)
End While
End Using
PostLog("服务端发送了文字:" & txt & " Length:" & len & " RemoteEndPoint:" & client.Client.RemoteEndPoint.ToString)
End Function
发送文件
vbnet
Function SendFile(FilePath As String, client As TcpClient)
Dim ns As NetworkStream
ns = client.GetStream()
Dim writer As BinaryWriter
writer = New BinaryWriter(ns)
Dim fileInfo As New FileInfo(FilePath)
writer.Write(SendType.File)
' 1. 发送文件名
writer.Write(fileInfo.Name)
' 2. 发送文件大小
writer.Write(fileInfo.Length)
' 3. 发送文件数据
Using fs As New FileStream(FilePath, FileMode.Open)
Dim buffer(8191) As Byte
Dim bytesRead As Integer
While True
bytesRead = fs.Read(buffer, 0, buffer.Length)
If bytesRead <= 0 Then Exit While
ns.Write(buffer, 0, bytesRead)
End While
End Using
ShowMsg("文件发送完毕")
PostLog("服务端发送了文件:" & FilePath & " Length:" & fileInfo.Length & " RemoteEndPoint:" & client.Client.RemoteEndPoint.ToString)
End Function
踢出客户端:
一定要用委托!!
vbnet
Sub CloseClient(client As TcpClient)
Me.Invoke(Sub()
Dim ns As NetworkStream
ns = client.GetStream()
Dim writer As BinaryWriter
writer = New BinaryWriter(ns)
writer.Write(SendType.Close)
End Sub)
End Sub
接收文件:
vbnet
Sub ReceiveClient(client As TcpClient)
Dim ID = client.Client.RemoteEndPoint.ToString
Dim myClient = client
Try
Using ns As NetworkStream = client.GetStream()
Using reader As New BinaryReader(ns)
While True
Dim SendType As SendType = reader.ReadInt32()
Select Case SendType
Case SendType.Text
Dim len As Integer = reader.ReadInt32
Using ms As New MemoryStream()
Dim buffer(8191) As Byte
Dim totalRead As Long = 0
While totalRead < len
Dim bytesRead As Integer = ns.Read(buffer, 0, buffer.Length)
ms.Write(buffer, 0, bytesRead)
totalRead += bytesRead
End While
ShowMsg(ID & " 说:" & Encoding.UTF8.GetString(ms.ToArray))
End Using
Case SendType.File
Dim FileName As String = reader.ReadString()
Dim fileSize As Long = reader.ReadInt64()
Dim ip = Strings.Split(ID, ":")(0)
Dim port = Strings.Split(ID, ":")(1)
Dim BasicPath = Application.StartupPath & "\" & ip & "_" & port
Directory.CreateDirectory(BasicPath)
Using fs As New FileStream(BasicPath & "\" & FileName, FileMode.Create)
Dim buffer(8191) As Byte
Dim totalRead As Long = 0
While totalRead < fileSize
Dim bytesRead As Integer = ns.Read(buffer, 0, buffer.Length)
fs.Write(buffer, 0, bytesRead)
totalRead += bytesRead
End While
ShowMsg(ID & "发送了一个文件")
AddFileReceiveInfo(FileName, ID, fileSize)
End Using
End Select
End While
End Using
End Using
Catch ex As Exception
ShowMsg(ID & "断开服务器")
RemoveClient(myClient)
ShowErr("错误: " & ex.Message)
End Try
End Sub
什么,你说这就结束了?这样一来其他客户端看不到我发的消息啊,其实非常简单,服务端只需要在接收的时候排除该客户端进行转发就行