Sissejuhatus
Kõik algas sellest, et kolleeg pakkus mulle välja mõelda väike veebi teenus. See pidi olema midagi nagu Tinder, aga IT kogukonna jaoks. Funktsionaalsus oli äärmiselt lihtne: registreerud, täidad oma profiili ja lähed edasi põhisisusse, nimelt vestluspartneri otsimise ja oma suhete laiendamise ning uute tutvuste saamise juurde.
Siin pean ma tegema commitendi ja natuke endast rääkima, et hiljem oleks selge, miks ma just selliseid samme arenduses ette võtsin.
Praegu olen ma Techniline Kunstnik ühes mängustuudios, minu C# programmeerimise kogemus piirdus ainult skriptide ja utiliitide kirjutamisega Unity jaoks ning lisaks selle madala taseme töötluse pluginatega Android-seadmetega. Selle väikese maailma piiridest ma veel väljapoole ei ole jõudnud ja nüüd tekkis võimalus.
Osa 1. Raami prototüüpimine
Otsustades, milline see teenus olema peaks, hakkasin ma otsima rakendamise võimalusi. Lihtsaim oleks olnud leida mõni valmis lahendus, millele nagu öökull globusele, saaksime oma mehhanismid rakendada ja kogu selle asja avalikku arutellu anda.
Aga see ei ole ju huvitav, ma ei näinud siin mingit väljakutset või mõtet, seetõttu hakkasin uurima veebitehnoloogiaid ja nende kasutamise meetodeid.
Uurimist alustasin C# .Net artiklite ja dokumentatsiooni lugemisest. Siit leidsin erinevaid viise ülesande täitmiseks. Siin on palju mehhanisme suhtlemiseks võrgus, alates täielikest lahendustest nagu ASP.Net või Azure teenused kuni otsese suhtlemiseni TcpHttp ühendustega.
Tehes oma esimese katse ASP-ga, hülgasin selle kohe, kuna see tundus minu arvates meie teenuse jaoks liiga keeruline lahendus. Me ei kasuta isegi kolmandikku selle platvormi võimalustest, seega jätkasin otsingut. Valik langes TCP ja Http kliendi-serveri vahel. Siin, Habras, sattusin artiklile , mille kokku kogusin ja testisin, otsustasin keskenduda TCP ühenduste kasutamisele, kuna mul oli tunne, et http ei võimalda mul luua platvormideüleset lahendust.
Esimene serveri versioon hõlmas ühenduste töötlemist, edastas staatilist veebilehe sisu ja sisaldas kasutajate andmebaasi. Alguses otsustasin ehitada funktsionaalsuse saidiga koostööks, et hiljem saaksin siia lisada ka rakenduse töötlemise Androidi ja iOS-i jaoks.
Siin on natuke koodi
Peamine voog, mis lõpmatus tsüklis kliente vastu võtab:
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);
}
}
}Klientide töötleja:
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 ="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 ="image/{Extension.Substring(1)}";
break;
default:
if (Extension.Length > 1)
{
ContentType ="application/{Extension.Substring(1)}";
}
muud
{
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 andmebaas, mis on loodud kohalikul SQL-il: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 users = db.GetTable(); foreach (var item in users) { Console.WriteLine("{item.login} {item.password}");"{item.login} {item.password}";
}
}
}
}
}
Nagu võib märgata, eristub see versioon vähe sellest, mis oli artiklis. Sisuliselt on siin ainult lisatud lehtede laadimine arvuti kaustast ja andmebaas (mille puhul ei töötanud see versioon vale ühenduse arhitektuuri tõttu).Peatükk 2. Rattad külge panemine
Serveri töötamise testimise järel jõudsin järeldusele, et see on meie teenuse jaoks suurepärane lahendus,spoiler: ei ole, mistõttu projekt hakkas loogikaga rikastuma.
Samm-sammult hakkasid ilmuma uued moodulid ning serveri funktsionaalsus laienes. Server sai testdomeeni ja ssl ühenduse krüpteerimise.Veidi veel koodi, mis kirjeldab serveri tööloogikat ja klientide töötlemist.
Uuendatud versioon serverist, mis sisaldab sertifikaadi kasutamist.kasutades System; kasutades System.Net; kasutades System.Net.Sockets; kasutades System.Reflection; kasutades System.Security; kasutades System.Security.Cryptography.X509Certificates; kasutades System.Security.Permissions; kasutades System.Security.Policy; kasutades System.Threading; nimega ClearServer { lõppklass 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("Käivitame serverit.." + 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("Lülitame teise domeeni"); 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(); } } }Ja uus SSL-autentimisega kliendihaldur:
kasutades ClearServer.Core.Requester; kasutades System; kasutades System.Net.Security; kasutades 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" +"|{DateTime.Now:g}n|------------n|{Client.Client.RemoteEndPoint}n|------------n|Viga: {e.Message}n|------------n|Autentimine ebaõnnestus - ühendus suletakse.n" +
"---------------------------------------------------------------------n");
SSlClientStream.Close();
Client.Close();
}
new RequestContext(SSlClientStream, Client);
}}
}
Kuna server töötab rangelt TCP-ühenduses, tuleb luua moodul, mis suudab tuvastada päringu konteksti. Otsustasin, et siia sobib parser, mis jagab kliendi päringu eraldi osadeks, millega saan suhelda, et edastada kliendile vajalikke vastuseid.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 HeadersValues; public List FormValues; private TcpClient TcpClient; private event Action 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(",{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;
{
string küpsis;
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 HeaderValues()
{
var values = new List();
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 ContentValues()
{
var values = new List();
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 põhiolemus on jagada päring regulaaravaldustega osadeks. Saame kliendilt sõnumi, tuvastame esimese rea, kus on meetod ja päringu URL. Seejärel loeme päiseid, mille paneme massiivi kujul NimiPäis=Sisukord, ning leiame, kui see on olemas, kaasneva sisu (näiteks querystring), mille paneme sarnasesse massiivi. Samuti selgitab parser välja, kas praegune klient on sisse loginud ja salvestab tema andmed. Kõik autoriseeritud klientide päringud sisaldavad autoriseerimise hash'i, mis salvestatakse küpsistesse, tänu millele on võimalik eristada edasist tööloogikat kahel erineval kliendi tüübil ja anda neile õiged vastused.Ja väikene, meeldiv funktsioon, mille oleks hea välja tuua eraldi moodulina, on päringute muutmine kujul „site.com/@Kasutajanimi“ dünaamiliselt genereeritavate kasutajalehtedena. Päringu töötlemise järel astuvad mängu järgmised moodulid.
Peatükk 3. Ratta paigaldamine, ketti määrimine
Kui parser on töö lõpetanud, asub tööle töötleja, mis annab serverile edasised juhised ja jagab juhtimise kaheks osaks.
Lihtne töötleja
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); }; } } }Põhimõtteliselt on siin ainult üks kontroll kasutaja autentimise kohta, pärast mida algab päringu töötlemine.
Kasutaja kontrollijad
Kui kasutaja ei ole autentitud, põhineb tema funktsionaalsus ainult kasutajaprofiilide kuvamisel ja registreerimise/autoriseerimise aknal. Autentitud kasutaja kood näeb välja enam-vähem sama, seega ei näe ma mõtet seda dubleerida.Autentimata 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);}
muud kui (Path.HasExtension(Context.RequestUrl) && File.Exists(ViewPath + Context.RequestUrl))
{
Header = WriteController.ContentType(Context.RequestUrl);
FilePath = ViewPath + Context.RequestUrl;
WriteController.DefaultWriter(Header, FilePath);
}
break;
}
break;juhtum "POST":
AuthorizationController.MethodRecognizer();
break;}
}
}
}
Kuid loomulikult peab kasutaja saama mingit sisu lehtedelt, seetõttu on olemas järgmine moodul, mis vastutab ressursside päringutele vastamise eest.WriterController
kasutades System; kasutades System.IO; kasutades System.Net.Security; kasutades System.Text; nimi ClearServer.Core.UserController { avalik klass WriteController { SslStream ClientStream; avalik WriteController(SslStream ClientStream) { see.ClientStream = ClientStream; } avalik void DefaultWriter(string Header, string FilePath) { FileStream fileStream; proovi { fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); Header ="{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 { }
}avalik string ContentType(string Uri)
{
string extension = Path.GetExtension(Uri);
string Header = "HTTP/1.1 200 OKnContent-Type:";
switch (extension)
{
case ".html":
case ".htm":
return"{Header} text/html";
case ".css":
return"{Header} text/css";
case ".js":
return"{Header} text/javascript";
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
return"{Header} image/{extension}";
default:
if (extension.Length > 1)
{
return"{Header} application/" + extension.Substring(1);
}
muud
{
return"{Header} application/unknown";
}
}
}avalik void OnFileRead(IAsyncResult ar)
{
if (ar.IsCompleted)
{
var file = (byte[])ar.AsyncState;
ClientStream.BeginWrite(file, 0, file.Length, OnClientSend, null);
}
}avalik void OnClientSend(IAsyncResult ar)
{
if (ar.IsCompleted)
{
ClientStream.Close();
}
}
}
Kuid et näidata kasutajale tema profiili ja teiste kasutajate profiile, otsustasin kasutada RazorEngine'i, täpsemalt selle osa. See hõlmab ka vigaste päringute töötlemist ja vastava 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 loomulikult, et autoriseeritud kasutajate kontroll oleks tõhus, on vajalik autoriseerimine. Autoriseerimismoodul suhtleb andmebaasiga. Saadud andmed veebivormidest tõlgitakse kontekstist, kasutaja salvestatakse ja saab vastutasuks küpsised ning 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 Püsivalt liigutatudnLocation: /@{user.login}nSet-Cookie: {cookies.AuthCookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnlynn");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);}
muud
{
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("Kasutaja on valmis");
Console.WriteLine(quot;{user.password} {user.password.Trim().Length}");
DatabaseWorker.UserRegister(user);
var response = Encoding.UTF8.GetBytes(quot;HTTP/1.1 301 Püsivalt liigutatudnLocation: /@{user.login}nSet-Cookie: {user.cookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnlynn");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);
}
muud
{
RazorController.ErrorLoader(401);
}
}
}
}
Nii näeb välja andmebaasi töötlemine:Andmebaas
using ClearServer.Core.UserController; using System; using System.Data.Linq; using System.Linq; namespace ClearServer { class DatabaseWorker { private readonly Table users = null; private readonly DataContext DataBase = null; private const string connectionStr = @"path_to_database"; public DatabaseWorker() { DataBase = new DataContext(connectionStr); users = DataBase.GetTable(); } 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("User {user.name} with id {user.uid} added"); } catch (Exception) { 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); if (UserToUpdate != null) { UserToUpdate = user; DataBase.SubmitChanges(); Console.WriteLine("User {UserToUpdate.name} with id {UserToUpdate.uid} updated"); } } public User CookieValidate(string CookieInput) { User user = null; user = users.SingleOrDefault(x => x.cookie == CookieInput); if (user != null) return user; return null; } public User FindUser(string login) { user = users.Single(x => x.login.ToLower() == login.ToLower()); return user; } } }"Kasutaja {user.name} ID-ga {user.uid} on lisatud";
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("Sisse logimine on juba olemas");
return false;
}
return true;
}
public void UserUpdate(User user)
{
var UserToUpdate = users.FirstOrDefault(x => x.uid == user.uid);
UserToUpdate = user;
DataBase.SubmitChanges();
Console.WriteLine("Kasutaja {UserToUpdate.name} ID-ga {UserToUpdate.uid} on uuendatud";
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());
kui (kasutaja != null)
{
tagasta kasutaja;
}
muud
{
return null;
}
}
catch (Exception)
{
return null;
}
}
}
}
Ja kõik töötab nagu kellavärk: autoriseerimine ja registreerimine toimivad, teenusele on minimaalne juurdepääsu funktsionaalsus juba olemas, ning nüüd on aeg kirjutada rakendus ja siduda kõik peamiste funktsioonidega, mille jaoks kõik see on loodud.Pealkiri 4. Ratta leiutamise vältimine
Kuna vähendada töökoormust kahe rakenduse kirjutamisel kahte platvormi jaoks, otsustasin luua ristplatvormi Xamarin.Forms'is. Jällegi, kuna see on kirjutatud C#-s. Tehes testrakenduse, mis lihtsalt saadab serverisse andmeid, sattusin huvitavale hetkele. Seoses seadme päringuga, rakendasin huvi pärast selle HttpClientis ja saatsin serverisse HttpRequestMessage'i, mis sisaldas autoriseerimisvormilt saadud andmeid JSON-formaadis. Ootamata suurt midagi, avasin serveri logi ja nägin sealt päringut seadmel koos kõigi andmetega. Kerge šokk, arusaamine kõigest, mis oli tehtud viimase kolme nädala jooksul kurnavates õhtutes. Saadetud andmete õigsuse kontrollimiseks koostasin testserveri HttpListener'il. Saades järjekordse päringu juba sellel, lahendasin paaririda koodi abil selle osadeks, saades KeyValuePair andmeid vormist. Päringu analüüs vähenes kahele reale.
Olen testinud edasi, varasemalt ei mainitud, kuid eelmises serveris panin üles ka veebisokkide põhjal toimiva chati. See töötas üllatavalt hästi, kuid Tcp kaudu suhtlemise põhimõte oli masendav, tuli tekitada liiga palju üleliigset, et kahe kasutaja sujuvat suhtlemist korralikult korraldada koos vestluse logimisega. See hõlmas päringu parsimist seoses ühenduse vahetamisega ja vastuse kogumist RFC 6455 protokolli kohaselt. Seetõttu otsustasin testserveris luua lihtsa veebisokkide ühenduse. Puhtalt huvi pärast.
Ühendamine chatiga
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(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 konfigureeris ühenduse iseseisvalt, genereeris vastusvõtme. Mul ei olnud isegi eraldi vaja ssl-iga serveri registreerimist seadistada, piisab sellest, et süsteemis on juba vajalikul pordil sertifikaat installitud.
Seadmest ja saidilt vahetasid kaks klienti sõnumeid, kõike seda logiti. Ei olnud vaja mingeid suuri parsereid, mis aeglustaksid serverit, midagi sellist ei nõutud. Vastusaeg vähenes 200 ms-st 40-30 ms-ni. Ja ma leidsin ainukese õige lahenduse.
Praegune TCP serveri rakendus kõrvaldada ja kõik ümber kirjutada HTTP alla. Projekt on nüüd uuesti projekteerimise etapis, kuid täiesti erinevate suhtlemispõhimõtete alusel. Seadmete ja saidi töö on sünkroniseeritud ja häälestatud ning sellel on ühine kontseptsioon, erandiga, et seadmete jaoks ei ole vaja genereerida HTML-lehti.
Kokkuvõte
„Ilma jõe põhja teadmata ei tasu vette astuda“ Mõtlen, et enne tööde alustamist oleks pidanud selgemalt määratlema eesmärgid ja ülesanded ning süvenema vajalikesse tehnoloogiatesse ning nende rakendamise meetoditesse erinevatel klientidel. Projekt on juba lõppfaasis, kuid võib-olla tagasi, et rääkida, kuidas ma jälle midagi valesti tegin. Olen arendusprotsessis palju õppinud, kuid veelgi enam on õppida ees. Kui sa jõudsid siia, siis aitäh selle eest.
Allikas: habr.com

