C# .Net çerçevesine dayalı gelişmiş çevrim veya istemci-sunucu uygulaması

Giriş

Her şey bir meslektaşımın küçük bir web hizmeti oluşturmamı önermesiyle başladı. Tinder gibi bir şey olması gerekiyordu ama BT topluluğu için. İşlevselliği son derece basittir; kaydolursunuz, bir profil doldurursunuz ve asıl noktaya, yani konuşacak birini bulup bağlantılarınızı genişletip yeni tanıdıklar edinmeye geçersiniz.

Burada biraz geri çekilmeli ve kendimden biraz bahsetmeliyim ki gelecekte neden gelişimde bu tür adımlar attığımı daha net anlayım.

Şu anda bir oyun stüdyosunda Teknik Sanatçı olarak görev yapıyorum, C#'taki programlama deneyimim yalnızca Unity için komut dosyaları ve yardımcı programlar yazmaya ve buna ek olarak Android cihazlarla düşük seviyeli çalışmalar için eklentiler oluşturmaya dayanıyordu. Henüz bu küçük dünyanın ötesine geçme cesaretini göstermemiştim ve sonra böyle bir fırsat ortaya çıktı.

Bölüm 1. Çerçeve prototipleme

Bu hizmetin nasıl olacağına karar verdikten sonra uygulama seçeneklerini aramaya başladım. En kolay şey, dünya üzerindeki bir baykuş gibi, mekaniklerimizin çekilebileceği ve her şeyin kamuoyunun kınamasına maruz kalabileceği bir tür hazır çözüm bulmak olacaktır.
Ancak bu ilginç değil, bunda herhangi bir zorluk veya anlam görmedim ve bu nedenle web teknolojilerini ve onlarla etkileşim kurma yöntemlerini incelemeye başladım.

C# .Net ile ilgili makalelere ve belgelere bakarak çalışmaya başladım. Burada görevi tamamlamanın çeşitli yollarını buldum. ASP.Net veya Azure hizmetleri gibi tam kapsamlı çözümlerden TcpHttp bağlantılarıyla doğrudan etkileşime kadar ağla etkileşime geçmek için birçok mekanizma vardır.

ASP ile ilk denememi yaptıktan sonra hemen reddettim; bence bu, hizmetimiz için çok zor bir karardı. Bu platformun yeteneklerinin üçte birini bile kullanamayacağız, bu yüzden arayışıma devam ettim. Seçim TCP ve Http istemci-sunucu arasındaydı. Burada, Habré'de şununla ilgili bir makaleye rastladım: çok iş parçacıklı sunucu, onu toplayıp test ettikten sonra, özellikle TCP bağlantılarıyla etkileşime odaklanmaya karar verdim, bazı nedenlerden dolayı http'nin platformlar arası bir çözüm oluşturmama izin vermeyeceğini düşündüm.

Sunucunun ilk sürümü bağlantı işlemeyi içeriyordu, statik web sayfası içeriği sunuyordu ve bir kullanıcı veritabanı içeriyordu. Ve başlangıçta, siteyle çalışmaya yönelik işlevsellik oluşturmaya karar verdim, böylece daha sonra uygulamanın Android ve iOS'ta işlenmesini ekleyebildim.

İşte bazı kodlar
İstemcileri sonsuz bir döngüde alan ana iş parçacığı:

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

İstemci işleyicisinin kendisi:

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

Ve yerel SQL üzerine kurulu ilk veritabanı:

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

Gördüğünüz gibi, bu sürüm makaledeki sürümden çok az farklı. Aslında, burada sadece bilgisayardaki bir klasörden ve bir veritabanından sayfaların yüklenmesini ekledik (bu arada, yanlış bağlantı mimarisi nedeniyle bu sürümde çalışmadı).

Bölüm 2. Tekerleklerin vidalanması

Sunucuyu test ettikten sonra bunun mükemmel bir çözüm olacağı sonucuna vardım(spoyler: hayır), hizmetimiz için, böylece proje mantık kazanmaya başladı.
Adım adım yeni modüller ortaya çıkmaya başladı ve sunucunun işlevselliği genişletildi. Sunucu bir test alanı ve SSL bağlantı şifrelemesi aldı.

Sunucu ve istemci işlemlerinin mantığını açıklayan biraz daha kod
Sertifika kullanımını içeren sunucunun güncellenmiş bir sürümü.

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

Ayrıca SSL yetkilendirmesine sahip yeni bir istemci işleyicisi:

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

}
}

Ancak sunucu yalnızca TCP bağlantısı üzerinde çalıştığı için isteğin içeriğini tanıyabilecek bir modül oluşturmak gerekir. Müşteriye gerekli yanıtları vermek için müşteriden gelen isteği ayrı parçalara bölecek ve etkileşimde bulunabileceğim bir ayrıştırıcının burada uygun olacağına karar verdim.

ayrıştırıcı

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

Bunun özü, normal ifadeleri kullanarak isteği parçalara ayırmaktır. İstemciden bir mesaj alıyoruz, yöntemi ve istek URL'sini içeren ilk satırı seçiyoruz. Daha sonra HeaderName=Content şeklinde bir diziye koyduğumuz başlıkları okuyoruz ve ayrıca benzer bir diziye koyduğumuz varsa eşlik eden içeriği de buluyoruz (örneğin sorgu dizesi). Ayrıca ayrıştırıcı mevcut istemcinin yetkili olup olmadığını öğrenir ve verilerini saklar. Yetkili istemcilerden gelen tüm talepler, çerezlerde saklanan bir yetkilendirme karması içerir; bu sayede iki tür istemci için daha fazla işletim mantığını ayırmak ve onlara doğru yanıtları vermek mümkündür.

Ayrı bir modüle konulmaya değer küçük ve hoş bir özellik, "site.com/@KullanıcıAdı" gibi sorguların dinamik olarak oluşturulmuş kullanıcı sayfalarına dönüştürülmesi. Talebi işledikten sonra aşağıdaki modüller devreye giriyor.

Bölüm 3. Direksiyon simidinin takılması, zincirin yağlanması

Ayrıştırıcı işini tamamlar tamamlamaz işleyici devreye girerek sunucuya daha fazla talimat verir ve kontrolü iki parçaya böler.

Basit işleyici

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

Aslında, kullanıcı yetkilendirmesi için tek bir kontrol vardır ve bundan sonra isteğin işlenmesi başlar.

İstemci denetleyicileri
Kullanıcı yetkili değilse, onun için işlevsellik yalnızca kullanıcı profillerinin görüntülenmesine ve yetkilendirme kayıt penceresine dayanır. Yetkili bir kullanıcının kodu hemen hemen aynı görünüyor, bu yüzden onu kopyalamak için bir neden göremiyorum.

Yetkisiz kullanıcı

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;

}

}

}
}

Ve elbette, kullanıcının bir tür sayfa içeriği alması gerekir, bu nedenle yanıtlar için kaynak isteklerine yanıt vermekten sorumlu olan aşağıdaki modül vardır.

YazarKontrolcü

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

Ancak kullanıcıya kendi profilini ve diğer kullanıcıların profillerini göstermek için RazorEngine'i veya daha doğrusu bir kısmını kullanmaya karar verdim. Ayrıca geçersiz isteklerin işlenmesini ve uygun bir hata kodunun verilmesini de içerir.

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

Ve elbette yetkili kullanıcıların doğrulanmasının çalışabilmesi için yetkilendirmeye ihtiyaç vardır. Yetkilendirme modülü veritabanıyla etkileşime girer. Sitedeki formlardan alınan veriler bağlamdan ayrıştırılır, kullanıcı kaydedilir ve karşılığında çerezler alınır ve hizmete erişim sağlanır.

Yetkilendirme modülü

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

Veritabanı işleme şuna benzer:

veritabanı

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


Ve her şey saat gibi işliyor, yetkilendirme ve kayıt çalışması, hizmete erişim için minimum işlevsellik zaten mevcut ve bir uygulama yazmanın ve her şeyi, her şeyin yapıldığı ana işlevlerle birbirine bağlamanın zamanı geldi.

Bölüm 4. Bisikleti çöpe atmak

İki platform için iki uygulama yazmanın işçilik maliyetlerini azaltmak için Xamarin.Forms üzerinde çapraz platform yapmaya karar verdim. Yine C# dilinde olması sayesinde. Sunucuya basitçe veri gönderen bir test uygulaması yaptığımda ilginç bir noktaya rastladım. Bir cihazdan gelen istek için, eğlence olsun diye, HttpClient üzerinde uyguladım ve yetkilendirme formundaki verileri json formatında içeren HttpRequestMessage sunucusuna gönderdim. Özellikle hiçbir şey beklemeden sunucu günlüğünü açtım ve orada cihazdan tüm verileri içeren bir istek gördüm. Hafif bir sersemlik, son 3 hafta boyunca durgun bir akşamda yapılan her şeyin farkındalığı. Gönderilen verilerin doğruluğunu kontrol etmek için HttpListner üzerinde bir test sunucusu kurdum. Halihazırda başka bir istek aldığım için bunu birkaç satır kodla parçalara ayırdım ve formdan bir KeyValuePair verisi aldım. Sorgunun ayrıştırılması iki satıra indirildi.

Daha fazla test etmeye başladım, daha önce bahsedilmemişti, ancak önceki sunucuda ayrıca websockets üzerine kurulu bir sohbet de uyguladım. Oldukça iyi çalıştı, ancak Tcp aracılığıyla etkileşim ilkesi bunaltıcıydı; iki kullanıcının etkileşimini bir yazışma günlüğüyle yetkin bir şekilde oluşturmak için çok fazla gereksiz iş yapılması gerekiyordu. Bu, bağlantıyı değiştirmek için bir isteği ayrıştırmayı ve RFC 6455 protokolünü kullanarak bir yanıt toplamayı içerir.Bu nedenle, test sunucusunda basit bir websocket bağlantısı oluşturmaya karar verdim. Sadece eğlence için.

Sohbete bağlan

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

Ve işe yaradı. Sunucunun kendisi bağlantıyı yapılandırdı ve bir yanıt anahtarı oluşturdu. Sunucu kaydını SSL aracılığıyla ayrı ayrı yapılandırmam bile gerekmedi; sistemin gerekli bağlantı noktasında zaten bir sertifikanın kurulu olması yeterliydi.

Cihaz tarafında ve site tarafında iki istemci mesaj alışverişinde bulundu, tüm bunlar günlüğe kaydedildi. Sunucuyu yavaşlatan büyük ayrıştırıcılar yok, bunların hiçbirine gerek yoktu. Tepki süresi 200 ms'den 40-30 ms'ye düştü. Ve tek doğru karara vardım.

C# .Net çerçevesine dayalı gelişmiş çevrim veya istemci-sunucu uygulaması

Tcp'deki mevcut sunucu uygulamasını atın ve her şeyi Http altına yeniden yazın. Şimdi proje yeniden tasarım aşamasında, ancak tamamen farklı etkileşim ilkelerine göre. Cihazların ve sitenin çalışması senkronize edilir, hata ayıklanır ve ortak bir konsepte sahiptir; tek fark, cihazlar için HTML sayfaları oluşturmaya gerek olmamasıdır.

Aviator apk

“Geçidi bilmiyorsanız suya girmeyin” Çalışmaya başlamadan önce, daha net tanımlanmış amaç ve hedeflere sahip olmam gerektiğini ve ayrıca bunların çeşitli müşteriler üzerinde uygulanması için gerekli teknolojiler ve yöntemler üzerinde çalışmam gerektiğini düşünüyorum. Proje zaten tamamlanmak üzere ama belki tekrar gelip bazı şeyleri nasıl kurtardığım hakkında konuşurum. Geliştirme sürecinde çok şey öğrendim ama gelecekte öğrenecek daha çok şey var. Buraya kadar okuduysanız, bunu yaptığınız için teşekkür ederiz.

Kaynak: habr.com