VB6局域网文件传输实例:Winsock控件实现TCP收发

📅 发布时间:2026/10/16 15:36:10
VB6局域网文件传输实例:Winsock控件实现TCP收发
简介这是一套VB6.0局域网内点对点文件传输示例面向VB网络编程学习者解决同一网段内同事间文件与图片互传的问题功能形态类似简化版QQ核心目的在于演示如何用VB实现文件的上传与接收逻辑而非追求生产级应用。压缩包总大小仅84KB包含20个文件6个frm窗体文件、5个frx窗体资源文件、3个bas标准模块、2个vbp工程文件、2个vbw工作区文件另有1个scc源代码管理文件与1个xml配置文件文件划分清晰适合逐步拆解。从内容预览可以看到工程内置了主窗体、好友列表窗体、消息发送窗体、文件发送窗体、个人设置以及图片查看等模块配合Module1、Module2、Module3三个公共模块有助于理解窗体间数据传递、文件流读写和局域网发送逻辑。已有603人在CSDN浏览或下载该资源可作为VB网络编程入门到进阶的练习素材读者在读懂收发流程后还可尝试扩展多文件批量传输、在线状态同步等新功能。1. 一个VB6局域网文件传输实例为什么它今天仍值得下载在Windows 10下双击VB6编译好的exe如果没做任何配置一般都会直接弹“无法找到主机”。但只要把这份源码里两个关键属性改掉局域网内的文件传输就能跑起来。这份“VB6.0局域网内传输文件的实例”是一份完整源码覆盖了基于Winsock控件的TCP传输、客户端/服务器双端逻辑、文件流读写和基础错误处理适合正在做内网小工具、旧设备维护或VB6毕业设计的开发者。它能让你省掉反复查资料的时间直接对照着改参数、跑通流程再按自己的需求去扩展。接下来我从代码结构、运行配置和常见坑三个层面把这份资源拆开讲透。2. 传输原理与选型VB6局域网文件传输的实现路径在动手解压源代码之前先要把传输原理说清楚。局域网文件传输不是单靠拖控件就能跑通的选错方案会让你在调试阶段浪费大量时间。这一章从实现路径、协议选择到数据收发机制逐步拆开给你看。2.1 为什么是Winsock控件而不是API或第三方库在VB6里实现网络传输有几种路子Winsock控件、Winsock API、甚至直接用MSComm串口。API方式灵活但代码量巨大你要自己整理状态机、处理句柄和重连逻辑MSComm走串口适合短距离或工业设备不适合普通局域网。第三方组件则涉及分发和注册问题在今天的Windows 10/11环境里经常被安全软件盯上。相比之下Winsock控件是系统自带的ActiveX组件VB6工程里直接引用即可。Winsock控件的核心是SendData和DataArrival事件。你不需要关心底层三次握手和滑动窗口它把这些封装成了“发送数据”和“有数据到达”两个动作和VB6的事件驱动模型天然契合。例子里的服务器端和客户端都基于这个控件代码量被压得非常少这也是老项目里最常见的做法。先用一段最精简的服务器端监听代码来感受一下Private Sub Form_Load() Winsock1.LocalPort 4567 Winsock1.Listen End Sub Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long) If Winsock1.State sckClosed Then Winsock1.Close End If Winsock1.Accept requestID End Sub这段代码的逻辑是基于Winsock控件的标准监听流程。LocalPort指定了服务器监听端口必须和客户端的RemotePort保持一致否则连接会被拒绝。Listen让控件进入监听状态等待客户端发起连接。一旦有连接请求Winsock控件会触发ConnectionRequest事件参数requestID是这条请求的唯一标识此时服务器先关闭当前状态如果是已连接状态再调用Accept接收它否则新连接永远进不来。参数说明里有一个容易忽略的坑requestID只能使用一次如果先把Winsock1.Close放在Accept前面某些情况下控件会丢失请求标识导致连接失败。我看到不少初学者在这里卡一夜实际上顺序反了也会报错但这里的示例代码顺序正确可以放心参照。2.2 TCP和UDP这份资源为什么选TCP文件传输和普通消息发送不一样数据必须完整、有序地到达对方。UDP虽然快但丢包后要自己处理重传、乱序和重复意味着你要在应用层写一套可靠传输协议这对大多数VB6项目来说是个大工程。局域网里如果只有几十KB的小配置包UDP或许可行但文件传输至少要保证完整所以这份实例采用TCP协议是有道理的。TCP是面向连接的字节流协议Winsock控件在TCP模式下工作相对简单客户端连接服务器后两边互相发送字节数组即可。但TCP也有自己的麻烦最典型的两个是粘包和半包。VB6的控件每次触发DataArrival时bytesTotal并不一定等于发送方一次SendData发送的字节数文件内容可能被拆成多次到达。因此你必须在应用层定义消息边界比如先发送文件名和文件长度再发送文件内容。这段代码展示了接收端如何处理半包问题Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long) Dim buffer() As Byte Dim header As String Dim fileLen As Long If Not bHeaderDone Then WinSock1.GetData header, vbString 假定头格式文件长度#文件名 fileLen Val(Left(header, InStr(header, #) - 1)) fileName Mid(header, InStr(header, #) 1) bHeaderDone True Else WinSock1.GetData buffer, vbArray vbByte, bytesTotal 写入文件的指定偏移位置 Put #fileHandle, offset 1, buffer offset offset bytesTotal If offset fileLen Then Close #fileHandle bHeaderDone False offset 0 End If End If End Sub这里用了bHeaderDone布尔变量区分“收到文件头”和“收到文件体”两种状态。GetData的第二个参数指定了数据类型传文件内容时必须是vbArray vbByte组合这样拿到的是一字节数组。第三个参数bytesTotal指定读取的字节数避免一次把缓冲区长内容全读空。文件写入用Put定位到偏移位置这是二进制文件操作的常规做法。实际写盘时每次DataArrival触发后都要把offset累加直到达到fileLen再关闭文件句柄否则下一次传输会接着上一次的偏移写把文件写坏。这个接收逻辑看起来简单但它是整个实例里最值得细读的部分因为你把文件改成任何扩展名都能正常传输依赖的就是这个“头协议”和偏移追加机制。3. 代码结构解析从RAR到可运行模块拿出一份RAR压缩包第一件事不是急着编译先看文件清单。VB6工程通常由.vbp工程文件、.frm窗体文件、.bas模块文件和资源文件组成这份实例也不例外。只有把文件角色和依赖关系搞明白后面改参数才有的放矢。3.1 工程文件与依赖打开项目前先看的文件解压后典型结构如下表所示文件/组件作用是否必需FileTransfer.vbp工程文件双击打开主项目必需Server.frm服务器端窗体包含Winsock控件和接收逻辑必需Client.frm客户端窗体包含文件选择和发送逻辑必需ModCommon.bas公共函数如获取本机IP、解析文件名可选MSWinsock.ocxWinsock控件运行库系统目录下查找必需data/接收文件的默认目录可选第一次双击.vbp时VB6会尝试加载MSWinsock.ocx。如果你用的是精简版开发环境或者系统缺少这个控件会直接弹错误提示。常见处理方式是用命令注册控件这一步在第五章节里单独讲。打开工程后先检查所有窗体是否加载成功再看“工程—引用”里有没有未勾选项。比较容易被忽视的是.bas公共模块。里面往往放了一个GetLocalIP函数用来自动获取本机局域网IP。这段代码用到Winsock连接时的本地IP解析如果你的电脑有多张网卡虚拟机和无线网卡共存返回的可能是第一个适配器的IP而非实际局域网地址。所以拿到代码后别急着用先在模块里确认是否存在类似Winsock1.LocalIP的调用。3.2 服务器端代码拆解监听、接收与写盘服务器端是接收文件的一方。它的核心职责有三个监听端口、接收连接、把字节流写到磁盘。上一章已经给出缩略版这里把完整逻辑拆开。完整的服务器端数据写入模块常常长这样Private Sub Command1_Click() CommonDialog1.Filter 所有文件|*.* CommonDialog1.ShowSave fileName CommonDialog1.FileName If fileName Then Open fileName For Binary As #fileHandle End If End Sub这只是一个保存对话框的交互。真正接收文件前的关键点在于文件打开方式必须是For Binary否则读写字节数组时会引入换行符转换破坏二进制内容。#fileHandle是文件操作句柄整个模块里要保证它全局可见否则每次事件触发都重新打开会让文件指针错乱。更完整的状态机在上层处理Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long) Static bHeaderDone As Boolean Static bFileOpen As Boolean Dim buffer() As Byte Dim data As String If Not bHeaderDone Then WinSock1.GetData data, vbString 头格式: 文件长度|文件名称 If InStr(data, |) 0 Then fileLen Val(Left(data, InStr(data, |) - 1)) fileName Mid(data, InStr(data, |) 1) Open D:\Recv\ fileName For Binary As #fileHandle bFileOpen True bHeaderDone True End If Else WinSock1.GetData buffer, vbArray vbByte, bytesTotal 写文件 Put #fileHandle, , buffer 检查是否接收完毕 If LOF(fileHandle) fileLen Then Close #fileHandle bFileOpen False bHeaderDone False End If End If End Sub这段代码用了Static变量来保持状态而不是模块级变量。Static在事件过程内声明只要对象存活就会保留值。GetData的头部分用vbString读取因为文件长度和文件名是文本。发送方如果约定好头格式这里的解析逻辑要和客户端严格对应一个字节的差异都会导致解析失败。Put命令使用逗号加空参数表示从当前文件位置追加写入这比指定偏移量更省事顺序保证是接收端接收字节流的自然顺序。最后用LOF和fileLen比较来判断文件是否写完LOF是文件总长度如果实际写入长度已达到预设长度就能安全关闭文件。有一个细节必须提醒接收目录D:\Recv\需要提前创建。如果客户端传来的文件名包含反斜杠攻击路径代码里没有做路径清理文件会写到盘符的其他目录。我一般会加一个Replace(fileName, \, )防止目录穿越。普通实例为了可读性未必做处理但你在复现时要留意。3.3 客户端代码拆解选文件、连接与发送客户端的任务相对简单选择文件、建立连接、发送头部、发送文件内容。但要让传输稳定需要一次读入还是分块发送代码里差别很大。一个典型的客户端发送文件流程Private Sub Command1_Click() Dim filePath As String Dim fileSize As Long Dim stream() As Byte 打开文件对话框 CommonDialog1.ShowOpen filePath CommonDialog1.FileName If filePath Then Exit Sub 获取文件大小 fileSize FileLen(filePath) 发送头部 Winsock1.SendData CStr(fileSize) | GetFileName(filePath) 读取文件并发送 Open filePath For Binary As #1 ReDim stream(1 To fileSize - 1) Get #1, , stream Close #1 Winsock1.SendData stream End Sub这里用了FileLen获取文件大小用Get一次性读取整个文件到字节数组。这种代码对小于2MB的文件没问题但遇到几百MB大文件会瞬间占用大量内存而且ReDim stream(1 To fileSize - 1)在VB6里对大数组性能很差严重时直接崩溃。所以这份实例如果原始代码就是这样你需要改成循环分块发送。修改后的分块发送逻辑更接近实际项目Private Sub Command2_Click() Dim filePath As String Dim fileNum As Integer Dim chunk() As Byte Const CHUNK_SIZE As Long 8192 Dim bytesRead As Long Dim fileSize As Long filePath txtFilePath.Text fileSize FileLen(filePath) Winsock1.SendData CStr(fileSize) | GetFileName(filePath) fileNum FreeFile Open filePath For Binary As #fileNum Do While True bytesRead CHUNK_SIZE If Loc(fileNum) CHUNK_SIZE fileSize Then bytesRead fileSize - Loc(fileNum) End If If bytesRead 0 Then Exit Do ReDim chunk(1 To bytesRead) Get #fileNum, , chunk Winsock1.SendData chunk Loop Close #fileNum End Sub这个分块的逻辑是每次读8KBCHUNK_SIZE发送前用Loc当前文件位置判断是否到达文件尾部。如果剩余不足8KB就把bytesRead改成剩余字节数避免读到无意义数据。FreeFile获取一个空闲文件号防止和全局句柄冲突。ReDim chunk在循环里会重复分配内存虽然有一定开销但比一次性加载大文件稳妥得多。8KB是网吧局域网时代常用的经验值兼顾了内存和网络吞吐现在千兆局域网可以调大到32KB甚至64KB减少循环次数但单次发送过大会卡住UI线程如果不想加多线程8到16KB依然是比较不容易出错的配置。到这里代码主流程已经拆完了。理解客户端和服务器的“头先到体后到”结构后面再改任何参数都容易。4. 参数配置与运行让实例真正跑起来代码是骨架参数是血液。如果IP、端口、防火墙、文件目录任何一个对不上程序都会以各种玄学方式表现异常。这一章直接给出能跑的配置参数和逐项检查步骤。4.1 网络环境与端口配置IP和端口的匹配局域网传输前先确定两台机器在同一网段。例如服务器是192.168.1.10客户端就必须在192.168.1.x网段否则数据包会被路由器丢弃。实例里的服务器端LocalPort建议改用高位端口比如4567以上避开RPC、NetBIOS等常用端口冲突。客户端配置与之对应Private Sub Form_Load() Winsock1.RemoteHost 192.168.1.10 Winsock1.RemotePort 4567 End SubRemoteHost是目标计算机的IP可以在命令提示符里用ipconfig确认。RemotePort必须和服务器LocalPort完全一致。如果机器名在局域网里可以解析RemoteHost也可以填计算机名但稳妥起见还是用IP因为某些环境里NetBIOS解析会失效。为了便于连不同机器实例里最好把IP放在文本框里而不是写死在代码里改起来不用重新编译。如果连接报错“无效的请求”先检查两台机器的端口是否一致再检查服务器Listen是否真正生效。Listen状态可以通过Winsock1.State判断sckListen的值为2。在按钮事件里加一行If Winsock1.State 2 Then Winsock1.Listen可以防止多次点击重复监听导致冲突。4.2 防火墙与杀毒软件为什么对方收不到数据局域网传输最常见的“翻车”场景是代码完全没问题但对方一直收不到请求。排查时先ping两台机器能ping通说明物理网络通那基本就是防火墙拦住了。Windows防火墙默认会拦截外来的TCP监听端口。第一次运行VB6编译的exe时会弹出“Windows安全警报”必须勾选“专用网络”并允许访问。如果不小心点了取消后面所有连接都会超时。解决方法是把程序添加到防火墙例外列表netsh advfirewall firewall add rule nameVBFileTransfer dirin actionallow protocolTCP localport4567这条命令的作用是放行TCP入站端口4567。name参数随便起dirin表示入站规则actionallow允许通过localport端口要和代码里的端口保持一致。杀毒软件同理很多国外杀软默认拦截未知程序的网络监听行为你需要把编译出的exe或整个源码目录加入白名单。这里要提醒的是如果服务器和客户端都在同一台机器上测试防火墙一般不会阻止回环地址连接但RemoteHost填127.0.0.1可以绕过很多网络配置问题。先本机自测再换两台真实机器联调这个顺序能极大缩短排错时间。4.3 大文件传输的分块处理超过缓冲区怎么办第三章已经给了分块发送代码这里分析参数怎么选。Winsock控件底层缓冲区默认8K字节如果你一次性发送几MB数据发送方不一定卡死但接收方会多次触发DataArrival每次拿到的bytesTotal都不等。如果客户端没有分块接收端就靠GetData逐个读取你无法预判哪次会截断文件内容。所以参数上要注意三点第一发送块大小不要超过控件缓冲区的整数倍8KB与默认缓冲区一致最稳第二接收端要循环处理bytesTotal不要假设一次数据就是完整文件第三VB6字节数组上限还要受字符串长度限制单个数组不能超过2GB分块发送也能绕过这个限制。另外如果传输界面假死多半是主线程被同步发送占住。VB6里可以用DoEvents让界面响应但它只是“嗑药式”缓解真正稳的方案是设置发送状态标志用Timer轮询当前发送进度或者干脆用Winsock控件的SendProgress事件。这个事件能反馈已经发送的字节数实例里若没写你也能自己补上。5. 避坑指南常见问题与排查这一章直接罗列我见过以及反复出现的几类问题。每条都按“现象→原因→解决”的结构写你照着顺序过一遍很多夜间调试的玄学问题都能找到答案。5.1 现象打开工程提示“MSWinsock.ocx 未注册”或“无法加载控件”原因系统缺少Winsock运行库或者VB6在64位系统下打开了32位控件却找不到对应注册项。注册表路径不一致也会导致控件能注册但工程无法加载。解决打开管理员模式下的命令提示符执行以下命令regsvr32 C:\Windows\SysWOW64\MSWinsock.ocx如果文件不存在先从VB6安装目录或可信任的控件库中获得该ocx放到固定目录后重新注册。注册成功后在VB6的“工程—部件”里勾选“Microsoft Winsock Control 6.0”确认后再退出重开工程。64位系统下32位VB6的加载路径是SysWOW64不要用System32否则会提示模块加载失败。5.2 现象传输小文件正常但文件名变成乱码原因VB6内部字符串是UnicodeUTF-16Winsock控件发送字符串时默认按Unicode编码送出而接收端如果直接按字符串读取和实际文件名编码不一致就会乱码。特别是中文Windows环境下默认字符集是GBK两边系统区域设置不同更容易踩中。解决不要用vbString发送文件名字符串统一转成UTF-8字节数组再发送。动手改时可以用ADODB.Stream对象进行字符串转字节Function StringToUtf8Bytes(s As String) As Byte() Dim stream As Object Set stream CreateObject(ADODB.Stream) stream.Type 2 文本 stream.Charset utf-8 stream.Open stream.WriteText s stream.Position 0 stream.Type 1 二进制 StringToUtf8Bytes stream.Read stream.Close End Function发送端先调用这个函数转换文件名接收端则用逆向方法把字节转回字符串。头部协议里的文件名部分不要直接用Mid解析而是先转成字符串再做分割。实例如果没做编码转换这个坑十有八九会在中文文件名下暴露。5.3 现象传几十MB文件时进度条卡住或程序完全无响应原因客户端一次性读取全部文件内容到内存或者发送循环里没有DoEvents导致界面消息泵被阻塞。接收端如果使用Put写入时没控制文件大小也会因为磁盘写慢而拖垮界面。解决使用分块发送并添加DoEvents让界面有机会重绘。但DoEvents不能乱用它会重入事件过程最好放在发送块和发送块之间的空隙。更优雅的方案是使用Timer控件用一个全局布尔变量busySending避免重入Private Sub Timer1_Timer() If busySending Then Exit Sub If offset fileSize Then Timer1.Enabled False Exit Sub End If busySending True 读取下一块并发送 SendNextChunk busySending False lblProgress.Caption Int(offset / fileSize * 100) % End Sub代码中offset是当前已发送的字节位置Timer1触发时间设50ms一次。busySending防止上一次数据块还没发完Timer再次进入过程。这里把读文件、发送、更新进度条三个动作放到一个Timer体内界面始终有刷新机会控制在几百毫秒内完成一次循环即可。如果你追求更平滑的进度条可以把Timer间隔设到20ms但CPU占用会增大局域网传输本身不太依赖CPU所以20-50ms都合理。5.4 现象连接只有一两次成功之后客户端提示“无效的Winsock实例”原因服务器端Accept后没有正确管理多个连接或者客户端断开后Winsock控件状态没复位。VB6的Winsock控件是单连接控件一个控件同一时间只能处理一条连接如果你试图用同一个控件连续接受多条连接就要在连接完成后关掉否则下一次Accept时旧状态还在。解决在WinSock1_Close事件里把状态变量重置并重新开始监听Private Sub Winsock1_Close() Winsock1.Close Winsock1.LocalPort 4567 Winsock1.Listen End Sub关键点在于重新设置LocalPort并监听。因为关闭后端口会释放不重新绑定就会只剩一个“死连接”。如果客户端会反复传输多个文件每次启动新发送前也要先检查自己的Winsock状态如果不是SockClosed则先Close再Connect否则Connect会失败。5.5 现象发送快接收慢甚至丢数据原因发送端网卡缓冲区和接收端Winsock控件缓冲区大小不匹配。VB6的Winsock控件默认接收缓冲区8KB如果发送端一次发64KB接收端来不及处理就会发生内部缓冲溢出导致数据丢失或乱码。解决用Winsock1.SetBufferSize设置接收缓冲区大小常见设置是64KB或256KB。发送端每发完一块后适当Sleep(10)让接收端有时间把数据写入文件。不过Sleep会阻塞线程配合Timer方式更好。还要注意“发送快、接收慢”往往是TCP窗口自动调整的效果不要慌张优先检查接收端DataArrival里是否把所有数据都读出来而不是只读一部分。6. 进阶扩展从示例到可用的内网传输工具到这一步你已经能让实例在两台机器间传文件了。但距离一个真正“能用”的内网传输工具还有三件事必须补上。6.1 增加传输进度条与取消按钮第八章节所示的分块发送逻辑已经可以计算进度。最简单的方法是维护一个模块级变量fakeProgress每次发送一块就累加字节数然后在Label或ProgressBar上显示。接收端则用LOF和fileLen的比例来显示接收进度。取消按钮的思路是设置一个布尔变量calcelFlag为True发送循环里每轮检查它为True就跳出循环并关闭连接。6.2 多文件批量传输压缩文件或循环发送如果你想传多个文件最简单的办法是先把所有待传文件用Zip压缩成一个包再用现有逻辑传。这样做缺点是用户眼睛里面看到的是压缩包不是原文件结构。更好的做法是在头部协议里加上文件数量然后依次发送每个文件的文件名和长度接收端循环处理。这样改动较大需要在状态机里增加“正在接收第几个文件”的索引。6.3 断点续传思路从临时文件恢复断点续传的常见做法是把接收到的数据写进.part临时文件文件名后缀加part同时用一个小日志记录已传输字节数。重连后客户端读取日志里的偏移量从该偏移位置继续发送后半部分数据。VB6实现起来稍繁琐但核心思路是不要让接收端每次从头写而是每次打开临时文件后直接Seek到之前保存的offset处再继续写入。这个技巧在很多老旧内网工具里出现理解它远比背代码有价值。我以前第一次做局域网传输工具时也不懂这些以为只要把一个文件字节流完整发出去就行。结果遇到一个200MB的数据库备份文件直接内存溢出当场翻车。从那以后我每次做完一个传输工具都会强制走一遍本机回环测试、双机防火墙测试、中文文件名测试和100MB以上大文件测试四关全部过了才敢交给别人用。断点续传也是那次翻车之后补的功课每次发送前先在头部带上文件长度和偏移量接收端遇到故障重连时先查本地临时文件的大小再让发送端从该位置继续发。希望帮到你。本文还有配套的精品资源点击获取