Täiustatud jalgratta- või klient-serveri rakendus, mis põhineb C# .Neti raamistikul

Kanne

Kõik sai alguse sellest, et kolleeg soovitas mul teha väike veebiteenus. See pidi olema midagi tinderi taolist, kuid IT hangouti jaoks. Funktsionaalsus on ülilihtne, registreerute, täidate profiili ja liigute põhipunkti juurde, nimelt vestluskaaslase leidmise ja sidemete laiendamise ning uute tutvuste loomiseni.

Siinkohal pean kõrvale kalduma ja natuke endast rääkima, et edaspidi oleks selgem, miks ma arengus just sellised sammud ette võtsin.

Hetkel töötan mängustuudios tehnilise kunstniku ametikohal, minu C# programmeerimiskogemus põhines ainult Unity jaoks skriptide ja utiliitide kirjutamisel ning lisaks sellele pistikprogrammide loomisel madala tasemega tööks android-seadmetega. Väljaspool seda väikest maailma pole ma veel välja saanud ja siis avanes selline võimalus.

Osa 1. Raami prototüüpimine

Olles otsustanud, milline see teenus olema saab, hakkasin otsima rakendusvõimalusi. Lihtsaim viis oleks leida mingi valmislahendus, millele nagu öökull maakeral meie mehhaanika peale tõmmata ja kogu asi avaliku tsenderduse alla panna.
Kuid see pole huvitav, ma ei näinud selles väljakutset ja mõtet ning seetõttu hakkasin uurima veebitehnoloogiaid ja nendega suhtlemise meetodeid.

Uuring algas C # .Neti artiklite ja dokumentatsiooni vaatamisega. Siit leidsin erinevaid viise ülesande täitmiseks. Võrguga suhtlemiseks on palju mehhanisme, alates täisväärtuslikest lahendustest, nagu ASP.Net või Azure'i teenused, kuni otsese suhtluseni TcpHttp-ühendustega.

Olles teinud esimese katse ASP-ga, tühistasin selle kohe, minu arvates oli see meie teenuse jaoks liiga raske otsus. Me ei kasuta kolmandikku selle platvormi võimalustest, seega jätkasin otsinguid. Valik tekkis TCP ja Http klient-serveri vahel. Siin, Habré's, kohtasin ma artiklit selle kohta mitme lõimega server, mille kogumisel ja testimisel otsustasin keskenduda TCP-ühendustega suhtlemisele, millegipärast arvasin, et http ei võimalda mul platvormidevahelist lahendust luua.

Serveri esimene versioon hõlmas ühenduste haldamist, staatilise veebilehe sisu teenindamist ja kasutajate andmebaasi. Alustuseks otsustasin luua saidiga töötamiseks funktsiooni, et hiljem saaksin siin siduda rakenduste töötlemise androidis ja ios-is.

Siin on mõni kood
Peamine lõim, mis võtab kliente vastu lõputus ahelas:

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

Kliendihaldur ise:

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

Ja esimene kohalikule SQL-ile ehitatud andmebaas:

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

Nagu näete, erineb see versioon artiklis olevast vähe. Siia lisati tegelikult ainult lehekülgede laadimine arvutis olevast kaustast ja andmebaas (mis, muide, antud versioonis ei töötanud, vale ühenduse arhitektuuri tõttu).

2. peatükk

Pärast serveri testimist jõudsin järeldusele, et see oleks suurepärane lahendus (spoiler: ei), meie teenuse jaoks, nii et projekt hakkas omandama loogikat.
Samm-sammult hakkasid ilmuma uued moodulid ja serveri funktsionaalsus kasvas. Serveril on testdomeen ja ssl-ühenduse krüptimine.

Veidi rohkem koodi, mis kirjeldab serveri loogikat ja klientide töötlemist
Serveri värskendatud versioon, sealhulgas sertifikaadi kasutamine.

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

Samuti uus kliendihaldur, millel on ssl-i kaudu volitus:

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

}
}

Kuid kuna server töötab eranditult TCP-ühendusel, on vaja luua moodul, mis tuvastaks päringu konteksti. Otsustasin, et siia sobib parser, mis jagab kliendi päringu eraldi osadeks, millega saan suhelda, et anda kliendile vajalikud vastused.

parser

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

Selle olemus seisneb selles, et regulaaravaldiste abil saab taotlus osadeks jagada. Saame kliendilt sõnumi, valime esimese rea, mis sisaldab meetodit ja päringu URL-i. Seejärel loeme päised, mille juhime massiivi kujul HeaderName = Content, ja leiame ka kaasneva sisu (näiteks päringustringi), kui see on olemas, mille samuti juhime sarnasesse massiivi. Lisaks selgitab parser välja, kas praegune klient on volitatud ja salvestab tema andmed endasse. Kõik volitatud klientide päringud sisaldavad autoriseerimisräsi, mis salvestatakse küpsistesse, tänu millele on võimalik kahte tüüpi klientide edasist tööloogikat eraldada ja neile õigeid vastuseid anda.

Noh, väike kena funktsioon, mis tuleks teisaldada eraldi moodulisse, teisendades sellised päringud nagu "site.com/@UserName" dünaamiliselt loodud kasutajalehtedeks. Pärast päringu töötlemist tulevad mängu järgmised moodulid.

Peatükk 3. Juhtraua paigaldamine, keti määrimine

Niipea, kui parser on lõpetatud, hakkab mängu töötleja, kes annab serverile täiendavaid juhiseid ja jagab juhtimise kaheks osaks.

lihtne käitleja

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

Tegelikult kontrollitakse kasutaja autoriseerimist ainult üks kord, pärast mida algab päringu töötlemine.

Kliendikontrollerid
Kui kasutajal pole autoriseerimist, siis tema jaoks põhineb funktsionaalsus ainult kasutajaprofiilide kuvamisel ja autoriseerimise registreerimise aknal. Volitatud kasutaja kood näeb välja umbes sama, nii et ma ei näe põhjust seda dubleerida.

Volitamata kasutaja

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;

}

}

}
}

Ja loomulikult peab kasutaja saama osa lehtede sisust, seega on vastuste jaoks järgmine moodul, mis vastutab ressursipäringule vastamise eest.

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

Kuid selleks, et näidata kasutajale tema ja teiste kasutajate profiile, otsustasin kasutada RazorEngine'i või pigem osa sellest. See hõlmab ka halbade taotluste käsitlemist ja sobiva veakoodi väljastamist.

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

Ja selleks, et volitatud kasutajate kontrollimine toimiks, on loomulikult vaja autoriseerimist. Autoriseerimismoodul suhtleb andmebaasiga. Saidi vormidelt saadud andmed sõelutakse kontekstist lähtuvalt, kasutaja salvestatakse ja saab vastutasuks küpsiseid ja juurdepääsu teenusele.

Autoriseerimismoodul

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

Ja andmebaas näeb välja selline:

Andmebaas

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


Ja kõik toimib nagu kellavärk, autoriseerimis- ja registreerimistööd, teenusele juurdepääsu minimaalne funktsionaalsus on juba olemas ja on aeg kirjutada avaldus ja siduda kogu asi põhifunktsioonidega, mille jaoks kõik on tehtud.

4. peatükk

Kahe rakenduse kahele platvormile kirjutamise tööjõukulude vähendamiseks otsustasin teha Xamarin.Formsis platvormiülese platvormi. Jällegi tänu sellele, et see on C#-s. Olles teinud testrakenduse, mis lihtsalt saadab andmeid serverisse, sattusin ühe huvitava hetkeni. Seadmelt päringu jaoks rakendasin selle nalja pärast HttpClienti ja viskasin serverisse HttpRequestMessage, mis sisaldab json-vormingus andmeid autoriseerimisvormist. Midagi erilist oodamata avasin serveri logi ja nägin seadmelt päringut koos kõigi seal olevate andmetega. Kerge uimasus, teadlikkus kõigest, mis on viimase 3 nädala kestnud õhtu jooksul tehtud. Saadetud andmete õigsuse kontrollimiseks koostasin HttpListneris testserveri. Saanud järgmise päringu juba peal, võtsin selle paari koodirea kaupa lahti, sain vormilt KeyValuePairi andmed. Päringu sõelumine on vähendatud kahele reale.

Hakkasin edasi testima, sellest polnud varem juttu, aga eelmises serveris rakendasin siiski websocketsidele üles ehitatud vestluse. See töötas päris hästi, kuid Tcp kaudu suhtlemise põhimõte oli masendav, kahe kasutaja suhtluse korrektseks koostamiseks kirjavahetuse logimisega tuli toota liiga palju lisa. See hõlmab ühenduse vahetamise päringu parsimist ja vastuse kogumist protokolli RFC 6455 abil. Seetõttu otsustasin testserveris luua lihtsa veebipesa ühenduse. Puhtalt huvi pärast.

Vestlusühendus

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

Ja see töötas. Server ise lõi ühenduse, genereeris vastusevõtme. Ma ei pidanud isegi ssl-i kaudu serveri registreerimist eraldi seadistama, piisab, kui süsteemis on juba vajalikku porti installitud sertifikaat.

Seadme poolel ja saidi poolel vahetasid kaks klienti sõnumeid, kõik see logiti. Ükski tohutu parseer ei aeglustaks serverit, seda polnud vaja. Reaktsiooniaega on vähendatud 200 ms-lt 40-30 ms-le. Ja ma tegin ainsa õige otsuse.

Täiustatud jalgratta- või klient-serveri rakendus, mis põhineb C# .Neti raamistikul

Visake praegune serveri juurutus Tcp-s välja ja kirjutage kõik Http alla. Nüüd on projekt ümberkujundamise staadiumis, kuid täiesti erinevate interaktsiooni põhimõtete järgi. Seadmete ja saidi töö on sünkroonitud ja silutud ning sellel on ühine kontseptsioon, ainsa erinevusega, et seadmed ei pea html-lehti genereerima.

Väljund

"Kui te ei tunne fordit, ärge pistke pead vette" Arvan, et enne tööle asumist oleksin pidanud eesmärgid ja eesmärgid selgemalt määratlema, samuti süvenema erinevatel klientidel nende rakendamiseks vajalike tehnoloogiate ja meetodite uurimisse. Projekt on juba lõpusirgel, aga võib-olla tulen tagasi, et rääkida, kuidas ma mingid asjad jälle sassi keerasin. Arendusprotsessi käigus õppisin palju, kuid tulevikus on veel õppida. Kui olete siiani lugenud, siis tänan teid lugemise eest.

Allikas: www.habr.com