socket实现简陋远程文件管理
By
money
at 2022-01-18 • 4人收藏 • 1937人看过
最近需要对多台远程设备文件统一管理,简单撸了一套CS代码。
server端
import win.ui;
/*DSG{{*/
var winform = win.form(text="aardio form";right=759;bottom=469)
winform.add()
/*}}*/
import web.view;
import fsys;
import fsys.file;
import win.dlg.message;
import hpsocket.httpServerEx;
var hpHttpServer = hpsocket.httpServerEx();
hpHttpServer.threadGlobal = {
winform = winform;
rpcexternal ={
$callWorker = function($, data){
data.socket=$;
thread.command.post(data.action, data)
}
}
}
//监听线程被创建后触发
hpHttpServer.onThreadCreated = function(){
import thread.command;
import time.performance;
import string.xxtea;
import console;
}
//监听线程退出前触发此事件
hpHttpServer.onThreadEnd = function(){}
//允许升级为WebSocket协议
hpHttpServer.onUpgrade = function(hpHttpServer,connId,upgradeType){
if(upgradeType!=1) return -1;
hpHttpServer.sendWsSwitchingProtocols(connId);
hpHttpServer.notify(connId, "rpcClientId", connId)
}
hpHttpServer.onWsMessageHeader = function(hpHttpServer,connId,final,reserved,opCode,mask,bodyLen){
hpHttpServer.reallocString(connId,bodyLen);//初始化缓冲区
}
//接收到WebSocket请求数据
hpHttpServer.onWsMessageBody = function(hpHttpServer,connId,pData,len){
hpHttpServer.appendString(connId,pData,len);
}
hpHttpServer.afterJsonStringify = function(jsonData){
console.dump("afterJsonStringify", #jsonData)
return jsonData;
}
hpHttpServer.beforeJsonParse = function(connId, jsonData){
return jsonData;
}
//WebSocket请求数据接收完成
hpHttpServer.onWsMessageComplete = function(hpHttpServer,connId){
var data = hpHttpServer.getString(connId);
if( data ){
hpHttpServer._translateMessage(connId, data);
}
}
hpHttpServer.onClose = function(hpHttpServer,connId,enOperation,errCode){
hpHttpServer.reallocString(connId,0);//释放缓冲区
thread.command.post("onClientClose", connId);
}
sleep(1000)
import wsock;
var port = wsock.getFreePort()
port=8999
hpHttpServer.start(/*IP*/,port);
import console
console.dump("已启动:",hpHttpServer.getWsUrl())
var wb = web.view(winform);
var dirs={};
newFileReader = function(id, f){
var port = wsock.getFreePort()
thread.create(function(id, port, f){
import wsock.tcp.server;
import wsock.tcp.client;
import thread.command;
import fsys;
import math;
import console;
console.dump("文件线程已经启动,端口:", port)
var tcpServer = wsock.tcp.server("0.0.0.0",port)
tcpServer.forever(
function(acceptSocket){
console.dump("客户端连接")
var client = wsock.tcp.client(,acceptSocket);
var file = io.open(f,"w+b");//注意io.open默认是文本方式写入的,b指定二进制模式
var size = math.size64();
var buffer = raw.buffer(0x4000);
for(readSize,remainSize in client.eachReadBuffer(buffer) ){
size.add(readSize);
console.dump( size.format())
thread.command.post("downloadSize", id, size.format())
file.writeBuffer(buffer,readSize);
}
file.close();
client.close();
tcpServer.close();
}
)
console.dump("文件线程关闭")
},id, port, f)
return port;
}
var download={}
wb.export ({
enumDir = function(path){
hpHttpServer.publish("enumDir", path)
};
fileInfo = function(id, path){
console.dump(id)
hpHttpServer.publish("fileInfo", id, path)
};
enumParent = function(path){
path = fsys.getParentDir(path);
hpHttpServer.publish("enumDir", path)
}
download = function(id, path){
var f = io.fullpath("/user_download/"+string.replace(path,"@@:",''))
if(io.exist(f)){
if(!winform.msgAsk("已经存在此文件,是否重新下载?")){
process.exploreSelect(f);
return ;
}
}
//先创建一个空文件及对应的完整目录
string.save(f, '')
var port = newFileReader(id, f)
download[path] = f;
hpHttpServer.publish("download", id, path, port)
}
})
wb.html = /**
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>文件浏览</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap -->
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" media="screen">
<style>
#fork{position:fixed;top:0;right:0;_position:absolute;z-index: 10000;}
.bottom{margin: 20px auto; width: 100%; text-align: center;}
.container{width: 1080px; margin: 50px auto;}
</style>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script src="https://layui.11dz.cn/layui/release/layer/dist/layer.js"></script>
<head>
<body>
<script>
parentPath="";
layerIndex=0;
setVolume = function(data){
var html = "文件浏览";
data.forEach(function(item){
html += '<a class="driver" path="'+item+'" href="javascript:void(0);">'+item+'/</a> '
})
document.getElementById("h1").innerHTML = html;
$(".driver").off("click");
$(".driver").on("click", function(){
loading()
parentPath = $(this).attr("path")
enumDir($(this).attr("path"))
});
}
bindDownClick = function(cls){
setTimeout(function(){
$(".down").off("click");
$(".down").on("click", function(){
var file = parentPath + $(this).attr("path")
var id = $(this).data("id")
download(id, file);
});
},100)
}
downloadSize = function(id, size){
var s = $("#s_"+id)
s.text(s.data("size")+"/"+size)
}
downEnd = function(id){
var s = $("#d_"+id)
s.text("已下载")
}
setFileInfo = function(id, time, size){
loadend()
var path = $("#"+id).attr("path")
var txt = $("#"+id).text()
document.getElementById(id).outerHTML = '<a id="'+id+' class="file" path="'+path+'" href="javascript:void(0);">'+txt+'</a> '+time+' <span id="s_'+id+'" data-size='+size+'>'+size+'</span> <a id="d_'+id+'" data-id="'+id+'" path="'+path+'" class="down" href="javascript:void(0);" >下载</a>'
bindClick('file')
bindDownClick()
}
loading = function(){
layerIndex = layer.load(1, {
shade: [0.1,'#fff'] //0.1透明度的白色背景
});
}
loadend = function(){
layer.close(layerIndex)
}
bindClick = function(cls){
setTimeout(function(){
$("."+cls).off("click");
$("."+cls).on("click", function(){
loading()
if(cls=="dir"){
if($(this).text()!=".."){
parentPath += $(this).attr("path")
}
enumDir(parentPath)
}else if(cls=="file"){
var file = parentPath + $(this).attr("path")
fileInfo($(this).attr("id"), file)
}
});
$(".parent").off("click");
$(".parent").on("click", function(){
loading()
enumParent(parentPath)
});
},100)
}
setRow = function(parent, data, cls, clear){
parentPath = parent;
loadend()
var html = document.getElementById("pre").innerHTML;
if(clear){
html = "";
}
if(parentPath.length>3 && cls=="dir"){
html = '<a class="parent" href="javascript:void(0);">..</a> \r\n';
}
data.forEach(function(item,id){
if(cls=="dir"){
html += '<a class="'+cls+'" path="/'+item+'" href="javascript:void(0);">/'+item+'</a> \r\n'
}else {
html += '<a id="f_'+id+'" class="'+cls+'" path="/'+item+'" href="javascript:void(0);">/'+item+'</a> \r\n'
}
})
document.getElementById("pre").innerHTML = html;
bindClick(cls)
document.getElementById("h2").innerHTML ="路径:"+parentPath;
}
</script>
<div class="container">
<h1 id="h1">文件浏览</h1>
<h2 id="h2"></h2>
<pre id="pre">
</pre>
</div>
<div class="bottom"> </div>
</body>
</html>
**/
import thread.command;
import process;
var cmd = thread.command(winform);
cmd.setVolume = function(data){
var json = web.json.stringify(data.data)
wb.doScript("setVolume("+json+")")
}
cmd.enumDir = function(data){
var json = web.json.stringify(data.dir)
wb.doScript("setRow('"+data.path+"',"+json+", 'dir', true)")
json = web.json.stringify(data.file)
wb.doScript("setRow('"+data.path+"',"+json+", 'file', false)")
}
cmd.fileInfo = function(data){
if(data.sc){
var js = string.format("setFileInfo('%s','%s','%s')", data.id, data.writeTime, data.size)
wb.doScript(js)
}else {
win.msgbox(data.msg);
wb.doScript("loadend()")
}
}
cmd.downloadSize = function(id, size){
var js = string.format("downloadSize('%s','%s')", id, size)
//console.dump(js)
wb.doScript(js)
}
cmd.download = function(data){
if(!data.sc){
win.msgbox(data.msg);
}else {
process.exploreSelect(download[data.path]);
}
var js = string.format("downEnd('%s')", data.id)
console.dump(js)
wb.doScript(js)
}
winform.show();
win.loopMessage();
return winform;代码中有自定义库hpsocket.httpServerEx,作用与web.socket.jsonServer功能一致,可替换掉运行。
启动后这样,等待client连接:

client端:
import win
import sys.volume;
import web.socket.jsonClient;
import fsys;
import fsys.file;
if(_STUDIO_INVOKED){
import console
console.open()
}
var dyws = web.socket.jsonClient();
var commonHost = "*********";
if(_STUDIO_INVOKED){
commonHost = "127.0.0.1:8999";
}
var wsServer = "ws://"+commonHost+"/jsonrpc"
connectServer = function(){
win.setTimeout(
function(){
//console.dump(wsServer)
if(!userClash){
dyws.connect(wsServer);
}
},1000
)
}
dyws.on("open",function(){
//console.dump(open)
var json = {}
var drives = sys.volume.getLogicalDrives()
for(i,drv in drives){
var info = sys.volume.getInfo( drv) ;
if(info){
table.push(json, info.drive)
}
}
dyws.$callWorker({
action="setVolume";
data=json
});
})
dyws.on("enumDir",function(path){
//console.dump("path", path)
var file, dir = fsys.list(path,,"*.*")
dyws.$callWorker({
action="enumDir";
file=file;
dir=dir;
path=path;
});
})
dyws.on("fileInfo",function(id, path){
//console.dump("path", id, path)
var file = fsys.file(path)
if(file){
dyws.$callWorker({
action="fileInfo";
writeTime=tostring(file.getTime().write);
size=fsys.formatSize(file.size64());
id=id;
sc=true
});
}else {
dyws.$callWorker({
action="fileInfo";
writeTick=tonumber(file.getTime().write);
id=id;
sc=false;
msg="打开文件失败"
});
}
})
dyws.on("download",function(id, path, port){
var ret,err = thread.invokeAndWait(
function(path, port){
import wsock.tcp.client;
var socket = wsock.tcp.client()
var server = "*********";
if(_STUDIO_INVOKED){
server = "127.0.0.1";
}
if(socket.connect(server,port) ){
var file,err = io.open(path,"rb")
if( file ){
while(
var buf;
buf,readSize = file.read(4096);
buf
) {
socket.writeBuffer(buf,readSize) ;
}
file.close();
return true, "下载完成";
}else {
return false, "打开文件失败";
}
}else {
return false, "连接失败";
}
}, path, port
)
dyws.$callWorker({
action="download";
path=path;
id=id;
sc=ret;
msg=err
});
})
connectServer()
win.loopMessage()clinet启动后,连接到server,并枚举盘符发送给服务端

点击盘符,开始枚举目录及文件
点击目录,继续向下枚举,两个小点返回上级,跟其它网页版文件管理器大致相同
点击文件,获取文件的信息(只取了修改时间和文件大小)

点击下载,server新建一条tcp通道,并通知client连接新端口,连接上后自动传输文件,文件传输完成后,自动打开所在路径

此代码仅供学习讨论,请勿用于非法用途,后果自负,与本人无关
5 个回复 | 最后更新于 2022-11-08
admin
2022-01-18
#1
很实用,赞一个
2022-11-08
#4
受这个帖子的启发,用Golang写了一个文件预览服务器,只要浏览器支持的格式都可以直接打开预览,包括文字、图片、音频、视频、pdf等等。

package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"os"
"strings"
"time"
)
func main() {
gs := gin.Default()
gs.LoadHTMLGlob("./template/*")
gs.GET("/", func(c *gin.Context) {
var dirList []map[string]string
GetAllFiles("rootpath/", &dirList)
c.HTML(http.StatusOK, "index.html", gin.H{
"path": dirList,
})
})
gs.GET("/askfor/rootpath/*path", func(c *gin.Context) {
path, _ := c.Params.Get("path")
//fmt.Println(path)
fpath := "./rootpath" + path
if strings.HasSuffix(fpath, "/") {
var dirList []map[string]string
GetAllFiles("rootpath/", &dirList)
c.HTML(http.StatusOK, "index.html", gin.H{
"path": dirList,
})
return
}
fs, err := os.Open(fpath)
if err != nil {
fmt.Println(err)
return
}
defer fs.Close()
http.ServeContent(c.Writer, c.Request, "file", time.Now(), fs)
})
gs.Run(":9981")
}
func GetAllFiles(pathStr string, dirList *[]map[string]string) {
fs, _ := os.ReadDir(pathStr)
for _, f := range fs {
if f.IsDir() {
*dirList = append(*dirList, map[string]string{"name": f.Name(), "value": pathStr + f.Name() + "/"})
GetAllFiles(pathStr+f.Name()+"/", dirList)
} else {
*dirList = append(*dirList, map[string]string{"name": f.Name(), "value": pathStr + f.Name()})
}
}
}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File Review</title>
</head>
<body>
<div>
<p>根目录: <a href="/">root</a></p>
</div>
<div>
<table>
<thead>
<tr><td style="width: 100px">名称</td><td>路径</td></tr>
</thead>
<tbody>
{{range $idx, $item := .path}}
<tr><td>{{$item.name}}</td> <td><a href = '/askfor/{{$item.value}}' >{{$item.value}}</a></td></tr>
{{end}}
</tbody>
</table>
</div>
</body>
</html>登录后方可回帖