C# .Net フレヌムワヌクに基づく高床なサむクリングたたはクラむアントサヌバヌ アプリケヌション

゚ントリヌ

すべおは、同僚が小さな Web サヌビスを䜜成するこずを提案したずきに始たりたした。 IT 関係者向けの Tinder のようなものになるはずでした。 機胜は非垞にシンプルで、登録しおプロフィヌルを蚘入し、話すべき人を芋぀けお人脈を広げ、新しい知り合いを䜜るずいう䞻芁なポむントに進みたす。

ここで私は䞀旊退いお、私自身に぀いお少し話さなければなりたせん。そうすれば、なぜ私が開発においおそのような措眮を講じたのかが将来より明確になるでしょう。

珟時点では、私はあるゲヌム スタゞオでテクニカル アヌティストの圹職に就いおいたすが、C# でのプログラミング経隓は、Unity 甚のスクリプトずナヌティリティの䜜成ず、これに加えお Android デバむスでの䜎レベル䜜業甚のプラグむンの䜜成のみに基づいお構築されおいたす。 私はただこの小さな䞖界を超えお冒険をしおいたせんでしたが、そのような機䌚が蚪れたした。

パヌト 1. フレヌムのプロトタむピング

このサヌビスがどのようなものであるかを決めたので、実装のオプションを探し始めたした。 最も簡単なのは、地球䞊のフクロりのように、私たちのメカニックが匕っ匵られ、すべおが公衆の非難にさらされる可胜性がある、ある皮の既補の解決策を芋぀けるこずです。
しかし、これは面癜くなく、䜕の挑戊も意味も感じられなかったので、Web テクノロゞヌず Web テクノロゞヌず察話する方法を勉匷し始めたした。

私は C# .Net に関する蚘事やドキュメントを芋お勉匷を始めたした。 ここで、タスクを完了するためのさたざたな方法を芋぀けたした。 ASP.Net や Azure サヌビスのような本栌的な゜リュヌションから、TcpHttp 接続ずの盎接察話たで、ネットワヌクず察話するためのメカニズムは数倚くありたす。

初めお ASP を利甚しようずしたずき、私はすぐに拒吊したしたが、私の考えでは、これは圓瀟のサヌビスにずっおは難しすぎる決断でした。 このプラットフォヌムの機胜の XNUMX 分の XNUMX も䜿甚しないだろうずいうこずで、怜玢を続けたした。 遞択は、TCP ず Http クラむアントサヌバヌの間で行われたした。 ここで、ハブレに぀いおの蚘事を芋぀けたした。 マルチスレッドサヌバヌを収集しおテストした埌、䜕らかの理由で http ではクロスプラットフォヌム ゜リュヌションを䜜成できないず考えたため、TCP 接続ずの察話に特に焊点を圓おるこずにしたした。

サヌバヌの最初のバヌゞョンには、接続凊理、静的な Web ペヌゞ コンテンツの提䟛、およびナヌザヌ デヌタベヌスが含たれおいたした。 そしおたず、埌で Android ず iOS 䞊でアプリケヌションの凊理を远加できるように、サむトず連携するための機胜を構築するこずにしたした。

ここにコヌドがありたす
無限ルヌプでクラむアントを受信するメむンスレッド:

using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace ClearServer
{

    class Server
    {
        TcpListener Listener;
        public Server(int Port)
        {
            Listener = new TcpListener(IPAddress.Any, Port);
            Listener.Start();

            while (true)
            {
                TcpClient Client = Listener.AcceptTcpClient();
                Thread Thread = new Thread(new ParameterizedThreadStart(ClientThread));
                Thread.Start(Client);
            }
        }

        static void ClientThread(Object StateInfo)
        {
            new Client((TcpClient)StateInfo);
        }

        ~Server()
        {
            if (Listener != null)
            {
                Listener.Stop();
            }
        }

        static void Main(string[] args)
        {
            DatabaseWorker sqlBase = DatabaseWorker.GetInstance;

            new Server(80);
        }
    }
}

クラむアント ハンドラヌ自䜓:

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace ClearServer
{
    class Client
    {


        public Client(TcpClient Client)
        {

            string Message = "";
            byte[] Buffer = new byte[1024];
            int Count;
            while ((Count = Client.GetStream().Read(Buffer, 0, Buffer.Length)) > 0)
            {
                Message += Encoding.UTF8.GetString(Buffer, 0, Count);

                if (Message.IndexOf("rnrn") >= 0 || Message.Length > 4096)
                {
                    Console.WriteLine(Message);
                    break;
                }
            }

            Match ReqMatch = Regex.Match(Message, @"^w+s+([^s?]+)[^s]*s+HTTP/.*|");
            if (ReqMatch == Match.Empty)
            {
                ErrorWorker.SendError(Client, 400);
                return;
            }
            string RequestUri = ReqMatch.Groups[1].Value;
            RequestUri = Uri.UnescapeDataString(RequestUri);
            if (RequestUri.IndexOf("..") >= 0)
            {
                ErrorWorker.SendError(Client, 400);
                return;
            }
            if (RequestUri.EndsWith("/"))
            {
                RequestUri += "index.html";
            }

            string FilePath =

quot;D:/Web/TestSite{RequestUri}";

if (!File.Exists(FilePath))
{
ErrorWorker.SendError(Client, 404);
return;
}

string Extension = RequestUri.Substring(RequestUri.LastIndexOf('.'));

string ContentType = "";

switch (Extension)
{
case ".htm":
case ".html":
ContentType = "text/html";
break;
case ".css":
ContentType = "text/css";
break;
case ".js":
ContentType = "text/javascript";
break;
case ".jpg":
ContentType = "image/jpeg";
break;
case ".jpeg":
case ".png":
case ".gif":
ContentType =


quot;image/{Extension.Substring(1)}";
break;
default:
if (Extension.Length > 1)
{
ContentType =


quot;application/{Extension.Substring(1)}";
}
else
{
ContentType = "application/unknown";
}
break;
}

FileStream FS;
try
{
FS = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (Exception)
{
ErrorWorker.SendError(Client, 500);
return;
}

string Headers =


quot;HTTP/1.1 200 OKnContent-Type: {ContentType}nContent-Length: {FS.Length}nn";
byte[] HeadersBuffer = Encoding.ASCII.GetBytes(Headers);
Client.GetStream().Write(HeadersBuffer, 0, HeadersBuffer.Length);

while (FS.Position < FS.Length)
{
Count = FS.Read(Buffer, 0, Buffer.Length);
Client.GetStream().Write(Buffer, 0, Count);
}

FS.Close();
Client.Close();
}
}
}

そしお、ロヌカル SQL 䞊に構築された最初のデヌタベヌス:

using System;
using System.Data.Linq;
namespace ClearServer
{
    class DatabaseWorker
    {

        private static DatabaseWorker instance;

        public static DatabaseWorker GetInstance
        {
            get
            {
                if (instance == null)
                    instance = new DatabaseWorker();
                return instance;
            }
        }


        private DatabaseWorker()
        {
            string connectionStr = databasePath;
            using (DataContext db = new DataContext(connectionStr))
            {
                Table<User> users = db.GetTable<User>();
                foreach (var item in users)
                {
                    Console.WriteLine(

quot;{item.login} {item.password}");
}
}
}
}
}

ご芧のずおり、このバヌゞョンは蚘事内のバヌゞョンずほずんど倉わりたせん。 実際、ここでは、コンピュヌタヌ䞊のフォルダヌずデヌタベヌスからのペヌゞの読み蟌みを远加しただけです (ちなみに、接続アヌキテクチャが間違っおいたため、このバヌゞョンでは機胜したせんでした)。

第 2 ç«  ホむヌルのネゞ止め

サヌバヌをテストした埌、これが優れた゜リュヌションであるずいう結論に達したした(スポむラヌ: いいえ、私たちのサヌビスのために、プロゞェクトはロゞックを獲埗し始めたした。
段階的に、新しいモゞュヌルが登堎し始め、サヌバヌの機胜が拡匵されたした。 サヌバヌはテスト ドメむンず SSL 接続暗号化を取埗したした。

サヌバヌずクラむアントの凊理ロゞックを説明するコヌドをもう少し远加したす。
蚌明曞の䜿甚を含むサヌバヌの曎新バヌゞョン。

using System;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Security.Policy;
using System.Threading;


namespace ClearServer
{

    sealed class Server
    {
        readonly bool ServerRunning = true;
        readonly TcpListener sslListner;
        public static X509Certificate serverCertificate = null;
        Server()
        {
            serverCertificate = X509Certificate.CreateFromSignedFile(@"C:sslitinder.online.crt");
            sslListner = new TcpListener(IPAddress.Any, 443);
            sslListner.Start();
            Console.WriteLine("Starting server.." + serverCertificate.Subject + "n" + Assembly.GetExecutingAssembly().Location);
            while (ServerRunning)
            {
                TcpClient SslClient = sslListner.AcceptTcpClient();
                Thread SslThread = new Thread(new ParameterizedThreadStart(ClientThread));
                SslThread.Start(SslClient);
            }
            
        }
        static void ClientThread(Object StateInfo)
        {
            new Client((TcpClient)StateInfo);
        }

        ~Server()
        {
            if (sslListner != null)
            {
                sslListner.Stop();
            }
        }

        public static void Main(string[] args)
        {
            if (AppDomain.CurrentDomain.IsDefaultAppDomain())
            {
                Console.WriteLine("Switching another domain");
                new AppDomainSetup
                {
                    ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
                };
                var current = AppDomain.CurrentDomain;
                var strongNames = new StrongName[0];
                var domain = AppDomain.CreateDomain(
                    "ClearServer", null,
                    current.SetupInformation, new PermissionSet(PermissionState.Unrestricted),
                    strongNames);
                domain.ExecuteAssembly(Assembly.GetExecutingAssembly().Location);
            }
            new Server();
        }
    }
}

たた、SSL 認蚌を備えた新しいクラむアント ハンドラヌも远加されたした。

using ClearServer.Core.Requester;
using System;
using System.Net.Security;
using System.Net.Sockets;

namespace ClearServer
{
    public class Client
    {
        public Client(TcpClient Client)
        {
            SslStream SSlClientStream = new SslStream(Client.GetStream(), false);
            try
            {
                SSlClientStream.AuthenticateAsServer(Server.serverCertificate, clientCertificateRequired: false, checkCertificateRevocation: true);
            }
            catch (Exception e)
            {
                Console.WriteLine(
                    "---------------------------------------------------------------------n" +


quot;|{DateTime.Now:g}n|------------n|{Client.Client.RemoteEndPoint}n|------------n|Exception: {e.Message}n|------------n|Authentication failed - closing the connection.n" +
"---------------------------------------------------------------------n");
SSlClientStream.Close();
Client.Close();
}
new RequestContext(SSlClientStream, Client);
}

}
}

ただし、サヌバヌは TCP 接続のみで実行されるため、リク゚ストのコンテキストを認識できるモゞュヌルを䜜成する必芁がありたす。 ここでは、クラむアントからのリク゚ストを個別の郚分に分割しお、クラむアントに必芁な回答を䞎えるために察話できるパヌサヌが適しおいるず刀断したした。

パヌサヌ

using ClearServer.Core.UserController;
using ReServer.Core.Classes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace ClearServer.Core.Requester
{
    public class RequestContext
    {
        public string Message = "";
        private readonly byte[] buffer = new byte[1024];
        public string RequestMethod;
        public string RequestUrl;
        public User RequestProfile;
        public User CurrentUser = null;
        public List<RequestValues> HeadersValues;
        public List<RequestValues> FormValues;
        private TcpClient TcpClient;

        private event Action<SslStream, RequestContext> OnRead = RequestHandler.OnHandle;

        DatabaseWorker databaseWorker = new DatabaseWorker();

        public RequestContext(SslStream ClientStream, TcpClient Client)
        {

            this.TcpClient = Client;
            try
            {
                ClientStream.BeginRead(buffer, 0, buffer.Length, ClientRead, ClientStream);
            }
            catch { return; }
        }
        private void ClientRead(IAsyncResult ar)
        {
            SslStream ClientStream = (SslStream)ar.AsyncState;

            if (ar.IsCompleted)
            {
                Message = Encoding.UTF8.GetString(buffer);
                Message = Uri.UnescapeDataString(Message);
                Console.WriteLine(

quot;n{DateTime.Now:g} Client IP:{TcpClient.Client.RemoteEndPoint}n{Message}");
RequestParse();
HeadersValues = HeaderValues();
FormValues = ContentValues();
UserParse();
ProfileParse();
OnRead?.Invoke(ClientStream, this);
}
}

private void RequestParse()
{
Match methodParse = Regex.Match(Message, @"(^w+)s+([^s?]+)[^s]*s+HTTP/.*|");
RequestMethod = methodParse.Groups[1].Value.Trim();
RequestUrl = methodParse.Groups[2].Value.Trim();
}
private void UserParse()
{
string cookie;
try
{
if (HeadersValues.Any(x => x.Name.Contains("Cookie")))
{
cookie = HeadersValues.FirstOrDefault(x => x.Name.Contains("Cookie")).Value;
try
{
CurrentUser = databaseWorker.CookieValidate(cookie);
}
catch { }
}
}
catch { }

}
private List<RequestValues> HeaderValues()
{
var values = new List<RequestValues>();
var parse = Regex.Matches(Message, @"(.*?): (.*?)n");
foreach (Match match in parse)
{
values.Add(new RequestValues()
{
Name = match.Groups[1].Value.Trim(),
Value = match.Groups[2].Value.Trim()
});
}
return values;
}

private void ProfileParse()
{
if (RequestUrl.Contains("@"))
{
RequestProfile = databaseWorker.FindUser(RequestUrl.Substring(2));
RequestUrl = "/profile";
}
}
private List<RequestValues> ContentValues()
{
var values = new List<RequestValues>();
var output = Message.Trim('n').Split().Last();
var parse = Regex.Matches(output, @"([^&].*?)=([^&]*b)");
foreach (Match match in parse)
{
values.Add(new RequestValues()
{
Name = match.Groups[1].Value.Trim(),
Value = match.Groups[2].Value.Trim().Replace('+', ' ')
});
}
return values;
}
}
}

その本質は、正芏衚珟を䜿甚しおリク゚ストを耇数の郚分に分割するこずです。 クラむアントからメッセヌゞを受け取り、メ゜ッドずリク゚スト URL が含たれる最初の行を遞択したす。 次に、ヘッダヌを読み取り、HeaderName=Content 圢匏の配列に入れたす。たた、利甚可胜な堎合は、付随するコンテンツ (ク゚リ文字列など) も芋぀けお、これも同様の配列に入れたす。 さらに、パヌサヌは珟圚のクラむアントが認蚌されおいるかどうかを調べ、そのデヌタを保存したす。 承認されたクラむアントからのすべおのリク゚ストには、Cookie に保存される承認ハッシュが含たれおいたす。これにより、XNUMX 皮類のクラむアントの動䜜ロゞックをさらに分離し、正しい回答を䞎えるこずができたす。

そうですね、「site.com/@UserName」のようなク゚リを動的に生成されるナヌザヌ ペヌゞに倉換するずいう、別のモゞュヌルに組み蟌む䟡倀のある小さくお優れた機胜です。 リク゚ストの凊理埌、次のモゞュヌルが機胜したす。

第3ç«  ステアリングホむヌルの取り付け、チェヌンの泚油

パヌサヌが䜜業を完了するずすぐにハンドラヌが動䜜し、サヌバヌにさらに指瀺を䞎え、制埡を XNUMX ぀の郚分に分割したす。

単玔なハンドラヌ

using ClearServer.Core.UserController;
using System.Net.Security;
namespace ClearServer.Core.Requester
{
    public class RequestHandler
    {
        public static void OnHandle(SslStream ClientStream, RequestContext context)
        {

            if (context.CurrentUser != null)
            {
                new AuthUserController(ClientStream, context);
            }
            else 
            {
                new NonAuthUserController(ClientStream, context);
            };
        }
    }
}

実際、ナヌザヌ認蚌のチェックは XNUMX 回だけで、その埌リク゚ストの凊理が開始されたす。

クラむアントコントロヌラヌ
ナヌザヌが認可されおいない堎合、そのナヌザヌの機胜はナヌザヌ プロファむルの衚瀺ず認可登録りィンドりのみに基づいおいたす。 蚱可されたナヌザヌのコヌドはほが同じなので、耇補する理由はありたせん。

䞍正なナヌザヌ

using ClearServer.Core.Requester;
using System.IO;
using System.Net.Security;

namespace ClearServer.Core.UserController
{
    internal class NonAuthUserController
    {
        private readonly SslStream ClientStream;
        private readonly RequestContext Context;
        private readonly WriteController WriteController;
        private readonly AuthorizationController AuthorizationController;

        private readonly string ViewPath = "C:/Users/drdre/source/repos/ClearServer/View";

        public NonAuthUserController(SslStream clientStream, RequestContext context)
        {
            this.ClientStream = clientStream;
            this.Context = context;
            this.WriteController = new WriteController(clientStream);
            this.AuthorizationController = new AuthorizationController(clientStream, context);
            ResourceLoad();
        }

        void ResourceLoad()
        {
            string[] blockextension = new string[] {"cshtml", "html", "htm"};
            bool block = false;
            foreach (var item in blockextension)
            {
                if (Context.RequestUrl.Contains(item))
                {
                    block = true;
                    break;
                }
            }
            string FilePath = "";
            string Header = "";
            var RazorController = new RazorController(Context, ClientStream);
            
            switch (Context.RequestMethod)
            {
                case "GET":
                    switch (Context.RequestUrl)
                    {
                        case "/":
                            FilePath = ViewPath + "/loginForm.html";
                            Header =

quot;HTTP/1.1 200 OKnContent-Type: text/html";
WriteController.DefaultWriter(Header, FilePath);
break;
case "/profile":
RazorController.ProfileLoader(ViewPath);
break;
default:
//в ЎаММПЌ блПке кПЎа прПОсхПЎОт ПтсечеМОе запрПсПв к серверу пП пряЌПЌу аЎресу страМОцы вОЎа site.com/page.html
if (!File.Exists(ViewPath + Context.RequestUrl) | block)
{
RazorController.ErrorLoader(404);

}
else if (Path.HasExtension(Context.RequestUrl) && File.Exists(ViewPath + Context.RequestUrl))
{
Header = WriteController.ContentType(Context.RequestUrl);
FilePath = ViewPath + Context.RequestUrl;
WriteController.DefaultWriter(Header, FilePath);
}
break;
}
break;

case "POST":
AuthorizationController.MethodRecognizer();
break;

}

}

}
}

そしおもちろん、ナヌザヌは䜕らかのペヌゞ コンテンツを受信する必芁があるため、応答に぀いおは、リ゜ヌス芁求ぞの応答を担圓する次のモゞュヌルがありたす。

ラむタヌコントロヌラヌ

using System;
using System.IO;
using System.Net.Security;
using System.Text;

namespace ClearServer.Core.UserController
{
    public class WriteController
    {
        SslStream ClientStream;
        public WriteController(SslStream ClientStream)
        {
            this.ClientStream = ClientStream;
        }

        public void DefaultWriter(string Header, string FilePath)
        {
            FileStream fileStream;
            try
            {
                fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                Header =

quot;{Header}nContent-Length: {fileStream.Length}nn";
ClientStream.Write(Encoding.UTF8.GetBytes(Header));
byte[] response = new byte[fileStream.Length];
fileStream.BeginRead(response, 0, response.Length, OnFileRead, response);
}
catch { }
}

public string ContentType(string Uri)
{
string extension = Path.GetExtension(Uri);
string Header = "HTTP/1.1 200 OKnContent-Type:";
switch (extension)
{
case ".html":
case ".htm":
return


quot;{Header} text/html";
case ".css":
return


quot;{Header} text/css";
case ".js":
return


quot;{Header} text/javascript";
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
return


quot;{Header} image/{extension}";
default:
if (extension.Length > 1)
{
return


quot;{Header} application/" + extension.Substring(1);
}
else
{
return


quot;{Header} application/unknown";
}
}
}

public void OnFileRead(IAsyncResult ar)
{
if (ar.IsCompleted)
{
var file = (byte[])ar.AsyncState;
ClientStream.BeginWrite(file, 0, file.Length, OnClientSend, null);
}
}

public void OnClientSend(IAsyncResult ar)
{
if (ar.IsCompleted)
{
ClientStream.Close();
}
}
}

しかし、ナヌザヌに自分のプロフィヌルず他のナヌザヌのプロフィヌルを衚瀺するために、RazorEngine たたはその䞀郚を䜿甚するこずにしたした。 これには、無効なリク゚ストの凊理ず適切な゚ラヌ コヌドの発行も含たれたす。

かみそりコントロヌラヌ

using ClearServer.Core.Requester;
using RazorEngine;
using RazorEngine.Templating;
using System;
using System.IO;
using System.Net;
using System.Net.Security;

namespace ClearServer.Core.UserController
{
    internal class RazorController
    {
        private RequestContext Context;
        private SslStream ClientStream;
        dynamic PageContent;


        public RazorController(RequestContext context, SslStream clientStream)
        {
            this.Context = context;
            this.ClientStream = clientStream;

        }

        public void ProfileLoader(string ViewPath)
        {
            string Filepath = ViewPath + "/profile.cshtml";
            if (Context.RequestProfile != null)
            {
                if (Context.CurrentUser != null && Context.RequestProfile.login == Context.CurrentUser.login)
                {
                    try
                    {
                        PageContent = new { isAuth = true, Name = Context.CurrentUser.name, Login = Context.CurrentUser.login, Skills = Context.CurrentUser.skills };
                        ClientSend(Filepath, Context.CurrentUser.login);
                    }
                    catch (Exception e) { Console.WriteLine(e); }

                }
                else
                {
                    try
                    {
                        PageContent = new { isAuth = false, Name = Context.RequestProfile.name, Login = Context.RequestProfile.login, Skills = Context.RequestProfile.skills };
                        ClientSend(Filepath, "PublicProfile:"+ Context.RequestProfile.login);
                    }
                    catch (Exception e) { Console.WriteLine(e); }
                }
            }
            else
            {
                ErrorLoader(404);
            }


        }

        public void ErrorLoader(int Code)
        {
            try
            {
                PageContent = new { ErrorCode = Code, Message = ((HttpStatusCode)Code).ToString() };
                string ErrorPage = "C:/Users/drdre/source/repos/ClearServer/View/Errors/ErrorPage.cshtml";
                ClientSend(ErrorPage, Code.ToString());
            }
            catch { }

        }

        private void ClientSend(string FilePath, string Key)
        {
            var template = File.ReadAllText(FilePath);
            var result = Engine.Razor.RunCompile(template, Key, null, (object)PageContent);
            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(result);
            ClientStream.BeginWrite(buffer, 0, buffer.Length, OnClientSend, ClientStream);
        }

        private void OnClientSend(IAsyncResult ar)
        {
            if (ar.IsCompleted)
            {
                ClientStream.Close();
            }
        }
    }
}

そしおもちろん、蚱可されたナヌザヌの怜蚌が機胜するには、蚱可が必芁です。 認可モゞュヌルはデヌタベヌスず察話したす。 サむト䞊のフォヌムから受信したデヌタはコンテキストから解析され、ナヌザヌは保存され、その代わりに Cookie を受け取り、サヌビスにアクセスしたす。

認可モゞュヌル

using ClearServer.Core.Cookies;
using ClearServer.Core.Requester;
using ClearServer.Core.Security;
using System;
using System.Linq;
using System.Net.Security;
using System.Text;

namespace ClearServer.Core.UserController
{
    internal class AuthorizationController
    {
        private SslStream ClientStream;
        private RequestContext Context;
        private UserCookies cookies;
        private WriteController WriteController;
        DatabaseWorker DatabaseWorker;
        RazorController RazorController;
        PasswordHasher PasswordHasher;
        public AuthorizationController(SslStream clientStream, RequestContext context)
        {
            ClientStream = clientStream;
            Context = context;
            DatabaseWorker = new DatabaseWorker();
            WriteController = new WriteController(ClientStream);
            RazorController = new RazorController(context, clientStream);
            PasswordHasher = new PasswordHasher();
        }

        internal void MethodRecognizer()
        {
            if (Context.FormValues.Count == 2 && Context.FormValues.Any(x => x.Name == "password")) Authorize();
            else if (Context.FormValues.Count == 3 && Context.FormValues.Any(x => x.Name == "regPass")) Registration();
            else
            {
                RazorController.ErrorLoader(401);
            }
        }

        private void Authorize()
        {
            var values = Context.FormValues;
            var user = new User()
            {
                login = values[0].Value,
                password = PasswordHasher.PasswordHash(values[1].Value)
            };
            user = DatabaseWorker.UserAuth(user);
            if (user != null)
            {
                cookies = new UserCookies(user.login, user.password);
                user.cookie = cookies.AuthCookie;
                DatabaseWorker.UserUpdate(user);
                var response = Encoding.UTF8.GetBytes(

quot;HTTP/1.1 301 Moved PermanentlynLocation: /@{user.login}nSet-Cookie: {cookies.AuthCookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnlynn");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);

}
else
{
RazorController.ErrorLoader(401);

}
}

private void Registration()
{
var values = Context.FormValues;
var user = new User()
{
name = values[0].Value,
login = values[1].Value,
password = PasswordHasher.PasswordHash(values[2].Value),
};
cookies = new UserCookies(user.login, user.password);
user.cookie = cookies.AuthCookie;
if (DatabaseWorker.LoginValidate(user.login))
{
Console.WriteLine("User ready");
Console.WriteLine(


quot;{user.password} {user.password.Trim().Length}");
DatabaseWorker.UserRegister(user);
var response = Encoding.UTF8.GetBytes(


quot;HTTP/1.1 301 Moved PermanentlynLocation: /@{user.login}nSet-Cookie: {user.cookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnlynn");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);
}
else
{
RazorController.ErrorLoader(401);
}
}
}
}

デヌタベヌス凊理は次のようになりたす。

デヌタベヌス

using ClearServer.Core.UserController;
using System;
using System.Data.Linq;
using System.Linq;

namespace ClearServer
{
    class DatabaseWorker
    {

        private readonly Table<User> users = null;
        private readonly DataContext DataBase = null;
        private const string connectionStr = @"путькбазе";

        public DatabaseWorker()
        {
            DataBase = new DataContext(connectionStr);
            users = DataBase.GetTable<User>();
        }

        public User UserAuth(User User)
        {
            try
            {
                var user = users.SingleOrDefault(t => t.login.ToLower() == User.login.ToLower() && t.password == User.password);
                if (user != null)
                    return user;
                else
                    return null;
            }
            catch (Exception)
            {
                return null;
            }

        }

        public void UserRegister(User user)
        {
            try
            {
                users.InsertOnSubmit(user);
                DataBase.SubmitChanges();
                Console.WriteLine(

quot;User{user.name} with id {user.uid} added");
foreach (var item in users)
{
Console.WriteLine(item.login + "n");
}
}
catch (Exception e)
{
Console.WriteLine(e);
}

}

public bool LoginValidate(string login)
{
if (users.Any(x => x.login.ToLower() == login.ToLower()))
{
Console.WriteLine("Login already exists");
return false;
}
return true;
}
public void UserUpdate(User user)
{
var UserToUpdate = users.FirstOrDefault(x => x.uid == user.uid);
UserToUpdate = user;
DataBase.SubmitChanges();
Console.WriteLine(


quot;User {UserToUpdate.name} with id {UserToUpdate.uid} updated");
foreach (var item in users)
{
Console.WriteLine(item.login + "n");
}
}
public User CookieValidate(string CookieInput)
{
User user = null;
try
{
user = users.SingleOrDefault(x => x.cookie == CookieInput);
}
catch
{
return null;
}
if (user != null) return user;
else return null;
}
public User FindUser(string login)
{
User user = null;
try
{
user = users.Single(x => x.login.ToLower() == login.ToLower());
if (user != null)
{
return user;
}
else
{
return null;
}
}
catch (Exception)
{
return null;
}
}
}
}


そしお、すべおが時蚈仕掛けのように機胜し、承認ず登録の䜜業が行われ、サヌビスにアクセスするための最小限の機胜がすでに存圚しおおり、アプリケヌションを䜜成しお、すべおを実行する䞻芁な機胜ず党䜓を結び付ける時期が来おいたす。

第4ç«  自転車を捚おる

3 ぀のプラットフォヌムで XNUMX ぀のアプリケヌションを䜜成する人件費を削枛するために、Xamarin.Forms でクロスプラットフォヌムを䜜成するこずにしたした。 繰り返したすが、C# であるずいう事実のおかげです。 サヌバヌにデヌタを送信するだけのテスト アプリケヌションを䜜成したずころ、興味深い点に気づきたした。 デバむスからのリク゚ストに぀いおは、楜しみのために HttpClient に実装し、JSON 圢匏の認蚌フォヌムからのデヌタを含む HttpRequestMessage サヌバヌに送信したした。 特に䜕も期埅せずにサヌバヌ ログを開くず、デバむスからのリク゚ストずすべおのデヌタが蚘録されおいたした。 わずかな昏迷、気だるい倜の䞭で過去XNUMX週間に行われたすべおのこずを意識したす。 送信されたデヌタの正確性を確認するために、HttpListner䞊にテストサヌバヌを構築したした。 すでに別のリク゚ストを受け取っおいたので、それを数行のコヌドで分解し、フォヌムからデヌタの KeyValuePair を受け取りたした。 ク゚リの解析は XNUMX 行に枛りたした。

先ほどは觊れおいたせんでしたが、以前のサヌバヌでは WebSocket 䞊に構築されたチャットも実装しお、さらにテストを開始したした。 これは非垞にうたく機胜したしたが、Tcp を介した察話の原理自䜓が憂鬱で、通信ログを䜿甚しお 6455 人のナヌザヌの察話を適切に構築するには、あたりにも倚くの䞍必芁な䜜業を行う必芁がありたした。 これには、接続を切り替えるリク゚ストの解析ず RFC XNUMX プロトコルを䜿甚したレスポンスの収集が含たれるため、テスト サヌバヌでは単玔な WebSocket 接続を䜜成するこずにしたした。 ただの楜しみのためです。

チャットに接続する

 private static async void HandleWebsocket(HttpListenerContext context)
        {
            var socketContext = await context.AcceptWebSocketAsync(null);
            var socket = socketContext.WebSocket;
            Locker.EnterWriteLock();
            try
            {
                Clients.Add(socket);
            }
            finally
            {
                Locker.ExitWriteLock();
            }

            while (true)
            {
                var buffer = new ArraySegment<byte>(new byte[1024]);
                var result = await socket.ReceiveAsync(buffer, CancellationToken.None);
                var str = Encoding.Default.GetString(buffer);
                Console.WriteLine(str);

                for (int i = 0; i < Clients.Count; i++)
                {
                    WebSocket client = Clients[i];

                    try
                    {
                        if (client.State == WebSocketState.Open)
                        {
                            
                            await client.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
                        }
                    }
                    catch (ObjectDisposedException)
                    {
                        Locker.EnterWriteLock();
                        try
                        {
                            Clients.Remove(client);
                            i--;
                        }
                        finally
                        {
                            Locker.ExitWriteLock();
                        }
                    }
                }
            }
        }

そしおそれはうたくいきたした。 サヌバヌ自䜓が接続を構成し、応答キヌを生成したした。 SSL 経由でサヌバヌ登録を個別に構成する必芁さえなく、システムの必芁なポヌトに蚌明曞がすでにむンストヌルされおいれば十分でした。

デバむス偎ずサむト偎で、200 ぀のクラむアントがメッセヌゞを亀換し、これがすべお蚘録されたした。 サヌバヌの速床を䜎䞋させる巚倧なパヌサヌはなく、これらはどれも必芁ありたせんでした。 応答時間は 40 ミリ秒から 30  XNUMX ミリ秒に短瞮されたした。 そしお私は唯䞀正しい決断に至りたした。

C# .Net フレヌムワヌクに基づく高床なサむクリングたたはクラむアントサヌバヌ アプリケヌション

Tcp 䞊の珟圚のサヌバヌ実装を砎棄し、Http の䞋にあるものをすべお曞き換えたす。 珟圚、プロゞェクトは再蚭蚈段階にありたすが、たったく異なる盞互䜜甚の原則に埓っおいたす。 デバむスずサむトの動䜜は同期しおデバッグされ、共通のコンセプトを持っおいたす。唯䞀の違いは、デバむス甚の HTML ペヌゞを生成する必芁がないこずです。

出力

「浅瀬を知らないなら、氎に入らないでください」 仕事を始める前に、目暙や目的をもっず明確に定矩し、さたざたなクラむアントに実装するために必芁な技術や手法を培底的に怜蚎する必芁があるず思いたす。 プロゞェクトはすでに完成に近づいおいたすが、私がどのようにしお特定のものを保存したかに぀いおは、もう䞀床お話しするかもしれたせん。 開発プロセス䞭に倚くのこずを孊びたしたが、将来的にはさらに倚くのこずを孊ぶ必芁がありたす。 ここたで読んでいただいた方、ありがずうございたす。

出所 habr.com