diff --git a/.cargo/config.toml b/.cargo/config.toml index 2ee5da39..012c7ee2 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,3 +9,7 @@ rustflags = ["-C", "target-feature=+crt-static"] [target.riscv64gc-unknown-linux-gnu] linker = "riscv64-linux-gnu-gcc" rustflags = ["-C", "target-feature=+crt-static"] + +[target.x86_64-pc-windows-msvc] +linker = "link" +rustflags = ["-C", "target-feature=+crt-static"] \ No newline at end of file diff --git a/.gitignore b/.gitignore index 02b309f4..3a9812da 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ web/front/*.sw? # dist server/web/dist client/web/dist + +/libcs/msvc-build/target \ No newline at end of file diff --git a/BuildOnWindows.md b/BuildOnWindows.md new file mode 100644 index 00000000..8103043e --- /dev/null +++ b/BuildOnWindows.md @@ -0,0 +1,27 @@ +# 构建指南(Windows) + +## 1.环境准备 +### webrtc编译环境(webrtc版本差异会导致所依赖的msvc工具差异): +(google搜索Windows编译webrtc能找到详细步骤)
+安装visual studio2022,
+MSVC版本: v143,
+Windows11 SDK(10.0.22621.0)
+启用C++ ATL和MFC支持
+勾选【基于Windows的clang(llvm)工具】 + + +### msquic编译环境: +[msquic构建文档](https://github.com/microsoft/msquic/blob/main/docs/BUILD.md)
+根据构建文档安装依赖项(.Net Core,Cmake,Perl) + + +### 本项目编译所需环境: +配置cl(msvc编译器)工具环境,使其能在powershell中调用
+配置clang(llvm)工具环境,使其能在powershell中调用
+配置INCLUDE和LIB环境,分别包含msvc工具的include/lib目录,和Windows SDK的include/lib目录下的所有子目录 +安装powershell 7 + + +## 编译 +启动powershell 7,在powershell执行根目录下的build.ps1构建脚本
+(如果是首次构建,为了初始化msquic项目,需要在管理员模式执行powershell 7) \ No newline at end of file diff --git a/bin/Cargo.toml b/bin/Cargo.toml index baa896a6..42ec1734 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -8,6 +8,7 @@ description.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +libloading = "0.8.2" clap = { version = "4.4.17", features = ["derive"] } env_logger = "0.10.1" log = "0.4.20" @@ -23,4 +24,4 @@ url = "2.5.0" webrtc = "0.9.0" serde_yaml = "0.9.30" notify = { version = "6.1.1", default-features = false, features = ["macos_kqueue"] } -futures = "0.3.30" +futures = "0.3.30" \ No newline at end of file diff --git a/bin/build.rs b/bin/build.rs index 2634de85..752c0211 100644 --- a/bin/build.rs +++ b/bin/build.rs @@ -20,14 +20,14 @@ use std::process::Command; fn main() { let target = env::var("TARGET").unwrap(); - println!("cargo:rerun-if-changed=libcs/release/{target}"); - println!("cargo:rustc-link-search=libcs/release/{target}"); - println!("cargo:rustc-link-lib=static=cs"); - println!("cargo:rustc-link-lib=static=webrtc"); - println!("cargo:rustc-link-lib=static=msquic"); let os = env::var("CARGO_CFG_TARGET_OS").unwrap(); match os.as_str() { "linux" => { + println!("cargo:rerun-if-changed=libcs/release/{target}"); + println!("cargo:rustc-link-search=libcs/release/{target}"); + println!("cargo:rustc-link-lib=static=cs"); + println!("cargo:rustc-link-lib=static=webrtc"); + println!("cargo:rustc-link-lib=static=msquic"); let output = Command::new(format!( "{}-linux-gnu-gcc", env::var("CARGO_CFG_TARGET_ARCH").unwrap() @@ -42,6 +42,11 @@ fn main() { println!("cargo:rustc-link-lib=static=stdc++"); } "macos" => { + println!("cargo:rerun-if-changed=libcs/release/{target}"); + println!("cargo:rustc-link-search=libcs/release/{target}"); + println!("cargo:rustc-link-lib=static=cs"); + println!("cargo:rustc-link-lib=static=webrtc"); + println!("cargo:rustc-link-lib=static=msquic"); println!("cargo:rustc-link-lib=dylib=resolv"); println!("cargo:rustc-link-lib=dylib=c++"); println!("cargo:rustc-link-lib=dylib=c++abi"); @@ -51,6 +56,9 @@ fn main() { println!("cargo:rustc-link-lib=framework=CoreMedia"); println!("cargo:rustc-link-lib=framework=AVFoundation"); } + "windows"=>{ + //do nothing + } os => { panic!("Unsupported OS: {}", os) } diff --git a/bin/src/cs_bindings.rs b/bin/src/cs_bindings.rs index fc1eecf0..863f36b2 100644 --- a/bin/src/cs_bindings.rs +++ b/bin/src/cs_bindings.rs @@ -16,20 +16,28 @@ /* automatically generated by rust-bindgen 0.69.1 */ +use std::env::temp_dir; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + #[derive(PartialEq, Copy, Clone, Hash, Debug, Default)] #[repr(C)] pub struct __BindgenComplex { pub re: T, pub im: T, } + pub type wchar_t = ::std::os::raw::c_int; pub type max_align_t = f64; + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _GoString_ { pub p: *const ::std::os::raw::c_char, pub n: isize, } + #[test] fn bindgen_test_layout__GoString_() { const UNINIT: ::std::mem::MaybeUninit<_GoString_> = ::std::mem::MaybeUninit::uninit(); @@ -48,23 +56,24 @@ fn bindgen_test_layout__GoString_() { unsafe { ::std::ptr::addr_of!((*ptr).p) as usize - ptr as usize }, 0usize, concat!( - "Offset of field: ", - stringify!(_GoString_), - "::", - stringify!(p) + "Offset of field: ", + stringify!(_GoString_), + "::", + stringify!(p) ) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).n) as usize - ptr as usize }, 8usize, concat!( - "Offset of field: ", - stringify!(_GoString_), - "::", - stringify!(n) + "Offset of field: ", + stringify!(_GoString_), + "::", + stringify!(n) ) ); } + pub type GoInt8 = ::std::os::raw::c_schar; pub type GoUint8 = ::std::os::raw::c_uchar; pub type GoInt16 = ::std::os::raw::c_short; @@ -108,20 +117,20 @@ fn bindgen_test_layout_GoInterface() { unsafe { ::std::ptr::addr_of!((*ptr).t) as usize - ptr as usize }, 0usize, concat!( - "Offset of field: ", - stringify!(GoInterface), - "::", - stringify!(t) + "Offset of field: ", + stringify!(GoInterface), + "::", + stringify!(t) ) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).v) as usize - ptr as usize }, 8usize, concat!( - "Offset of field: ", - stringify!(GoInterface), - "::", - stringify!(v) + "Offset of field: ", + stringify!(GoInterface), + "::", + stringify!(v) ) ); } @@ -150,36 +159,68 @@ fn bindgen_test_layout_GoSlice() { unsafe { ::std::ptr::addr_of!((*ptr).data) as usize - ptr as usize }, 0usize, concat!( - "Offset of field: ", - stringify!(GoSlice), - "::", - stringify!(data) + "Offset of field: ", + stringify!(GoSlice), + "::", + stringify!(data) ) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).len) as usize - ptr as usize }, 8usize, concat!( - "Offset of field: ", - stringify!(GoSlice), - "::", - stringify!(len) + "Offset of field: ", + stringify!(GoSlice), + "::", + stringify!(len) ) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).cap) as usize - ptr as usize }, 16usize, concat!( - "Offset of field: ", - stringify!(GoSlice), - "::", - stringify!(cap) + "Offset of field: ", + stringify!(GoSlice), + "::", + stringify!(cap) ) ); } + + +#[cfg(not(target_os = "windows"))] extern "C" { pub fn RunServer(args: GoSlice); } + +#[cfg(not(target_os = "windows"))] extern "C" { pub fn RunClient(args: GoSlice); } + +#[cfg(target_os = "windows")] +const DLL_DATA: &'static [u8] = include_bytes!("../../libcs/msvc-build/target/gt.dll"); + +#[cfg(target_os = "windows")] +pub fn InitGtDll() { + let mut dll_file = File::create( temp_dir().join("gt.dll")).expect("Failed to create DLL file"); + dll_file.write_all(DLL_DATA).expect("Failed to write DLL data to file"); +} + +#[cfg(target_os = "windows")] +fn RunServer(args: GoSlice) { + unsafe { + let lib = libloading::Library::new( temp_dir().join("gt.dll")).unwrap(); + let func: libloading::Symbol = lib.get(b"RunServer").unwrap(); + func(args); + } +} + +#[cfg(target_os = "windows")] +fn RunClient(args: GoSlice) { + unsafe { + let lib = libloading::Library::new( temp_dir().join("gt.dll")).unwrap(); + let func: libloading::Symbol = lib.get(b"RunClient").unwrap(); + func(args); + } +} \ No newline at end of file diff --git a/bin/src/main.rs b/bin/src/main.rs index 2b788d18..3c958549 100644 --- a/bin/src/main.rs +++ b/bin/src/main.rs @@ -58,6 +58,11 @@ enum Commands { } fn main() { + #[cfg(target_os = "windows")] + { + cs::InitGtDll(); + } + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let cli = Cli::parse(); if let Some(signal) = cli.signal { diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 00000000..f702c35a --- /dev/null +++ b/build.ps1 @@ -0,0 +1,92 @@ +$WORD_DIR = $PSScriptRoot +$WEBRTC_DIR="$WORD_DIR/libcs/dep/_google-webrtc" +$MSQUIC_DIR="$WORD_DIR/libcs/dep/_msquic" +$WEBRTC_OUT_DIR="$WEBRTC_DIR/src/out/release/obj" +$MSQUIC_OUT_DIR="$MSQUIC_DIR/build/windows/x64_schannel/obj/Release" +$MSVC_BUILD_DIR="$WORD_DIR/libcs/msvc-build" +$RUST_TARGET_DIR="$WORD_DIR/target/x86_64-pc-windows-msvc/release" + +$env:CC="clang" +$env:CXX="clang++" +$env:CXXFLAGS="-I$WEBRTC_DIR/src -I$WEBRTC_DIR/src/third_party/abseil-cpp -I$MSQUIC_DIR/src/inc -std=c++17 -DWEBRTC_WIN -DQUIC_API_ENABLE_PREVIEW_FEATURES -DNOMINMAX" +$env:CGO_LDFLAGS="-L$MSQUIC_DIR/build/windows/x64_schannel/obj/Release -L$WEBRTC_DIR/src/out/release/obj -lmsquic.lib -lwebrtc.lib" +$env:CARGO_CFG_TARGET_OS="windows" + +Set-Location $WORD_DIR +function complie_webrtc{ + Set-Location "$WEBRTC_DIR/src" + gn gen out/release --args="clang_use_chrome_plugins=false is_clang=true enable_libaom=false is_component_build=false is_debug=false libyuv_disable_jpeg=true libyuv_include_tests=false rtc_build_examples=false rtc_build_tools=false rtc_enable_grpc=false rtc_enable_protobuf=false rtc_include_builtin_audio_codecs=false rtc_include_dav1d_in_internal_decoder_factory=false rtc_include_ilbc=false rtc_include_internal_audio_device=false rtc_include_tests=false rtc_use_h264=false rtc_use_x11=false treat_warnings_as_errors=false use_custom_libcxx=false use_gold=false use_lld=true use_rtti=true use_sysroot=false" + ninja -C out/release + Set-Location $WORD_DIR +} +if (!(Test-Path -Path "$WEBRTC_OUT_DIR/webrtc.lib")){ + complie_webrtc +} + + +function complie_msquic{ + Set-Location $MSQUIC_DIR + &./scripts/prepare-machine.ps1 + &./scripts/build.ps1 -Config Release -Clean -Static -DisableTest -DisableTools -StaticCRT + Set-Location $WORD_DIR +} +if (!(Test-Path -Path "$MSQUIC_OUT_DIR/msquic.lib")){ + if (!([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { + Write-Host "请以管理员权限运行此脚本" + exit + } + complie_msquic +} + + +function release_gt_dylib{ + Set-Location ./libcs + go build -tags release -trimpath -ldflags "-s -w" -buildmode=c-archive -o release/gt.lib ./lib/export + Set-Location ./msvc-build + + # 检查target目录是否存在 + $directory = "$WORD_DIR/libcs/msvc-build/target" + if (-not (Test-Path -Path $directory -PathType Container)) { + New-Item -Path $directory -ItemType Directory -Force + Write-Host "目录已创建:$directory" + } else { + Write-Host "目录已存在:$directory" + } + + cl /LD /MT /Fe:./target/gt.dll gt.cpp /link /DEF:gt.def "../release/gt.lib" "$MSQUIC_OUT_DIR\msquic.lib" "$WEBRTC_OUT_DIR\webrtc.lib" ntdll.lib + Set-Location $WORD_DIR +} +function release_gt_lib{ + Set-Location ./libcs + go build -tags release -trimpath -ldflags "-s -w" -buildmode=c-archive -o release/gt.lib ./lib/export + Set-Location ./msvc-build + + # 检查target目录是否存在 + $directory = "$WORD_DIR/libcs/msvc-build/target" + if (-not (Test-Path -Path $directory -PathType Container)) { + New-Item -Path $directory -ItemType Directory -Force + Write-Host "目录已创建:$directory" + } else { + Write-Host "目录已存在:$directory" + } + Set-Location $WORD_DIR +} +release_gt_dylib + + +function release_gt_exe{ + cargo build --target x86_64-pc-windows-msvc -r +} +release_gt_exe + + +function release_gt_with_dll{ + # 设置要打包的文件和文件夹路径 + $filesToCompress = @("$RUST_TARGET_DIR/gt.exe", "$MSVC_BUILD_DIR/target/gt.dll") + + # 设置自解压文件的输出路径和名称 + $outputFile = "$RUST_TARGET_DIR/gt-manager.exe" + + # 使用 7-Zip 创建自解压文件 + & 7z a -sfx"D:\Tools\7-Zip\7z.sfx" $outputFile $filesToCompress +} \ No newline at end of file diff --git a/libcs/conn/msquic/quic.cpp b/libcs/conn/msquic/quic.cpp index 39a934f8..8cfeaa48 100644 --- a/libcs/conn/msquic/quic.cpp +++ b/libcs/conn/msquic/quic.cpp @@ -85,8 +85,10 @@ const char *StringStatus(QUIC_STATUS status) { return "stream limit reached"; case QUIC_STATUS_ALPN_IN_USE: return "alpn in use"; + #ifndef _WIN64 case QUIC_STATUS_ADDRESS_NOT_AVAILABLE: return "address not available"; + #endif case QUIC_STATUS_CLOSE_NOTIFY: return "close notify"; case QUIC_STATUS_BAD_CERTIFICATE: diff --git a/libcs/conn/msquic/stream.cpp b/libcs/conn/msquic/stream.cpp index 24e20eb5..904aa2ab 100644 --- a/libcs/conn/msquic/stream.cpp +++ b/libcs/conn/msquic/stream.cpp @@ -2,6 +2,10 @@ #include "quic.hpp" #include "stream.hpp" +#ifdef _WIN64 +#include "stdlib.h" +#endif + Stream::Stream(void *context) : context(context) {} Stream::Stream(HQUIC stream) : stream(stream) { diff --git a/libcs/msvc-build/cgo.cpp b/libcs/msvc-build/cgo.cpp new file mode 100644 index 00000000..eadd31c6 --- /dev/null +++ b/libcs/msvc-build/cgo.cpp @@ -0,0 +1,17 @@ +#ifdef _MSC_VER +#ifdef __cplusplus +extern "C" { +#endif + + void _rt0_amd64_windows_lib(); + + __pragma(section(".CRT$XCU", read)); + __declspec(allocate(".CRT$XCU")) void (*init1)() = _rt0_amd64_windows_lib; + __pragma(comment(linker, "/include:init1")); + +#ifdef __cplusplus +} +#endif +#endif + +int main() { return 0; } diff --git a/libcs/msvc-build/gt.cpp b/libcs/msvc-build/gt.cpp new file mode 100644 index 00000000..4c51508e --- /dev/null +++ b/libcs/msvc-build/gt.cpp @@ -0,0 +1,19 @@ +#include "../release/gt.h" + +#ifdef _MSC_VER +#ifdef __cplusplus +extern "C" { +#endif + + void _rt0_amd64_windows_lib(); + + __pragma(section(".CRT$XCU", read)); + __declspec(allocate(".CRT$XCU")) void (*init1)() = _rt0_amd64_windows_lib; + __pragma(comment(linker, "/include:init1")); + +#ifdef __cplusplus +} +#endif +#endif + +int main() { return 0; } diff --git a/libcs/msvc-build/gt.def b/libcs/msvc-build/gt.def new file mode 100644 index 00000000..e011c9d1 --- /dev/null +++ b/libcs/msvc-build/gt.def @@ -0,0 +1,4 @@ +EXPORTS + _rt0_amd64_windows_lib + RunServer + RunClient \ No newline at end of file diff --git a/libcs/web/server/util/system.go b/libcs/web/server/util/system.go index f3165685..175afb90 100644 --- a/libcs/web/server/util/system.go +++ b/libcs/web/server/util/system.go @@ -1,3 +1,5 @@ +//go:build !windows + package util import ( diff --git a/libcs/web/server/util/system_win.go b/libcs/web/server/util/system_win.go new file mode 100644 index 00000000..ace9bcbc --- /dev/null +++ b/libcs/web/server/util/system_win.go @@ -0,0 +1,116 @@ +//go:build windows + +package util + +import ( + "errors" + "github.com/isrc-cas/gt/web/server/model/request" + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/disk" + "github.com/shirou/gopsutil/v3/mem" + "os" + "os/exec" + "runtime" + "syscall" + "time" +) + +const ( + B = 1 + KB = 1024 * B + MB = 1024 * KB + GB = 1024 * MB +) + +// SendSignal will create a new process to restart the services +func SendSignal(signal string) (err error) { + execPath, err := os.Executable() + if err != nil { + return + } + var cmd *exec.Cmd + switch signal { + case "reload": + cmd = exec.Command(execPath, "-s", "reload") + case "restart": + cmd = exec.Command(execPath, "-s", "restart") + case "stop": + cmd = exec.Command(execPath, "-s", "stop") + case "kill": + cmd = exec.Command(execPath, "-s", "kill") + default: + err = errors.New("unknown signal") + return + } + cmd.SysProcAttr = &syscall.SysProcAttr{ + // Setpgid: true + } + err = cmd.Start() + if err != nil { + return + } + err = cmd.Process.Release() + return +} + +func GetServerInfo() (server *request.Server, err error) { + var s request.Server + s.Os = initOS() + if s.Cpu, err = initCPU(); err != nil { + return nil, err + } + if s.Ram, err = initRAM(); err != nil { + return nil, err + } + if s.Disk, err = initDisk(); err != nil { + return nil, err + } + return &s, nil +} + +func initOS() (o request.Os) { + o.GOOS = runtime.GOOS + o.NumCPU = runtime.NumCPU() + o.Compiler = runtime.Compiler + o.GoVersion = runtime.Version() + o.NumGoroutine = runtime.NumGoroutine() + return o +} + +func initCPU() (c request.Cpu, err error) { + if cores, err := cpu.Counts(false); err != nil { + return c, err + } else { + c.Cores = cores + } + if cpus, err := cpu.Percent(time.Duration(200)*time.Millisecond, true); err != nil { + return c, err + } else { + c.Cpus = cpus + } + return c, nil +} + +func initRAM() (r request.Ram, err error) { + if u, err := mem.VirtualMemory(); err != nil { + return r, err + } else { + r.UsedMB = int(u.Used) / MB + r.TotalMB = int(u.Total) / MB + r.UsedPercent = int(u.UsedPercent) + } + return r, nil +} + +func initDisk() (d request.Disk, err error) { + if u, err := disk.Usage("/"); err != nil { + return d, err + } else { + d.UsedMB = int(u.Used) / MB + d.UsedGB = int(u.Used) / GB + d.TotalMB = int(u.Total) / MB + d.TotalGB = int(u.Total) / GB + d.UsedPercent = int(u.UsedPercent) + } + return d, nil +}