Bevezetés
Minden azzal kezdődött, hogy egy kollégám javasolta, hogy készítsek egy kis webszolgáltatást. Ennek valami olyasminak kellett lennie, mint a Tinder, de az IT közösség számára. A funkcionalitás rendkívül egyszerű: regisztrálsz, kitöltöd a profilodat, és áttérsz a lényegre, nevezetesen a beszélgetőpartner keresésére, a kapcsolatok bővítésére és új ismeretségek megszerzésére.
Itt meg kell tennem egy kis kitérőt, és egy kicsit beszélni magamról, hogy később érthetőbb legyen, miért éppen ilyen lépéseket tettem a fejlesztés során.
Jelenleg a Technikai Művész pozíciót töltöm be egy játékstúdiónál, a C# programozási tapasztalatom csak Unity számára írt szkriptek és eszközök készítésére korlátozódott, emellett alacsony szintű Pluginok létrehozására androidos eszközökhöz. Ezen a területen még nem kalandoztam el, de itt adódott egy lehetőség.
1. rész. A keret prototípusa
Miután eldöntöttem, hogy milyen szolgáltatást szeretnék létrehozni, elkezdtem keresni a megvalósítás lehetőségeit. A legegyszerűbb az lett volna, ha találok valamilyen kész megoldást, amire, mint a baglyot a földgömbre, rányomhatjuk a mechanikáinkat, és mindezt a közönség elé tárhatjuk.
De ez nem lenne érdekes, semmi kihívás nem volt benne, és nem láttam értelmét, ezért elkezdtem tanulmányozni a webtechnológiákat és az azokkal való interakció módszereit.
A tanulmányozást az C# .Net cikkek és dokumentációk átnézésével kezdtem. Itt sokféle módszert találtam a feladat végrehajtására. Számos mechanizmus áll rendelkezésre a hálózati interakcióhoz, a teljes megoldásoktól, mint például az ASP.Net vagy az Azure szolgáltatások, a TcpHttp kapcsolatok közvetlen kezeléséig.
Az első próbálkozás után az ASP-vel azonnal elvetettem, számomra ez túl nehéz megoldás volt a szolgáltatásunk számára. Nem fogjuk használni a platform lehetőségeinek harmadát sem, így folytattam a keresést. A választás a TCP és a Http kliens-szerver között dőlt el. Itt, a Habr-on, akadtam egy cikkre a összegyűjtéséről és teszteléséről, ami után úgy döntöttem, hogy a TCP kapcsolatokkal való interakcióra fogok fókuszálni, mert úgy számoltam, hogy a http nem engedi meg, hogy keresztplatformos megoldást készítsek.
Az első verzió szerver lehetővé tette a kapcsolatok kezelését, statikus weboldaltartalmat szolgáltatott, és felhasználói adatbázist tartalmazott. Először is olyan funkciót akartam létrehozni, amely lehetővé teszi az oldal kezelését, hogy később csatlakoztathassam az android és ios alkalmazás programozását is.
Itt van egy kis kód
A fő szál, amely folyamatos ciklusban fogadja a klienseket:
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);
}
}
}A klienskezelő önállóan:
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)}";
}
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 =
"HTTP/1.1 200 OK\nContent-Type: {ContentType}\nContent-Length: {FS.Length}\n\n";
byte[] HeadersBuffer = Encoding.ASCII.GetBytes(Headers);
Client.GetStream().Write(HeadersBuffer, 0, HeadersBuffer.Length);while (FS.Position < FS.Length)
{
Count = FS.Read(Buffer, 0, Buffer.Length);
Client.GetStream().Write(Buffer, 0, Count);
}FS.Close();
Client.Close();
}
}
}
És az első adatbázis, amely helyi SQL-re épült: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}"
}
}
}
}
}
Mint látható, ez a verzió alig különbözik attól, ami a cikkben szerepelt. Lényegében itt csak annyival bővült, hogy lehetővé vált a fájlok betöltése a számítógép mappájából, és az adatbázis (amely egyébként ebben a verzióban nem működött a helytelen kapcsolati architektúra miatt).2. fejezet. A kerekek felszerelése
A szerver működésének tesztelése után arra a következtetésre jutottam, hogy ez nagyszerű megoldás lesz (spoiler: nem), a szolgáltatásunk számára, ezért a projekt logikával kezdett el bővülni.
Fokról fokra új modulok és funkciók jelentek meg a szerver bővítésén. A szerver egy tesztdomain-t kapott és ssl az összeköttetés titkosítását.Még egy kis kód, amely a szerver működésének logikáját és a kliensek kezelését írja le
Frissített verzió a szerverről, amely tartalmazza a tanúsítvány használatát.használva System; használva System.Net; használva System.Net.Sockets; használva System.Reflection; használva System.Security; használva System.Security.Cryptography.X509Certificates; használva System.Security.Permissions; használva System.Security.Policy; használva 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("A szerver indítása.." + 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("Átváltás egy másik domainre"); 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(); } } }És egy új klienskezelő SSL hitelesítéssel:
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" +"|{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);
}}
}
Mivel a szerver kizárólag TCP kapcsolaton működik, szükséges létrehozni egy modult, amely képes azonosítani a kérés kontextusát. Úgy döntöttem, hogy itt egy parser lesz a megfelelő, amely a kliens által küldött kérést különböző részekre bontja, amellyel interakcióba léphetek, hogy a kliensnek a megfelelő válaszokat adhassam.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;
{
if (HeadersValues.Any(x => x.Name.Contains("Cookie")))
try
{
cookie = HeadersValues.FirstOrDefault(x => x.Name.Contains("Cookie")).Value;
{
CurrentUser = databaseWorker.CookieValidate(cookie);
try
{
catch { }
}
private List HeaderValues()
}
}
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 output = Message.Trim('\n').Split().Last();
{
var parse = Regex.Matches(Message, @"(.*?): (.*?)\n");
var parse = Regex.Matches(output, @"([^&].*?)=([^&]*)\b");
Value = match.Groups[2].Value.Trim().Replace('+', ' ');
values.Add(new RequestValues()
{
Name = match.Groups[1].Value.Trim(),
{
Value = match.Groups[2].Value.Trim()
Érték = match.Groups[2].Value.Trim().Replace('+', ' ')
});
}
private void ProfileParse()
}
}
}
A lényege az, hogy reguláris kifejezések segítségével felbontsuk a kérést részekre. Megkapjuk az üzenetet az ügyféltől, kiemeljük az első sort, amelyben a kérés metodikája és url-je található. Ezután elolvassuk a fejlécet, amelyeket egy névérték párként átkonvertálunk, és ha van, megtaláljuk a kísérő tartalmat (például querystring), amelyet szintén hasonló tömbbe írunk. Ezenkívül a parser megállapítja, hogy a jelenlegi ügyfél hitelesítve van-e, és tárolja az adatait. Minden hitelesített ügyfél kérésében található egy hitelesítési hash, amely a sütikben tárolódik, így meg lehet különböztetni a további munkafolyamat logikáját a két típusú ügyfél számára, és megfelelő válaszokat tudunk adni.Nos, és egy kis, kellemes funkció, amit érdemes lenne külön modulba kiemelni, a kérés "site.com/@UserName" formátumainak átalakítása dinamikusan generált felhasználói oldalakká. A kérés feldolgozása után következnek a következő modulok.
3. fejezet. Kormányzó beállítása, lánc kenése
Amint a parser dolgozott, a feldolgozó lép működésbe, amely további utasításokat ad a szervernek, és két részre osztja az irányítást.
Egyszerű feldolgozó
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); }; } } }Lényegében itt csak egy ellenőrzés történik a felhasználó hitelesítésére, ezt követően kezdődik a kérés feldolgozása.
Ügyfélkontrollerek
Ha a felhasználó nincs hitelesítve, akkor számára a funkció kizárólag a felhasználói profilok megjelenítésére és a regisztráció/bejelentkezés ablakára korlátozódik. A hitelesített felhasználó kódja körülbelül ugyanígy néz ki, ezért nem látom értelmét megismételni.Hitelesítetlen felhasználó
using ClearServer.Core.Requester; using System.IO; using System.Net.Security; namespace ClearServer.Core.UserController { internal class NonAuthUserController { private readonly SslStream ClientStream; private readonly RequestContext Context; private readonly WriteController WriteController; private readonly AuthorizationController AuthorizationController; private readonly string ViewPath = "C:/Users/drdre/source/repos/ClearServer/View"; public NonAuthUserController(SslStream clientStream, RequestContext context) { this.ClientStream = clientStream; this.Context = context; this.WriteController = new WriteController(clientStream); this.AuthorizationController = new AuthorizationController(clientStream, context); ResourceLoad(); } void ResourceLoad() { string[] blockextension = new string[] {"cshtml", "html", "htm"}; bool block = false; foreach (var item in blockextension) { if (Context.RequestUrl.Contains(item)) { block = true; break; } } string FilePath = ""; string Header = ""; var RazorController = new RazorController(Context, ClientStream); switch (Context.RequestMethod) { case "GET": switch (Context.RequestUrl) { case "/": FilePath = ViewPath + "/loginForm.html"; Header =quot;HTTP/1.1 200 OKnContent-Type: text/html";
WriteController.DefaultWriter(Header, FilePath);
break;
case "/profile":
RazorController.ProfileLoader(ViewPath);
break;
default:
//в данном блоке кода происходит отсечение запросов к серверу по прямому адресу страницы вида site.com/page.html
if (!File.Exists(ViewPath + Context.RequestUrl) | block)
{
RazorController.ErrorLoader(404);}
else if (Path.HasExtension(Context.RequestUrl) && File.Exists(ViewPath + Context.RequestUrl))
{
Header = WriteController.ContentType(Context.RequestUrl);
FilePath = ViewPath + Context.RequestUrl;
WriteController.DefaultWriter(Header, FilePath);
}
break;
}
break;case "POST":
AuthorizationController.MethodRecognizer();
break;}
}
}
}
Természetesen a felhasználónak valamilyen tartalmat kell kapnia az oldalakról, ezért a válaszokhoz a következő modul felelős az erőforrások kérésére.WriterController
using System; using System.IO; using System.Net.Security; using System.Text; namespace ClearServer.Core.UserController { public class WriteController { SslStream ClientStream; public WriteController(SslStream ClientStream) { this.ClientStream = ClientStream; } public void DefaultWriter(string Header, string FilePath) { FileStream fileStream; try { fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); Header =quot;{Header}nContent-Length: {fileStream.Length}nn";
ClientStream.Write(Encoding.UTF8.GetBytes(Header));
byte[] response = new byte[fileStream.Length];
fileStream.BeginRead(response, 0, response.Length, OnFileRead, response);
}
private List HeaderValues()
}public string ContentType(string Uri)
{
string extension = Path.GetExtension(Uri);
string Header = "HTTP/1.1 200 OKnContent-Type:";
switch (extension)
{
case ".html":
case ".htm":
returnquot;{Header} text/html";
case ".css":
returnquot;{Header} text/css";
case ".js":
returnquot;{Header} text/javascript";
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
returnquot;{Header} image/{extension}";
default:
if (extension.Length > 1)
{
returnquot;{Header} application/" + extension.Substring(1);
}
else
{
returnquot;{Header} application/unknown";
}
}
}public void OnFileRead(IAsyncResult ar)
{
if (ar.IsCompleted)
{
var file = (byte[])ar.AsyncState;
ClientStream.BeginWrite(file, 0, file.Length, OnClientSend, null);
}
}public void OnClientSend(IAsyncResult ar)
{
if (ar.IsCompleted)
{
ClientStream.Close();
}
}
}
De hogy a felhasználónak meg tudjam mutatni a profilját és más felhasználók profilját, úgy döntöttem, hogy a RazorEngine-t fogom használni, pontosabban annak egy részét. Ez a helytelen kérések kezelését és a megfelelő hibaüzenet kiadását is magában foglalja.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(); } } } }Természetesen az autorizált felhasználók ellenőrzéséhez szükség van az azonosításra. Az azonosító modul kapcsolatba lép az adatbázissal. Az űrlapokból a weboldalon kapott adatokat a kontextusból elemzik, a felhasználó megőrzi, és cserébe sütit és hozzáférést kap a szolgáltatáshoz.
Az azonosító modul
using ClearServer.Core.Cookies; using ClearServer.Core.Requester; using ClearServer.Core.Security; using System; using System.Linq; using System.Net.Security; using System.Text; namespace ClearServer.Core.UserController { internal class AuthorizationController { private SslStream ClientStream; private RequestContext Context; private UserCookies cookies; private WriteController WriteController; DatabaseWorker DatabaseWorker; RazorController RazorController; PasswordHasher PasswordHasher; public AuthorizationController(SslStream clientStream, RequestContext context) { ClientStream = clientStream; Context = context; DatabaseWorker = new DatabaseWorker(); WriteController = new WriteController(ClientStream); RazorController = new RazorController(context, clientStream); PasswordHasher = new PasswordHasher(); } internal void MethodRecognizer() { if (Context.FormValues.Count == 2 && Context.FormValues.Any(x => x.Name == "password")) Authorize(); else if (Context.FormValues.Count == 3 && Context.FormValues.Any(x => x.Name == "regPass")) Registration(); else { RazorController.ErrorLoader(401); } } private void Authorize() { var values = Context.FormValues; var user = new User() { login = values[0].Value, password = PasswordHasher.PasswordHash(values[1].Value) }; user = DatabaseWorker.UserAuth(user); if (user != null) { cookies = new UserCookies(user.login, user.password); user.cookie = cookies.AuthCookie; DatabaseWorker.UserUpdate(user); var response = Encoding.UTF8.GetBytes("HTTP/1.1 301 Moved Permanently\nLocation: /@{user.login}\nSet-Cookie: {cookies.AuthCookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly\n\n";
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("{user.password} {user.password.Trim().Length}");
DatabaseWorker.UserRegister(user);
var response = Encoding.UTF8.GetBytes("HTTP/1.1 301 Moved Permanently\nLocation: /@{user.login}\nSet-Cookie: {user.cookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnly\n\n");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);
}
else
{
RazorController.ErrorLoader(401);
}
}
}
}
Így néz ki az adatbázis feldolgozása:Adatbázis
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 = @"adatbázis útvonal"; 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("Felhasználó {user.name} azonosítóval {user.uid} hozzáadva");User {user.name} azonosítóval {user.uid} hozzáadva";
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("Bejelentkezési név már létezik");
return false;
}
return true;
}
public void UserUpdate(User user)
{
var UserToUpdate = users.FirstOrDefault(x => x.uid == user.uid);
UserToUpdate = user;
DataBase.SubmitChanges();
Console.WriteLine(Console.WriteLine("Felhasználó {UserToUpdate.name} azonosítóval {UserToUpdate.uid} frissítve");
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;
}
}
}
}
Minden úgy működik, mint a karóra, az azonosítás és regisztráció működik, a szolgáltatáshoz való minimális hozzáférési funkciók már rendelkezésre állnak, és itt az ideje, hogy elkezdjük az alkalmazás írását, és összekapcsoljuk mindezt a fő funkciókkal, amelyekért az egészet készítjük.4. Fejezet. A kerék újratervezése
A fáradság csökkentése érdekében, mivel két alkalmazást kellett írnom két különböző platformra, úgy döntöttem, hogy a Xamarin.Forms keretében egy keresztplatformos megoldást készítek. Ismét csak azért, mert C#-ban van. Készítettem egy tesztalkalmazást, amely egyszerűen elküldi az adatokat a szervernek, és egy érdekes dologgal találkoztam. Az érintőeszközről történő kéréshez érdekességképpen megvalósítottam azt HttpClient segítségével, és elküldtem egy HttpRequestMessage-t a szervernek, amely tartalmazza az autentikálási űrlap adatait JSON formátumban. Különösebb elvárások nélkül megnyitottam a szerver naplóját, és láttam ott a készülékről érkező kérést minden adattal. Kis megdöbbenés, és az összes, az utolsó három hét fárasztó estéin végzett munka tudatosulása. Az elküldött adatok helyességének ellenőrzésére egy teszt szervert készítettem HttpListener használatával. Miután megkaptam a következő kérést rajta, néhány sor kóddal felbontottam azt részekre, és KeyValuePair formátumban nyertem az űrlap adatait. A kérés lebontása mindössze két sorra csökkent.
Tovább kezdtem tesztelni, korábban nem említettem, de a régi szerveren már megvalósítottam egy webszakértői alapú csevegőt. Az elég jól működött, de a TCP kapcsolatokon keresztüli interakció kialakítása kiábrándító volt, túl sok felesleges dolgot kellett kezelnem ahhoz, hogy két felhasználó közötti interakciót megfelelően építsek fel, beleértve a levelezési napló nyilvántartását is. Ez magában foglalta a kérés elemzését a kapcsolat váltásának érdekében, valamint a válasz összegyűjtését az RFC 6455 protokoll szerint. Ezért a teszt szerveren úgy döntöttem, hogy létrehozok egy egyszerű webszakértő kapcsolatot. Csak a kíváncsiság kedvéért.
Csatlakozás a csevegőhöz
privát statikus 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(); } } } } }És működött. A szerver automatikusan beállította a kapcsolatot, generálta a válasz kulcsot. Még a szerver SSL regisztrációját sem kellett külön beállítanom, elég volt, hogy a rendszerben már telepítve van a tanúsítvány a szükséges porton.
Az eszköz és a webhely oldalán két ügyfél üzenetet váltott, mindez naplózásra került. Nem volt szükség hatalmas elemzőkre, amelyek lelassítják a szerver működését, semmire sem volt szükség. A válaszidő 200 ms-ról 40-30 ms-ra csökkent. Egységes megoldásra jutottam.
El kellett dobni a jelenlegi TCP-szerver megvalósítást, és mindent átírni HTTP-ra. A projekt most újratervezés alatt áll, de már teljesen más interakciós elvek szerint. Az eszközök és a webhely munkája szinkronizálva és tökéletesítve van, és közös koncepción alapul, azzal a különbséggel, hogy az eszközöknek nem kell HTML oldalakat generálniuk.
Következtetés
„Nem tudva a víz mélységét, ne merülj bele” Azt hiszem, a munka megkezdése előtt világosabban kellett volna meghatároznom a célokat és feladatokat, valamint alaposabban belemerülnöm a szükséges technológiák és módszerek tanulmányozásába a különböző ügyfeleken. A projekt már a befejezéshez közelít, de lehet, hogy még visszatérek, hogy elmeséljem, hogyan csesztem el ezt-azt. Sok mindent tanultam a fejlesztési folyamat során, de még sok mindent kell elsajátítanom a jövőben. Ha eddig eljutottál, köszönöm, hogy megtetted.
Forrás: habr.com

