C# .Net ํ”„๋ ˆ์ž„์›Œํฌ ๊ธฐ๋ฐ˜์˜ ๊ณ ๊ธ‰ ์‚ฌ์ดํด๋ง ๋˜๋Š” ํด๋ผ์ด์–ธํŠธ-์„œ๋ฒ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

๊ธฐ์ž…

๋™๋ฃŒ๊ฐ€ ์ž‘์€ ์›น ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค์ž๊ณ  ์ œ์•ˆํ•˜๋ฉด์„œ ๋ชจ๋“  ๊ฒƒ์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ถ€์‹ฏ๊นƒ๊ณผ ๊ฐ™์€ ๊ฒƒ์ด์ง€๋งŒ IT ํ–‰ ์•„์›ƒ์„์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋Šฅ์€ ์™„์ „ํžˆ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ๋“ฑ๋กํ•˜๊ณ  ํ”„๋กœํ•„์„ ์ž‘์„ฑํ•˜๊ณ  ๋Œ€๋‹ด์ž๋ฅผ ์ฐพ๊ณ  ์—ฐ๊ฒฐ์„ ํ™•์žฅํ•˜๊ณ  ์ƒˆ๋กœ์šด ์ง€์ธ์„ ๋งŒ๋“œ๋Š” ์ฃผ์š” ์ง€์ ์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์—์„œ ๋‚˜๋Š” ๋‚˜ ์ž์‹ ์— ๋Œ€ํ•ด ์กฐ๊ธˆ ์ด์•ผ๊ธฐํ•˜๊ณ  ์ด์•ผ๊ธฐํ•ด์•ผํ•˜๋ฏ€๋กœ ์•ž์œผ๋กœ ๋‚ด๊ฐ€ ์™œ ๊ทธ๋Ÿฌํ•œ ๊ฐœ๋ฐœ ๋‹จ๊ณ„๋ฅผ ๋ฐŸ์•˜๋Š”์ง€ ๋” ๋ช…ํ™•ํ•ด์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ˜„์žฌ ์ €๋Š” ๊ฒŒ์ž„ ์ŠคํŠœ๋””์˜ค์—์„œ ํ…Œํฌ๋‹ˆ์ปฌ ์•„ํ‹ฐ์ŠคํŠธ์˜ ์ง์ฑ…์„ ๋งก๊ณ  ์žˆ์œผ๋ฉฐ C# ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฒฝํ—˜์€ Unity์šฉ ์Šคํฌ๋ฆฝํŠธ ๋ฐ ์œ ํ‹ธ๋ฆฌํ‹ฐ ์ž‘์„ฑ๊ณผ ๋”๋ถˆ์–ด ์•ˆ๋“œ๋กœ์ด๋“œ ์žฅ์น˜๋กœ ์ €์ˆ˜์ค€ ์ž‘์—…์„ ์œ„ํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ƒ์„ฑ์—๋งŒ ๊ธฐ๋ฐ˜์„ ๋‘๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์„ธ์ƒ ๋ฐ–์—์„œ๋Š” ์•„์ง ๊ทธ๋Ÿฐ ๊ธฐํšŒ๋ฅผ ์„ ํƒํ•ด์„œ ์ฐพ์•„๋ณธ ์ ์ด ์—†์Šต๋‹ˆ๋‹ค.

ํŒŒํŠธ 1. ํ”„๋ ˆ์ž„ ํ”„๋กœํ† ํƒ€์ดํ•‘

์ด ์„œ๋น„์Šค๊ฐ€ ๋ฌด์—‡์ธ์ง€ ๊ฒฐ์ •ํ•œ ํ›„ ๊ตฌํ˜„ ์˜ต์…˜์„ ์ฐพ๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ ์ผ์ข…์˜ ๊ธฐ์„ฑ ์†”๋ฃจ์…˜์„ ์ฐพ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ง€๊ตฌ๋ณธ์˜ ์˜ฌ๋นผ๋ฏธ์ฒ˜๋Ÿผ ์šฐ๋ฆฌ์˜ ์—ญํ•™์„ ๋Œ์–ด๋‚ด์–ด ๋Œ€์ค‘์˜ ๋น„๋‚œ์„ ์œ„ํ•ด ๋ชจ๋“  ๊ฒƒ์„ ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์€ ํฅ๋ฏธ๋กญ์ง€ ์•Š๊ณ  ์ด๊ฒƒ์—์„œ ์–ด๋–ค ๋„์ „๊ณผ ์˜๋ฏธ๋„ ๋ณด์ง€ ๋ชปํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์›น ๊ธฐ์ˆ ๊ณผ ์ƒํ˜ธ ์ž‘์šฉ ๋ฐฉ๋ฒ•์„ ์—ฐ๊ตฌํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค.

์—ฐ๊ตฌ๋Š” C# .Net์—์„œ ๊ธฐ์‚ฌ์™€ ๋ฌธ์„œ๋ฅผ ๋ณด๋Š” ๊ฒƒ์œผ๋กœ ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค. ASP.Net ๋˜๋Š” Azure ์„œ๋น„์Šค์™€ ๊ฐ™์€ ๋ณธ๊ฒฉ์ ์ธ ์†”๋ฃจ์…˜์—์„œ TcpHttp ์—ฐ๊ฒฐ๊ณผ์˜ ์ง์ ‘์ ์ธ ์ƒํ˜ธ ์ž‘์šฉ์— ์ด๋ฅด๊ธฐ๊นŒ์ง€ ๋„คํŠธ์›Œํฌ์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋งŽ์€ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

ASP๋กœ ์ฒซ ์‹œ๋„๋ฅผ ํ•œ ํ›„ ์ฆ‰์‹œ ์ทจ์†Œํ–ˆ๋Š”๋ฐ ์ €ํฌ ์„œ๋น„์Šค์— ๋„ˆ๋ฌด ์–ด๋ ค์šด ๊ฒฐ์ •์ด์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด ํ”Œ๋žซํผ ๊ธฐ๋Šฅ์˜ XNUMX/XNUMX๋„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๊ฒƒ์ด๋ฏ€๋กœ ๊ฒ€์ƒ‰์„ ๊ณ„์†ํ–ˆ์Šต๋‹ˆ๋‹ค. TCP์™€ Http ํด๋ผ์ด์–ธํŠธ-์„œ๋ฒ„ ์‚ฌ์ด์—์„œ ์„ ํƒ์ด ์ด๋ฃจ์–ด์กŒ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ Habrรฉ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ์‚ฌ๋ฅผ ์ ‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ ์„œ๋ฒ„, ์ˆ˜์ง‘ํ•˜๊ณ  ํ…Œ์ŠคํŠธ ํ•œ ํ›„ TCP ์—ฐ๊ฒฐ๊ณผ์˜ ์ƒํ˜ธ ์ž‘์šฉ์— ์ง‘์ค‘ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ์ด์œ ๋กœ http๊ฐ€ ํฌ๋กœ์Šค ํ”Œ๋žซํผ ์†”๋ฃจ์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

์„œ๋ฒ„์˜ ์ฒซ ๋ฒˆ์งธ ๋ฒ„์ „์—๋Š” ์—ฐ๊ฒฐ ์ฒ˜๋ฆฌ, ์ •์  ์›น ํŽ˜์ด์ง€ ์ฝ˜ํ…์ธ  ์ œ๊ณต ๋ฐ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ํฌํ•จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์šฐ์„  ์‚ฌ์ดํŠธ ์ž‘์—…์„ ์œ„ํ•œ ๊ธฐ๋Šฅ์„ ๊ตฌ์ถ•ํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ์—ฌ๊ธฐ์—์„œ Android ๋ฐ iOS์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฒ˜๋ฆฌ๋ฅผ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์— ๋ช‡ ๊ฐ€์ง€ ์ฝ”๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
๋ฌดํ•œ ๋ฃจํ”„์—์„œ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ˆ˜๋ฝํ•˜๋Š” ๊ธฐ๋ณธ ์Šค๋ ˆ๋“œ:

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

ํด๋ผ์ด์–ธํŠธ ์ฒ˜๋ฆฌ๊ธฐ ์ž์ฒด:

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

๊ทธ๋ฆฌ๊ณ  ๋กœ์ปฌ SQL์— ๊ตฌ์ถ•๋œ ์ตœ์ดˆ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค:

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

๋ณด์‹œ๋‹ค์‹œํ”ผ ์ด ๋ฒ„์ „์€ ๊ธฐ์‚ฌ์˜ ๋ฒ„์ „๊ณผ ์•ฝ๊ฐ„ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์ปดํ“จํ„ฐ์˜ ํด๋”์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ•˜๋Š” ๊ฒƒ๋งŒ ์—ฌ๊ธฐ์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค(๊ทธ๋Ÿฐ๋ฐ ์ž˜๋ชป๋œ ์—ฐ๊ฒฐ ์•„ํ‚คํ…์ฒ˜๋กœ ์ธํ•ด ์ด ๋ฒ„์ „์—์„œ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค).

์ œ 2 ์žฅ

์„œ๋ฒ„๋ฅผ ํ…Œ์ŠคํŠธํ•œ ํ›„ ์ด๊ฒƒ์ด ํ›Œ๋ฅญํ•œ ์†”๋ฃจ์…˜์ด๋ผ๋Š” ๊ฒฐ๋ก ์— ๋„๋‹ฌํ–ˆ์Šต๋‹ˆ๋‹ค(์Šคํฌ์ผ๋Ÿฌ: ์•„๋‹ˆ์š”), ์šฐ๋ฆฌ ์„œ๋น„์Šค๋ฅผ ์œ„ํ•ด ํ”„๋กœ์ ํŠธ๊ฐ€ ๋…ผ๋ฆฌ๋ฅผ ์–ป๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค.
๋‹จ๊ณ„์ ์œผ๋กœ ์ƒˆ๋กœ์šด ๋ชจ๋“ˆ์ด ๋‚˜ํƒ€๋‚˜๊ธฐ ์‹œ์ž‘ํ–ˆ๊ณ  ์„œ๋ฒ„์˜ ๊ธฐ๋Šฅ์ด ์„ฑ์žฅํ–ˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„์— ํ…Œ์ŠคํŠธ ๋„๋ฉ”์ธ๊ณผ SSL ์—ฐ๊ฒฐ ์•”ํ˜ธํ™”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์„œ๋ฒ„์˜ ๋…ผ๋ฆฌ์™€ ํด๋ผ์ด์–ธํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์„ค๋ช…ํ•˜๋Š” ์กฐ๊ธˆ ๋” ๋งŽ์€ ์ฝ”๋“œ
์ธ์ฆ์„œ ์‚ฌ์šฉ์„ ํฌํ•จํ•˜์—ฌ ์„œ๋ฒ„์˜ ์—…๋ฐ์ดํŠธ๋œ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค.

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

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

}
}

ํ•˜์ง€๋งŒ ์„œ๋ฒ„๋Š” TCP ์—ฐ๊ฒฐ๋กœ๋งŒ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์š”์ฒญ ์ปจํ…์ŠคํŠธ๋ฅผ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“ˆ์„ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ํ•„์š”ํ•œ ๋‹ต๋ณ€์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ๋‚ด๊ฐ€ ์ƒํ˜ธ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ณ„๋„์˜ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋ˆŒ ํŒŒ์„œ๊ฐ€ ์—ฌ๊ธฐ์— ์ ํ•ฉํ•˜๋‹ค๊ณ  ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํŒŒ์„œ

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

๊ทธ ๋ณธ์งˆ์€ ์ •๊ทœ ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์„ ์—ฌ๋Ÿฌ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋ˆˆ๋‹ค๋Š” ์‚ฌ์‹ค์— ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›๊ณ  ๋ฉ”์„œ๋“œ์™€ ์š”์ฒญ URL์ด ํฌํ•จ๋œ ์ฒซ ๋ฒˆ์งธ ์ค„์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ HeaderName = Content ํ˜•์‹์˜ ๋ฐฐ์—ด๋กœ ๊ตฌ๋™ํ•˜๋Š” ํ—ค๋”๋ฅผ ์ฝ๊ณ  ์œ ์‚ฌํ•œ ๋ฐฐ์—ด๋กœ ๊ตฌ๋™ํ•˜๋Š” ์ˆ˜๋ฐ˜๋˜๋Š” ์ฝ˜ํ…์ธ (์˜ˆ: ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด)๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ฐพ๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ํŒŒ์„œ๋Š” ํ˜„์žฌ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์Šน์ธ๋œ ํด๋ผ์ด์–ธํŠธ์˜ ๋ชจ๋“  ์š”์ฒญ์—๋Š” ์ฟ ํ‚ค์— ์ €์žฅ๋˜๋Š” ์Šน์ธ ํ•ด์‹œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋•๋ถ„์— ๋‘ ๊ฐ€์ง€ ์œ ํ˜•์˜ ํด๋ผ์ด์–ธํŠธ์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์ž‘์—… ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅธ ๋‹ต๋ณ€์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์Œ, "site.com/@UserName"๊ณผ ๊ฐ™์€ ์š”์ฒญ์„ ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ํŽ˜์ด์ง€๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ณ„๋„์˜ ๋ชจ๋“ˆ๋กœ ์ด๋™ํ•ด์•ผ ํ•˜๋Š” ์ž‘๊ณ  ๋ฉ‹์ง„ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ ํ›„ ๋‹ค์Œ ๋ชจ๋“ˆ์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

Chapter 3. ํ•ธ๋“ค๋ฐ” ์„ค์น˜, ์ฒด์ธ ์œคํ™œ

ํŒŒ์„œ๊ฐ€ ์™„๋ฃŒ๋˜์ž๋งˆ์ž ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ž‘๋™ํ•˜์—ฌ ์„œ๋ฒ„์— ์ถ”๊ฐ€ ์ง€์นจ์„ ์ œ๊ณตํ•˜๊ณ  ์ œ์–ด๋ฅผ ๋‘ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋ˆ•๋‹ˆ๋‹ค.

๋‹จ์ˆœ ํ•ธ๋“ค๋Ÿฌ

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

์‹ค์ œ๋กœ ์š”์ฒญ ์ฒ˜๋ฆฌ๊ฐ€ ์‹œ์ž‘๋œ ํ›„ ์‚ฌ์šฉ์ž ์ธ์ฆ์— ๋Œ€ํ•œ ํ™•์ธ์ด ํ•œ ๋ฒˆ๋งŒ ์žˆ์Šต๋‹ˆ๋‹ค.

ํด๋ผ์ด์–ธํŠธ ์ปจํŠธ๋กค๋Ÿฌ
์‚ฌ์šฉ์ž๊ฐ€ ๊ถŒํ•œ์ด ์—†๋Š” ๊ฒฝ์šฐ ๊ธฐ๋Šฅ์€ ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ํ‘œ์‹œ ๋ฐ ๊ถŒํ•œ ๋“ฑ๋ก ์ฐฝ์—๋งŒ ๊ธฐ๋ฐ˜ํ•ฉ๋‹ˆ๋‹ค. ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ฝ”๋“œ๋Š” ๊ฑฐ์˜ ๋™์ผํ•˜๊ฒŒ ๋ณด์ด๋ฏ€๋กœ ๋ณต์ œํ•  ์ด์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์Šน์ธ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž

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;

}

}

}
}

๋ฌผ๋ก  ์‚ฌ์šฉ์ž๋Š” ํŽ˜์ด์ง€์˜ ์ผ๋ถ€ ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐ›์•„์•ผ ํ•˜๋ฏ€๋กœ ์‘๋‹ต์„ ์œ„ํ•ด ๋ฆฌ์†Œ์Šค ์š”์ฒญ์— ์‘๋‹ตํ•˜๋Š” ๋‹ค์Œ ๋ชจ๋“ˆ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ž‘๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ

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

๊ทธ๋Ÿฌ๋‚˜ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ž์‹ ์˜ ํ”„๋กœํ•„๊ณผ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœํ•„์„ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด RazorEngine ๋˜๋Š” ๊ทธ ์ผ๋ถ€๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ž˜๋ชป๋œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ ์ ˆํ•œ ์˜ค๋ฅ˜ ์ฝ”๋“œ๋ฅผ ๋ฐœํ–‰ํ•˜๋Š” ๊ฒƒ๋„ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

๋ ˆ์ด์ € ์ปจํŠธ๋กค๋Ÿฌ

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

๋ฌผ๋ก  ๊ถŒํ•œ์ด ์žˆ๋Š” ์‚ฌ์šฉ์ž์˜ ํ™•์ธ์ด ์ž‘๋™ํ•˜๋ ค๋ฉด ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ธ์ฆ ๋ชจ๋“ˆ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ ์ž‘์šฉํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์ดํŠธ์˜ ์–‘์‹์—์„œ ์ˆ˜์‹ ๋œ ๋ฐ์ดํ„ฐ๋Š” ์ปจํ…์ŠคํŠธ์—์„œ ๊ตฌ๋ฌธ ๋ถ„์„๋˜๊ณ  ์‚ฌ์šฉ์ž๋Š” ์ €์žฅ๋˜๋ฉฐ ์ฟ ํ‚ค๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ  ๊ทธ ๋Œ€๊ฐ€๋กœ ์„œ๋น„์Šค์— ์•ก์„ธ์Šคํ•ฉ๋‹ˆ๋‹ค.

์ธ์ฆ ๋ชจ๋“ˆ

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

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค

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


๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ๊ฒƒ์ด ์‹œ๊ณ„, ์ธ์ฆ ๋ฐ ๋“ฑ๋ก ์ž‘์—…์ฒ˜๋Ÿผ ์ž‘๋™ํ•˜๋ฉฐ ์„œ๋น„์Šค์— ๋Œ€ํ•œ ์•ก์„ธ์Šค์˜ ์ตœ์†Œ ๊ธฐ๋Šฅ์€ ์ด๋ฏธ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜๊ณ  ๋ชจ๋“  ๊ฒƒ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ฃผ์š” ๊ธฐ๋Šฅ๊ณผ ๋ชจ๋“  ๊ฒƒ์„ ์—ฐ๊ฒฐํ•  ๋•Œ์ž…๋‹ˆ๋‹ค.

4์žฅ

๋‘ ํ”Œ๋žซํผ์— ๋‘ ๊ฐœ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž‘์„ฑํ•˜๋Š” ์ธ๊ฑด๋น„๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด Xamarin.Forms์—์„œ ํฌ๋กœ์Šค ํ”Œ๋žซํผ์„ ๋งŒ๋“ค๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋งํ•˜์ง€๋งŒ C#์— ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค ๋•๋ถ„์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๋Š” ํ…Œ์ŠคํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“  ํ›„ ํฅ๋ฏธ๋กœ์šด ์ˆœ๊ฐ„์„ ๋งŒ๋‚ฌ์Šต๋‹ˆ๋‹ค. ์žฅ์น˜์˜ ์š”์ฒญ์— ๋Œ€ํ•ด์„œ๋Š” ์žฌ๋ฏธ๋กœ HttpClient์— ๊ตฌํ˜„ํ•˜๊ณ  json ํ˜•์‹์˜ ์Šน์ธ ์–‘์‹ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌํ•จ๋œ HttpRequestMessage ์„œ๋ฒ„์— ๋˜์กŒ์Šต๋‹ˆ๋‹ค. ํŠน๋ณ„ํ•œ ๊ฒƒ์„ ๊ธฐ๋Œ€ํ•˜์ง€ ์•Š๊ณ  ์„œ๋ฒ„ ๋กœ๊ทธ๋ฅผ ์—ด์—ˆ๊ณ  ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” ์žฅ์น˜์˜ ์š”์ฒญ์„ ๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๊ฐ€๋ฒผ์šด ๋ฌด๊ฐ๊ฐ, ๋‚˜๋ฅธํ•œ ์ €๋…์˜ ์ง€๋‚œ 3์ฃผ ๋™์•ˆ ํ–‰ํ•ด์ง„ ๋ชจ๋“  ๊ฒƒ์— ๋Œ€ํ•œ ์ž๊ฐ. ์ „์†ก๋œ ๋ฐ์ดํ„ฐ์˜ ์ •ํ™•์„ฑ์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด HttpListner์— ํ…Œ์ŠคํŠธ ์„œ๋ฒ„๋ฅผ ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์š”์ฒญ์„ ์ด๋ฏธ ๋ฐ›์•˜์œผ๋ฏ€๋กœ ๋ช‡ ์ค„์˜ ์ฝ”๋“œ๋กœ ๋ถ„ํ•ดํ•˜๊ณ  ์–‘์‹์—์„œ KeyValuePair ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค. ์ฟผ๋ฆฌ ๊ตฌ๋ฌธ ๋ถ„์„์ด ๋‘ ์ค„๋กœ ์ค„์—ˆ์Šต๋‹ˆ๋‹ค.

์ถ”๊ฐ€ ํ…Œ์ŠคํŠธ๋ฅผ ์‹œ์ž‘ํ–ˆ๋Š”๋ฐ ์ด์ „์—๋Š” ์–ธ๊ธ‰๋˜์ง€ ์•Š์•˜์ง€๋งŒ ์ด์ „ ์„œ๋ฒ„์—์„œ๋Š” ์—ฌ์ „ํžˆ ์›น ์†Œ์ผ“์— ๊ตฌ์ถ•๋œ ์ฑ„ํŒ…์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๊ฝค ์ž˜ ์ž‘๋™ํ–ˆ์ง€๋งŒ Tcp๋ฅผ ํ†ตํ•œ ์ƒํ˜ธ ์ž‘์šฉ์˜ ์›๋ฆฌ ์ž์ฒด๊ฐ€ ์šฐ์šธํ–ˆ์Šต๋‹ˆ๋‹ค. ์„œ์‹  ๋กœ๊น…์œผ๋กœ ๋‘ ์‚ฌ์šฉ์ž์˜ ์ƒํ˜ธ ์ž‘์šฉ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌ์ถ•ํ•˜๋ ค๋ฉด ๋„ˆ๋ฌด ๋งŽ์€ ์ถ”๊ฐ€ ์ž‘์—…์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—๋Š” RFC 6455 ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ฒฐ ์ „ํ™˜ ์š”์ฒญ์„ ๊ตฌ๋ฌธ ๋ถ„์„ํ•˜๊ณ  ์‘๋‹ต์„ ์ˆ˜์ง‘ํ•˜๋Š” ๊ฒƒ์ด ํฌํ•จ๋˜๋ฏ€๋กœ ํ…Œ์ŠคํŠธ ์„œ๋ฒ„์—์„œ ๊ฐ„๋‹จํ•œ ์›น ์†Œ์ผ“ ์—ฐ๊ฒฐ์„ ๋งŒ๋“ค๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ˆœ์ „ํžˆ ๊ด€์‹ฌ์„ ์œ„ํ•ด.

์ฑ„ํŒ… ์—ฐ๊ฒฐ

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

๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ์€ ํšจ๊ณผ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ž์ฒด๊ฐ€ ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•˜๊ณ  ์‘๋‹ต ํ‚ค๋ฅผ ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. SSL์„ ํ†ตํ•ด ์„œ๋ฒ„ ๋“ฑ๋ก์„ ๋ณ„๋„๋กœ ๊ตฌ์„ฑํ•  ํ•„์š”๋„ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ์‹œ์Šคํ…œ์— ์ด๋ฏธ ํ•„์š”ํ•œ ํฌํŠธ์— ์ธ์ฆ์„œ๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ์œผ๋ฉด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.

์žฅ์น˜ ์ธก๊ณผ ์‚ฌ์ดํŠธ ์ธก์—์„œ ๋‘ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ๊ตํ™˜ํ–ˆ๊ณ  ์ด ๋ชจ๋“  ๊ฒƒ์ด ๊ธฐ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์†๋„๋ฅผ ๋Šฆ์ถ”๋Š” ๊ฑฐ๋Œ€ํ•œ ํŒŒ์„œ๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ์ค‘ ์–ด๋Š ๊ฒƒ๋„ ํ•„์š”ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์‘๋‹ต ์‹œ๊ฐ„์ด 200ms์—์„œ 40-30ms๋กœ ๋‹จ์ถ•๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‚˜๋Š” ์œ ์ผํ•˜๊ฒŒ ์˜ฌ๋ฐ”๋ฅธ ๊ฒฐ์ •์„ ๋‚ด๋ ธ์Šต๋‹ˆ๋‹ค.

C# .Net ํ”„๋ ˆ์ž„์›Œํฌ ๊ธฐ๋ฐ˜์˜ ๊ณ ๊ธ‰ ์‚ฌ์ดํด๋ง ๋˜๋Š” ํด๋ผ์ด์–ธํŠธ-์„œ๋ฒ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

Tcp์—์„œ ํ˜„์žฌ ์„œ๋ฒ„ ๊ตฌํ˜„์„ ๋ฒ„๋ฆฌ๊ณ  Http์—์„œ ๋ชจ๋“  ๊ฒƒ์„ ๋‹ค์‹œ ์ž‘์„ฑํ•˜์‹ญ์‹œ์˜ค. ์ด์ œ ํ”„๋กœ์ ํŠธ๋Š” ์žฌ์„ค๊ณ„ ๋‹จ๊ณ„์— ์žˆ์ง€๋งŒ ์™„์ „ํžˆ ๋‹ค๋ฅธ ์ƒํ˜ธ ์ž‘์šฉ ์›์น™์— ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. ์žฅ์น˜์™€ ์‚ฌ์ดํŠธ์˜ ์ž‘๋™์€ ๋™๊ธฐํ™”๋˜๊ณ  ๋””๋ฒ„๊น…๋˜๋ฉฐ ์žฅ์น˜๊ฐ€ html ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ์œ ์ผํ•œ ์ฐจ์ด์ ์„ ์ œ์™ธํ•˜๊ณ ๋Š” ๊ณตํ†ต ๊ฐœ๋…์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

์ถœ๋ ฅ

"๋‚˜๋ฃจ๋„ ๋ชจ๋ฅด๊ณ  ๋ฌผ์— ๋จธ๋ฆฌ๋ฅผ ์ฐŒ๋ฅด์ง€ ๋ง๋ผ" ์ž‘์—…์„ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ๋ชฉํ‘œ์™€ ๋ชฉํ‘œ๋ฅผ ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ์ •์˜ํ•˜๊ณ  ๋‹ค์–‘ํ•œ ํด๋ผ์ด์–ธํŠธ์—์„œ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๊ธฐ์ˆ ๊ณผ ๋ฐฉ๋ฒ•์„ ์—ฐ๊ตฌํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ด ํ”„๋กœ์ ํŠธ๋Š” ์ด๋ฏธ ์™„์„ฑ์— ๊ฐ€๊นŒ์›Œ์ง€๊ณ  ์žˆ์ง€๋งŒ, ๋‚ด๊ฐ€ ์–ด๋–ค ์ผ์„ ๋˜ ์–ด๋–ป๊ฒŒ ๋ง์ณค๋Š”์ง€์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์‹œ ๋Œ์•„์˜ฌ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ๋งŽ์€ ๊ฒƒ์„ ๋ฐฐ์› ์ง€๋งŒ ์•ž์œผ๋กœ ๋ฐฐ์šธ ๊ฒƒ์ด ๋” ๋งŽ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์œผ์…จ๋‹ค๋ฉด ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ถœ์ฒ˜ : habr.com