initial commit for client with c#

This commit is contained in:
Mattuy
2018-12-15 00:29:41 +08:00
parent a6190a9bb1
commit 145c242d4b
30 changed files with 1836 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Collections.Concurrent;
using Newtonsoft.Json.Linq;
namespace Interact
{
//连接中断
public delegate void DisconnectHandler(Exception ex);
//登录反馈到达
public delegate void LoginDoneHandler(JObject head, User user);
public static partial class StormClient
{
//连接中断事件
public static event DisconnectHandler OnDisconnect;
//登录结果处理事件
public static event LoginDoneHandler OnLoginDone;
//TCP客户端
private static TcpClient tcpClient;
//消息发送队列
private static BlockingCollection<Packet> sendQueue;
//数据读取线程
private static Thread readLoopThread;
//数据发送线程
private static Thread sendLoopThread;
static StormClient()
{
tcpClient = new TcpClient();
sendQueue = new BlockingCollection<Packet>();
}
/// <summary>
/// 初始化客户端,连接服务器。如果成功则启动数据接收和发送线程
/// </summary>
/// <returns>如果已连接到服务器返回true,否则返回false。</returns>
public static bool Initialize()
{
if (StormClient.tcpClient.Connected)
return true;
StormClient.tcpClient.Connect(Interact.Properties.Resources.RemoteServerAddr, int.Parse(Interact.Properties.Resources.RemoteServerPort));
if (!StormClient.tcpClient.Connected)
return false;
if (sendLoopThread != null)
sendLoopThread.Abort();
if (readLoopThread != null)
readLoopThread.Abort();
//启动数据收发线程
sendLoopThread = new Thread(new ThreadStart(SendLoop));
readLoopThread = new Thread(new ThreadStart(ReadLoop));
//设置为后台线程
sendLoopThread.IsBackground = readLoopThread.IsBackground = true;
sendLoopThread.Start();
readLoopThread.Start();
return true;
}
/// <summary>
/// 登录。此操作为异步操作,完成后产生OnLoginDone事件。
/// </summary>
/// <returns>成功将请求加入发送队列返回true,否则返回false。</returns>
/// <param name="user">用户名</param>
/// <param name="password">密码</param>
/// <param name="callback">数据发送完成回调函数。注意,结果处理回调请设置OnLoginDone事件</param>
public static bool RequestLogin(string user, string password, Action<BaseHead> callback = null)
{
JsonLogInfoHead logInfo = new JsonLogInfoHead()
{
Token = "",
Operation = Operations.Login.ToString(),
User = user,
Pwd = password
};
Packet packet = new Packet
{
Head = logInfo,
Data = null,
CallBack = callback
};
return Send(packet);
}
}
}
+45
View File
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Interact
{
//对数据的处理相关内容
public partial class StormClient
{
//根据包头信息处理数据
private static void HandleAll(byte[] headData, byte[] data)
{
string head = Encoding.UTF8.GetString(headData);
JObject jsonObj = (JObject)JsonConvert.DeserializeObject(head);
switch ((Operations)Enum.Parse(typeof(Operations), jsonObj[Strings.Operation].ToString()))
{
case Operations.Login:
HandleLoginResult(jsonObj, data);
break;
case Operations.Panic:
case Operations.TransMessage:
case Operations.SendMessage:
case Operations.Ping:
case Operations.Logout:
case Operations.Offline:
case Operations.GetUserList:
case Operations.UpdateUserInfo:
case Operations.GetUserPhoto:
default:
break;
}
}
//处理登录反馈消息
private static void HandleLoginResult(JObject head, byte[] data)
{
User user = null;
if (head[Strings.Error].ToString() == "")
user = JsonConvert.DeserializeObject<User>(Encoding.UTF8.GetString(data)) as User;
OnLoginDone?.Invoke(head, user);
}
}
}
+68
View File
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{CEF054AF-95B7-4B88-934E-02705FEEA554}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Interact</RootNamespace>
<AssemblyName>Interact</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>.\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Client.cs" />
<Compile Include="Handle.cs" />
<Compile Include="JsonObject.cs" />
<Compile Include="Message.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Read.cs" />
<Compile Include="Send.cs" />
<Compile Include="Strings.cs" />
<Compile Include="User.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//此文件定义Json序列化与反序列化时使用的数据结构,仅内部使用
namespace Interact
{
//登录信息包头
internal class JsonLogInfoHead : BaseHead
{
public string User;
public string Pwd;
}
}
+57
View File
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Interact
{
//定义一则用户消息
public class Message
{
public static UInt32 MaxHeadLength; //最大包头长度
public static UInt32 MaxMessageLength; //最大消息长度
public DateTime When { get; } //服务器接收时间
public User From; //发送者
public string Content; //消息内容
}
//包头基本结构
public class BaseHead
{
//请求ID,由发送方定义的随机字符串。如果接受方有返回数据应包含相同的Token
public string Token;
//操作类型,决定数据的其他内容
public string Operation;
}
// 接受方返回的执行结果反馈包头,包含错误文本
public class ResultHead : BaseHead
{
//错误文本,为空则执行成功
public string Error;
}
//从服务器接收的原始数据封包
internal class Packet
{
public BaseHead Head; //包头
public byte[] Data; //包内容
public Action<BaseHead> CallBack; //数据发送完成的回调函数。注意,此时仅保证数据已发送,
//服务器可能并没有处理完成请求
}
//允许请求的操作列表。具体定义见设计文档
public enum Operations
{
Panic,
TransMessage,
SendMessage,
Ping,
Login,
Logout,
Offline,
GetUserList,
UpdateUserInfo,
GetUserPhoto
}
}
+36
View File
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Interact")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Interact")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("cef054af-95b7-4b88-934e-02705feea554")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
+81
View File
@@ -0,0 +1,81 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Interact.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Interact.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to localhost.
/// </summary>
internal static string RemoteServerAddr {
get {
return ResourceManager.GetString("RemoteServerAddr", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 3727.
/// </summary>
internal static string RemoteServerPort {
get {
return ResourceManager.GetString("RemoteServerPort", resourceCulture);
}
}
}
}
+126
View File
@@ -0,0 +1,126 @@
<?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>
<data name="RemoteServerAddr" xml:space="preserve">
<value>localhost</value>
</data>
<data name="RemoteServerPort" xml:space="preserve">
<value>3727</value>
</data>
</root>
+52
View File
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace Interact
{
//读取数据相关内容
public partial class StormClient
{
// 数据读取线程,从tcp连接读取数据
private static void ReadLoop()
{
NetworkStream stream = StormClient.tcpClient.GetStream();
try
{
while (tcpClient.Connected)
{
byte[] head = ReadDataWithLength(stream);
byte[] data = ReadDataWithLength(stream);
HandleAll(head, data);
}
}
catch (System.IO.IOException ex)
{
tcpClient.Close();
OnDisconnect(ex);
}
catch (ObjectDisposedException ex)
{
tcpClient.Close();
OnDisconnect(ex);
}
}
// 从网络流读取数据
private static byte[] ReadDataWithLength(NetworkStream stream)
{
byte[] lenBuffer = new byte[4]; //数据长度
//获取数据长度
stream.Read(lenBuffer, 0, 4);
int length = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lenBuffer, 0));
if (length <= 0)
return null;
//读取数据
byte[] data = new byte[length];
stream.Read(data, 0, length);
return data;
}
}
}
+65
View File
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using System.Net;
using System.Net.Sockets;
namespace Interact
{
//发送数据相关内容
public partial class StormClient
{
// 消息写入线程,向tcp连接写入数据
private static void SendLoop()
{
NetworkStream stream = tcpClient.GetStream();
try
{
foreach (Packet packet in sendQueue.GetConsumingEnumerable())
{
//结束循环
if (!tcpClient.Connected)
break;
//向tcp连接写数据
byte[] lenBuffer; //数据长度缓存
byte[] headData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(packet.Head));
//写包头
lenBuffer = BitConverter.GetBytes((UInt32)IPAddress.HostToNetworkOrder(headData.Length));
stream.Write(lenBuffer, 0, 4);
stream.Write(headData, 0, headData.Length);
//写数据
int length = 0;
if (packet.Data != null)
length = packet.Data.Length;
lenBuffer = BitConverter.GetBytes((UInt32)IPAddress.HostToNetworkOrder(length));
stream.Write(lenBuffer, 0, 4);
if (length > 0)
stream.Write(packet.Data, 0, packet.Data.Length);
//发送完成回调
packet.CallBack?.Invoke(packet.Head);
}
}
catch (System.IO.IOException ex)
{
tcpClient.Close();
OnDisconnect?.Invoke(ex);
}
catch (ObjectDisposedException ex)
{
tcpClient.Close();
OnDisconnect?.Invoke(ex);
}
}
//发送数据
private static bool Send(Packet packet)
{
if (sendLoopThread == null || !sendLoopThread.IsAlive)
return false;
//加入数据发送队列等待
sendQueue.Add(packet);
return true;
}
}
}
+15
View File
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Interact
{
//字符串常量
public static class Strings
{
public const string Token = "Token";
public const string Operation = "Operation";
public const string Error = "Error";
}
}
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Interact
{
//用户
public class User
{
public static User Me; //当前用户
public string Name; //用户名
public string NickName; //昵称
public string Motto; //个性签名
public UserGroup Group; //用户组
}
//用户类型
public enum UserGroup
{
User, //普通用户
Vip, //会员
Admin, //管理员
Group //群组
}
}