This commit is contained in:
sin365 2024-04-26 17:51:55 +08:00
parent e5b6fce974
commit 58eec5e094
25 changed files with 1457 additions and 45 deletions

View File

@ -1,17 +1,25 @@
using ClientCore; using ClientCore;
using ClientCore.Data; using ClientCore.Data;
using ClientCore.Enum;
using ClientCore.Event; using ClientCore.Event;
App.Init("127.0.0.1", 23846, 23847); App.Init("127.0.0.1", 23846, 23847, OnNetLog);
//App.Init("main.axibug.com", 23846, 23847, OnNetLog);
//注册事件 //注册事件
EventSystem.Instance.RegisterEvent(EEvent.UserLogin, OnUserLogin); EventSystem.Instance.RegisterEvent(EEvent.UserLogin, OnUserLogin);
EventSystem.Instance.RegisterEvent(EEvent.TcpTunnelHelloResp, OnTcpTunnelHelloResp); EventSystem.Instance.RegisterEvent(EEvent.TcpTunnelHelloResp, OnTcpTunnelHelloResp);
EventSystem.Instance.RegisterEvent<long>(EEvent.UserJoin, OnUserJoin); EventSystem.Instance.RegisterEvent<long>(EEvent.TcpTunnelP2PStateUpdate, OnTcpTunnelP2PStateUpdate);
EventSystem.Instance.RegisterEvent<long>(EEvent.UserLeave, OnUserLeave); EventSystem.Instance.RegisterEvent<long, string>(EEvent.UserJoin, OnUserJoin);
EventSystem.Instance.RegisterEvent<long, string>(EEvent.UserLeave, OnUserLeave);
EventSystem.Instance.RegisterEvent<string, string>(EEvent.OnChatMsg, OnChatMsg); EventSystem.Instance.RegisterEvent<string, string>(EEvent.OnChatMsg, OnChatMsg);
EventSystem.Instance.RegisterEvent<long, string>(EEvent.OnP2PChatMsg, OnP2PChatMsg); EventSystem.Instance.RegisterEvent<long, string>(EEvent.OnP2PChatMsg, OnP2PChatMsg);
App.Connect();
//设置自动同意接收
App.p2pFile.bAutoRecv = true;
while (true) while (true)
{ {
string CommandStr = Console.ReadLine(); string CommandStr = Console.ReadLine();
@ -91,6 +99,12 @@ while (true)
} }
} }
static void OnNetLog(int LogLevel, string msg)
{
Console.WriteLine(msg);
}
void OnUserLogin() void OnUserLogin()
{ {
Console.WriteLine($"[User]登录成功"); Console.WriteLine($"[User]登录成功");
@ -104,12 +118,18 @@ void OnTcpTunnelHelloResp()
Console.WriteLine($"[TcpTunnel]TcpTunnelHelloResp"); Console.WriteLine($"[TcpTunnel]TcpTunnelHelloResp");
} }
void OnUserJoin(long UID)
void OnTcpTunnelP2PStateUpdate(long UID)
{ {
Console.WriteLine($"[User]用户{UID}上线"); Console.WriteLine($"[TcpTunnel]OnTcpTunnelP2PStateUpdate");
} }
void OnUserLeave(long UID) void OnUserJoin(long UID, string NickName)
{
Console.WriteLine($"[User]用户{NickName}上线");
}
void OnUserLeave(long UID, string NickName)
{ {
Console.WriteLine($"[User]用户{UID}下线"); Console.WriteLine($"[User]用户{UID}下线");
} }

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<RootNamespace>Client_GUI</RootNamespace>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ReaLTaiizor" Version="3.8.0.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ClientCore\ClientCore.csproj" />
</ItemGroup>
</Project>

85
Client-GUI/LoginUI.Designer.cs generated Normal file
View File

@ -0,0 +1,85 @@
namespace Client_GUI
{
partial class LoginUI
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
tb_Name = new TextBox();
label1 = new Label();
btn_Login = new Button();
SuspendLayout();
//
// tb_Name
//
tb_Name.Location = new Point(50, 12);
tb_Name.Name = "tb_Name";
tb_Name.Size = new Size(128, 23);
tb_Name.TabIndex = 0;
//
// label1
//
label1.AutoSize = true;
label1.Location = new Point(12, 15);
label1.Name = "label1";
label1.Size = new Size(32, 17);
label1.TabIndex = 1;
label1.Text = "昵称";
//
// btn_Login
//
btn_Login.Location = new Point(12, 46);
btn_Login.Name = "btn_Login";
btn_Login.Size = new Size(166, 23);
btn_Login.TabIndex = 2;
btn_Login.Text = "登录";
btn_Login.UseVisualStyleBackColor = true;
btn_Login.Click += btn_Login_Click;
//
// LoginUI
//
AutoScaleDimensions = new SizeF(7F, 17F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(191, 81);
Controls.Add(btn_Login);
Controls.Add(label1);
Controls.Add(tb_Name);
FormBorderStyle = FormBorderStyle.FixedToolWindow;
Name = "LoginUI";
Text = "Login";
FormClosing += LoginUI_FormClosing;
Shown += LoginUI_Shown;
ResumeLayout(false);
PerformLayout();
}
#endregion
private TextBox tb_Name;
private Label label1;
private Button btn_Login;
}
}

45
Client-GUI/LoginUI.cs Normal file
View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Client_GUI
{
public partial class LoginUI : Form
{
public LoginUI()
{
InitializeComponent();
}
private void btn_Login_Click(object sender, EventArgs e)
{
StaticComm.bClickLogin = false;
if (string.IsNullOrEmpty(tb_Name.Text))
{
MessageBox.Show("不可以为空");
return;
}
StaticComm.bClickLogin = true;
StaticComm.LoginName = tb_Name.Text;
this.Close();
}
private void LoginUI_Shown(object sender, EventArgs e)
{
StaticComm.bClickLogin = false;
}
private void LoginUI_FormClosing(object sender, FormClosingEventArgs e)
{
}
}
}

120
Client-GUI/LoginUI.resx Normal file
View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

417
Client-GUI/MainUI.Designer.cs generated Normal file
View File

@ -0,0 +1,417 @@
namespace Client_GUI
{
partial class MainUI
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
listbox_log = new ListBox();
listBox_OnlineUser = new ListBox();
textBox_chatsendmsg = new TextBox();
btn_chatsend = new Button();
tabControl_Main = new ReaLTaiizor.Controls.MetroTabControl();
tabPage1 = new TabPage();
listBox_AllChatMsg = new ListBox();
tabPage2 = new TabPage();
label_P2P_SendFile_Pr = new ReaLTaiizor.Controls.MaterialLabel();
metroLabel5 = new ReaLTaiizor.Controls.MetroLabel();
label_P2P_SendFile_State = new ReaLTaiizor.Controls.MaterialLabel();
metroLabel4 = new ReaLTaiizor.Controls.MetroLabel();
btn_P2P_SendFile = new ReaLTaiizor.Controls.MaterialButton();
foxLabel2 = new ReaLTaiizor.Controls.FoxLabel();
foxLabel1 = new ReaLTaiizor.Controls.FoxLabel();
label_P2P_SendFilePath = new ReaLTaiizor.Controls.MaterialLabel();
metroLabel3 = new ReaLTaiizor.Controls.MetroLabel();
btn_P2P_ChangeSendFile = new ReaLTaiizor.Controls.MaterialButton();
label_P2P_State = new ReaLTaiizor.Controls.MaterialLabel();
metroLabel2 = new ReaLTaiizor.Controls.MetroLabel();
label_P2P_TargetUName = new ReaLTaiizor.Controls.MaterialLabel();
metroLabel1 = new ReaLTaiizor.Controls.MetroLabel();
foxLabel3 = new ReaLTaiizor.Controls.FoxLabel();
tabControl_Main.SuspendLayout();
tabPage1.SuspendLayout();
tabPage2.SuspendLayout();
SuspendLayout();
//
// listbox_log
//
listbox_log.FormattingEnabled = true;
listbox_log.ItemHeight = 17;
listbox_log.Location = new Point(12, 335);
listbox_log.Name = "listbox_log";
listbox_log.Size = new Size(776, 106);
listbox_log.TabIndex = 0;
//
// listBox_OnlineUser
//
listBox_OnlineUser.FormattingEnabled = true;
listBox_OnlineUser.ItemHeight = 17;
listBox_OnlineUser.Location = new Point(12, 46);
listBox_OnlineUser.Name = "listBox_OnlineUser";
listBox_OnlineUser.Size = new Size(160, 276);
listBox_OnlineUser.TabIndex = 1;
listBox_OnlineUser.DoubleClick += listBox_OnlineUser_DoubleClick;
//
// textBox_chatsendmsg
//
textBox_chatsendmsg.Location = new Point(3, 234);
textBox_chatsendmsg.Name = "textBox_chatsendmsg";
textBox_chatsendmsg.Size = new Size(485, 23);
textBox_chatsendmsg.TabIndex = 3;
//
// btn_chatsend
//
btn_chatsend.Location = new Point(494, 234);
btn_chatsend.Name = "btn_chatsend";
btn_chatsend.Size = new Size(102, 23);
btn_chatsend.TabIndex = 4;
btn_chatsend.Text = "发送";
btn_chatsend.UseVisualStyleBackColor = true;
btn_chatsend.Click += btn_chatsend_Click;
//
// tabControl_Main
//
tabControl_Main.AnimateEasingType = ReaLTaiizor.Enum.Metro.EasingType.CubeOut;
tabControl_Main.AnimateTime = 200;
tabControl_Main.BackgroundColor = Color.White;
tabControl_Main.Controls.Add(tabPage1);
tabControl_Main.Controls.Add(tabPage2);
tabControl_Main.ControlsVisible = true;
tabControl_Main.IsDerivedStyle = true;
tabControl_Main.ItemSize = new Size(100, 38);
tabControl_Main.Location = new Point(178, 12);
tabControl_Main.MCursor = Cursors.Hand;
tabControl_Main.Name = "tabControl_Main";
tabControl_Main.SelectedIndex = 0;
tabControl_Main.SelectedTextColor = Color.White;
tabControl_Main.Size = new Size(610, 310);
tabControl_Main.SizeMode = TabSizeMode.Fixed;
tabControl_Main.Speed = 100;
tabControl_Main.Style = ReaLTaiizor.Enum.Metro.Style.Light;
tabControl_Main.StyleManager = null;
tabControl_Main.TabIndex = 6;
tabControl_Main.ThemeAuthor = "Taiizor";
tabControl_Main.ThemeName = "MetroLight";
tabControl_Main.UnselectedTextColor = Color.Gray;
//
// tabPage1
//
tabPage1.Controls.Add(listBox_AllChatMsg);
tabPage1.Controls.Add(btn_chatsend);
tabPage1.Controls.Add(textBox_chatsendmsg);
tabPage1.Location = new Point(4, 42);
tabPage1.Name = "tabPage1";
tabPage1.Padding = new Padding(3);
tabPage1.Size = new Size(602, 264);
tabPage1.TabIndex = 0;
tabPage1.Text = "聊天室";
tabPage1.UseVisualStyleBackColor = true;
//
// listBox_AllChatMsg
//
listBox_AllChatMsg.FormattingEnabled = true;
listBox_AllChatMsg.ItemHeight = 17;
listBox_AllChatMsg.Location = new Point(3, 3);
listBox_AllChatMsg.Name = "listBox_AllChatMsg";
listBox_AllChatMsg.Size = new Size(593, 225);
listBox_AllChatMsg.TabIndex = 0;
//
// tabPage2
//
tabPage2.Controls.Add(label_P2P_SendFile_Pr);
tabPage2.Controls.Add(metroLabel5);
tabPage2.Controls.Add(label_P2P_SendFile_State);
tabPage2.Controls.Add(metroLabel4);
tabPage2.Controls.Add(btn_P2P_SendFile);
tabPage2.Controls.Add(foxLabel2);
tabPage2.Controls.Add(foxLabel1);
tabPage2.Controls.Add(label_P2P_SendFilePath);
tabPage2.Controls.Add(metroLabel3);
tabPage2.Controls.Add(btn_P2P_ChangeSendFile);
tabPage2.Controls.Add(label_P2P_State);
tabPage2.Controls.Add(metroLabel2);
tabPage2.Controls.Add(label_P2P_TargetUName);
tabPage2.Controls.Add(metroLabel1);
tabPage2.Location = new Point(4, 42);
tabPage2.Name = "tabPage2";
tabPage2.Padding = new Padding(3);
tabPage2.Size = new Size(602, 264);
tabPage2.TabIndex = 1;
tabPage2.Text = "P2P传文件";
tabPage2.UseVisualStyleBackColor = true;
//
// label_P2P_SendFile_Pr
//
label_P2P_SendFile_Pr.AutoSize = true;
label_P2P_SendFile_Pr.Depth = 0;
label_P2P_SendFile_Pr.Font = new Font("Roboto", 14F, FontStyle.Regular, GraphicsUnit.Pixel);
label_P2P_SendFile_Pr.Location = new Point(269, 89);
label_P2P_SendFile_Pr.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER;
label_P2P_SendFile_Pr.Name = "label_P2P_SendFile_Pr";
label_P2P_SendFile_Pr.Size = new Size(49, 19);
label_P2P_SendFile_Pr.TabIndex = 13;
label_P2P_SendFile_Pr.Text = "未发送";
//
// metroLabel5
//
metroLabel5.Font = new Font("Microsoft Sans Serif", 10F);
metroLabel5.IsDerivedStyle = true;
metroLabel5.Location = new Point(229, 89);
metroLabel5.Name = "metroLabel5";
metroLabel5.Size = new Size(45, 23);
metroLabel5.Style = ReaLTaiizor.Enum.Metro.Style.Light;
metroLabel5.StyleManager = null;
metroLabel5.TabIndex = 12;
metroLabel5.Text = "进度:";
metroLabel5.ThemeAuthor = "Taiizor";
metroLabel5.ThemeName = "MetroLight";
//
// label_P2P_SendFile_State
//
label_P2P_SendFile_State.AutoSize = true;
label_P2P_SendFile_State.Depth = 0;
label_P2P_SendFile_State.Font = new Font("Roboto", 14F, FontStyle.Regular, GraphicsUnit.Pixel);
label_P2P_SendFile_State.Location = new Point(139, 89);
label_P2P_SendFile_State.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER;
label_P2P_SendFile_State.Name = "label_P2P_SendFile_State";
label_P2P_SendFile_State.Size = new Size(49, 19);
label_P2P_SendFile_State.TabIndex = 11;
label_P2P_SendFile_State.Text = "未发送";
//
// metroLabel4
//
metroLabel4.Font = new Font("Microsoft Sans Serif", 10F);
metroLabel4.IsDerivedStyle = true;
metroLabel4.Location = new Point(99, 89);
metroLabel4.Name = "metroLabel4";
metroLabel4.Size = new Size(45, 23);
metroLabel4.Style = ReaLTaiizor.Enum.Metro.Style.Light;
metroLabel4.StyleManager = null;
metroLabel4.TabIndex = 10;
metroLabel4.Text = "状态:";
metroLabel4.ThemeAuthor = "Taiizor";
metroLabel4.ThemeName = "MetroLight";
//
// btn_P2P_SendFile
//
btn_P2P_SendFile.AutoSizeMode = AutoSizeMode.GrowAndShrink;
btn_P2P_SendFile.Density = ReaLTaiizor.Controls.MaterialButton.MaterialButtonDensity.Default;
btn_P2P_SendFile.Depth = 0;
btn_P2P_SendFile.HighEmphasis = true;
btn_P2P_SendFile.Icon = null;
btn_P2P_SendFile.IconType = ReaLTaiizor.Controls.MaterialButton.MaterialIconType.Rebase;
btn_P2P_SendFile.Location = new Point(7, 89);
btn_P2P_SendFile.Margin = new Padding(4, 6, 4, 6);
btn_P2P_SendFile.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER;
btn_P2P_SendFile.Name = "btn_P2P_SendFile";
btn_P2P_SendFile.NoAccentTextColor = Color.Empty;
btn_P2P_SendFile.Size = new Size(85, 36);
btn_P2P_SendFile.TabIndex = 9;
btn_P2P_SendFile.Text = "发送文件";
btn_P2P_SendFile.Type = ReaLTaiizor.Controls.MaterialButton.MaterialButtonType.Contained;
btn_P2P_SendFile.UseAccentColor = false;
btn_P2P_SendFile.UseVisualStyleBackColor = true;
btn_P2P_SendFile.Click += btn_P2P_SendFile_Click;
//
// foxLabel2
//
foxLabel2.BackColor = Color.Transparent;
foxLabel2.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
foxLabel2.ForeColor = Color.FromArgb(76, 88, 100);
foxLabel2.Location = new Point(139, 131);
foxLabel2.Name = "foxLabel2";
foxLabel2.Size = new Size(243, 19);
foxLabel2.TabIndex = 8;
foxLabel2.Text = "------------------收文件------------------";
//
// foxLabel1
//
foxLabel1.BackColor = Color.Transparent;
foxLabel1.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
foxLabel1.ForeColor = Color.FromArgb(76, 88, 100);
foxLabel1.Location = new Point(139, 25);
foxLabel1.Name = "foxLabel1";
foxLabel1.Size = new Size(243, 19);
foxLabel1.TabIndex = 7;
foxLabel1.Text = "------------------传文件------------------";
//
// label_P2P_SendFilePath
//
label_P2P_SendFilePath.AutoSize = true;
label_P2P_SendFilePath.Depth = 0;
label_P2P_SendFilePath.Font = new Font("Roboto", 14F, FontStyle.Regular, GraphicsUnit.Pixel);
label_P2P_SendFilePath.Location = new Point(139, 57);
label_P2P_SendFilePath.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER;
label_P2P_SendFilePath.Name = "label_P2P_SendFilePath";
label_P2P_SendFilePath.Size = new Size(104, 19);
label_P2P_SendFilePath.TabIndex = 6;
label_P2P_SendFilePath.Text = "C:\\abc\\123.txt";
//
// metroLabel3
//
metroLabel3.Font = new Font("Microsoft Sans Serif", 10F);
metroLabel3.IsDerivedStyle = true;
metroLabel3.Location = new Point(99, 57);
metroLabel3.Name = "metroLabel3";
metroLabel3.Size = new Size(45, 23);
metroLabel3.Style = ReaLTaiizor.Enum.Metro.Style.Light;
metroLabel3.StyleManager = null;
metroLabel3.TabIndex = 5;
metroLabel3.Text = "文件:";
metroLabel3.ThemeAuthor = "Taiizor";
metroLabel3.ThemeName = "MetroLight";
//
// btn_P2P_ChangeSendFile
//
btn_P2P_ChangeSendFile.AutoSizeMode = AutoSizeMode.GrowAndShrink;
btn_P2P_ChangeSendFile.Density = ReaLTaiizor.Controls.MaterialButton.MaterialButtonDensity.Default;
btn_P2P_ChangeSendFile.Depth = 0;
btn_P2P_ChangeSendFile.HighEmphasis = true;
btn_P2P_ChangeSendFile.Icon = null;
btn_P2P_ChangeSendFile.IconType = ReaLTaiizor.Controls.MaterialButton.MaterialIconType.Rebase;
btn_P2P_ChangeSendFile.Location = new Point(7, 47);
btn_P2P_ChangeSendFile.Margin = new Padding(4, 6, 4, 6);
btn_P2P_ChangeSendFile.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER;
btn_P2P_ChangeSendFile.Name = "btn_P2P_ChangeSendFile";
btn_P2P_ChangeSendFile.NoAccentTextColor = Color.Empty;
btn_P2P_ChangeSendFile.Size = new Size(85, 36);
btn_P2P_ChangeSendFile.TabIndex = 4;
btn_P2P_ChangeSendFile.Text = "选择文件";
btn_P2P_ChangeSendFile.Type = ReaLTaiizor.Controls.MaterialButton.MaterialButtonType.Contained;
btn_P2P_ChangeSendFile.UseAccentColor = false;
btn_P2P_ChangeSendFile.UseVisualStyleBackColor = true;
btn_P2P_ChangeSendFile.Click += btn_P2P_ChangeSendFile_Click;
//
// label_P2P_State
//
label_P2P_State.AutoSize = true;
label_P2P_State.Depth = 0;
label_P2P_State.Font = new Font("Roboto", 14F, FontStyle.Regular, GraphicsUnit.Pixel);
label_P2P_State.Location = new Point(269, 3);
label_P2P_State.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER;
label_P2P_State.Name = "label_P2P_State";
label_P2P_State.Size = new Size(49, 19);
label_P2P_State.TabIndex = 3;
label_P2P_State.Text = "未连接";
//
// metroLabel2
//
metroLabel2.Font = new Font("Microsoft Sans Serif", 10F);
metroLabel2.IsDerivedStyle = true;
metroLabel2.Location = new Point(197, 3);
metroLabel2.Name = "metroLabel2";
metroLabel2.Size = new Size(77, 23);
metroLabel2.Style = ReaLTaiizor.Enum.Metro.Style.Light;
metroLabel2.StyleManager = null;
metroLabel2.TabIndex = 2;
metroLabel2.Text = "P2P状态:";
metroLabel2.ThemeAuthor = "Taiizor";
metroLabel2.ThemeName = "MetroLight";
//
// label_P2P_TargetUName
//
label_P2P_TargetUName.AutoSize = true;
label_P2P_TargetUName.Depth = 0;
label_P2P_TargetUName.Font = new Font("Roboto", 14F, FontStyle.Regular, GraphicsUnit.Pixel);
label_P2P_TargetUName.Location = new Point(78, 3);
label_P2P_TargetUName.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER;
label_P2P_TargetUName.Name = "label_P2P_TargetUName";
label_P2P_TargetUName.Size = new Size(49, 19);
label_P2P_TargetUName.TabIndex = 1;
label_P2P_TargetUName.Text = "用户名";
//
// metroLabel1
//
metroLabel1.Font = new Font("Microsoft Sans Serif", 10F);
metroLabel1.IsDerivedStyle = true;
metroLabel1.Location = new Point(6, 3);
metroLabel1.Name = "metroLabel1";
metroLabel1.Size = new Size(77, 23);
metroLabel1.Style = ReaLTaiizor.Enum.Metro.Style.Light;
metroLabel1.StyleManager = null;
metroLabel1.TabIndex = 0;
metroLabel1.Text = "目标对象:";
metroLabel1.ThemeAuthor = "Taiizor";
metroLabel1.ThemeName = "MetroLight";
//
// foxLabel3
//
foxLabel3.BackColor = Color.Transparent;
foxLabel3.Font = new Font("Segoe UI", 10F, FontStyle.Bold);
foxLabel3.ForeColor = Color.FromArgb(76, 88, 100);
foxLabel3.Location = new Point(12, 21);
foxLabel3.Name = "foxLabel3";
foxLabel3.Size = new Size(160, 19);
foxLabel3.TabIndex = 14;
foxLabel3.Text = "在线用户";
//
// MainUI
//
AutoScaleDimensions = new SizeF(7F, 17F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(796, 450);
Controls.Add(foxLabel3);
Controls.Add(tabControl_Main);
Controls.Add(listBox_OnlineUser);
Controls.Add(listbox_log);
Name = "MainUI";
Text = "HaoYueTunnel";
Load += MainUI_Load;
tabControl_Main.ResumeLayout(false);
tabPage1.ResumeLayout(false);
tabPage1.PerformLayout();
tabPage2.ResumeLayout(false);
tabPage2.PerformLayout();
ResumeLayout(false);
}
#endregion
private ListBox listbox_log;
private ListBox listBox_OnlineUser;
private TextBox textBox_chatsendmsg;
private Button btn_chatsend;
private ReaLTaiizor.Controls.MetroTabControl tabControl_Main;
private TabPage tabPage1;
private TabPage tabPage2;
private ListBox listBox_AllChatMsg;
private ReaLTaiizor.Controls.MaterialLabel label_P2P_TargetUName;
private ReaLTaiizor.Controls.MetroLabel metroLabel1;
private ReaLTaiizor.Controls.MaterialLabel label_P2P_State;
private ReaLTaiizor.Controls.MetroLabel metroLabel2;
private ReaLTaiizor.Controls.MaterialButton btn_P2P_ChangeSendFile;
private ReaLTaiizor.Controls.MaterialLabel label_P2P_SendFilePath;
private ReaLTaiizor.Controls.MetroLabel metroLabel3;
private ReaLTaiizor.Controls.FoxLabel foxLabel2;
private ReaLTaiizor.Controls.FoxLabel foxLabel1;
private ReaLTaiizor.Controls.MaterialButton btn_P2P_SendFile;
private ReaLTaiizor.Controls.MaterialLabel label_P2P_SendFile_State;
private ReaLTaiizor.Controls.MetroLabel metroLabel4;
private ReaLTaiizor.Controls.MaterialLabel label_P2P_SendFile_Pr;
private ReaLTaiizor.Controls.MetroLabel metroLabel5;
private ReaLTaiizor.Controls.FoxLabel foxLabel3;
}
}

347
Client-GUI/MainUI.cs Normal file
View File

@ -0,0 +1,347 @@
using ClientCore;
using ClientCore.Data;
using ClientCore.Enum;
using ClientCore.Event;
using ClientCore.Network;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
namespace Client_GUI
{
public partial class MainUI : Form
{
public delegate void DSetLogStr(List<string> value);
public delegate void DSetStr(string value);
public delegate void DSetBool(bool value);
public delegate void DSetVoid();
const int LogLimit = 200;
const int ChatShowLimit = 200;
long mCurrUID = 0;
public MainUI()
{
InitializeComponent();
}
private void MainUI_Load(object sender, EventArgs e)
{
//注册事件
EventSystem.Instance.RegisterEvent(EEvent.UserLogin, OnUserLogin);
EventSystem.Instance.RegisterEvent(EEvent.TcpTunnelHelloResp, OnTcpTunnelHelloResp);
EventSystem.Instance.RegisterEvent<long>(EEvent.TcpTunnelP2PStateUpdate, OnTcpTunnelP2PStateUpdate);
EventSystem.Instance.RegisterEvent<long, string>(EEvent.UserJoin, OnUserJoin);
EventSystem.Instance.RegisterEvent<long, string>(EEvent.UserLeave, OnUserLeave);
EventSystem.Instance.RegisterEvent<string, string>(EEvent.OnChatMsg, OnChatMsg);
EventSystem.Instance.RegisterEvent<long, string>(EEvent.OnP2PChatMsg, OnP2PChatMsg);
EventSystem.Instance.RegisterEvent<int>(EEvent.OnFileConfirmChoice, OnFileConfirmChoice);
//EventSystem.Instance.RegisterEvent<long>(EEvent.OnFilePushStart, );
//EventSystem.Instance.RegisterEvent<long>(EEvent.OnFilePushEnd, );
System.Timers.Timer bTimer = new System.Timers.Timer();
bTimer.Elapsed += new System.Timers.ElapsedEventHandler(TimeEvent_Log);
// 设置引发时间的时间间隔 此处设置为1秒(1000毫秒)
bTimer.Interval = 500;
bTimer.Enabled = true;
App.Init("127.0.0.1", 23846, 23847, OnNetLog);
//App.Init("main.axibug.com", 23846, 23847, OnNetLog);
if (App.Connect())
{
App.login.Login(StaticComm.LoginName);
}
UpdateP2PTargetState();
}
#region
void OnNetLog(int LogLevel, string msg)
{
AddLog(msg);
}
void OnUserLogin()
{
AddLog($"[User]登录成功");
App.userMgr.Send_GetUserList();
App.clientMgr.ConnectTcpTunnelServer();
}
void OnTcpTunnelHelloResp()
{
AddLog($"[TcpTunnel]OnTcpTunnelHelloResp");
}
void OnTcpTunnelP2PStateUpdate(long UID)
{
AddLog($"[TcpTunnel]OnTcpTunnelP2PStateUpdate");
if (UID == mCurrUID)
{
UpdateP2PTargetState();
}
else
{
mCurrUID = UID;
UpdateP2PTargetState();
}
}
void OnUserJoin(long UID, string NickName)
{
AddLog($"[User]用户{NickName}上线");
AddChat($"[{NickName}]上线");
UpdateOnlineUser();
}
void OnUserLeave(long UID, string NickName)
{
AddLog($"[User]用户{UID}下线");
AddChat($"[{NickName}]离开");
UpdateOnlineUser();
}
void OnChatMsg(string str1, string str2)
{
AddLog($"[Chat]{str1}:{str2}");
AddChat($"[{str1}]: {str2}");
}
void OnP2PChatMsg(long uid, string str2)
{
AddLog($"[P2PChatMsg]{uid}:{str2}");
}
void OnFileConfirmChoice(int TaskID)
{
DialogResult MsgBoxResult;//设置对话框的返回值
MsgBoxResult = System.Windows.Forms.MessageBox.Show("是否接受文件?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2);//定义对话框的按钮式样
if (MsgBoxResult.ToString() == "Yes")//如果对话框的返回值是YES按"Y"按钮)
{
//选择了Yes继续
App.p2pFile.receiver_AgreeRecvFile(TaskID, 1);
}
if (MsgBoxResult.ToString() == "No")//如果对话框的返回值是NO按"N"按钮)
{
//选择了No
App.p2pFile.receiver_AgreeRecvFile(TaskID, 0);
}
}
void OnFilePushStart(long TaskID)
{
AddLog($"[P2PFile]文件传输开始");
}
#endregion
#region
#endregion
void AddChat(string str)
{
this.BeginInvoke(new DSetStr(AddChat_delegate), str);
}
void AddChat_delegate(string str)
{
this.listBox_AllChatMsg.Items.Add(str);
while (listbox_log.Items.Count > ChatShowLimit)
{
listbox_log.Items.RemoveAt(listbox_log.Items.Count - ChatShowLimit);
}
}
void UpdateOnlineUser()
{
this.BeginInvoke(new DSetVoid(UpdateOnlineUser_delegate));
}
Dictionary<long, long> DictUserShowIdx2UID = new Dictionary<long, long>();
void UpdateOnlineUser_delegate()
{
DictUserShowIdx2UID.Clear();
UserInfo[] ulist = App.userMgr.GetUserInfo();
Console.WriteLine("User总数" + ulist.Length);
this.listBox_OnlineUser.Items.Clear();
for (int i = 0; i < ulist.Length; i++)
{
DictUserShowIdx2UID[i] = ulist[i].UID;
this.listBox_OnlineUser.Items.Add(ulist[i].NickName);
}
}
void UpdateP2PTargetState()
{
this.BeginInvoke(new DSetVoid(UpdateP2PTargetState_delegate));
}
void UpdateP2PTargetState_delegate()
{
bool bCanUse = false;
string StateName = "";
if (mCurrUID <= 0)
{
this.label_P2P_TargetUName.Text = "未选择";
}
else
{
App.clientMgr.GetP2PTargetState(mCurrUID, out E_P2P_STATE state);
switch (state)
{
case E_P2P_STATE.None:
StateName = "未连接";
break;
case E_P2P_STATE.Doing:
StateName = "打洞中……";
break;
case E_P2P_STATE.Fail:
StateName = "打洞失败";
break;
case E_P2P_STATE.Success:
StateName = "隧道已连通";
bCanUse = true;
break;
}
}
this.label_P2P_State.Text = StateName;
if (bCanUse)
{
btn_P2P_SendFile.Enabled = true;
btn_P2P_ChangeSendFile.Enabled = true;
}
else
{
btn_P2P_SendFile.Enabled = false;
btn_P2P_ChangeSendFile.Enabled = false;
}
}
#region
static Queue<string> logQueue = new Queue<string>();
public static void AddLog(string val)
{
logQueue.Enqueue($"{DateTime.Now.ToString("HH:mm:ss")}> {val}");
}
private void TimeEvent_Log(object source, System.Timers.ElapsedEventArgs e)
{
List<string> input = new List<string>();
while (logQueue.Count > 0)
{
string str = logQueue.Dequeue();
input.Add(str);
}
if (input.Count < 1)
return;
this.BeginInvoke(new DSetLogStr(ShowLog), input);
}
void ShowLog(List<string> value)
{
foreach (var v in value)
{
this.listbox_log.Items.Add(v);
}
while (listbox_log.Items.Count > LogLimit)
{
listbox_log.Items.RemoveAt(listbox_log.Items.Count - LogLimit);
}
//TODO 最下方
//listbox_log.ScrollIntoView(listbox_log.Items[listbox_log.Items.Count - 1]);
}
#endregion
private void btn_chatsend_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(textBox_chatsendmsg.Text))
return;
App.chat.SendChatMsg(textBox_chatsendmsg.Text);
textBox_chatsendmsg.Text = string.Empty;
}
private void listBox_OnlineUser_DoubleClick(object sender, EventArgs e)
{
ListBox listBox = sender as ListBox;
if (listBox == null)
return;
// 当ListBox有选中项时
if (listBox.SelectedIndex <= -1)
return;
if (!DictUserShowIdx2UID.ContainsKey(listBox.SelectedIndex))
return;
long Uid = DictUserShowIdx2UID[listBox.SelectedIndex];
if (Uid == App.userMgr.MainPlayer.UID)
{
MessageBox.Show("不能和自己建立P2P隧道");
return;
}
if (mCurrUID == Uid)
return;
UserInfo uinfo = App.userMgr.GetUserByUid(Uid);
//已有
if (App.clientMgr.GetP2PTargetState(Uid, out E_P2P_STATE state))
{
mCurrUID = Uid;
this.label_P2P_TargetUName.Text = uinfo.NickName;
this.tabControl_Main.SelectedIndex = 1;
UpdateP2PTargetState();
}
else
{
if (uinfo != null)
{
mCurrUID = Uid;
this.label_P2P_TargetUName.Text = uinfo.NickName;
this.tabControl_Main.SelectedIndex = 1;
App.p2ptcp.SendDoTunnel(Convert.ToInt64(uinfo.UID));
}
}
}
private void btn_P2P_ChangeSendFile_Click(object sender, EventArgs e)
{
// 1. 打开文件管理器选择文件
OpenFileDialog openFileDialog1 = new OpenFileDialog(); //显示选择文件对话框
openFileDialog1.InitialDirectory = "c:\\";
openFileDialog1.Filter = "All files (*.*)|*.*"; //所有的文件格式
openFileDialog1.FilterIndex = 2;
openFileDialog1.RestoreDirectory = true;
// 2. 查看可执行文件路径
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
this.label_P2P_SendFilePath.Text = openFileDialog1.FileName;
}
}
private void btn_P2P_SendFile_Click(object sender, EventArgs e)
{
string filePath = this.label_P2P_SendFilePath.Text;
if (string.IsNullOrEmpty(filePath))
{
MessageBox.Show("未选择文件");
return;
}
if (!File.Exists(filePath))
{
MessageBox.Show("选择的文件不存在");
return;
}
App.p2pFile.sender_FilePushConfirmToTarget(mCurrUID, filePath);
}
}
}

120
Client-GUI/MainUI.resx Normal file
View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

28
Client-GUI/Program.cs Normal file
View File

@ -0,0 +1,28 @@
using ClientCore;
namespace Client_GUI
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
LoginUI loginUI = new LoginUI();
loginUI.ShowDialog();
if (!StaticComm.bClickLogin)
{
Application.Exit();
return;
}
Application.Run(new MainUI());
}
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net8.0-windows\publish\win-x86\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0-windows</TargetFramework>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
</Project>

13
Client-GUI/StaticComm.cs Normal file
View File

@ -0,0 +1,13 @@
using ClientCore;
using ClientCore.Event;
namespace Client_GUI
{
internal class StaticComm
{
public static bool bClickLogin = false;
public static string LoginName;
}
}

View File

@ -26,22 +26,33 @@ namespace ClientCore
public static AppChat chat; public static AppChat chat;
public static UserMgr userMgr; public static UserMgr userMgr;
public static void Init(string IP, int port, int tcptunnelport) static string mIP;
static int mPort;
public static void Init(string IP, int port, int tcptunnelport, LogManager.OnLogHandler onLog = null)
{ {
mIP = IP;
mPort = port;
log = new LogManager(); log = new LogManager();
if (onLog != null)
LogManager.OnLog += onLog;
login = new AppLogin(); login = new AppLogin();
chat = new AppChat(); chat = new AppChat();
userMgr = new UserMgr(); userMgr = new UserMgr();
clientMgr = new ClientManager(); clientMgr = new ClientManager();
clientMgr.SetIpData(IP, port, tcptunnelport); clientMgr.SetIpData(mIP, mPort, tcptunnelport);
p2ptcp = new P2PTcp(); p2ptcp = new P2PTcp();
p2pChat = new P2PChat(); p2pChat = new P2PChat();
p2pFile = new P2PFile(); p2pFile = new P2PFile();
networkMain = new NetworkHelper(Enum.ServerType.MainServer); networkMain = new NetworkHelper(Enum.ServerType.MainServer);
networkMain.Init(IP, port);
//networkTcp2S = new NetworkHelper(Enum.ServerType.TcpTunnelServer); //networkTcp2S = new NetworkHelper(Enum.ServerType.TcpTunnelServer);
//networkTcp2S.Init(IP, tcptunnelport); //networkTcp2S.Init(IP, tcptunnelport);
} }
public static bool Connect()
{
return networkMain.Init(mIP, mPort);
}
} }

View File

@ -13,4 +13,12 @@ namespace ClientCore.Enum
TcpP2PTarget TcpP2PTarget
} }
public enum E_P2P_STATE
{
None = 0,
Doing,//
Fail,//
Success,//进行成功
}
} }

View File

@ -17,11 +17,21 @@ namespace ClientCore.Event
//打洞流程 //打洞流程
TcpTunnelHelloResp, TcpTunnelHelloResp,
TcpTunnelP2PStateUpdate,//打洞状态
//连接管理 //连接管理
OnSocketConnect, OnSocketConnect,
OnSocketDisconnect, OnSocketDisconnect,
//文件传输 //文件传输
/// <summary>
/// 同意传输
/// </summary>
OnFileConfirmChoice,
OnFilePushStart,
OnFilePushPr,
OnFilePushEnd,
} }
} }

View File

@ -42,12 +42,12 @@ namespace ClientCore.Manager
{ {
if (IsOk) if (IsOk)
{ {
Console.WriteLine("MainServer连接成功"); App.log.Info("MainServer连接成功");
} }
else else
{ {
Console.WriteLine("MainServer连接失败"); App.log.Info("MainServer连接失败");
} }
} }
@ -57,12 +57,12 @@ namespace ClientCore.Manager
bTryReConTcpTunnelServer = false; bTryReConTcpTunnelServer = false;
if (IsOk) if (IsOk)
{ {
Console.WriteLine("TcpTunnelServer连接成功"); App.log.Info("TcpTunnelServer连接成功");
App.p2ptcp.SendHellToSev(); App.p2ptcp.SendHellToSev();
} }
else else
{ {
Console.WriteLine("TcpTunnelServer连接失败"); App.log.Info("TcpTunnelServer连接失败");
} }
} }
break; break;
@ -70,7 +70,7 @@ namespace ClientCore.Manager
{ {
if (IsOk) if (IsOk)
{ {
Console.WriteLine("TcpP2PTarget连接成功"); App.log.Info("TcpP2PTarget连接成功");
} }
} }
break; break;
@ -82,20 +82,20 @@ namespace ClientCore.Manager
{ {
case ServerType.MainServer: case ServerType.MainServer:
{ {
Console.WriteLine("MainServer连接断开"); App.log.Info("MainServer连接断开");
} }
break; break;
case ServerType.TcpTunnelServer: case ServerType.TcpTunnelServer:
{ {
Console.WriteLine("TcpTunnelServer连接断开"); App.log.Info("TcpTunnelServer连接断开");
Console.WriteLine("TcpTunnelServer,尝试重连"); App.log.Info("TcpTunnelServer,尝试重连");
ReConnectTcpTunnelServer(); ReConnectTcpTunnelServer();
} }
break; break;
case ServerType.TcpP2PTarget: case ServerType.TcpP2PTarget:
{ {
Console.WriteLine("TcpP2PTarget连接断开"); App.log.Info("TcpP2PTarget连接断开");
RemoveTargetSocket(uid); RemoveP2PTargetSocket(uid);
} }
break; break;
} }
@ -132,12 +132,31 @@ namespace ClientCore.Manager
#region P2PTarget #region P2PTarget
void AddTargetSocket(NetworkP2PHelper targetSocket) public void AddP2PTargetSocket(NetworkP2PHelper targetSocket)
{ {
DictUID2TcpTaret[targetSocket.mUID] = targetSocket; DictUID2TcpTaret[targetSocket.mUID] = targetSocket;
} }
void RemoveTargetSocket(long UID) public void SetP2PTargetState(long UID,E_P2P_STATE state)
{
if (!DictUID2TcpTaret.ContainsKey(UID))
return;
DictUID2TcpTaret[UID].e_P2P_STATE = state;
}
public bool GetP2PTargetState(long UID, out E_P2P_STATE state)
{
if (DictUID2TcpTaret.ContainsKey(UID))
{
state = DictUID2TcpTaret[UID].e_P2P_STATE;
return true;
}
state = E_P2P_STATE.None;
return false;
}
void RemoveP2PTargetSocket(long UID)
{ {
if (DictUID2TcpTaret.ContainsKey(UID)) if (DictUID2TcpTaret.ContainsKey(UID))
{ {
@ -183,9 +202,39 @@ namespace ClientCore.Manager
public void ConnectTCPTargetP2PThread(object obj) public void ConnectTCPTargetP2PThread(object obj)
{ {
Thread.Sleep(1000); Thread.Sleep(1000);
int userBindPort = LastlocalPort; int userBindPort = LastlocalPort;
Protobuf_TcpTunnel_DoTunnel_RESP msg = (Protobuf_TcpTunnel_DoTunnel_RESP)obj; Protobuf_TcpTunnel_DoTunnel_RESP msg = (Protobuf_TcpTunnel_DoTunnel_RESP)obj;
Console.WriteLine("LocalEndPoint Port" + userBindPort); App.log.Info("LocalEndPoint Port" + userBindPort);
NetworkP2PHelper targetSocket = GetTargetSocket(msg.TargetUID);
targetSocket.Init(true, userBindPort);
//尝试5次连接
for (int j = 0; j < 5; j++)
{
try
{
if (!targetSocket.Connect(msg.OtherIP, msg.OtherPort))
continue;
App.log.Info($"Connect成功 目标用户UID:{msg.TargetUID},{msg.OtherIP},{msg.OtherPort}");
//AddTargetSocket(targetSocket);
SetP2PTargetState(msg.TargetUID, E_P2P_STATE.Success);
EventSystem.Instance.PostEvent(EEvent.TcpTunnelP2PStateUpdate, msg.TargetUID);
return;
}
catch (Exception)
{
App.log.Info("Connect失败");
}
}
SetP2PTargetState(msg.TargetUID, E_P2P_STATE.Fail);
EventSystem.Instance.PostEvent(EEvent.TcpTunnelP2PStateUpdate, msg.TargetUID);
/*
int userBindPort = LastlocalPort;
Protobuf_TcpTunnel_DoTunnel_RESP msg = (Protobuf_TcpTunnel_DoTunnel_RESP)obj;
App.log.Info("LocalEndPoint Port" + userBindPort);
NetworkP2PHelper targetSocket = new NetworkP2PHelper(Enum.ServerType.TcpP2PTarget, msg.TargetUID); NetworkP2PHelper targetSocket = new NetworkP2PHelper(Enum.ServerType.TcpP2PTarget, msg.TargetUID);
targetSocket.Init(msg.OtherIP, msg.OtherPort, true, userBindPort); targetSocket.Init(msg.OtherIP, msg.OtherPort, true, userBindPort);
//尝试5次连接 //尝试5次连接
@ -194,15 +243,18 @@ namespace ClientCore.Manager
try try
{ {
targetSocket.Connect(msg.OtherIP, msg.OtherPort); targetSocket.Connect(msg.OtherIP, msg.OtherPort);
Console.WriteLine("Connect成功{0},{1}", msg.OtherIP, msg.OtherPort); App.log.Info($"Connect成功 目标用户UID:{msg.TargetUID},{msg.OtherIP},{msg.OtherPort}");
AddTargetSocket(targetSocket); AddTargetSocket(targetSocket);
break; return;
} }
catch (Exception) catch (Exception)
{ {
Console.WriteLine("Connect失败"); App.log.Info("Connect失败");
} }
} }
EventSystem.Instance.PostEvent(EEvent.TcpTunnelFail, msg.TargetUID);
*/
} }
#endregion #endregion

View File

@ -2,19 +2,48 @@
{ {
public class LogManager public class LogManager
{ {
public enum E_LogType : byte
{
Info = 0,
Debug = 1,
Warning = 2,
Error = 3,
}
/// <summary>
/// 日志
/// </summary>
/// <param name="sk"></param>
public delegate void OnLogHandler(int debuglv, string msg);
/// <summary>
/// 内部输出
/// </summary>
public static event OnLogHandler OnLog;
public void Info(string str)
{
Log(E_LogType.Info, str);
}
public void Debug(string str) public void Debug(string str)
{ {
Console.WriteLine(str); Log(E_LogType.Debug, str);
} }
public void Warning(string str) public void Warning(string str)
{ {
Console.WriteLine(str); Log(E_LogType.Warning, str);
} }
public void Error(string str) public void Error(string str)
{ {
Console.WriteLine(str); Log(E_LogType.Error, str);
}
public void Log(E_LogType logtype, string str)
{
OnLog?.Invoke((int)logtype, str);
} }
} }
} }

View File

@ -1,5 +1,6 @@
using AxibugProtobuf; using AxibugProtobuf;
using ClientCore.Common; using ClientCore.Common;
using ClientCore.Enum;
using ClientCore.Event; using ClientCore.Event;
using ClientCore.Network; using ClientCore.Network;
using Google.Protobuf; using Google.Protobuf;
@ -16,7 +17,7 @@ namespace ClientCore.Manager
public int cPackSize = 5 * 1024; public int cPackSize = 5 * 1024;
public int WaitSendPackCount = 100; public int WaitSendPackCount = 100;
//是否自动同意接收 //是否自动同意接收
public bool bAutoRecv = true; public bool bAutoRecv = false;
public P2PFile() public P2PFile()
{ {
//发送者协议注册 //发送者协议注册
@ -159,7 +160,11 @@ namespace ClientCore.Manager
//开辟临时缓存内存 //开辟临时缓存内存
byte[] byteArrayRead = new byte[cPackSize]; // 1字节*1024 = 1k 1k*1024 = 1M内存 byte[] byteArrayRead = new byte[cPackSize]; // 1字节*1024 = 1k 1k*1024 = 1M内存
App.log.Debug($"[文件发送者]开始发送文件!!"); App.log.Info($"[文件发送者]开始发送文件!!");
//开始发送文件事件
EventSystem.Instance.PostEvent(EEvent.OnFilePushStart, TaskID);
//通过死缓存去读文本中的内容 //通过死缓存去读文本中的内容
while (true) while (true)
{ {
@ -187,9 +192,11 @@ namespace ClientCore.Manager
//既然是死循环 那么什么时候我们停止读取文本内容 我们知道文本最后一行的大小肯定是小于缓存内存大小的 //既然是死循环 那么什么时候我们停止读取文本内容 我们知道文本最后一行的大小肯定是小于缓存内存大小的
if (readCount < byteArrayRead.Length) if (readCount < byteArrayRead.Length)
{ {
App.log.Debug($"[文件发送者]文件发送完毕!! task.CurrIndex->{task.CurrIndex}"); App.log.Info($"[文件发送者]文件发送完毕!! task.CurrIndex->{task.CurrIndex}");
DictTaskID2Task_SendDo.Remove(TaskID); DictTaskID2Task_SendDo.Remove(TaskID);
fsRead.Close(); fsRead.Close();
//开始发送文件事件
EventSystem.Instance.PostEvent(EEvent.OnFilePushEnd, TaskID);
break; //结束循环 break; //结束循环
} }
} }
@ -279,7 +286,11 @@ namespace ClientCore.Manager
//自动接收文件 //自动接收文件
if (bAutoRecv) if (bAutoRecv)
{ {
receiver_AgreeRecvFile(task.TaskID); receiver_AgreeRecvFile(task.TaskID, 1);
}
else
{
EventSystem.Instance.PostEvent(EEvent.OnFileConfirmChoice, task.TaskID);
} }
} }
@ -287,7 +298,7 @@ namespace ClientCore.Manager
/// 同意文件传输 /// 同意文件传输
/// </summary> /// </summary>
/// <param name="TaskID"></param> /// <param name="TaskID"></param>
public void receiver_AgreeRecvFile(int TaskID) public void receiver_AgreeRecvFile(int TaskID, int bAgree)
{ {
if (DictTaskID2Task_RecvReady.ContainsKey(TaskID)) if (DictTaskID2Task_RecvReady.ContainsKey(TaskID))
{ {
@ -295,8 +306,8 @@ namespace ClientCore.Manager
DictTaskID2Task_RecvDo[TaskID] = task; DictTaskID2Task_RecvDo[TaskID] = task;
DictTaskID2Task_RecvReady.Remove(TaskID); DictTaskID2Task_RecvReady.Remove(TaskID);
receiver_StartRecvFile(TaskID); receiver_StartRecvFile(TaskID);
App.log.Debug("[文件接收者]:发送同意接收"); App.log.Info("[文件接收者]:发送同意接收");
SendFilePushConfirm_Resp(task.fromUID, task.TaskID, 1); SendFilePushConfirm_Resp(task.fromUID, task.TaskID, bAgree);
} }
} }
@ -317,7 +328,7 @@ namespace ClientCore.Manager
Thread thread = new Thread(receiver_StartRecvFileThread); Thread thread = new Thread(receiver_StartRecvFileThread);
thread.IsBackground = true; thread.IsBackground = true;
thread.Start(TaskID); thread.Start(TaskID);
App.log.Debug("[文件接收者]:准备接收线程"); App.log.Info("[文件接收者]:准备接收线程");
} }
/// <summary> /// <summary>
@ -373,7 +384,7 @@ namespace ClientCore.Manager
if (task.CurrIndex + 1 >= task.PackCount) if (task.CurrIndex + 1 >= task.PackCount)
{ {
//文件接收完毕 //文件接收完毕
App.log.Debug($"[文件接收者]文件接收完毕!! task.CurrIndex->{task.CurrIndex} task.PackCount->{task.PackCount}"); App.log.Info($"[文件接收者]文件接收完毕!! task.CurrIndex->{task.CurrIndex} task.PackCount->{task.PackCount}");
//删除进行中字典 //删除进行中字典
DictTaskID2Task_RecvDo.Remove(TaskID); DictTaskID2Task_RecvDo.Remove(TaskID);
//删除进行中数据队列 //删除进行中数据队列

View File

@ -1,5 +1,6 @@
using AxibugProtobuf; using AxibugProtobuf;
using ClientCore.Common; using ClientCore.Common;
using ClientCore.Enum;
using ClientCore.Event; using ClientCore.Event;
using ClientCore.Network; using ClientCore.Network;
using System.Net; using System.Net;
@ -38,7 +39,7 @@ namespace ClientCore.Manager
Protobuf_TcpTunnel_DoTunnel msg = new Protobuf_TcpTunnel_DoTunnel() Protobuf_TcpTunnel_DoTunnel msg = new Protobuf_TcpTunnel_DoTunnel()
{ {
UID = App.userMgr.MainPlayer.UID, UID = App.userMgr.MainPlayer.UID,
TargetUID = targetUID TargetUID = targetUID,
}; };
App.networkTcp2S.SendToServer((int)CommandID.CmdTcptunnelDo, ProtoBufHelper.Serizlize(msg)); App.networkTcp2S.SendToServer((int)CommandID.CmdTcptunnelDo, ProtoBufHelper.Serizlize(msg));
} }
@ -47,13 +48,22 @@ namespace ClientCore.Manager
{ {
Protobuf_TcpTunnel_DoTunnel_RESP msg = ProtoBufHelper.DeSerizlize<Protobuf_TcpTunnel_DoTunnel_RESP>(reqData); Protobuf_TcpTunnel_DoTunnel_RESP msg = ProtoBufHelper.DeSerizlize<Protobuf_TcpTunnel_DoTunnel_RESP>(reqData);
//TODO 打洞 //TODO 打洞
Console.WriteLine($"打洞目标信息 {msg.TargetUID} {msg.OtherIP} {msg.OtherPort}"); App.log.Info($"打洞目标信息 {msg.TargetUID} {msg.OtherIP} {msg.OtherPort}");
int localPort = ((IPEndPoint)App.networkTcp2S.GetClientSocket().LocalEndPoint).Port; int localPort = ((IPEndPoint)App.networkTcp2S.GetClientSocket().LocalEndPoint).Port;
NetworkP2PHelper targetSocket = new NetworkP2PHelper(Enum.ServerType.TcpP2PTarget, msg.TargetUID);
App.clientMgr.AddP2PTargetSocket(targetSocket);
App.clientMgr.SetP2PTargetState(msg.TargetUID, E_P2P_STATE.Doing);
//断开连接 //断开连接
App.networkTcp2S.CloseConntect(); App.networkTcp2S.CloseConntect();
//TODO重新连接打洞服务器
//进行打洞事件
EventSystem.Instance.PostEvent(EEvent.TcpTunnelP2PStateUpdate, msg.TargetUID);
//P2P连接尝试
App.clientMgr.ConnectTCPTargetP2P(msg, localPort); App.clientMgr.ConnectTCPTargetP2P(msg, localPort);
} }

View File

@ -73,6 +73,8 @@ namespace ClientCore.Manager
State = minfo.State, State = minfo.State,
NickName = minfo.NickName, NickName = minfo.NickName,
}; };
//抛出用户加入事件
EventSystem.Instance.PostEvent(EEvent.UserJoin, minfo.UID, minfo.NickName);
} }
else else
{ {
@ -80,17 +82,17 @@ namespace ClientCore.Manager
DictUID2User[minfo.UID].NickName= minfo.NickName; DictUID2User[minfo.UID].NickName= minfo.NickName;
} }
} }
//抛出用户加入事件
EventSystem.Instance.PostEvent(EEvent.UserJoin, minfo.UID);
} }
public void RemoveUser(long UID) public void RemoveUser(long UID)
{ {
bool bflag = false; bool bflag = false;
string UName = "";
lock (DictUID2User) lock (DictUID2User)
{ {
if (DictUID2User.ContainsKey(UID)) if (DictUID2User.ContainsKey(UID))
{ {
UName = DictUID2User[UID].NickName;
DictUID2User.Remove(UID); DictUID2User.Remove(UID);
bflag = true; bflag = true;
} }
@ -98,7 +100,18 @@ namespace ClientCore.Manager
if (bflag) if (bflag)
{ {
//抛出用户离开事件 //抛出用户离开事件
EventSystem.Instance.PostEvent(EEvent.UserLeave, UID); EventSystem.Instance.PostEvent(EEvent.UserLeave, UID, UName);
}
}
public UserInfo GetUserByUid(long UID)
{
lock (DictUID2User)
{
if (DictUID2User.ContainsKey(UID))
{
return DictUID2User[UID];
}
return null;
} }
} }

View File

@ -51,7 +51,8 @@ namespace ClientCore.Network
//用于Unity内的输出 //用于Unity内的输出
//Debug.Log("NetCoreDebug >> "+str); //Debug.Log("NetCoreDebug >> "+str);
Console.WriteLine("NetCoreDebug >> " + str); //App.log.Info("NetCoreDebug >> " + str);
App.log.Debug(str);
} }
/// <summary> /// <summary>

View File

@ -17,6 +17,7 @@ namespace ClientCore.Network
{ {
ServerType mServerType; ServerType mServerType;
public long mUID { get; private set; } = -1; public long mUID { get; private set; } = -1;
public E_P2P_STATE e_P2P_STATE = E_P2P_STATE.None;
public NetworkP2PHelper(ServerType serverType,long UID = -1) public NetworkP2PHelper(ServerType serverType,long UID = -1)
{ {
//指定接收服务器数据事件 //指定接收服务器数据事件
@ -27,6 +28,7 @@ namespace ClientCore.Network
OnLogOut += NetworkDeBugLog; OnLogOut += NetworkDeBugLog;
OnConnected += NetworkConnected; OnConnected += NetworkConnected;
mServerType = serverType; mServerType = serverType;
e_P2P_STATE = E_P2P_STATE.None;
mUID = UID; mUID = UID;
} }
@ -51,7 +53,7 @@ namespace ClientCore.Network
//用于Unity内的输出 //用于Unity内的输出
//Debug.Log("NetCoreDebug >> "+str); //Debug.Log("NetCoreDebug >> "+str);
Console.WriteLine("NetCoreDebug >> " + str); App.log.Info("NetCoreDebug >> " + str);
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net8.0\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
</PropertyGroup>
</Project>

View File

@ -18,7 +18,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientCore", "ClientCore\Cl
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Protobuf", "Protobuf\Protobuf.csproj", "{07B4FB42-CA26-4C68-AFE8-47A50BABFB64}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Protobuf", "Protobuf\Protobuf.csproj", "{07B4FB42-CA26-4C68-AFE8-47A50BABFB64}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client-Cli", "Client-Cli\Client-Cli.csproj", "{A363C9A2-FBFB-40D7-8B34-2BE384C49A29}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client-Cli", "Client-Cli\Client-Cli.csproj", "{A363C9A2-FBFB-40D7-8B34-2BE384C49A29}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client-GUI", "Client-GUI\Client-GUI.csproj", "{D2C54B2E-08F7-4F9B-909F-A62ED6B60CCB}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -46,6 +48,10 @@ Global
{A363C9A2-FBFB-40D7-8B34-2BE384C49A29}.Debug|Any CPU.Build.0 = Debug|Any CPU {A363C9A2-FBFB-40D7-8B34-2BE384C49A29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A363C9A2-FBFB-40D7-8B34-2BE384C49A29}.Release|Any CPU.ActiveCfg = Release|Any CPU {A363C9A2-FBFB-40D7-8B34-2BE384C49A29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A363C9A2-FBFB-40D7-8B34-2BE384C49A29}.Release|Any CPU.Build.0 = Release|Any CPU {A363C9A2-FBFB-40D7-8B34-2BE384C49A29}.Release|Any CPU.Build.0 = Release|Any CPU
{D2C54B2E-08F7-4F9B-909F-A62ED6B60CCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D2C54B2E-08F7-4F9B-909F-A62ED6B60CCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D2C54B2E-08F7-4F9B-909F-A62ED6B60CCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D2C54B2E-08F7-4F9B-909F-A62ED6B60CCB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

Binary file not shown.

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net8.0\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
</PropertyGroup>
</Project>