C# .Net шеңберіне негізделген кеңейтілген циклдік немесе клиент-сервер қолданбасы

кіру

Барлығы бір әріптесім шағын веб-сервис жасауды ұсынған кезде басталды. Бұл Tinder сияқты нәрсе болуы керек еді, бірақ IT тобы үшін. Функционалдық өте қарапайым, сіз тіркелесіз, профильді толтырасыз және негізгі мәселеге, атап айтқанда сөйлесетін адамды табуға және байланыстарыңызды кеңейтуге және жаңа таныстарға көшуге болады.

Бұл жерде мен бір шегініс жасап, өзім туралы аздап айтып беруім керек, болашақта неліктен дамуда мұндай қадамдарға барғанымды түсіну үшін.

Қазіргі уақытта мен бір ойын студиясында Техникалық суретші лауазымын атқарамын, менің C# тіліндегі бағдарламалау тәжірибем тек Unity үшін сценарийлер мен утилиталарды жазуға және оған қоса Android құрылғыларымен төмен деңгейлі жұмыс үшін плагиндерді жасауға негізделген. Мен бұл кішкентай әлемнен әлі ары қарай ұмтылған жоқпын, содан кейін мұндай мүмкіндік пайда болды.

1-бөлім. Жақтаудың прототипі

Бұл қызметтің қандай болатынын шешіп, мен іске асыру нұсқаларын іздей бастадым. Ең оңайы – жер шарындағы үкі сияқты біздің механиканы тартып, жұртшылықтың сынына ұшырататын қандай да бір дайын шешім табу.
Бірақ бұл қызық емес, мен одан ешқандай қиындық немесе мағына көрмедім, сондықтан мен веб-технологияларды және олармен әрекеттесу әдістерін зерттей бастадым.

Мен оқуды C# .Net тіліндегі мақалалар мен құжаттарды қарау арқылы бастадым. Мұнда мен тапсырманы орындаудың әртүрлі жолдарын таптым. ASP.Net немесе Azure қызметтері сияқты толыққанды шешімдерден TcpHttp қосылымдарымен тікелей әрекеттесуге дейін желімен өзара әрекеттесу үшін көптеген механизмдер бар.

ASP-пен бірінші әрекетті жасағаннан кейін мен одан бірден бас тарттым; менің ойымша, бұл біздің қызмет үшін өте қиын шешім болды. Біз бұл платформа мүмкіндіктерінің үштен бірін де пайдаланбайтын едік, сондықтан мен іздеуді жалғастырдым. Таңдау TCP және Http клиент-сервері арасында болды. Міне, Хабреде мен туралы мақалаға тап болдым көп ағынды сервер, оны жинап, сынақтан өткізгеннен кейін мен 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 пішінінің массивіне енгізген тақырыптарды оқимыз, сондай-ақ, егер бар болса, ілеспе мазмұнды (мысалы, сұрау жолы) табамыз, оны да ұқсас массивке енгіземіз. Сонымен қатар, талдаушы ағымдағы клиенттің рұқсаты бар-жоғын анықтайды және оның деректерін сақтайды. Рұқсат етілген клиенттердің барлық сұрауларында cookie файлдарында сақталатын авторизация хэші бар, соның арқасында клиенттердің екі түрі үшін әрі қарай операциялық логиканы бөліп, оларға дұрыс жауаптар беруге болады.

Жеке модульге қоюға тұрарлық шағын, жақсы мүмкіндік, «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();
            }
        }
    }
}

Әрине, авторизацияланған пайдаланушылардың жұмыс істеуін тексеру үшін авторизация қажет. Авторизация модулі дерекқормен әрекеттеседі. Сайттағы пішіндерден алынған деректер контекстен талданады, пайдаланушы сақталады және оның орнына 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-тарау. Велосипедті лақтыру

Екі платформаға екі қосымшаны жазудағы еңбек шығындарын азайту үшін мен Xamarin.Forms сайтында кросс-платформа жасауды шештім. Тағы да, бұл C# тілінде болғанының арқасында. Деректерді серверге жіберетін сынақ қосымшасын жасап, мен қызықты сәтке тап болдым. Құрылғыдан сұрау үшін, көңіл көтеру үшін мен оны HttpClient жүйесінде іске асырдым және оны json пішіміндегі авторизация пішініндегі деректерді қамтитын HttpRequestMessage серверіне жібердім. Ештеңені күтпестен мен сервер журналын аштым және ол жерде барлық деректері бар құрылғыдан сұрауды көрдім. Кішкене ессіздік, соңғы 3 аптаның ішінде түнгі кеште жасалған барлық нәрселерді білу. Жіберілген деректердің дұрыстығын тексеру үшін мен HttpListner сайтында сынақ серверін жинадым. Ол бойынша басқа сұрауды алғаннан кейін мен оны бірнеше код жолына бөліп алдым және пішіннен KeyValuePair деректерін алдым. Сұрауды талдау екі жолға қысқарды.

Мен әрі қарай тестілеуді бастадым, бұл бұрын айтылмаған, бірақ алдыңғы серверде мен веб-сокеттерге салынған чатты да іске асырдым. Бұл өте жақсы жұмыс істеді, бірақ Tcp арқылы өзара әрекеттесу принципінің өзі көңілсіз болды; екі пайдаланушының хат алмасу журналымен өзара әрекеттесуін сауатты құру үшін тым көп қажетсіз жұмыс істеу керек болды. Бұған қосылымды ауыстыру сұрауын талдау және RFC 6455 протоколы арқылы жауап жинау кіреді.Сондықтан сынақ серверінде мен қарапайым 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 мс дейін қысқарды. Және жалғыз дұрыс шешімге келдім.

C# .Net шеңберіне негізделген кеңейтілген циклдік немесе клиент-сервер қолданбасы

Tcp серверіндегі ағымдағы іске қосуды тастаңыз және барлығын Http астында қайта жазыңыз. Қазір жоба қайта құру сатысында, бірақ өзара әрекеттесудің мүлдем басқа принциптеріне сәйкес. Құрылғылар мен сайттың жұмысы синхрондалған және жөнделген және жалпы тұжырымдамаға ие, жалғыз айырмашылығы - құрылғылар үшін HTML беттерін жасаудың қажеті жоқ.

қорытынды

«Егер сіз өткелді білмесеңіз, суға түспеңіз» Менің ойымша, жұмысқа кіріспес бұрын менде нақтырақ анықталған мақсаттар мен міндеттер болуы керек, сондай-ақ әртүрлі клиенттерге оларды енгізудің қажетті технологиялары мен әдістерін зерттеуге кірісу керек. Жоба қазірдің өзінде аяқталуға жақын, бірақ мен кейбір нәрселерді қалай сақтағаным туралы сөйлесу үшін қайта оралатын шығармын. Даму барысында мен көп нәрсені үйрендім, бірақ болашақта одан да көп нәрсені үйрену керек. Осы уақытқа дейін оқыған болсаңыз, мұны істегеніңіз үшін рахмет.

Ақпарат көзі: www.habr.com