本帖最后由 零度先生 于 2026-2-9 10:46 编辑
本P2P由两部分组成,HTA收发端界面、PS2收发端功能。
P2P接收端.HTA的源码如下:
[HTML] 纯文本查看 复制代码 <html>
<head><title>文件接收端</title><HTA:APPLICATION SINGLEINSTANCE="yes"></head>
<body>
<button>启动监听</button>
<button>关闭监听</button>
<button>检测</button>
<script>var sh=new ActiveXObject('WScript.Shell');</script>
</body>
</html>
P2P发送端.HTA的源码如下:
[HTML] 纯文本查看 复制代码 <html>
<head>
<title>文件文件夹发送端</title>
<HTA:APPLICATION SINGLEINSTANCE="yes"/>
<style>
body{font-family:sans-serif;padding:10px}
button{margin:2px;padding:5px 10px}
#tableContainer{width:800px;height:300px;overflow:auto;border:1px solid #ccc}
tr.selected{background-color:#cce5ff}
table{border-collapse:collapse;width:100%;table-layout:fixed}
th,td{border:1px solid #ccc;padding:3px}
th{text-align:center}
</style>
</head>
<body>
<h3>文件文件夹发送端</h3>
IP:<input id="ip" value="172.20.201.41" style="width:150px">
<button>发送列表中所有文件文件夹</button>
<br><br>
<button>选择文件</button>
<button>选择文件夹</button>
<button>删除选中行</button>
<button>清空列表</button>
<br><br>
<div id="tableContainer">
<table id="fileTable">
<thead>
<tr>
<th style="width:50px">序号</th>
<th style="width:300px">名称</th>
<th style="width:450px">路径</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<script>
var counter=1
var sh=new ActiveXObject("WScript.Shell")
var table=document.getElementById("fileTable")
table.onclick=function(e){
e=e||window.event
var t=e.target||e.srcElement
var tr=t.parentNode
if(tr.tagName.toLowerCase()==="tr"&&tr.parentNode.tagName.toLowerCase()==="tbody"){
if(tr.className.indexOf("selected")===-1)tr.className+=" selected"
else tr.className=tr.className.replace(" selected","")
}
}
function removeSelected(){
var tbody=document.getElementById("fileTable").getElementsByTagName("tbody")[0]
for(var i=tbody.rows.length-1;i>=0;i--){
if(tbody.rows.className.indexOf("selected")!==-1)tbody.deleteRow(i)
}
for(var i=0;i<tbody.rows.length;i++)tbody.rows.cells[0].innerText=i+1
counter=tbody.rows.length+1
}
function clearTable(){
var tbody=document.getElementById("fileTable").getElementsByTagName("tbody")[0]
while(tbody.rows.length>0)tbody.deleteRow(0)
counter=1
}
function pickFile(){
try{
var exec=sh.Exec('powershell -NoProfile -ExecutionPolicy Bypass -File "FilePicker.ps1" -Mode file')
var output=String(exec.StdOut.ReadAll())
if(!output)return
var lines=output.split(/\r?\n/)
var tbody=document.getElementById("fileTable").getElementsByTagName("tbody")[0]
for(var i=0;i<lines.length;i++){
var path=lines
if(!path)continue
var exists=false
for(var j=0;j<tbody.rows.length;j++){
if(tbody.rows[j].cells[2].innerText.toLowerCase()===path.toLowerCase()){exists=true;break}
}
if(exists)continue
var name=path.replace(/^.*[\\\/]/,'')
var row=tbody.insertRow()
row.insertCell(0).innerText=counter
row.cells[0].style.textAlign="center"
row.insertCell(1).innerText=name
row.insertCell(2).innerText=path
counter++
}
}catch(e){}
}
function pickFolder(){
try{
var s=new ActiveXObject("Shell.Application"),f
while(true){
f=s.BrowseForFolder(0,"请选择文件夹(点取消结束)",0)
if(!f)break
var path=f.Self.Path
var tbody=document.getElementById("fileTable").getElementsByTagName("tbody")[0]
var exists=false
for(var j=0;j<tbody.rows.length;j++){
if(tbody.rows[j].cells[2].innerText.toLowerCase()===path.toLowerCase()){exists=true;break}
}
if(exists)continue
var name=path.replace(/^.*[\\\/]/,'')
var row=tbody.insertRow()
row.insertCell(0).innerText=counter
row.cells[0].style.textAlign="center"
row.insertCell(1).innerText=name
row.insertCell(2).innerText=path
counter++
}
}catch(e){}
}
function sendAll(){
var tbody=document.getElementById("fileTable").getElementsByTagName("tbody")[0]
var ip=document.getElementById("ip").value
if(!ip||tbody.rows.length===0)return
var fso=new ActiveXObject("Scripting.FileSystemObject")
var tmp=fso.GetSpecialFolder(2)+"\\sendlist_"+new Date().getTime()+".txt"
var s=fso.CreateTextFile(tmp,true)
for(var i=0;i<tbody.rows.length;i++){
var p=tbody.rows.cells[2].innerText
if(p)s.WriteLine(p)
}
s.Close()
sh.Run('powershell -NoExit -NoProfile -ExecutionPolicy Bypass -File send.ps1 -IP "'+ip+'" -ListFile "'+tmp+'"',1,false)
}
</script>
</body>
</html>
FilePicker.ps1(用于支持发送端批量选择文件)的源码如下:
[PowerShell] 纯文本查看 复制代码 # === 这是FilePicker.ps1批量选择文件功能代码 ===
param([ValidateSet("file")][string]$Mode="file")
Add-Type -AssemblyName System.Windows.Forms
$d=New-Object System.Windows.Forms.OpenFileDialog
$d.Multiselect=$true
if($d.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){
$d.FileNames -join "`n"
}
recv.ps1接收端功能的源码如下:
[PowerShell] 纯文本查看 复制代码 # 以下是recv.ps1接收端代码,自动保存到脚本所在目录。
# === 【脚本开头】规范化保存目录(关键!)===
$SaveDir = [System.IO.Path]::GetFullPath($PSScriptRoot)
if (-not $SaveDir.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
$SaveDir += [System.IO.Path]::DirectorySeparatorChar # 强制末尾带 \
}
$Port = 9000
$bufSize = 64MB
# === 统计显示下限(只影响日志)===
$MIN_TIME = 0.3
$MIN_BYTES = 64KB
# === 单位换算变量 ===
$KB = 1024
$MB = 1024*$KB
$GB = 1024*$MB
$TB = 1024*$GB
# === 格式化大小函数(所有单位保留2位小数)===
function Format-Size{
param([double]$Bytes)
if($Bytes -ge $TB){return "{0:N2}TB" -f ($Bytes/$TB)}
elseif($Bytes -ge $GB){return "{0:N2}GB" -f ($Bytes/$GB)}
elseif($Bytes -ge $MB){return "{0:N2}MB" -f ($Bytes/$MB)}
elseif($Bytes -ge $KB){return "{0:N2}KB" -f ($Bytes/$KB)}
else{return "{0:N2}B" -f $Bytes}
}
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Any,$Port)
$listener.Start()
Write-Host "等待发送端连接,监听端口: $Port"
while($true){
try{
$client = $listener.AcceptTcpClient()
$remoteEnd = $client.Client.RemoteEndPoint
Write-Host "发送端已连接: $remoteEnd"
$stream = $client.GetStream()
$reader = [System.IO.BinaryReader]::new($stream)
$count = 0
while($true){
$nameLenBytes = $reader.ReadBytes(4)
if($nameLenBytes.Length -lt 4){break}
$nameLen = [BitConverter]::ToInt32($nameLenBytes,0)
$nameBytes = $reader.ReadBytes($nameLen)
if($nameBytes.Length -lt $nameLen){break}
$relPath = [Text.Encoding]::UTF8.GetString($nameBytes)
$fileLenBytes = $reader.ReadBytes(8)
$fileLen = [BitConverter]::ToInt64($fileLenBytes,0)
# === 【接收文件时】替换您的校验段 ===
$fullPath = [System.IO.Path]::GetFullPath((Join-Path $SaveDir $relPath))
# 严格校验:必须以"规范化目录+分隔符"开头(不区分大小写)
if (-not $fullPath.StartsWith($SaveDir, [System.StringComparison]::OrdinalIgnoreCase)) {
Write-Host "非法路径,拒绝接收:$relPath"
# 必须补充:跳过该文件的二进制数据(否则流错位!)
if ($fileLen -gt 0) {
$remaining = $fileLen
$skipBuf = New-Object byte[] 65536
while ($remaining -gt 0) {
$read = $reader.Read($skipBuf, 0, [Math]::Min($remaining, $skipBuf.Length))
if ($read -le 0) { break }
$remaining -= $read
}
}
continue
}
$dir = Split-Path $fullPath -Parent
if(!(Test-Path $dir)){New-Item -Path $dir -ItemType Directory -Force|Out-Null}
if($fileLen -eq -1){
if(!(Test-Path $fullPath)){New-Item -Path $fullPath -ItemType Directory -Force|Out-Null}
Write-Host ("准备接收:{0} 大小:空文件夹" -f $relPath)
Write-Host ("接收完成:{0} 大小:空文件夹 速率:瞬时" -f $relPath)
$count++;continue
}
if($fileLen -eq 0){
New-Item -Path $fullPath -ItemType File -Force|Out-Null
Write-Host ("准备接收:{0} 大小:空文件" -f $relPath)
Write-Host ("接收完成:{0} 大小:空文件 速率:瞬时" -f $relPath)
$count++;continue
}
if($fileLen -eq -2){
Write-Host ("准备接收:{0} 大小:跳过" -f $relPath)
Write-Host ("接收完成:{0} 大小:跳过 速率:瞬时" -f $relPath)
$count++;continue
}
$fs = [System.IO.File]::OpenWrite($fullPath)
$received = 0
$prevTotal = 0
$startTime = Get-Date
$lastUpdate = Get-Date
Write-Host ("准备接收:{0} 大小:{1}" -f $relPath,(Format-Size $fileLen))
while($received -lt $fileLen){
$toRead = if(($fileLen-$received)-lt $bufSize){[int]($fileLen-$received)}else{[int]$bufSize}
$data = $reader.ReadBytes($toRead)
if($data.Length -eq 0){break}
$fs.Write($data,0,$data.Length)
$received += $data.Length
$now = Get-Date
$elapsed = ($now-$lastUpdate).TotalSeconds
if($elapsed -ge 1 -or $received -eq $fileLen){
$deltaBytes = $received-$prevTotal
$receivedSize = Format-Size $received
if($elapsed -lt $MIN_TIME -and $received -lt $MIN_BYTES){
Write-Host ("接收完成:{0} 大小:{1} 速率:瞬时" -f $relPath,$receivedSize)
}else{
if($deltaBytes -lt $KB){$rateVal=[math]::Round($deltaBytes,2);$rateUnit="B/s"}
elseif($deltaBytes -lt $MB){$rateVal=[math]::Round($deltaBytes/$KB,2);$rateUnit="KB/s"}
elseif($deltaBytes -lt $GB){$rateVal=[math]::Round($deltaBytes/$MB,2);$rateUnit="MB/s"}
else{$rateVal=[math]::Round($deltaBytes/$GB,2);$rateUnit="GB/s"}
$rateStr="{0:N2}{1}" -f $rateVal,$rateUnit
Write-Host ("正在接收:{0} 大小:{1} 已接收:{2} 速率:{3}" -f $relPath,(Format-Size $fileLen),$receivedSize,$rateStr)
}
$prevTotal=$received
$lastUpdate=$now
}
}
$fs.Close()
$count++
if($fileLen -ge $MIN_BYTES){Write-Host ("接收完成:{0} 大小:{1}" -f $relPath,(Format-Size $fileLen))}
}
Write-Host "全部接收完成,共 $count 个条目"
Write-Host "等待新的发送端连接..."
$reader.Close();$stream.Close();$client.Close()
}catch{
Write-Host "连接异常,继续监听..."
}
}
send.ps1发送端功能的源码如下:
[PowerShell] 纯文本查看 复制代码 # 以下是send.ps1发送端代码
param(
[Parameter(Mandatory=$true)][string]$IP,
[Parameter(Mandatory=$true)][string]$ListFile
)
$Files=Get-Content $ListFile|Where-Object {$_ -ne ""}
# === 统计显示下限(只影响日志)===
$MIN_TIME=0.3
$MIN_BYTES=64KB
# === 单位换算变量 ===
$KB=1024
$MB=1024*$KB
$GB=1024*$MB
$TB=1024*$GB
# === 格式化大小函数(所有单位保留2位小数)===
function Format-Size{
param([double]$Bytes)
if($Bytes -ge $TB){return "{0:N2}TB" -f ($Bytes/$TB)}
elseif($Bytes -ge $GB){return "{0:N2}GB" -f ($Bytes/$GB)}
elseif($Bytes -ge $MB){return "{0:N2}MB" -f ($Bytes/$MB)}
elseif($Bytes -ge $KB){return "{0:N2}KB" -f ($Bytes/$KB)}
else{return "{0:N2}B" -f $Bytes}
}
function Send-File{
param([System.Net.Sockets.TcpClient]$Client,[string]$FullPath,[string]$RelativePath,[long]$Length)
$stream=$Client.GetStream()
$writer=New-Object System.IO.BinaryWriter($stream)
try{
if($Length -gt 0){$fs=[System.IO.File]::OpenRead($FullPath)}
}catch{
Write-Host ("准备发送:{0} 大小:跳过" -f $RelativePath)
Write-Host ("发送完成:{0} 大小:跳过 速率:瞬时 原因: {1}" -f $RelativePath,$_.Exception.Message)
$nameBytes=[Text.Encoding]::UTF8.GetBytes($RelativePath)
$writer.Write([int]$nameBytes.Length)
$writer.Write($nameBytes)
$writer.Write([long]-2)
return
}
$nameBytes=[Text.Encoding]::UTF8.GetBytes($RelativePath)
$writer.Write([int]$nameBytes.Length)
$writer.Write($nameBytes)
$writer.Write([long]$Length)
if($Length -eq -1){
Write-Host ("准备发送:{0} 大小:空文件夹" -f $RelativePath)
Write-Host ("发送完成:{0} 大小:空文件夹 速率:瞬时" -f $RelativePath)
return
}
if($Length -eq 0){
Write-Host ("准备发送:{0} 大小:空文件" -f $RelativePath)
Write-Host ("发送完成:{0} 大小:空文件 速率:瞬时" -f $RelativePath)
return
}
Write-Host ("准备发送:{0} 大小:{1}" -f $RelativePath,(Format-Size $Length))
$bufSize=64MB
$buffer=New-Object byte[] $bufSize
$totalSent=0
$prevTotal=0
$startTime=Get-Date
$lastUpdate=Get-Date
while(($read=$fs.Read($buffer,0,$buffer.Length)) -gt 0){
$stream.Write($buffer,0,$read)
$totalSent+=$read
$now=Get-Date
$elapsed=($now-$lastUpdate).TotalSeconds
if($elapsed -ge 1 -or $totalSent -eq $Length){
$deltaBytes=$totalSent-$prevTotal
$sentSize=Format-Size $totalSent
if($elapsed -lt $MIN_TIME -and $totalSent -lt $MIN_BYTES){
Write-Host ("发送完成:{0} 大小:{1} 速率:瞬时" -f $RelativePath,$sentSize)
}else{
if($deltaBytes -lt $KB){$rateVal=[math]::Round($deltaBytes,2);$rateUnit="B/s"}
elseif($deltaBytes -lt $MB){$rateVal=[math]::Round($deltaBytes/$KB,2);$rateUnit="KB/s"}
elseif($deltaBytes -lt $GB){$rateVal=[math]::Round($deltaBytes/$MB,2);$rateUnit="MB/s"}
else{$rateVal=[math]::Round($deltaBytes/$GB,2);$rateUnit="GB/s"}
$rateStr="{0:N2}{1}" -f $rateVal,$rateUnit
Write-Host ("正在发送:{0} 大小:{1} 已发送:{2} 速率:{3}" -f $RelativePath,(Format-Size $Length),$sentSize,$rateStr)
}
$prevTotal=$totalSent
$lastUpdate=$now
}
}
$fs.Close()
if($Length -ge $MIN_BYTES){Write-Host ("发送完成:{0} 大小:{1}" -f $RelativePath,(Format-Size $Length))}
}
$allToSend=@()
foreach($File in $Files){
if(-not (Test-Path $File)){Write-Host "路径不存在,跳过:$File";continue}
$item=Get-Item $File
if($item.PSIsContainer){
$rootName=$item.Name
$rootFull=$item.FullName.TrimEnd('\')
Get-ChildItem -Path $rootFull -Recurse -File|ForEach-Object{
$rel=Join-Path $rootName $_.FullName.Substring($rootFull.Length+1)
$allToSend+=[pscustomobject]@{Full=$_.FullName;Relative=$rel;Length=$_.Length}
}
Get-ChildItem -Path $rootFull -Recurse -Directory|ForEach-Object{
$hasFiles=Get-ChildItem $_.FullName -File -ErrorAction SilentlyContinue
$hasDirs=Get-ChildItem $_.FullName -Directory -ErrorAction SilentlyContinue
if($hasFiles.Count -eq 0 -and $hasDirs.Count -eq 0){
$rel=Join-Path $rootName $_.FullName.Substring($rootFull.Length+1)
$allToSend+=[pscustomobject]@{Full=$_.FullName;Relative=$rel;Length=-1}
}
}
}else{
$allToSend+=[pscustomobject]@{Full=$item.FullName;Relative=$item.Name;Length=$item.Length}
}
}
if($allToSend.Count -eq 0){Write-Host "没有需要发送的内容";Start-Sleep -Seconds 9999}
Write-Host "等待连接接收端,请稍后......"
# === 连接目标(严格3秒超时)===
$client = $null
try {
$client = New-Object System.Net.Sockets.TcpClient
$connectResult = $client.BeginConnect($IP, 9000, $null, $null)
if (-not $connectResult.AsyncWaitHandle.WaitOne(5000)) {
throw "连接超时,超过5秒无法连接,请确认接收端的IP地址正确并且已开启监听,请关闭并本窗重试。 $IP:9000"
}
$client.EndConnect($connectResult)
$client.NoDelay = $true
Write-Host "已连接到: $IP"
} catch {
if ($client) { try { $client.Close() } catch {} }
Write-Host "连接失败:$($_.Exception.Message)"
Start-Sleep -Seconds 9999
exit
}
foreach($f in $allToSend){
Send-File -Client $client -FullPath $f.Full -RelativePath $f.Relative -Length $f.Length
}
$client.Close()
Write-Host ("全部发送完成,共 {0} 个条目" -f $allToSend.Count)
Write-Host "窗口保持打开状态,可查看完整日志"
Start-Sleep -Seconds 9999
如界面截图所示,只需要把代码复制粘贴到文本文档.TXT另存为ANSI编码格式的PS1文件和HTA文件即可,注意文件名如界面截图所示哈。
使用方法:先打开接收端.HTA开启监听。
再打开发送端.HTA,添加要发送的文件及文件夹,输入接收端的IP地址,点击发送即可。
适合用于局域网文件PP2传输。
目前支持WINDOWS操作系统。
后续我想研发一款HTML版的(有朝一日希望手机、PC、平板、WIN\APPLE\LINUX\安卓都能用,内外网都可以用)
|