Հեծանիվների առաջադեմ շենք կամ հաճախորդ-սերվեր հավելված՝ հիմնված C# .Net Framework-ի վրա

Մուտք

Ամեն ինչ սկսվեց նրանից, որ գործընկերներից մեկն ինձ առաջարկեց ստեղծել մի փոքրիկ վեբ ծառայություն: Այն պետք է լիներ Tinder-ի նման մի բան, բայց ՏՏ ամբոխի համար։ Ֆունկցիոնալությունը չափազանց պարզ է, դուք գրանցվում եք, լրացնում եք պրոֆիլը և անցնում հիմնական կետին, այն է՝ գտնել մարդ, ում հետ կարող եք զրուցել և ընդլայնել ձեր կապերը և ձեռք բերել նոր ծանոթություններ:

Այստեղ ես պետք է նահանջեմ և մի փոքր պատմեմ իմ մասին, որպեսզի հետագայում ավելի պարզ լինի, թե ինչու եմ զարգացման նման քայլեր ձեռնարկել։

Այս պահին ես զբաղեցնում եմ Տեխնիկական նկարչի պաշտոնը մեկ խաղային ստուդիայում, C#-ում իմ ծրագրավորման փորձը հիմնված է միայն Unity-ի համար սցենարներ և կոմունալ ծրագրեր գրելու և, ի հավելումն դրան, Android սարքերի հետ ցածր մակարդակի աշխատանքի համար պլագինների ստեղծման վրա: Ես դեռ չէի համարձակվել այս փոքրիկ աշխարհից այն կողմ, և հետո այդպիսի հնարավորություն հայտնվեց:

Մաս 1. Շրջանակի նախատիպավորում

Որոշելով, թե ինչպիսին կլինի այս ծառայությունը, ես սկսեցի փնտրել իրականացման տարբերակներ: Ամենահեշտը կլինի ինչ-որ պատրաստի լուծում գտնելը, որի վրա, ինչպես գլոբուսի վրա գտնվող բու, կարող են քաշել մեր մեխանիկները, և ամբողջը ենթարկվել հանրային քննադատության:
Բայց սա հետաքրքիր չէ, ես դրա մեջ ոչ մի մարտահրավեր կամ իմաստ չտեսա, և, հետևաբար, սկսեցի ուսումնասիրել վեբ տեխնոլոգիաները և դրանց հետ շփվելու մեթոդները:

Ես սկսեցի ուսումնասիրել՝ դիտելով հոդվածներ և փաստաթղթեր C# .Net-ում: Այստեղ ես գտա առաջադրանքը կատարելու տարբեր եղանակներ: Ցանցի հետ փոխգործակցության բազմաթիվ մեխանիզմներ կան՝ սկսած լիարժեք լուծումներից, ինչպիսիք են ASP.Net կամ Azure ծառայությունները, մինչև TcpHttp կապերի հետ անմիջական փոխազդեցություն:

Առաջին փորձս կատարելով ASP-ի հետ՝ ես անմիջապես մերժեցի այն, իմ կարծիքով սա չափազանց դժվար որոշում էր մեր ծառայության համար։ Մենք չէինք օգտագործի այս հարթակի հնարավորությունների նույնիսկ մեկ երրորդը, ուստի ես շարունակեցի իմ որոնումը: Ընտրությունը եղել է TCP-ի և Http հաճախորդ-սերվերի միջև: Այստեղ, Habré-ում, ես հանդիպեցի մի հոդվածի մասին բազմաթելային սերվեր, հավաքելով և փորձարկելով այն, ես որոշեցի կենտրոնանալ հատուկ TCP կապերի հետ փոխազդեցության վրա, ինչ-ինչ պատճառներով կարծում էի, որ http-ը թույլ չի տա ինձ ստեղծել խաչմերուկային լուծում:

Սերվերի առաջին տարբերակը ներառում էր կապի մշակում, սպասարկում էր վեբ էջի ստատիկ բովանդակություն և ներառում էր օգտվողների տվյալների բազա: Եվ սկզբից ես որոշեցի ստեղծել ֆունկցիոնալություն կայքի հետ աշխատելու համար, որպեսզի հետագայում կարողանամ ավելացնել հավելվածի մշակումը 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 ձևի զանգվածի մեջ, և մենք գտնում ենք նաև, եթե այդպիսիք կան, ուղեկցող բովանդակություն (օրինակ՝ querystring), որը նույնպես տեղադրում ենք նմանատիպ զանգվածում: Բացի այդ, վերլուծիչը պարզում է, թե արդյոք ներկայիս հաճախորդը լիազորված է և պահպանում է նրա տվյալները: Լիազորված հաճախորդների բոլոր հարցումները պարունակում են թույլտվության հեշ, որը պահվում է թխուկների մեջ, դրա շնորհիվ հնարավոր է առանձնացնել հետագա գործառնական տրամաբանությունը երկու տեսակի հաճախորդների համար և տալ նրանց ճիշտ պատասխաններ:

Դե, մի փոքրիկ, գեղեցիկ հատկություն, որն արժե առանձին մոդուլի մեջ դնել՝ «site.com/@UserName»-ի նման հարցումների փոխակերպումը դինամիկ ձևավորված օգտատերերի էջերի: Հարցումը մշակելուց հետո գործում են հետևյալ մոդուլները.

Գլուխ 3. Ղեկի տեղադրում, շղթայի յուղում

Հենց որ վերլուծիչն ավարտում է իր աշխատանքը, գործի մեջ է մտնում կարգավորիչը՝ սերվերին տալով հետագա հրահանգներ և կառավարումը բաժանելով երկու մասի:

Պարզ կառավարիչ

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);
            };
        }
    }
}

Փաստորեն, կա միայն մեկ ստուգում օգտվողի թույլտվության համար, որից հետո սկսվում է հարցման մշակումը:

Հաճախորդների վերահսկիչներ
Եթե ​​օգտագործողը լիազորված չէ, ապա նրա համար ֆունկցիոնալությունը հիմնված է միայն օգտվողի պրոֆիլների ցուցադրման և թույլտվության գրանցման պատուհանի վրա: Լիազորված օգտվողի կոդը մոտավորապես նույն տեսքն ունի, ուստի ես այն կրկնօրինակելու պատճառ չեմ տեսնում:

Չլիազորված օգտվող

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;

}

}

}
}

Եվ իհարկե, օգտատերը պետք է ստանա էջի ինչ-որ բովանդակություն, ուստի պատասխանների համար կա հետևյալ մոդուլը, որը պատասխանատու է ռեսուրսների հարցումներին պատասխանելու համար։

WriterController

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-ը, ավելի ճիշտ՝ դրա մի մասը։ Այն ներառում է նաև անվավեր հարցումների մշակում և համապատասխան սխալի կոդ թողարկում:

RazorController

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();
            }
        }
    }
}

Եվ իհարկե, որպեսզի լիազորված օգտատերերի ստուգումն աշխատի, անհրաժեշտ է թույլտվություն։ Թույլտվության մոդուլը փոխազդում է տվյալների բազայի հետ: Կայքում առկա ձևերից ստացված տվյալները վերլուծվում են համատեքստից, օգտատերը պահվում է և դրա դիմաց ստանում թխուկներ և մուտք դեպի ծառայություն:

Թույլտվության մոդուլ

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. Հեծանիվը դեն նետելը

Երկու պլատֆորմի համար երկու հավելված գրելու աշխատուժի ծախսերը նվազեցնելու համար ես որոշեցի ստեղծել խաչմերուկ Xamarin.Forms-ում: Կրկին, շնորհիվ այն բանի, որ այն գտնվում է C#-ում։ Ստեղծելով թեստային հավելված, որը պարզապես տվյալներ է ուղարկում սերվերին, ես հանդիպեցի մի հետաքրքիր կետի. Սարքի խնդրանքով, զվարճանալու համար, ես այն ներդրեցի HttpClient-ում և ուղարկեցի HttpRequestMessage սերվերին, որը պարունակում է տվյալներ թույլտվության ձևից json ձևաչափով: Առանց որևէ բան ակնկալելու, ես բացեցի սերվերի գրանցամատյանը և այնտեղ տեսա սարքի հարցումը բոլոր տվյալներով։ Թեթև ապուշություն, գիտակցություն այն ամենի մասին, ինչ արվել է վերջին 3 շաբաթվա ընթացքում տխուր երեկոյում: Ուղարկված տվյալների ճշգրտությունը ստուգելու համար ես HttpListner-ում փորձնական սերվեր հավաքեցի: Ստանալով մեկ այլ հարցում արդեն դրա վրա, ես այն բաժանեցի մի քանի տող կոդի մեջ և ստացա KeyValuePair տվյալների ձևից: Հարցման վերլուծությունը կրճատվել է երկու տողի:

Ես սկսեցի հետագա թեստավորումը, դրա մասին ավելի վաղ նշված չէր, բայց նախորդ սերվերում ես նաև ներդրեցի վեբսոկետների վրա կառուցված չաթ։ Այն բավականին լավ էր աշխատում, բայց Tcp-ի միջոցով փոխազդեցության սկզբունքը վհատեցնող էր. չափազանց շատ անհարկի աշխատանք պետք է կատարվեր, որպեսզի գրագետ ձևավորվեր երկու օգտատերերի փոխազդեցությունը նամակագրության մատյանով: Սա ներառում է կապը փոխարկելու հարցումը վերլուծելը և RFC 6455 արձանագրության միջոցով պատասխան հավաքելը: Հետևաբար, թեստային սերվերում ես որոշեցի ստեղծել պարզ վեբսոկետ կապ: Պարզապես հաճույքի համար.

Միացեք զրույցին

 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-ի միջոցով, բավական էր, որ համակարգն արդեն ուներ սերտիֆիկատ տեղադրված պահանջվող նավահանգստում:

Սարքի և կայքի կողմից երկու հաճախորդներ փոխանակեցին հաղորդագրություններ, այս ամենը գրանցվեց: Սերվերը դանդաղեցնող հսկայական վերլուծիչներ չկան, սրանից ոչ մեկը պարտադիր չէր: Արձագանքման ժամանակը 200ms-ից նվազել է մինչև 40-30ms: Եվ ես հանգեցի միակ ճիշտ որոշմանը.

Հեծանիվների առաջադեմ շենք կամ հաճախորդ-սերվեր հավելված՝ հիմնված C# .Net Framework-ի վրա

Հեռացրեք ընթացիկ սերվերի ներդրումը Tcp-ում և վերագրեք ամեն ինչ Http-ի տակ: Այժմ նախագիծը վերանախագծման փուլում է, բայց փոխգործակցության բոլորովին այլ սկզբունքներով։ Սարքերի և կայքի աշխատանքը սինխրոնիզացված և կարգաբերված է և ունի ընդհանուր հայեցակարգ, միայն այն տարբերությամբ, որ սարքերի համար HTML էջեր ստեղծելու կարիք չկա:

Արտադրողականություն

«Եթե դուք չգիտեք Ֆորդը, մի մտեք ջուրը» Կարծում եմ, որ աշխատանք սկսելուց առաջ ես պետք է ունենամ ավելի հստակ սահմանված նպատակներ և խնդիրներ, ինչպես նաև խորամուխ լինեի տարբեր հաճախորդների վրա դրանց ներդրման համար անհրաժեշտ տեխնոլոգիաների և մեթոդների ուսումնասիրության մեջ: Ծրագիրն արդեն մոտենում է ավարտին, բայց միգուցե ես վերադառնամ և խոսեմ այն ​​մասին, թե ինչպես եմ ես նորից փրկել որոշ բաներ: Ես շատ բան սովորեցի զարգացման գործընթացում, բայց դեռ ավելին կա սովորելու ապագայում: Եթե ​​դուք կարդացել եք այսքանը, շնորհակալություն, որ դա արեցիք:

Source: www.habr.com