C# .Net keretrendszeren alapuló fejlett kerékpáros vagy kliens-szerver alkalmazás

Belépés

Az egész úgy kezdődött, hogy egy kolléga javasolta, készítsek egy kis webszolgáltatást. Valami tindernek kellett volna lennie, de informatikai társalgóhoz. A funkcionalitás teljesen egyszerű, regisztrál, kitölt egy profilt, és továbblép a lényegre, nevezetesen a beszélgetőpartner megtalálására, kapcsolatainak bővítésére és új ismeretségek megkötésére.

Itt ki kell térnem, és mesélnem kell egy kicsit magamról, hogy a jövőben érthetőbb legyen, miért tettem ilyen lépéseket a fejlesztésben.

Jelenleg technikai művész pozíciót töltök be egy játékstúdióban, a C# programozási tapasztalataim csak a Unity-hez szkriptek és segédprogramok írásán alapultak, és ezen túlmenően az alacsony szintű androidos eszközökkel végzett munkához szükséges bővítmények készítésén. Ezen a világon kívül még nem választottam, majd nem vetettem fel ilyen lehetőséget.

1. rész. Keret prototípus készítése

Miután eldöntöttem, hogy milyen lesz ez a szolgáltatás, elkezdtem keresni a megvalósítás lehetőségeit. A legegyszerűbb az lenne, ha találnánk valami kész megoldást, amelyen, mint bagoly a földgömbön, ráhúzhatjuk a mechanikánkat, és kiteríthetjük az egészet a közbizalomra.
De ez nem érdekes, nem láttam ebben kihívást és értelmét, ezért elkezdtem tanulmányozni a webes technológiákat és a velük való interakció módszereit.

A tanulmány a C # .Neten található cikkek és dokumentáció megtekintésével kezdődött. Itt különféle módokat találtam a feladat elvégzésére. Számos mechanizmus létezik a hálózattal való interakcióhoz, a teljes körű megoldásoktól, például az ASP.Net-től vagy az Azure-szolgáltatásoktól a TcpHttp-kapcsolatokkal való közvetlen interakcióig.

Miután megtettem az első próbálkozást az ASP-vel, azonnal lemondtam, véleményem szerint túl nehéz döntés volt a szolgáltatásunk számára. A platform képességeinek a harmadát sem fogjuk kihasználni, így folytattam a keresést. A választás a TCP és a Http kliens-szerver között merült fel. Itt, a Habré-n találkoztam egy cikkel arról többszálú szerver, miután összegyűjtöttem és teszteltem, úgy döntöttem, hogy a TCP-kapcsolatokkal való interakcióra helyezem a hangsúlyt, valamiért úgy gondoltam, hogy a http nem teszi lehetővé többplatformos megoldás létrehozását.

A szerver első verziója magában foglalta a kapcsolatok kezelését, a statikus weboldal tartalmának kiszolgálását és egy felhasználói adatbázist. Kezdetnek pedig úgy döntöttem, hogy építek egy funkcionálist az oldallal való munkavégzéshez, hogy később ide tudjam kötni az Android és ios alkalmazások feldolgozását.

Itt van néhány kód
Az ügyfeleket végtelen körben fogadó fő szál:

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

Maga az ügyfélkezelő:

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

És az első helyi SQL-re épített adatbázis:

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

Amint látja, ez a verzió alig különbözik a cikkben szereplőtől. Valójában itt csak az oldalak betöltése a számítógépen lévő mappából és az adatbázis került be (ami egyébként ebben a verzióban nem működött, a hibás kapcsolati architektúra miatt).

2. fejezet

A szerver tesztelése után arra a következtetésre jutottam, hogy ez jó megoldás lenne (spoiler: nem), szolgáltatásunk számára, így a projekt kezdett logikát nyerni.
Lépésről lépésre új modulok kezdtek megjelenni, és a szerver funkcionalitása bővült. A szerver teszttartományt és ssl-kapcsolati titkosítást kapott.

Még egy kis kód, amely leírja a szerver logikáját és a kliensek feldolgozását
A kiszolgáló frissített verziója, beleértve a tanúsítvány használatát.

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

Valamint egy új ügyfélkezelő ssl-en keresztüli jogosultsággal:

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

}
}

De mivel a szerver kizárólag TCP-kapcsolaton működik, létre kell hozni egy olyan modult, amely képes felismerni a kéréskörnyezetet. Úgy döntöttem, hogy itt egy értelmező alkalmas, amely az ügyféltől érkező kérést külön részekre bontja, amelyekkel interakcióba léphetek, hogy megadjam a kliensnek a szükséges válaszokat.

elemző

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

Lényege abban rejlik, hogy reguláris kifejezések segítségével részekre bontja a kérést. Üzenetet kapunk a klienstől, válassza ki az első sort, amely tartalmazza a módszert és a kérelem url-jét. Ezután beolvassuk a fejléceket, amelyeket egy HeaderName = Content formátumú tömbbe vezetünk, és megkeressük, ha van, a hozzá tartozó tartalmat (például lekérdezési karakterláncot), amelyet szintén egy hasonló tömbbe vezetünk. Ezenkívül az elemző megtudja, hogy az aktuális ügyfél jogosult-e, és elmenti az adatait. Minden jogosult klienstől érkező kérés tartalmaz egy engedélyezési hash-t, amely cookie-kban tárolódik, aminek köszönhetően lehetőség nyílik két klienstípus további munkalogikájának elkülönítésére és a helyes válaszok megadására.

Nos, egy kicsi, szép funkció, amelyet egy külön modulba kell helyezni, és az olyan lekérdezéseket, mint a „site.com/@Felhasználónév” dinamikusan generált felhasználói oldalakká alakítja. A kérés feldolgozása után a következő modulok lépnek működésbe.

3. fejezet A kormány felszerelése, a lánc kenése

Amint az értelmező befejeződött, a kezelő lép működésbe, további utasításokat adva a szervernek, és két részre osztva a vezérlést.

egyszerű kezelő

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

Valójában csak egyszer kell ellenőrizni a felhasználói jogosultságot, és ezután kezdődik a kérés feldolgozása.

Ügyfélvezérlők
Ha a felhasználó nem jogosult, akkor számára a funkcionalitás csak a felhasználói profilok megjelenítésén és a jogosultság regisztrációs ablakon alapul. A jogosult felhasználó kódja nagyjából ugyanúgy néz ki, ezért nem látok okot a megkettőzésre.

Jogosulatlan felhasználó

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;

}

}

}
}

És természetesen a felhasználónak meg kell kapnia az oldalak bizonyos tartalmát, így a válaszokhoz a következő modul található, amely a forrásigénylés megválaszolásáért felel.

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

De annak érdekében, hogy megmutassam a felhasználónak a profilját és a többi felhasználó profilját, úgy döntöttem, hogy a RazorEngine-t, vagy inkább annak egy részét használom. Ez magában foglalja a rossz kérések kezelését és a megfelelő hibakód kiadását is.

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

És természetesen ahhoz, hogy a jogosult felhasználók ellenőrzése működjön, engedélyre van szükség. Az engedélyezési modul kölcsönhatásba lép az adatbázissal. Az oldalon található űrlapokról kapott adatok a kontextusból elemzésre kerülnek, a felhasználó mentésre kerül, és cserébe cookie-kat és hozzáférést kap a szolgáltatáshoz.

Engedélyezési modul

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

És így néz ki az adatbázis:

Adatbázis

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


És minden úgy működik, mint az óramű, az engedélyezési és regisztrációs munka, a szolgáltatáshoz való hozzáférés minimális funkcionalitása már elérhető, és itt az ideje, hogy írjon egy kérelmet, és az egészet összekapcsolja a fő funkciókkal, amelyekért minden megtörténik.

4. fejezet

Hogy csökkentsem a két alkalmazás két platformra írásának munkaerőköltségét, úgy döntöttem, hogy létrehozok egy keresztplatformot a Xamarin.Forms-on. Még egyszer, hála annak, hogy C#-ban van. Miután elkészítettem egy tesztalkalmazást, amely egyszerűen adatokat küld a szervernek, egy érdekes pillanatba futottam. Az eszköztől érkező kéréshez, szórakozásból, implementáltam a HttpClient-en, és rádobtam a szerverre HttpRequestMessage, amely az engedélyezési űrlap adatait tartalmazza json formátumban. Anélkül, hogy bármire különösebben számítottam volna, megnyitottam a szervernaplót, és láttam egy kérést az eszköztől az ott található összes adattal. Könnyű kábulat, mindennek a tudata, ami az elmúlt 3 hét bágyadt estéje során történt. Az elküldött adatok helyességének ellenőrzésére összeállítottam egy tesztszervert a HttpListneren. Miután a következő kérés már rajta volt, pár sornyi kódban szétszedtem, az űrlapból megkaptam a KeyValuePair adatokat. A lekérdezés elemzése két sorra csökkent.

Elkezdtem tovább tesztelni, korábban nem volt szó róla, de az előző szerveren mégis megvalósítottam egy websocketekre épített chatet. Elég jól működött, de maga a Tcp-n keresztüli interakció elve nyomasztó volt, túl sok pluszt kellett produkálni ahhoz, hogy két felhasználó interakcióját helyesen építsük fel a levelezés naplózásával. Ez magában foglalja a kapcsolatváltási kérés elemzését és a válasz gyűjtését az RFC 6455 protokoll segítségével, ezért a tesztszerverben egy egyszerű websocket kapcsolat létrehozása mellett döntöttem. Pusztán az érdeklődés kedvéért.

Chat kapcsolat

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

És működött. A szerver maga hozta létre a kapcsolatot, generált egy válaszkulcsot. Még az ssl-en keresztüli szerver regisztrációt sem kellett külön konfigurálnom, elég, ha a rendszerben már telepítve van egy tanúsítvány a kívánt portra.

Eszközoldalon és oldaloldalon két kliens váltott üzenetet, mindez naplózásra került. Nincsenek hatalmas elemzők, amelyek lelassítják a szervert, erre nem volt szükség. A válaszidő 200 ms-ról 40-30 ms-ra csökkent. És az egyetlen helyes döntésre jutottam.

C# .Net keretrendszeren alapuló fejlett kerékpáros vagy kliens-szerver alkalmazás

Dobd ki a jelenlegi szerver implementációt Tcp-n és írj át mindent Http alá. Most a projekt az újratervezés szakaszában van, de teljesen más interakciós elvek szerint. Az eszközök és az oldal működése szinkronizált és hibakereső, közös koncepcióval rendelkezik, azzal a különbséggel, hogy az eszközöknek nem kell html oldalakat generálniuk.

Teljesítmény

"Ha nem ismered a gázlót, ne dugd a fejed a vízbe" Úgy gondolom, hogy a munka megkezdése előtt pontosabban meg kellett volna határoznom a célokat és a célkitűzéseket, valamint el kell mélyednem a szükséges technológiák és módszerek tanulmányozásában a különféle ügyfeleken történő megvalósításukhoz. A projekt már a végéhez közeledik, de talán még visszatérek és beszélek arról, hogyan rontottam el megint bizonyos dolgokat. Sokat tanultam a fejlesztési folyamat során, de van még mit tanulnom a jövőben. Ha idáig olvastad, akkor köszönöm, hogy elolvastad.

Forrás: will.com