Berbasikal lanjutan atau aplikasi pelayan pelanggan berdasarkan rangka kerja C# .Net

Entry

Semuanya bermula apabila rakan sekerja mencadangkan saya membuat perkhidmatan web kecil. Ia sepatutnya menjadi sesuatu seperti tinder, tetapi untuk hangout IT. Fungsinya sangat mudah, anda mendaftar, mengisi profil dan beralih ke perkara utama, iaitu, mencari teman bicara dan mengembangkan hubungan anda dan membuat kenalan baru.

Di sini saya perlu menyimpang dan menceritakan sedikit tentang diri saya, supaya pada masa akan datang lebih jelas mengapa saya mengambil langkah-langkah pembangunan.

Pada masa ini saya memegang jawatan sebagai Artis Teknikal dalam studio permainan, pengalaman pengaturcaraan C# saya hanya berdasarkan penulisan skrip dan utiliti untuk Unity dan, sebagai tambahan kepada ini, mencipta pemalam untuk kerja peringkat rendah dengan peranti android. Di luar dunia ini, saya belum lagi memilih dan kemudian muncul peluang sedemikian.

Bahagian 1. Prototaip Bingkai

Setelah memutuskan seperti apa perkhidmatan ini, saya mula mencari pilihan untuk pelaksanaan. Cara paling mudah ialah mencari beberapa jenis penyelesaian siap, di mana, seperti burung hantu di dunia, anda boleh menarik mekanik kami dan meletakkan semuanya untuk kecaman awam.
Tetapi ini tidak menarik, saya tidak melihat apa-apa cabaran dan rasa dalam hal ini, dan oleh itu saya mula mempelajari teknologi web dan kaedah berinteraksi dengan mereka.

Kajian bermula dengan melihat artikel dan dokumentasi mengenai C # .Net. Di sini saya menemui pelbagai cara untuk menyelesaikan tugas. Terdapat banyak mekanisme untuk berinteraksi dengan rangkaian, daripada penyelesaian lengkap seperti perkhidmatan ASP.Net atau Azure, kepada interaksi langsung dengan sambungan TcpHttp.

Setelah membuat percubaan pertama dengan ASP, saya segera membatalkannya, pada pendapat saya ia adalah keputusan yang terlalu sukar untuk perkhidmatan kami. Kami tidak akan menggunakan walaupun satu pertiga daripada keupayaan platform ini, jadi saya meneruskan pencarian saya. Pilihan timbul antara TCP dan pelayan pelanggan Http. Di sini, di HabrΓ©, saya terjumpa satu artikel tentang pelayan berbilang benang, setelah mengumpul dan menguji yang mana, saya memutuskan untuk menumpukan pada berinteraksi dengan sambungan TCP, atas sebab tertentu saya fikir http tidak akan membenarkan saya mencipta penyelesaian merentas platform.

Versi pertama pelayan termasuk pengendalian sambungan, menyediakan kandungan halaman web statik, dan termasuk pangkalan data pengguna. Dan sebagai permulaan, saya memutuskan untuk membina fungsi untuk bekerja dengan tapak, supaya kemudian saya boleh mengikat pemprosesan aplikasi pada android dan ios di sini.

Berikut adalah beberapa kod
Benang utama menerima pelanggan dalam gelung yang tidak berkesudahan:

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

Pengendali pelanggan itu sendiri:

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

Dan pangkalan data pertama yang dibina pada SQL tempatan:

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

Seperti yang anda lihat, versi ini berbeza sedikit daripada versi dalam artikel. Sebenarnya, hanya memuatkan halaman dari folder pada komputer dan pangkalan data telah ditambahkan di sini (yang, dengan cara itu, tidak berfungsi dalam versi ini, disebabkan oleh seni bina sambungan yang salah).

bab 2

Selepas menguji pelayan, saya membuat kesimpulan bahawa ini akan menjadi penyelesaian yang hebat (spoiler: tidak), untuk perkhidmatan kami, jadi projek itu mula memperoleh logik.
Langkah demi langkah, modul baharu mula muncul dan kefungsian pelayan bertambah. Pelayan telah mendapat domain ujian dan penyulitan sambungan ssl.

Sedikit lagi kod yang menerangkan logik pelayan dan pemprosesan pelanggan
Versi pelayan yang dikemas kini, termasuk penggunaan sijil.

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

Serta pengendali pelanggan baharu dengan kebenaran melalui 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);
}

}
}

Tetapi oleh kerana pelayan berfungsi secara eksklusif pada sambungan TCP, adalah perlu untuk mencipta modul yang boleh mengenali konteks permintaan. Saya memutuskan bahawa penghurai adalah sesuai di sini yang akan memecahkan permintaan daripada pelanggan kepada bahagian yang berasingan yang boleh saya berinteraksi untuk memberikan pelanggan jawapan yang diperlukan.

penghurai

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

Intipatinya terletak pada hakikat bahawa dengan bantuan ungkapan biasa untuk memecahkan permintaan kepada beberapa bahagian. Kami menerima mesej daripada pelanggan, pilih baris pertama, yang mengandungi kaedah dan url permintaan. Kemudian kita membaca pengepala, yang kita pandu ke dalam tatasusunan bentuk HeaderName = Kandungan, dan juga mencari, jika ada, kandungan yang disertakan (contohnya, rentetan pertanyaan) yang juga kita pacu ke dalam tatasusunan yang serupa. Di samping itu, penghurai mengetahui sama ada klien semasa dibenarkan dan menyimpan datanya. Semua permintaan daripada pelanggan yang diberi kuasa mengandungi cincang keizinan, yang disimpan dalam kuki, yang mana anda boleh memisahkan logik kerja selanjutnya untuk dua jenis pelanggan dan memberi mereka jawapan yang betul.

Nah, ciri kecil dan bagus yang harus dialihkan ke dalam modul yang berasingan, menukar permintaan seperti "site.com/@UserName" kepada halaman pengguna yang dijana secara dinamik. Selepas memproses permintaan, modul berikut mula dimainkan.

Bab 3. Memasang bar hendal, melincirkan rantai

Sebaik sahaja penghurai selesai, pengendali mula bermain, memberikan arahan lanjut kepada pelayan dan membahagikan kawalan kepada dua bahagian.

pengendali mudah

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

Malah, terdapat hanya satu semakan untuk kebenaran pengguna, selepas itu pemprosesan permintaan bermula.

Pengawal Pelanggan
Sekiranya pengguna tidak diberi kuasa, maka baginya fungsi itu hanya berdasarkan paparan profil pengguna dan tetingkap pendaftaran kebenaran. Kod untuk pengguna yang dibenarkan kelihatan lebih kurang sama, jadi saya tidak nampak sebab untuk menduplikasikannya.

Pengguna yang tidak dibenarkan

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;

}

}

}
}

Dan sudah tentu, pengguna mesti menerima beberapa kandungan halaman, jadi untuk jawapan terdapat modul berikut, yang bertanggungjawab untuk menjawab permintaan untuk sumber.

Pengawal Penulis

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

Tetapi untuk menunjukkan kepada pengguna profilnya dan profil pengguna lain, saya memutuskan untuk menggunakan RazorEngine, atau lebih tepatnya sebahagian daripadanya. Ia juga termasuk mengendalikan permintaan buruk dan mengeluarkan kod ralat yang sesuai.

Pengawal Cukur

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

Dan sudah tentu, untuk pengesahan pengguna yang dibenarkan berfungsi, kebenaran diperlukan. Modul kebenaran berinteraksi dengan pangkalan data. Data yang diterima daripada borang di tapak dihuraikan daripada konteks, pengguna disimpan dan menerima kuki dan akses kepada perkhidmatan sebagai balasan.

Modul kebenaran

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

Dan ini adalah bagaimana pangkalan data kelihatan seperti:

Pangkalan data

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


Dan semuanya berfungsi seperti kerja jam, kebenaran dan kerja pendaftaran, kefungsian minimum akses kepada perkhidmatan sudah tersedia dan sudah tiba masanya untuk menulis aplikasi dan mengikat semuanya dengan fungsi utama yang semuanya dilakukan.

Bab 4

Untuk mengurangkan kos buruh menulis dua aplikasi untuk dua platform, saya memutuskan untuk membuat platform silang pada Xamarin.Forms. Sekali lagi, terima kasih kepada fakta bahawa ia berada dalam C#. Setelah membuat aplikasi ujian yang hanya menghantar data ke pelayan, saya menghadapi satu detik yang menarik. Untuk permintaan daripada peranti, untuk keseronokan, saya melaksanakannya pada HttpClient dan melemparkannya pada pelayan HttpRequestMessage yang mengandungi data daripada borang kebenaran dalam format json. Tanpa mengharapkan apa-apa khususnya, saya membuka log pelayan dan melihat permintaan daripada peranti dengan semua data di sana. Bosan ringan, kesedaran tentang segala yang telah dilakukan sepanjang 3 minggu petang yang lesu. Untuk menyemak ketepatan data yang dihantar, saya memasang pelayan ujian pada HttpListner. Setelah menerima permintaan seterusnya, saya membahagikannya dalam beberapa baris kod, mendapatkan data KeyValuePair daripada borang. Penghuraian pertanyaan dikurangkan kepada dua baris.

Saya mula menguji lebih lanjut, ia tidak disebutkan sebelum ini, tetapi pada pelayan sebelumnya saya masih melaksanakan sembang yang dibina pada soket web. Ia berfungsi dengan baik, tetapi prinsip interaksi melalui Tcp sangat menyedihkan, terlalu banyak tambahan perlu dihasilkan untuk membina interaksi dua pengguna dengan betul dengan pengelogan surat-menyurat. Ini termasuk menghuraikan permintaan untuk penukaran sambungan dan mengumpul respons menggunakan protokol RFC 6455. Oleh itu, dalam pelayan ujian, saya memutuskan untuk membuat sambungan soket web yang mudah. Semata-mata untuk kepentingan.

Sambungan sembang

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

Dan ia berjaya. Pelayan itu sendiri menyediakan sambungan, menghasilkan kunci tindak balas. Saya tidak perlu mengkonfigurasi pendaftaran pelayan secara berasingan melalui ssl, sudah cukup bahawa sistem sudah mempunyai sijil yang dipasang pada port yang diperlukan.

Di bahagian peranti dan di sisi tapak, dua pelanggan bertukar-tukar mesej, semua ini telah direkodkan. Tiada penghurai besar yang memperlahankan pelayan, semua ini tidak diperlukan. Masa tindak balas telah dikurangkan daripada 200ms kepada 40-30ms. Dan saya datang kepada satu-satunya keputusan yang tepat.

Berbasikal lanjutan atau aplikasi pelayan pelanggan berdasarkan rangka kerja C# .Net

Buang pelaksanaan pelayan semasa pada Tcp dan tulis semula semuanya di bawah Http. Kini projek itu berada di peringkat reka bentuk semula, tetapi mengikut prinsip interaksi yang sama sekali berbeza. Pengendalian peranti dan tapak disegerakkan dan dinyahpepijat serta mempunyai konsep yang sama, dengan satu-satunya perbezaan bahawa peranti tidak perlu menjana halaman html.

Output

"Tidak tahu ford, jangan cucuk kepala anda ke dalam air" Saya fikir, sebelum memulakan kerja, saya sepatutnya menentukan matlamat dan objektif dengan lebih jelas, serta menyelidiki kajian teknologi dan kaedah yang diperlukan untuk pelaksanaannya pada pelbagai pelanggan. Projek itu sudah hampir siap, tetapi mungkin saya akan kembali untuk bercakap tentang bagaimana saya mengacaukan perkara tertentu lagi. Saya belajar banyak semasa proses pembangunan, tetapi banyak lagi yang perlu dipelajari pada masa hadapan. Jika anda telah membaca sejauh ini, maka terima kasih kerana membaca.

Sumber: www.habr.com