
[ ์น์ 0. ๊ฐ๋ก ]
# ์๋ฒ OT
- ์๋ฒ๋ ๋ค๋ฅธ ์ปดํจํฐ์์ ์ฐ๊ฒฐ์ด ๊ฐ๋ฅํ๋๋ก ๋๊ธฐ ์ํ๋ก ์์ ์คํ์ค์ธ ํ๋ก๊ทธ๋จ์ด๋ค.
- Web ์๋ฒ (aka. HTTP Server)
~> ํ ์ดํฌ์์ ํฌ์ฅ ์ ๋ฌธ ์๋น
~> ์๋์ด ์์์ ๋ฐ์์ ๋ ๋๋ฉด, ๊ทธ ์ดํ๋ก ์ฐ๋ฝ์ด ๋๊ธด๋ค.
~> ๋๋ฌผ๊ฒ ์ ๋ณด๋ฅผ ์์ฒญ/๊ฐฑ์ ํ๋ค.
~> ์๋น์์ ์๋ํํ ๋จผ์ ์ ๊ทผํ ์ผ์ ์๋ค. (ex : ๋ฌผ ๋ฐ๋ผ๋๋ฆด๊น์?)
~> ์ฃผ๋ฌธ ํ ์๋์ด ๋ฐ๋ก ๋ ๋๋ฉด, ์๋์ ์ํ๋ฅผ ๋น๋ถ๊ฐ ์๊ณ ์ง๋ธ๋ค. (Stateless)
~> Web ์๋ฒ ์ ์์ ์ฒ์๋ถํฐ ๋ง๋๋ ๊ฒฝ์ฐ๋ ์ฌ์ค์ ์๊ณ , Framework๋ฅผ ํ๋ ๊ณจ๋ผ์ ์ฌ์ฉํ๋ค.
~> ์ง์/์๋ต ํํ
- Game ์๋ฒ (aka. TCP Server, Binary Server, Stateful Server ...)
~> ์ผ๋ฐ ์๋น
~> ์๋น ์ง์์ด ์์ ์๋์๊ฒ ๋ฌผ์ด๋ณผ ์๋ ์๊ณ , ์๋์ด ์ถ๊ฐ ์ฃผ๋ฌธ์ ํ๊ธฐ๋ ํ๋ค.
~> ์์ฒญ/๊ฐฑ์ ํ์๊ฐ ๋ง๋ค.
~> ์ธ์ ๋ผ๋ ์ง์์ด ์๋ํํ ์ ๊ทผ์ด ๊ฐ๋ฅํด์ผ ํ๋ค.
~> ์๋์ด ์๋น์ ๋จธ๋ฌด๋ ๋์, ์๋์ ์ํ๋ฅผ ๋ณด๋ฉฐ ์ต์์ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ค. (Stateful)
~> ๊ฒ์/์ฅ๋ฅด์ ๋ฐ๋ผ ์๊ตฌ์ฌํญ์ด ๋๋ฌด ๋ฌ๋ผ ์ต์ ์ Framework๋ผ๋ ๊ฒ์ด ์กด์ฌํ๊ธฐ ์ ๋งคํ๋ค.
~> ์ค์๊ฐ Interaction์ด ์กด์ฌ

# ํ๊ฒฝ์ค์
- [ ์๋ฃจ์ ] - [ ์ค๋ฅธ์ชฝ ๋ง์ฐ์ค ] - [ ์์ฑ ] ์ [ ๊ณต์ฉ ์์ฑ ] - [ ์์ ํ๋ก์ ํธ ] ์์ [ ํ ๊ฐ์ ์์ ํ๋ก์ ํธ ] ๊ฐ ์๋
[ ์ฌ๋ฌ ๊ฐ์ ์์ ํ๋ก์ ํธ ] ๋ฅผ ์ ํํ ๋ค ๋์์ ์คํํ๊ณ ์ํ๋ ํ๋ก์ ํธ์ ์์ ์ํ๋ฅผ [ ์์ ] ์์ [ ์์ ] ์ผ๋ก
๋ณ๊ฒฝํ๋ฉด ์์ ์ํ๊ฐ [ ์์ ] ์ผ๋ก ์ค์ ๋ ์ฌ๋ฌ๊ฐ์ ํ๋ก์ ํธ๋ฅผ ๋์์ ์คํ ๊ฐ๋ฅํ๋ค.


[ ์น์ 1. ๋ฉํฐ์ฐ๋ ๋ ํ๋ก๊ทธ๋๋ฐ ]
# ๋ฉํฐ์ฐ๋ ๋ ๊ฐ๋ก

- Process๋ ์ด์์ฒด์ ๋ก๋ถํฐ ์์์ ํ ๋น๋ฐ๋ ์์ ์ ๋จ์์ด๋ค.
- Thread๋ ํ Process๊ฐ ํ ๋น๋ฐ์ ์์์ ์ด์ฉํ๋ ์คํ์ ๋จ์์ด๋ค.
- MultiThread๋ ํ๋์ Process๋ฅผ ๋ค์์ ์คํ ๋จ์๋ก ๊ตฌ๋ถํ์ฌ, ์์์ ๊ณต์ ํ๊ณ ์์์ ์์ฑ๊ณผ ๊ด๋ฆฌ์ ์ค๋ณต์ฑ์
์ต์ํํ์ฌ ์ํ ๋ฅ๋ ฅ์ ํฅ์ ์ํค๋ ๊ฒ์ด๋ค.

- ํ ๊ณ ๊ธ ๋ ์คํ ๋์ด ์ด์๋๊ธฐ ์ํด์๋ ๋ก๋ด ์ง์๋ค์ด ํ์ํ๋ฉฐ ๋ก๋ด ์ง์ ๊ฐ๊ฐ์๊ฒ๋ ์ ๋ฌด๊ฐ ์ฃผ์ด์ง๋ค. (๊ณ์ฐ, ์๋น ...)
~> ๋ํ, ์ํผ์ ๊ฐ์๊ฐ ํ์ ์ ์ด๊ธฐ ๋๋ฌธ์ ๋์์ ์๋์ํฌ ์ ์๋ ๋ก๋ด ์ง์์ ์ ์ญ์ ์ ํ๋๋ค.
- ์์ ๋ด์ฉ์์ ๊ณ ๊ธ ๋ ์คํ ๋์ Process, ๋ก๋ด ์ง์์ Thread, ์ํผ์ CPU ์ฝ์ด์ ํด๋นํ๋ค.

- ์ํผ์ ๊ฐ์๊ฐ 1๊ฐ๋ก ํ์ ์ ์ด๊ธฐ ๋๋ฌธ์ 1๋ช ์ ๋ก๋ด ์ง์๋ง ์๋์ํฌ ์ ์๋ค.
~> ๋ง์ฝ, ํด๋น ์ํผ์ 4๋ช ์ ์ง์์๊ฒ 0.1์ด์ฉ ๋น ๋ฅด๊ฒ ๋ฒ๊ฐ์๊ฐ๋ฉด์ ์ฃผ์ ํ ๊ฒฝ์ฐ ์ด๋ป๊ฒ ๋ ๊น?
~> ์ฐ๋ฆฌ์ ๋์๋ 4๋ช ์ ์ง์ ๋ชจ๋ ์๋ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ผ ๊ฒ์ด๋ค.

- ์ฐ๋ฆฌ๊ฐ ๋ณด๊ธฐ์๋ ๋ค์์ Process๊ฐ ๋์์ ๋์๋๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง, ์ค์ ๋ก ์ปดํจํฐ๋ ์ด์์ฒด์ ์ Scheduling์ ์ํด
Task๋ฅผ ์์ฃผ ์งง์ ์๊ฐ๋์ ๋ฒ๊ฐ์๊ฐ๋ฉฐ ์ํํ๋ ๊ฒ์ด๋ค.
~> ์ด๋ฅผ MultiTasking ์ด๋ผ๊ณ ํ๋ค.

- CPU ์ฝ์ด์ Clock Cycle ์ฆ๊ฐ๋ฅผ ํตํด ์ปดํจํฐ์ ์ฑ๋ฅ ๊ฐ์ ์ ๊ฟ๊ฟจ๋ ๊ฐ๋ฐ์๋ค์ ์ ๋ ฅ ๋ฌธ์ ์ ๋ฐ์ด ๋ฌธ์ ๋ผ๋ ํ๊ณ์ ๋๋ฌ
ํ๊ฒ ๋๊ณ , ์ด์ ์ ํํ ๋์์ด CPU ์ฝ์ด์ ๊ฐ์๋ฅผ ๋๋ฆฌ๋ ๊ฒ์ด๋ค.
~> ์ด๋ฅผ MultiProcessor ๋ผ๊ณ ํ๋ค.
- CPU ์ฝ์ด๊ฐ 4๊ฐ์ผ ๊ฒฝ์ฐ ๋์์ ์คํํ ์ ์๋ Thread๋ 4๊ฐ์ด๋ฏ๋ก CPU ์ฝ์ด์ ์๊ฐ ๋ง๋ค๊ณ ํด์ Thread์ ์๋ฅผ ๋๋ ค
๋ดค์ ํ๊ณ๊ฐ ์กด์ฌํ๋ค. ( CPU ์ฝ์ด์ ์์ Thread์ ์๋ฅผ ์ต๋ํ ๋ง์ถฐ์ฃผ๋ ๊ฒ์ด ์ค์ )
~> ๋ํ, Thread์ ์๊ฐ ๋ง์ผ๋ฉด ๋ง์์๋ก MultiTasking ๋ถํ๊ฐ ์๋นํด์ง๋ค.

- Thread ์ฌ์ฉ์ Thread ๋ฐฐ์น๋ ๊ตฌ์ฑํ๊ธฐ ๋๋ฆ์ด๋ค.

- MultiProcess ๋ ๋ฐ์ดํฐ ์์ญ, Stack ์์ญ, Heap ์์ญ์ Process ๋ผ๋ฆฌ ์ ๋ถ ๊ณต์ ํ์ง ์๋๋ค.
- MultiThread ๋ Stack ์์ญ์ ์ ์ธํ ๋ฐ์ดํฐ ์์ญ, Heap ์์ญ์ Thread ๋ผ๋ฆฌ ์ ๋ถ ๊ณต์ ํ๋ค.
+ ์ถ๊ฐ ๊ฒ์ (https://inpa.tistory.com/entry/%F0%9F%91%A9%E2%80%8D%F0%9F%92%BB-multi-process-multi-thread)
- MultiProcessor ์ MultiProcess ์ ์ฐจ์ด์
~> Processor๋ CPU ์ฝ์ด๋ฅผ, Process๋ ํ๋ก๊ทธ๋จ์ ์คํ ์ํ๋ฅผ ์ผ์ปซ๋๋ค.
~> MultiProcessor ๋ ์ฌ๋ฌ๊ฐ์ CPU ์ฝ์ด๊ฐ ํ๋์ ์์คํ ์์ ๋์์ ์คํ๋๋ ๊ฒ์ ์๋ฏธํ๋ค.
~> MultiProcess ๋ ํ๋์ ํ๋ก๊ทธ๋จ์์ ์ฌ๋ฌ ๊ฐ์ Process๋ฅผ ์คํํ๋ ๊ฒ์ ์๋ฏธํ๋ค. ( ex : fork() )
+ ์ถ๊ฐ ๊ฒ์ ( https://wooody92.github.io/os/%EB%A9%80%ED%8B%B0-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C/ )
- MultiProcess ์ MultiThread ์ ์ฐจ์ด์
1. MultiThread๋ MultiProcess ๋ณด๋ค ์ ์ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ์ ์ฐจ์งํ๊ณ , Context Switching ์ด ๋น ๋ฅด๋ค๋ ์ฅ์ ์ ๊ฐ์ง๋ค.
~> ๊ทธ๋ฌ๋ MultiThread์ ๋จ์ ์ผ๋ก๋ ๋๊ธฐํ ๋ฌธ์ ์ 1๊ฐ์ Thread ์ฅ์ ๊ฐ ์ ์ฒด Thread ์๊ฒ ์ํฅ์ ์ค ์ ์๋ค๋ ๊ฒ์ด๋ค.
2. MultiProcess๋ 1๊ฐ์ Process๊ฐ ์ฃฝ๋๋ผ๋ ๋ค๋ฅธ Process ์๊ฒ ์ํฅ์ ์ฃผ์ง ์์ ์์ ์ฑ์ด ๋๋ค๋ ์ฅ์ ์ ๊ฐ์ง๋ค.
~> ๊ทธ๋ฌ๋ MultiProcess์ ๋จ์ ์ผ๋ก๋ MultiThread ๋ณด๋ค ๋ง์ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ๊ณผ CPU ์๊ฐ์ ์ฐจ์งํ๋ค๋ ๊ฒ์ด๋ค.
# ์ฐ๋ ๋ ์์ฑ
- ๊ณ ๊ธ ๋ ์คํ ๋์์ ์ ์ง์์ ๊ณ ์ฉํ๋ ๊ฒ์ ์๋นํ ๋ถ๋ด์ค๋ฝ๋ค. ( ์ธ๊ฑด๋น, 4๋ ๋ณดํ ๋ฑ ... )
~> ์ธ๋ ฅ ์๋ด์์์ ๋จ๊ธฐ ์๋ฐ๋ฅผ ๊ตฌํ๋ ๊ฒ์ด ์ข๋ค.
- ์ฆ, Thread๋ฅผ ์์ฑํ๋ ๊ฒ์ ์๋นํ ๋ถํ๋ฅผ ๊ฐ์ ธ๋ค ์ค๋ค.
~> C#์ ์ฌ์ฉ ๊ฐ๋ฅํ Thread๋ฅผ ํ ๋น ๋ฐ์ ์ฌ์ฉํ ์ ์๋ Thread Pool์ ์ง์ํ๋ค. ( ์ฌ์ฉ ํ ๋ฐํ / ์ผ๊พผ์ด ์์ผ๋ฉด ๋๊ธฐ )
Thread ์์ฉ
using System;
using System.Threading; // ์ถ๊ฐํด์ผ Thread ์ฌ์ฉ ๊ฐ๋ฅ
namespace ServerCore
{
class Program
{
static void MainThread()
{
for (int i = 0; i < 5; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
Thread t = new Thread(MainThread); // ํ๋ช
์ ์ง์์ ๊ณ ์ฉํ ๋ค MainThread ๋ผ๋ ์
๋ฌด๋ฅผ ํ ๋นํ ๊ฒ
t.IsBackground = true; // Main ํจ์๊ฐ ์ข
๋ฃ๋ ๊ฒฝ์ฐ ํด๋น Thread๋ ์ข
๋ฃ (๊ธฐ๋ณธ๊ฐ์ foreground)
t.Name = "Test Thread"; // Thread ์ด๋ฆ ์ค์
t.Start(); // Thread ์คํ
Console.WriteLine("Waiting for Thread");
t.Join(); // ํด๋น Thread๊ฐ ์ข
๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ
Console.WriteLine("Hello World");
}
}
}โ
Thread Pool ์์ฉ
using System;
using System.Threading;
namespace ServerCore
{
class Program
{
static void MainThread(object state)
{
for (int i = 0; i < 5; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
// Thread๋ฅผ ์ต์ 1๊ฐ์์ ์ต๋ 5๊ฐ๊น์ง๋ง ๋น๋ ค์ค ์ ์๋๋ก ์ ํ
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(5, 5);
// ๋๋ค์์ ํตํด Pool์์ Thread๋ฅผ ๋น๋ฆฐ๋ค ์์ํ ๋ฐํํ์ง ์๋ ํจ์ ์์ฑ
for (int i = 0; i < 5; i++)
ThreadPool.QueueUserWorkItem((obj) => { while (true) { } });
// Thread Pool์ ๋์ด์ ๋จ์์๋ Thread๊ฐ ์์ด ์คํ X
ThreadPool.QueueUserWorkItem(MainThread);
}
}
}โ
Task ์์ฉ
using System;
using System.Threading;
using System.Threading.Tasks; // ์ถ๊ฐํด์ผ Task ์ฌ์ฉ ๊ฐ๋ฅ
namespace ServerCore
{
class Program
{
static void MainThread(object state)
{
for (int i = 0; i < 5; i++)
Console.WriteLine("Hello Thread!");
}
static void Main(string[] args)
{
// Thread๋ฅผ ์ต์ 1๊ฐ์์ ์ต๋ 5๊ฐ๊น์ง๋ง ๋น๋ ค์ค ์ ์๋๋ก ์ ํ
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(5, 5);
for (int i = 0; i < 5; i++)
{
// Task๋ก ์ง์์ด ํ ์ผ๊ฐ์ ์์ฑํ์ฌ ๋์ ธ์ฃผ๋ฉด, ThreadPool์ ๋๊ธฐ์ค์ธ Thread๊ฐ ํด๋น ์ผ๊ฐ์ ์คํ
// Task ์์ฑ์ TaskCreationOptions.LongRunning ์ต์
์ ์ค์ ํ ๊ฒฝ์ฐ Thread๋ฅผ ๋ฐ๋ก ๊ด๋ฆฌ
Task t = new Task(() => { while (true) { } }, TaskCreationOptions.LongRunning);
t.Start();
}
// TaskCreationOptions.LongRunning ์ต์
์ ํตํด Thread๋ฅผ ๋ฐ๋ก ๊ด๋ฆฌํ๋ฏ๋ก ์คํ ๊ฐ๋ฅ
ThreadPool.QueueUserWorkItem(MainThread);
}
}
}
# ์ปดํ์ผ๋ฌ ์ต์ ํ


- Debug ๋ชจ๋์ Release ๋ชจ๋์ ๊ฐ์ฅ ํฐ ์ฐจ์ด์ ์ Debug ๋ชจ๋์์๋ ์ฝ๋ ์ต์ ํ๋ฅผ ํ์ง ์๊ณ , Release ๋ชจ๋์์๋ ์ฝ๋
์ต์ ํ๋ฅผ ํ๋ค๋ ๊ฒ์ด๋ค.
~> ๋ฐ๋ผ์ Debug ๋ชจ๋์์๋ ์ ์คํ๋๋ ์ฝ๋๊ฐ Release ๋ชจ๋์์๋ ์คํ๋์ง ์์ ์ ์๋ค.
~> volatile Keyword๋ฅผ ํตํด ์ต์ ํ๋ฅผ ๊ธ์ง์ํฌ ์ ์๋ค.
volatile Keyword ์์ฉ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
// volatile Keyword๋ฅผ ํตํด ํด๋น ๋ณ์์ ๋ํ ์ต์ ํ๋ฅผ ๊ธ์ง์ํจ๋ค.
volatile static bool _stop = false; // ์ ์ญ ๋ณ์๋ ๋ชจ๋ Thread๋ค์ด ๊ณต์
static void ThreadMain()
{
Console.WriteLine("Thread ์์!");
// Debug ๋ชจ๋๊ฐ ์๋ Release ๋ชจ๋์์๋ ์ฝ๋ ์ต์ ํ๋ก ์ธํด ์ผ์ด๋์ง ์๋ Bug๋ค์ด ๋ฐ์ํ ์ ์๋ค.
while (_stop == false)
{
// ๋๊ตฐ๊ฐ๊ฐ stop ์ ํธ๋ฅผ true๋ก ๋ฐ๊ฟ์ฃผ๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฐ๋ค.
}
// ์์ while๋ฌธ์ ์ปดํ์ผ๋ฌ ์ต์ ํ์ ์๋์ ์ฝ๋์ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ค. ( ์ฆ, ์๋ Bug ๋ฐ์ )
//if (_stop == false)
//{
// while (true)
// {
// ๋ฌดํ ๋ฃจํ์ ๋น ์ง๋ค.
// }
//}
Console.WriteLine("Thread ์ข
๋ฃ!");
}
static void Main(string[] args)
{
Task t = new Task(ThreadMain);
t.Start();
Thread.Sleep(1000); // 1์ด๋์ ์ผ์์ ์ง
_stop = true;
Console.WriteLine("Stop ํธ์ถ!");
Console.WriteLine("์ข
๋ฃ ๋๊ธฐ์ค");
t.Wait(); // ํด๋น Task๊ฐ ์ข
๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ ( Thread์ Join() ๊ณผ ๊ฐ์ ๊ธฐ๋ฅ )
Console.WriteLine("์ข
๋ฃ ์ฑ๊ณต");
}
}
}โ
# ์บ์ ์ด๋ก

- ์ง์์ด ์๋น์ ํตํด ์ฃผ๋ฌธ์ ๋ฐ๊ณ ์ฃผ๋ฐฉ๊ณผ ์๊ฒฉ์ผ๋ก ์ฐ๊ฒฐ๋ ์ฃผ๋ฌธ ํํฉ์ ์ฃผ๋ฌธ ๋ด์ญ์ ์ ๋ ฅํ ๊ฒฝ์ฐ ์ฃผ๋ฐฉ์์ ์กฐ๋ฆฌ๋ฅผ
์์ํ๋ค. ( ์ด๋ ์ง์์ด ์ฃผ๋ฌธ ํํฉ์ ์ ๋ ฅํ๊ธฐ ์ํ ๋์ ์์ฒด๊ฐ ๋ง์ด ๊ธธ๋ค๊ณ ๊ฐ์ )
- ์ง์์ ๋ณด๋ค ๋ ํจ์จ์ ์ผ๋ก ์์ง์ด๊ธฐ ์ํด์ ์ฃผ๋ฌธ์ 1๊ฐ ๋ฐ์๋ง์ ๋ฉ๋ฆฌ ๋จ์ด์ง ์ฃผ๋ฌธ ํํฉ์ ์ฃผ๋ฌธ ๋ด์ญ์ ์ ๋ ฅํ๋ฌ
๊ฐ๋ ๊ฒ์ด ์๋, ๋จ๊ธฐ ๊ธฐ์ต/๋ฏธ๋ ์์ฒฉ/๋ํ ์์ฒฉ ์ ํ์ฉํ์ฌ ์ฃผ๋ฌธ๋ค์ ์ ๋ถ ๊ธฐ๋กํด๋ ๋ค ๋์ค์ ํ๊บผ๋ฒ์ ์ ๋ ฅ์ ํ๋ค.
~> ๋ง์ฝ ์๋์ด ์ฃผ๋ฌธ์ ๋ฒ๋ณตํ ๊ฒฝ์ฐ ๋ณธ์ธ์ด ์์ฒฉ์ ๊ธฐ๋กํด๋ ์ฃผ๋ฌธ๋ง ์์ ํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ๋ณด๋ค ๋ ํจ์จ์ ์ด๋ค.

- ๊ทธ๋ฌ๋ ์ด๋ฌํ ๋ฐฉ๋ฒ์ด MultiThread ์์๋ ๊ณจ์น์ํ ์ ์๋ค.
~> ์ง์ 1์ด 2๋ฒ ํ ์ด๋ธ์ ๋ํ ์ฝ๋ผ ์ฃผ๋ฌธ์ ๋ฐ๊ณ , 2๋ฒ ํ ์ด๋ธ ์๋๋ค์ด ์ง์ 2์๊ฒ ์ฝ๋ผ๊ฐ ์๋ ์ฌ์ด๋ค๋ฅผ ๋ฌ๋ผ๊ณ
์ฃผ๋ฌธ์ ๋ฒ๋ณตํ ๊ฒฝ์ฐ ํผ๋์ด ๋ฐ์ํ๋ค.
~> ์ง์ 2 ์ ์ฅ์์๋ ๋ณธ์ธ์ด 2๋ฒ ํ ์ด๋ธ์ ๋ํด ์ฝ๋ผ ์ฃผ๋ฌธ์ ๋ฐ์ง๋ ์์๊ณ , ํ์ธํ๊ณ ์ ์ฃผ๋ฌธ ํํฉ์ ํ์ธํด๋ด๋
2๋ฒ ํ ์ด๋ธ์ด ์ฝ๋ผ๋ฅผ ์ฃผ๋ฌธํ๋ค๋ ์ฌ์ค์ ์ ์ ์๋ค.
( 2๋ฒ ํ ์ด๋ธ์ ๋ํ ์ฝ๋ผ ์ฃผ๋ฌธ์ ์์ง๊น์ง๋ ์ง์ 1์ ๋ฏธ๋ ์์ฒฉ์ ๊ธฐ๋ก๋จ )

- ์์ ๋ด์ฉ์์ ๋ก๋ด ์ง์์ Thread, ๋จ๊ธฐ ๊ธฐ์ต/๋ฏธ๋ ์์ฒฉ/๋ํ ์์ฒฉ์ด Cache, ์ฃผ๋ฌธ ํํฉ์ Main Memory์ ํด๋นํ๋ค.

- ์บ์ ์ฒ ํ์ผ๋ก ์๊ฐ์ ์ง์ญ์ฑ, ๊ณต๊ฐ์ ์ง์ญ์ฑ์ด ์๋ค.
~> ์๊ฐ์ ์ง์ญ์ฑ์ ์ต๊ทผ์ ์ฐธ์กฐ๋ ์ฃผ์์ ๋ด์ฉ์ด ๋ค์ ์ฐธ์กฐ๋ ๊ฐ๋ฅ์ฑ์ด ๋๋ค๋ ๊ฒ์ด๋ค.
~> ๊ณต๊ฐ์ ์ง์ญ์ฑ์ ๊ธฐ์ต์ฅ์น ๋ด์ ์๋ก ์ธ์ ํ์ฌ ์ ์ฅ๋์ด ์๋ ๋ฐ์ดํฐ๋ค์ด ์ฐ์์ ์ผ๋ก ์ ๊ทผ๋ ๊ฐ๋ฅ์ฑ์ด ๋๋ค๋ ๊ฒ์ด๋ค.
์บ์ ์์ฉ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static void Main(string[] args)
{
int[,] arr = new int[10000, 10000];
{
long start = DateTime.Now.Ticks;
for (int x = 0; x < 10000; x++)
for (int y = 0; y < 10000; y++)
arr[x, y] = 1;
long end = DateTime.Now.Ticks;
Console.WriteLine($"(x, y) ์์ ๊ฑธ๋ฆฐ ์๊ฐ {end - start}"); // ๊ฒฐ๊ณผ : 3138744
}
{
long start = DateTime.Now.Ticks;
for (int x = 0; x < 10000; x++)
for (int y = 0; y < 10000; y++)
arr[y, x] = 1;
long end = DateTime.Now.Ticks;
Console.WriteLine($"(y, x) ์์ ๊ฑธ๋ฆฐ ์๊ฐ {end - start}"); // ๊ฒฐ๊ณผ : 4003940
}
}
}
}โ
# ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด
- ์ฝ๋ ์ต์ ํ ๋ฟ๋ง์ด ์๋๋ผ HW ์ญ์ ์ต์ ํ๋ฅผ ์งํํ๋ค.
~> CPU์๊ฒ ์ผ๋ จ์ ๋ช ๋ น์ด๋ค์ ์ํฌ ๊ฒฝ์ฐ CPU๊ฐ ์์กด์ฑ์ด ์๋ค๊ณ ํ๋จ๋๋ ๋ช ๋ น์ด๋ค์ ์์๋ฅผ ์ ๋ฉ๋๋ก ์ฌ๋ฐฐ์นํ๋ค.
~> Memory Barrier๋ฅผ ํตํด ์ฝ๋ ์์ ์ฌ๋ฐฐ์น๋ฅผ ์ต์ ํ ์ ์๋ค.
- Memory Barrier๋ฅผ ํตํด ๊ฐ์์ฑ ๋ํ ํ๋ณดํ ์ ์๋ค.
~> ์ด๋์ ๊ฐ์์ฑ ํ๋ณด๋ MultiThread ํ๊ฒฝ์์ Thread์ Cache์ ์ํฉ์ Main Memory์ ๋ฐ์ํ๋ ๊ฒ์ ๋งํ๋ค.
~> ๋ฐ๋ผ์ Store ํ์ 1๋ฒ, Load ์ ์ 1๋ฒ ํด์ฃผ๋ ๊ฒ์ด ๊ฐ์์ฑ ํ๋ณด์ ๋์์ด ๋๋ค. ( ์ต์ ์ ๋ณด ๋ฐ์ ๋ฐ ์ฌ์ฉ )
HW ์ต์ ํ ์์
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static int x = 0;
static int y = 0;
static int result1 = 0;
static int result2 = 0;
static void Thread_1()
{
// CPU๊ฐ ์๋์ ๋ช
๋ น์ด๋ค์ด ์์กด์ฑ์ด ์๋ค๊ณ ํ๋จํ์ฌ ์ฝ๋์ ์์๋ฅผ ์ ๋ฉ๋๋ก ์ฌ๋ฐฐ์น
y = 1; // Store y
result1 = x; // Load x
}
static void Thread_2()
{
// CPU๊ฐ ์๋์ ๋ช
๋ น์ด๋ค์ด ์์กด์ฑ์ด ์๋ค๊ณ ํ๋จํ์ฌ ์ฝ๋์ ์์๋ฅผ ์ ๋ฉ๋๋ก ์ฌ๋ฐฐ์น
x = 1; // Store x
result2 = y; // Load y
}
static void Main(string[] args)
{
int count = 0;
while (true)
{
count++;
x = y = result1 = result2 = 0;
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2); // 2๊ฐ์ Thread๊ฐ ์ ๋ถ ๋๋ ๋๊น์ง Main Thread๋ ๋๊ธฐ
// CPU๊ฐ ์ฝ๋์ ์์๋ฅผ ์ ๋ฉ๋๋ก ์ฌ๋ฐฐ์นํ์ฌ ๋ ๋ค 0์ธ ์ํฉ์ด ๋ฐ์ํ ์ ์์๋ค.
if (result1 == 0 && result2 == 0)
break;
}
Console.WriteLine($"{count}๋ฒ๋ง์ ๋น ์ ธ๋์ด!");
}
}
}โ
Memory Barrier ์์ฉ ( ์ฝ๋ ์์ ์ฌ๋ฐฐ์น ์ต์ , ๊ฐ์์ฑ )
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
// ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ๊ธฐ๋ฅ
// A) ์ฝ๋ ์์ ์ฌ๋ฐฐ์น ์ต์
// B) ๊ฐ์์ฑ ํ๋ณด (MultiThread ํ๊ฒฝ์์ Thread์ Cache์ ์ํฉ์ Main Memory์ ๋ฐ์)
// ๋ฉ๋ชจ๋ฆฌ ๋ฐฐ๋ฆฌ์ด ์ข
๋ฅ
// 1) Full Memory Barrier (์ด์
๋ธ๋ฆฌ์ด : MFENCE, C# : Thread.MemoryBarrier) : Store/Load ๋ ๋ค ๋ง๋๋ค.
// 2) Store Memory Barrier (์ด์
๋ธ๋ฆฌ์ด : SFENCE) : Store ๋ง ๋ง๋๋ค.
// 3) Load Memory Barrier (์ด์
๋ธ๋ฆฌ์ด : LFENCE) : Load ๋ง ๋ง๋๋ค.
class Program
{
static int x = 0;
static int y = 0;
static int result1 = 0;
static int result2 = 0;
static void Thread_1()
{
y = 1; // Store y ( Main Memory์ y์ ๊ฐ์ ๋ฐ์ )
Thread.MemoryBarrier(); // ์ฝ๋ ์์ ์ฌ๋ฐฐ์น ์ต์
result1 = x; // Load x ( Main Memory์ ์๋ x์ ๊ฐ์ ๋ฐ์ )
}
static void Thread_2()
{
x = 1; // Store x ( Main Memory์ x์ ๊ฐ์ ๋ฐ์ )
Thread.MemoryBarrier(); // ์ฝ๋ ์์ ์ฌ๋ฐฐ์น ์ต์
result2 = y; // Load y ( Main Memory์ ์๋ y์ ๊ฐ์ ๋ฐ์ )
}
static void Main(string[] args)
{
int count = 0;
while (true)
{
count++;
x = y = result1 = result2 = 0;
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2); // 2๊ฐ์ Thread๊ฐ ์ ๋ถ ๋๋ ๋๊น์ง Main Thread๋ ๋๊ธฐ
// CPU์ ์ฝ๋ ์ฌ๋ฐฐ์น๋ฅผ ์ต์ ํ์๊ธฐ ๋๋ฌธ์ ๋ ๋ค 0์ธ ๊ฒฝ์ฐ๋ ์กด์ฌ X ~> ๋ฌดํ๋ฃจํ
if (result1 == 0 && result2 == 0)
break;
}
Console.WriteLine($"{count}๋ฒ๋ง์ ๋น ์ ธ๋์ด!");
}
}
}
# interlocked
- Race Condition (๊ฒฝํฉ ์กฐ๊ฑด) ์ด๋ 2๊ฐ ์ด์์ Process ํน์ Thread๊ฐ ๊ณต์ ์์์ ์๋ก ์ฌ์ฉํ๋ ค๊ณ ๊ฒฝํฉ(Race)ํ๋
ํ์์ ์๋ฏธํ๋ค.
- MultiThread ํ๊ฒฝ์์๋ Process ๋ด์ ๋ชจ๋ ์์์ ๊ณต์ ํ ์ ์๋ค๋ ์ ์์ ๋๊ธฐํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
~> Interlocked ๋ฅผ ํตํด ํด๊ฒฐ์ด ๊ฐ๋ฅํ๋ค. ( ๋ฉ๋ชจ๋ฆฌ ๋๊ธฐํ ๊ตฌํ )
~> Interlocked ๋ ์ ์๋ง ์ฌ์ฉํ ์ ์๋ค๋ ๋จ์ ์ด ์กด์ฌํ๋ค.
Race Condition ์์
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static int number = 0;
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
number++;
// number++์ ์ด์
๋ธ๋ฆฌ์ด์์ ์๋์ ๊ฐ์ด ๋์ํ๋ค. (์ฆ, atomic operation์ด X)
// int temp = number;
// temp += 1;
// number = temp;
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
number--;
// number--์ ์ด์
๋ธ๋ฆฌ์ด์์ ์๋์ ๊ฐ์ด ๋์ํ๋ค. (์ฆ, atomic operation์ด X)
// int temp = number;
// temp -= 1;
// number = temp;
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
// ์์ ์ฃผ์ ์ฒ๋ฆฌ๋ 6์ค์ ์ฝ๋ ์์๋ฅผ ์ ํํ ์์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ๊ฒฐ๊ณผ ์์ธก X
Console.WriteLine(number);
}
}
}โ
Interlocked ๋ฅผ ํตํ ์์์ฑ ๋ฐ ์์ ๋ณด์ฅ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static int number = 0;
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
Interlocked.Increment(ref number); // ์์์ฑ ๋ณด์ฅ ๋ฐ ์์ ๋ณด์ฅ (All or Nothing)
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
Interlocked.Decrement(ref number); // ์์์ฑ ๋ณด์ฅ ๋ฐ ์์ ๋ณด์ฅ (All or Nothing)
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(number);
}
}
}โ
# Lock ๊ธฐ์ด
- Critical Section (์๊ณ ์์ญ) ์ 2๊ฐ ์ด์์ Thread๊ฐ ๋์์ ์ ๊ทผํ ์ ์๋ ๊ณต์ ์์์ ์ฌ์ฉํ๋ ์ฝ๋ ์์ญ์ ์๋ฏธํ๋ค.
- Mutual Exclusive (์ํธ ๋ฐฐ์ ) ๋ ํน์ ์์ ์์ ๊ณต์ ์์์ 1๊ฐ์ Thread ๋ง์ด ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ๋ค๋ฅธ Thread ๋ค์
์ ๊ทผํ์ง ๋ชปํ๋๋ก ์ ์ดํ๋ ๋ฐฉ๋ฒ์ ์๋ฏธํ๋ค.
Critical Section ๋ฐ Mutual Exclusive ์์ฉ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static int number = 0;
static object _obj = new object();
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
{
Monitor.Enter(_obj); // ๋ฌธ์ ์ ๊ธ์ฅ์น ์ค์ (์ด๋ฏธ ์ ๊ฒจ์์ ๊ฒฝ์ฐ ๋๊ธฐ)
number++;
Monitor.Exit(_obj); // ๋ฌธ์ ์ ๊ธ์ฅ์น ํด์ (์๋ตํ ๊ฒฝ์ฐ deadlock ๋ฐ์ ๊ฐ๋ฅ)
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
{
Monitor.Enter(_obj); // ๋ฌธ์ ์ ๊ธ์ฅ์น ์ค์ (์ด๋ฏธ ์ ๊ฒจ์์ ๊ฒฝ์ฐ ๋๊ธฐ)
number--;
Monitor.Exit(_obj); // ๋ฌธ์ ์ ๊ธ์ฅ์น ํด์ (์๋ตํ ๊ฒฝ์ฐ deadlock ๋ฐ์ ๊ฐ๋ฅ)
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(number);
}
}
}โ
lock Keyword ์์ฉ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static int number = 0;
static object _obj = new object();
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
{
lock(_obj) // ๋ด๋ถ๋ Monitor.Enter ๊ณผ Monitor.Exit ๋ก ๊ตฌํ๋จ (์๋์ผ๋ก lock ํด์ )
{
number++;
}
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
{
lock (_obj) // ๋ด๋ถ๋ Monitor.Enter ๊ณผ Monitor.Exit ๋ก ๊ตฌํ๋จ (์๋์ผ๋ก lock ํด์ )
{
number--;
}
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(number);
}
}
}โ
# DeadLock
- Dead Lock (๊ต์ฐฉ ์ํ) ์ด๋ 2๊ฐ ์ด์์ Process ๋๋ Thread๊ฐ Mutual Exclusive ์ผ๋ก ์ฌ์ฉํ๋ ์์์ ์์ฒญํ๋ฉด์
์๋ก๊ฐ ๊ฐ์ง ์์์ ๋๊ธฐํ๋ ํ์์ ๋งํ๋ค.
- Dead Lock์ try-catch๋ฌธ, Monitor.TryEnter()๋ฅผ ํตํด ์์ธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ๋ณด๋จ Crash ๋ฅผ ํตํด ์ค๋ฅ๋ฅผ ์์ ํ๋ ๊ฒ์ด ๋ ์ข๋ค.
- Dead Lock์ ๊ฐ๋ฐ ๋จ๊ณ์์ ์ ๋ฐ์ํ์ง ์๋ค๊ฐ ์ถ์ ์ดํ User๋ค์ด ๋ชฐ๋ฆฌ๋ ์ํฉ์ ๊ฐ์์ค๋ฝ๊ฒ ๋ฐ์ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค.
Dead Lock ์์ฉ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class SessionManager
{
static object _lock = new object();
public static void TestSession()
{
lock (_lock)
{
}
}
public static void Test()
{
lock (_lock)
{
UserManager.TestUser();
}
}
}
class UserManager
{
static object _lock = new object();
public static void TestUser()
{
lock (_lock)
{
}
}
public static void Test()
{
lock (_lock)
{
SessionManager.TestSession();
}
}
}
class Program
{
static void Thread_1()
{
for (int i = 0; i < 10000; i++)
{
SessionManager.Test();
}
}
static void Thread_2()
{
for (int i = 0; i < 10000; i++)
{
UserManager.Test();
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
}
}
}โ
# Lock ๊ตฌํ ์ด๋ก
- Lock ์์ฒญ์ ์ด๋ฏธ ๋๊ฐ Lock์ ์ ์ ํ๊ณ ์์ ๊ฒฝ์ฐ ์ด๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง์ ๋ฐ๋ผ ์ฑ๋ฅ์ด ๊ฒฐ์ ๋๋ค.
1. ๋ฃจํ๋ฅผ ๋๋ฉด์ ๊ณ์ ์ ์ ๋ฅผ ์๋ํ๋ค. ( SpinLock )
2. Thread๊ฐ ์์ ์ ์์ ๊ถ์ ํฌ๊ธฐํ๊ณ , ๋์ค์ ๋ค์ lock์ ์์ฒญํ๋ค. ( ๋๋ค์ฑ / context switching ๋น์ฉ ๋ฐ์ )
3. ์ด์์ฒด์ ์๊ฒ lock์ด ํด์ ๋ ๊ฒฝ์ฐ ๋ณธ์ธ์๊ฒ ํต๋ณดํด๋ฌ๋ผ๊ณ ์์ฒญํ๋ค. ( AutoResetEvent )
# SpinLock
์ค๋ฅ๊ฐ ๋ฐ์ํ๋ SpinLock ๊ตฌํ (์ฆ, ๋ฃจํ๋ฅผ ๋๋ฉด์ ๊ณ์ ์ ์ ๋ฅผ ์๋)
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class SpinLock
{
volatile bool _locked = false; // lock ์ ๊ธ ์ํ (ํด๋น lock์ ์ฌ์ฉ ์ ๋ฌด)
public void Acquire() // lock ์ค์
{
// ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ์ด์ ๋ lock ์ ๊ธ ์ํ ํ์ธ๊ณผ lock์ ์ ๊ทธ๋ ๊ฒ์ ๋ฐ๋ก ํ๊ณ ์๊ธฐ ๋๋ฌธ
// ๋ง์ฝ 2๊ฐ์ Thread๊ฐ ๊ฑฐ์ ๋์๋ค๋ฐ์ ์ผ๋ก ๋ค์ด์ ๋์์ while ๋ฌธ์ ํต๊ณผํ ๊ฒฝ์ฐ ๋ฌธ์ ๋ฐ์
// ๋ฐ๋ผ์ ์๋์ lock ์ ๊ธ ์ํ ํ์ธ๊ณผ lock์ ์ ๊ทธ๋ ์ฝ๋๋ฅผ ํ๋๋ก ๋ฌถ์ด์ค์ผ ํ๋ค.
// ์ค๋ฅ ๋ฐ์ ์์ธ ์์
while (_locked)
{
// ์ ๊ธ ์ํ๊ฐ ํ๋ฆด๋๊น์ง ๋ฌด์์ ๊ธฐ๋ค๋ฆฐ๋ค.
}
_locked = true;
// ์ค๋ฅ ๋ฐ์ ์์ธ ์ข
๋ฃ
}
public void Release() // lock ํด์
{
_locked = false;
}
}
class Program
{
static int _num = 0;
static SpinLock _lock = new SpinLock();
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
{
_lock.Acquire();
_num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
{
_lock.Acquire();
_num--;
_lock.Release();
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(_num);
}
}
}โ
์ ์์ ์ผ๋ก ๋์ํ๋ SpinLock ๊ตฌํ (์ฆ, ๋ฃจํ๋ฅผ ๋๋ฉด์ ๊ณ์ ์ ์ ๋ฅผ ์๋)
using System; using System.Threading; using System.Threading.Tasks; namespace ServerCore { class SpinLock { volatile int _locked = 0; // lock ์ ๊ธ ์ํ (ํด๋น lock์ ์ฌ์ฉ ์ ๋ฌด) public void Acquire() // lock ์ค์ { while (true) { // ๋ฐฉ๋ฒ #1 // Interlocked.Exchange๋ 1๋ฒ์งธ ์ธ์์ ๊ฐ์ 2๋ฒ์งธ ์ธ์์ ๊ฐ์ผ๋ก ๋ณ๊ฒฝํ๋ฉฐ, ๋ฐํ๊ฐ์ 1๋ฒ์งธ ์ธ์์ ๋ณ๊ฒฝ ์ ๊ฐ์ด๋ค. int original = Interlocked.Exchange(ref _locked, 1); if (original == 0) // ์ฆ, ์๋ฌด๋ lock์ ์ฌ์ฉํ์ง ์๋ ์ํฉ์์ ๋ด๊ฐ lock์ ๊ฑด ๊ฒฝ์ฐ break; // ๋ฌด์์ ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ์ ๊ทธ๋ง๋๊ฒ ๋ค. (์ฆ, lock ํ๋ ์ฑ๊ณต) // ๋ฐฉ๋ฒ #2 // Interlocked.CompareExchange๋ 1, 3๋ฒ์งธ ์ธ์์ ๊ฐ์ด ๊ฐ๋ค๋ฉด 2๋ฒ์งธ ์ธ์์ ๊ฐ์ 1๋ฒ์งธ ์ธ์์ ๋์ ํ๋ฉฐ, ๋ฐํ๊ฐ์ 1๋ฒ์งธ ์ธ์์ ๋ณ๊ฒฝ ์ ๊ฐ์ด๋ค. // Interlocked.CompareExchange๋ CAS (Compare-And-Swap) ์ฐ์ฐ ์ํ int expected = 0; // ์์ํ ๊ฐ int desired = 1; // ๊ธฐ๋ํ ๊ฐ int original2 = Interlocked.CompareExchange(ref _locked, desired, expected); if (original2 == 0) // ์ฆ, ์๋ฌด๋ lock์ ์ฌ์ฉํ์ง ์๋ ์ํฉ์์ ๋ด๊ฐ lock์ ๊ฑด ๊ฒฝ์ฐ break; // ๋ฌด์์ ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ์ ๊ทธ๋ง๋๊ฒ ๋ค. (์ฆ, lock ํ๋ ์ฑ๊ณต) } } public void Release() // lock ํด์ { _locked = 0; } } class Program { static int _num = 0; static SpinLock _lock = new SpinLock(); static void Thread_1() { for (int i = 0; i < 100000; i++) { _lock.Acquire(); _num++; _lock.Release(); } } static void Thread_2() { for (int i = 0; i < 100000; i++) { _lock.Acquire(); _num--; _lock.Release(); } } static void Main(string[] args) { Task t1 = new Task(Thread_1); Task t2 = new Task(Thread_2); t1.Start(); t2.Start(); Task.WaitAll(t1, t2); Console.WriteLine(_num); } } }โ
# Context Switching
- ๋ฃจํ๋ฅผ ๋๋ฉด์ ๊ณ์ ์ ์ ๋ฅผ ์๋ํ๋ SpinLock๊ณผ ๋ฌ๋ฆฌ, Sleep๊ณผ Yield๋ฅผ ํตํด Thread๊ฐ ์์ ์ ์์ ๊ถ์ ํฌ๊ธฐํ๊ณ ,
๋์ค์ ๋ค์ lock์ ์์ฒญํ๋ lock ๊ตฌํ ๋ฐฉ์์ด ๋ง๋ฅ ์ฅ์ ๋ง ์กด์ฌํ๋ค๊ณ ๋ ํ ์ ์๋ค.
~> Context Switching ๋น์ฉ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
- Context Switching ์ด๋ CPU๊ฐ ์ด๋ค Process ๋๋ Thread๋ฅผ ์คํํ๊ณ ์๋ ์ํ์์ ์ด์์ฒด์ ์ Scheduling์ ์ํด
๋ ๋์ ์ฐ์ ์์๋ฅผ ๊ฐ์ง Process ๋๋ Thread๊ฐ ์คํ๋์ด์ผ ํ ๋, Register์ ์ ์ฅ๋ ๊ธฐ์กด Process ๋๋ Thread์ ์ ๋ณด
๋ฅผ Kernel์ ์ ์ฅํ๊ณ ์คํํ๊ณ ์ ํ๋ Process ๋๋ Thread์ ์ ๋ณด๋ฅผ ๋ณต์ํ๋ ์ผ๋ จ์ ๊ณผ์ ์ ๋งํ๋ฉฐ ์ด๋ฌํ ๊ณผ์ ์
lock ๊ตฌํ๋๋ฟ๋ง์ด ์๋ ๋์ ๋ฐ์ํ๊ณ ์๋ ์์ฐ์ค๋ฌ์ด ํ์์ด๋ค.
Sleep๊ณผ Yield ๋ฅผ ํตํ lock ๊ตฌํ (์ฆ, Thread๊ฐ ์์ ์ ์์ ๊ถ์ ํฌ๊ธฐํ๊ณ , ๋์ค์ ๋ค์ lock์ ์์ฒญ)
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Lock
{
volatile int _locked = 0; // lock ์ ๊ธ ์ํ (ํด๋น lock์ ์ฌ์ฉ ์ ๋ฌด)
public void Acquire() // lock ์ค์
{
while (true)
{
// ๋ฐฉ๋ฒ #1
int original = Interlocked.Exchange(ref _locked, 1);
if (original == 0)
break;
// ๋ฐฉ๋ฒ #2
int expected = 0;
int desired = 1;
int original2 = Interlocked.CompareExchange(ref _locked, desired, expected);
if (original2 == 0)
break;
// ์ ์ ์ฌ๋ค ์ฌ๊ฒ~ (3๊ฐ์ ๋ฐฉ๋ฒ์ค 1๊ฐ ์ ํ)
Thread.Sleep(1); // ๋ฌด์กฐ๊ฑด ํด์ => 1ms ์ ๋ ์ฌ๊ณ ์ถ์๋ฐ ์ ํํ ์๊ฐ์ ์ด์์ฒด์ ๊ฐ ๊ฒฐ์
Thread.Sleep(0); // ์กฐ๊ฑด๋ถ ์๋ณด => ๋๋ณด๋ค ์ฐ์ ์์๊ฐ ๋ฎ์ ์ ๋คํํ
๋ ์๋ณด X => ๋๋ณด๋ค ์ฐ์ ์์๊ฐ ๋๊ฑฐ๋ ๊ฐ์ ์ ๋ค์ด ์๋ค๋ฉด ๋จ์ ์๊ฐ์ ๋ณธ์ธ์ด ์์ง
Thread.Yield(); // ๊ด๋ํ ์๋ณด => ๊ด๋ํ๊ฒ ์๋ณดํ ํ
๋, ์ง๊ธ ์คํ์ด ๊ฐ๋ฅํ ์ ๊ฐ ์๋ค๋ฉด ์คํํ์ธ์ => ์คํ ๊ฐ๋ฅํ ์ ๊ฐ ์๋ค๋ฉด ๋จ์ ์๊ฐ์ ๋ณธ์ธ์ด ์์ง
}
}
public void Release() // lock ํด์
{
_locked = 0;
}
}
class Program
{
static int _num = 0;
static Lock _lock = new Lock();
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
{
_lock.Acquire();
_num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
{
_lock.Acquire();
_num--;
_lock.Release();
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(_num);
}
}
}โ
# AutoResetEvent
AutoResetEvent๋ฅผ ํตํ lock ๊ตฌํ (์ฆ, ์ด์์ฒด์ ์๊ฒ lock์ด ํด์ ๋ ๊ฒฝ์ฐ ๋ณธ์ธ์๊ฒ ํต๋ณดํด๋ฌ๋ผ๊ณ ์์ฒญ)
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Lock
{
// bool <- Kernel
AutoResetEvent _avaliable = new AutoResetEvent(true); // true์ผ ๊ฒฝ์ฐ ์๋ฌด๋ ๋ค์ด์ฌ ์ ์๋ ์ํ, false์ผ ๊ฒฝ์ฐ ๋๊ตฌ๋ ๋ค์ด์ฌ ์ ์๋ ์ํ
public void Acquire() // lock ์ค์
{
_avaliable.WaitOne(); // ์
์ฅ ์๋ (true์ผ ๊ฒฝ์ฐ ์
์ฅ์ด ๊ฐ๋ฅํ๋ฉฐ, ์๋์ผ๋ก ๋ฌธ์ ๋ซ์์ค๋ค.)
// _avaliable.Reset()์ bool์ ๊ฐ์ false๋ก ๋ณ๊ฒฝ -> AutoResetEvent์์๋ WaitOne() ๋ด๋ถ์ ํฌํจ๋์ด ์๋ค.
}
public void Release() // lock ํด์
{
_avaliable.Set(); // bool์ ๊ฐ์ true๋ก ๋ณ๊ฒฝ
}
}
class Program
{
static int _num = 0;
static Lock _lock = new Lock();
static void Thread_1()
{
for (int i = 0; i < 1000; i++)
{
_lock.Acquire();
_num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 1000; i++)
{
_lock.Acquire();
_num--;
_lock.Release();
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(_num);
}
}
}โ
์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ManualResetEvent๋ฅผ ํตํ lock ๊ตฌํ
(์ฆ, ์ด์์ฒด์ ์๊ฒ lock์ด ํด์ ๋ ๊ฒฝ์ฐ ๋ณธ์ธ์๊ฒ ํต๋ณดํด๋ฌ๋ผ๊ณ ์์ฒญ)
using System; using System.Threading; using System.Threading.Tasks; namespace ServerCore { class Lock { // bool <- Kernel ManualResetEvent _avaliable = new ManualResetEvent(true); // true์ผ ๊ฒฝ์ฐ ์๋ฌด๋ ๋ค์ด์ฌ ์ ์๋ ์ํ, false์ผ ๊ฒฝ์ฐ ๋๊ตฌ๋ ๋ค์ด์ฌ ์ ์๋ ์ํ public void Acquire() // lock ์ค์ { // ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ ์ด์ ๋ ์ ์ฅ ์๋์ ๋ฌธ์ ๋ซ๋ ๊ฒ์ ๋ฐ๋ก ํ๊ณ ์๊ธฐ ๋๋ฌธ _avaliable.WaitOne(); // ์ ์ฅ ์๋ (true์ผ ๊ฒฝ์ฐ ์ ์ฅ์ด ๊ฐ๋ฅํ๋ฉฐ, ์๋์ผ๋ก ๋ฌธ์ ๋ซ์์ฃผ์ง ์๋๋ค.) _avaliable.Reset(); // bool์ ๊ฐ์ false๋ก ๋ณ๊ฒฝ -> ManualResetEvent์์๋ WaitOne() ๋ด๋ถ์ ํฌํจ๋์ด ์์ง ์๋ค. } public void Release() // lock ํด์ { _avaliable.Set(); // bool์ ๊ฐ์ true๋ก ๋ณ๊ฒฝ } } class Program { static int _num = 0; static Lock _lock = new Lock(); static void Thread_1() { for (int i = 0; i < 1000; i++) { _lock.Acquire(); _num++; _lock.Release(); } } static void Thread_2() { for (int i = 0; i < 1000; i++) { _lock.Acquire(); _num--; _lock.Release(); } } static void Main(string[] args) { Task t1 = new Task(Thread_1); Task t2 = new Task(Thread_2); t1.Start(); t2.Start(); Task.WaitAll(t1, t2); Console.WriteLine(_num); } } }โ
Mutex๋ฅผ ํตํ lock ๊ตฌํ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static int _num = 0;
static Mutex _lock = new Mutex();
static void Thread_1()
{
for (int i = 0; i < 1000; i++)
{
_lock.WaitOne(); // ์
์ฅ ์๋ (์
์ฅ ์ฑ๊ณต์ ๋ฌธ์ ์ ๊ทผ๋ค.)
_num++;
_lock.ReleaseMutex(); // ๋๊ฐ๋ฉด์ ๋ฌธ์ ์ ๊ธ์ ํด์ ํ๋ค.
}
}
static void Thread_2()
{
for (int i = 0; i < 1000; i++)
{
_lock.WaitOne(); // ์
์ฅ ์๋ (์
์ฅ ์ฑ๊ณต์ ๋ฌธ์ ์ ๊ทผ๋ค.)
_num--;
_lock.ReleaseMutex(); // ๋๊ฐ๋ฉด์ ๋ฌธ์ ์ ๊ธ์ ํด์ ํ๋ค.
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2 = new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(_num);
}
}
}
+ ์ถ๊ฐ ์ ๋ฆฌ
- ManualResetEvent ์ฌ์ฉ ์ด์ ?
~> ManualResetEvent๋ 1๊ฐ์ Thread๋ง ํต๊ณผ์ํค๊ณ ๋ฌธ์ ๋ซ๋ AutoResetEvent์ ๋ฌ๋ฆฌ, ๋ฌธ์ด 1๋ฒ ์ด๋ฆฌ๋ฉด ๋๊ธฐ์ค์ด๋
์ฌ๋ฌ๊ฐ์ Thread ๋ฅผ ๋์์ ์ํํ๊ณ ์ ํ ๋ ์ ์ฉํ๋ค.
- AutoResetEvent์ Mutex์ ์ฐจ์ด?
~> Mutex๋ AutoResetEvent ๋ณด๋ค ๋ ๋ง์ ์ ๋ณด๋ฅผ ๊ฐ์ง๋ค. (์ ๊ธ ํ์, Thread ID ๋ฑ...) ์ด์ ๋ฐ๋ฅธ ์ถ๊ฐ๋น์ฉ๋ ์กด์ฌํ๋ค.
# ReaderWriterLock
ReaderWriterLock ์์ฉ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
// ์๋ฅผ ๋ค์ด ๊ฒ์์์ ์ผ์ผํ์คํธ ๋ณด์ ์ง๊ธ์ ์ฝ์ธ, ๊ฒฝํ์น, ๋ฌผ์ฝ์ ๊ณ ์ ์ ์ธ ๋ณด์์ด๋ฉฐ
// ์ฃผ๋ง, ๋ช
์ ์ด๋ฒคํธ๋ก ์ผ์ผ ํ์คํธ ๋ณด์์ +a ์ ๋์ ๋ณด์์ด ์ถ๊ฐ๋ก ์ง๊ธ๋๋ค๊ณ ๊ฐ์ ํ์.
// ๋ณด์์ ์ป๋ ํจ์๋ ์์์ด ์คํ๋ ๊ฑฐ๊ณ , ๋ณด์์ ์ถ๊ฐํ๋ ํจ์๋ ๊ฐ๋ ์คํ๋ ๊ฒ์ด๋ค.
// ๋ฐ๋ผ์ ๋ณด์์ ์ถ๊ฐํ๋ ํจ์์ lock์ ์คํ๋ ํ๋ฅ ์ด 1.0%, ์คํ๋์ง ์์ ํ๋ฅ ์ด 99.0% ์ด๋ค.
// ์ด๋ด๋ ์ฐ๋ lock์ด ReaderWriterLock์ด๋ค. (ReaderWriterLockSlim์ด ๋ ์ต์ ๋ฒ์ )
class Reward
{
}
static Reward GetRewardById(int id)
{
_lock.EnterReadLock(); // ์๋ฌด๋ WriteLock ์ ์ก๊ณ ์์ง ์์ ๊ฒฝ์ฐ์ ๋ง์น lock์ด ์๋๊ฒ์ฒ๋ผ Thread๋ค์ด ๋์๋ค๋ฐ์ ์ผ๋ก ๋ค์ด์ฌ ์ ์๋ค.
_lock.ExitReadLock();
return null;
}
void AddReward(Reward reward)
{
_lock.EnterWriteLock(); // 1๋ฒ์ 1๊ฐ์ Thread๋ง ํ๋ํ ์ ์๋ค.
_lock.ExitWriteLock();
}
static void Main(string[] args)
{
}
}
}
+ ์ถ๊ฐ ๊ฒ์ ( https://rito15.github.io/posts/06-cs-reader-writer-lock/ )
- Thread ๊ฐ์ ๊ณต์ ๋๋ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ ๊ฒฝ์ฐ, ํญ์ ๋ชจ๋ Thread๊ฐ ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ณ ์ฐ๋ ๊ฒ์ ์๋๋ค.
~> ์ด๋ค Thread๋ ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ธฐ๋ง ํ๊ณ , ์ด๋ค Thread๋ ํด๋น ๋ฐ์ดํฐ๋ฅผ ์ฐ๊ธฐ๋ง ํ ์๋ ์๋ค.
~> ๋ํ ๋ค์์ ์ฝ๊ธฐ Thread, ์์์ ์ฐ๊ธฐ Thread๊ฐ ์ํ๋๋ ๊ฒฝ์ฐ๋ ์กด์ฌํ๋ค.
~> ์ด๋ด๋ ์ผ๋ฐ์ ์ธ lock์ ๊ตฌํํ์ฌ ์ฝ๊ธฐ/์ฐ๊ธฐ๊ฐ ์ํ๋๋ ๋์์ ํญ์ lock์ ์ค์ /ํด์ ํ ๊ฒฝ์ฐ
๋ฐ์ดํฐ๋ฅผ ๋จ์ํ ์ฝ๊ธฐ๋ง ํ์ฌ ๊ฐ์ด ๋ณ๊ฒฝ๋์ง ์๋ ์ํฉ์๋ ๋ถํ์ํ๊ฒ Critical Section์ ๋ง๋ค๊ฒ ๋๋ฏ๋ก
์ฑ๋ฅ ๊ด์ ์์ ์ํด๋ค.
~> ReaderWriterlock ์ ํตํด ํด๊ฒฐ์ด ๊ฐ๋ฅํ๋ค.
- ReaderWriterlock์ ๋ฐ์ดํฐ์ ๋ํ ์ฐ๊ธฐ ์์ ์์๋ง lock์ ์ค์ ํ๊ณ ์ฝ๊ธฐ ์์ ์์๋ lock์ ์ค์ ํ์ง ์๋
๋น๋์นญ์ ์ธ lock์ ๊ตฌํํ์ฌ ์ฑ๋ฅ ๊ด์ ์์ ์ด๋์ ์ทจํ๋ค.
# ReaderWriterLock ๊ตฌํ ์ฐ์ต
์ฌ๊ท์ lock์ ํ์ฉํ์ง ์๋ ReaderWriterLock ๊ตฌํ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
// ์ฌ๊ท์ lock์ ํ์ฉํ ์ง (No)
// Spinlock ์ ์ฑ
(5000๋ฒ Spin ํ Yield)
class Lock
{
const int EMPTY_FLAG = 0x00000000;
const int WRITE_MASK = 0X7FFF0000; // WRITE ๋ถ๋ถ๋ง ์ถ์ถํ๊ธฐ ์ํ MASK (AND ์ฐ์ฐ์ ํตํ ์ถ์ถ)
const int READ_MASK = 0x0000FFFF; // READ ๋ถ๋ถ๋ง ์ถ์ถํ๊ธฐ ์ํ MASK (AND ์ฐ์ฐ์ ํตํ ์ถ์ถ)
const int MAX_SPIN_COUNT = 5000;
// [ Unused (1bit) ] [ WriteThreadId (15bit) ] [ ReadCount (16bit) ] ~> 32bit (intํ)
// Unused : ์ต์์ bit๋ ๋ถํธ bit์ด๋ฏ๋ก ์์๊ฐ ๋ ๊ฐ๋ฅ์ฑ์ด ์์ด ์ฌ์ฉ X / WriteThreadId : Write lock์ ๊ฐ์ง Thread์ Id / ReadCount : ์ฝ๊ธฐ Thread์ ์ด ์
int _flag = EMPTY_FLAG;
public void WriteLock()
{
// ์๋ฌด๋ WriteLock of ReadLock์ ํ๋ํ๊ณ ์์ง ์์ ๋, ๊ฒฝํฉํด์ ์์ ๊ถ์ ์ป๋๋ค.
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK; // WriteThreadId (15bit)์๋ ๋ณธ์ธ์ Thread Id๋ฅผ , WriteThreadId (15bit)๋ฅผ ์ ์ธํ ๋๋จธ์ง bit๋ ์ ๋ถ 0์ผ๋ก ์ค์
while (true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
// ์๋๋ฅผ ํด์ ์ฑ๊ณตํ๋ฉด return
if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
return;
}
Thread.Yield();
}
}
public void WriteUnlock()
{
Interlocked.Exchange(ref _flag, EMPTY_FLAG);
}
public void ReadLock()
{
// ์๋ฌด๋ WriteLock์ ํ๋ํ๊ณ ์์ง ์์ ๋, ReadCount๋ฅผ 1 ๋๋ฆฐ๋ค.
while (true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
int expected = (_flag & READ_MASK); // READ_MASK์ AND ์ฐ์ฐ์ WRITE ๋ถ๋ถ๋ 0 -> ์๋ฌด๋ WriteLock์ ํ๋ํ๊ณ ์์ง X
if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
return;
// ์์ ์ฐ์ฐ์ด ์คํจํ๋ ๊ฒฝ์ฐ ?
// #1. ๋๊ตฐ๊ฐ WriteLock์ ํ๋ํ๊ณ ์๋ ๊ฒฝ์ฐ
// #2. A์ B Thread๊ฐ ๊ฑฐ์ ๋์์ ๋์ฐฉํ์ฌ expected์ ๊ฐ์ ๋์ผํ๊ฒ ์ป์ง๋ง,
// A Thread๊ฐ ๋จผ์ CompareExchange ๋ฅผ ํตํด expected์ ๊ฐ์ +1 ํ ๊ฒฝ์ฐ
// B Thread๊ฐ CompareExchange ์ _flag์ ๊ฐ์ด expected + 1 ๋ก ๋ณ๊ฒฝ๋์ด ์์ฒญ์ด ์คํจํ๋ค.
}
Thread.Yield();
}
}
public void ReadUnlock()
{
Interlocked.Decrement(ref _flag);
}
}
}โ
์ฌ๊ท์ lock์ ํ์ฉํ๋ ReaderWriterLock ๊ตฌํ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
// ์ฌ๊ท์ lock์ ํ์ฉํ ์ง (Yes) : WriteLock ~> WriteLock (OK), WriteLock ~> ReadLock (OK), ReadLock ~> WriteLock (Not OK)
class Lock
{
const int EMPTY_FLAG = 0x00000000;
const int WRITE_MASK = 0X7FFF0000;
const int READ_MASK = 0x0000FFFF;
const int MAX_SPIN_COUNT = 5000;
int _flag = EMPTY_FLAG;
int _writeCount = 0; // ์ฌ๊ท์ ์ผ๋ก ๋ช๊ฐ์ Write๋ฅผ ํ ๊ฒ์ธ์ง
public void WriteLock()
{
// ๋์ผ Thread๊ฐ WriteLock์ ์ด๋ฏธ ํ๋ํ๊ณ ์๋์ง ํ์ธ
int lockThreadId = (_flag & WRITE_MASK);
if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
{
_writeCount++;
return;
}
// ์๋ฌด๋ WriteLock of ReadLock์ ํ๋ํ๊ณ ์์ง ์์ ๋, ๊ฒฝํฉํด์ ์์ ๊ถ์ ์ป๋๋ค.
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK; // WriteThreadId (15bit)์๋ ๋ณธ์ธ์ Thread Id๋ฅผ , WriteThreadId (15bit)๋ฅผ ์ ์ธํ ๋๋จธ์ง bit๋ ์ ๋ถ 0์ผ๋ก ์ค์
while (true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
// ์๋๋ฅผ ํด์ ์ฑ๊ณตํ๋ฉด return
if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
{
_writeCount = 1;
return;
}
}
Thread.Yield();
}
}
public void WriteUnlock()
{
int lockCount = --_writeCount;
if (lockCount == 0)
Interlocked.Exchange(ref _flag, EMPTY_FLAG);
}
public void ReadLock()
{
// ๋์ผ Thread๊ฐ WriteLock์ ์ด๋ฏธ ํ๋ํ๊ณ ์๋์ง ํ์ธ
int lockThreadId = (_flag & WRITE_MASK);
if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
{
Interlocked.Increment(ref _flag);
return;
}
// ์๋ฌด๋ WriteLock์ ํ๋ํ๊ณ ์์ง ์์ ๋, ReadCount๋ฅผ 1 ๋๋ฆฐ๋ค.
while (true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
int expected = (_flag & READ_MASK); // READ_MASK์ AND ์ฐ์ฐ์ WRITE ๋ถ๋ถ๋ 0 -> ์๋ฌด๋ WriteLock์ ํ๋ํ๊ณ ์์ง X
if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
return;
}
Thread.Yield();
}
}
public void ReadUnlock()
{
Interlocked.Decrement(ref _flag);
}
}
}โ
๊ตฌํํ ReaderWriterLock ๋์ ๊ฒฐ๊ณผ ํ์ธ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static volatile int count = 0;
static Lock _lock = new Lock();
static void Main(string[] args)
{
Task t1 = new Task(delegate ()
{
for (int i = 0; i < 100000; i++)
{
_lock.WriteLock();
count++;
_lock.WriteUnlock();
}
});
Task t2 = new Task(delegate ()
{
for (int i = 0; i < 100000; i++)
{
_lock.WriteLock();
count--;
_lock.WriteUnlock();
}
});
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(count);
}
}
}โ
# Thread Local Storage

- MultiThread ํ๊ฒฝ์์ ์๋ง์ Thread๊ฐ ํ๋์ ์์์ ์ ๊ทผํ๊ณ ์ ํ ๋ lock์ ์ํธ ๋ฐฐํ์ ์ธ ๊ฐ๋ ์ด๊ธฐ ๋๋ฌธ์
1๋ฒ์ 1๊ฐ์ ์์ ๋ง์ ์ฒ๋ฆฌํ ์ ์๋ค. ์ด์ MultiThread ํ๊ฒฝ์ ์ฅ์ ์ ํ์ฉํ์ง ๋ชปํ ๋ฟ๋๋ฌ ํ๋์ Thread๊ฐ ์ฒ๋ฆฌํ๋
์๋๋ณด๋ค ๋ชปํ ์๋ ์๋ค.
~> Thread Local Storage๋ฅผ ํตํด ํด๊ฒฐ์ด ๊ฐ๋ฅํ๋ค.
- Thread Local Storage๋ ์ ์ญ๋ณ์์ด์ง๋ง, Thread ๋ง๋ค ๊ณ ์ ํ๊ฒ ์ ๊ทผํ ์ ์๋ ์ ์ญ ๋ณ์์ด๋ค.
Thread Local Storage ์์ฉ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
// Thread ๋ง๋ค ๊ณ ์ ํ๊ฒ ์ ๊ทผํ ์ ์๋ ์ ์ญ๋ณ์
static ThreadLocal<string> ThreadName = new ThreadLocal<string>();
static void WhoAmI()
{
ThreadName.Value = $"My Name Is {Thread.CurrentThread.ManagedThreadId}";
Thread.Sleep(1000);
Console.WriteLine(ThreadName.Value);
}
static void Main(string[] args)
{
Parallel.Invoke(WhoAmI, WhoAmI, WhoAmI, WhoAmI); // ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅํ๋ Action ๋งํผ Task๋ฅผ ๋ง๋ค์ด์ค๋ค.
ThreadName.Dispose(); // ์์ ํด์
}
}
}โ
๋ณด๋ค ์ฑ๋ฅ์ ๊ฐ์ ํ Thread Local Storage ์์ฉ
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
// Thread ๋ง๋ค ๊ณ ์ ํ๊ฒ ์ ๊ทผํ ์ ์๋ ์ ์ญ๋ณ์
// new ThreadLocal์ ์ธ์๋ก Func delegate๋ฅผ ๋ฐ์ ์ ์๋ค.
// ์ด๋ฅผ ํตํด TLS๊ฐ ์๋ก ๋ง๋ค์ด์ง ๊ฒฝ์ฐ ThreadLocal.Value์ return ๊ฐ์ ๋ฃ์ด์ค ๋ค ๊ทธ๋๋ก ๋ณด๊ดํ๋ค.
static ThreadLocal<string> ThreadName = new ThreadLocal<string>(() => { return $"My Name Is {Thread.CurrentThread.ManagedThreadId}"; });
static void WhoAmI()
{
// IsValueCreated๋ ThreadLocal.Value์ ๊ฐ์ด ์ด๊ธฐํ ๋ ๊ฒฝ์ฐ true, ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ false๋ฅผ ๋ฐํํ๋ค.
bool repeat = ThreadName.IsValueCreated;
if (repeat)
Console.WriteLine(ThreadName.Value + " (repeat)");
else
Console.WriteLine(ThreadName.Value);
}
static void Main(string[] args)
{
ThreadPool.SetMinThreads(1, 1);
ThreadPool.SetMaxThreads(3, 3);
Parallel.Invoke(WhoAmI, WhoAmI, WhoAmI, WhoAmI, WhoAmI, WhoAmI, WhoAmI, WhoAmI); // ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅํ๋ Action ๋งํผ Task๋ฅผ ๋ง๋ค์ด์ค๋ค.
ThreadName.Dispose(); // ์์ ํด์
}
}
}โ
[ ์น์ 2. ๋คํธ์ํฌ ํ๋ก๊ทธ๋๋ฐ ]
# ๋คํธ์ํฌ ๊ธฐ์ด ์ด๋ก


- ์ผ์ฑ ์ํํธ 201ํธ์ ์ฌ๋ ์ฌ๋์ด ์ผ์ฑ ์ํํธ 102ํธ์ ์ฌ๋ ์ฌ๋์๊ฒ ํ๋ฐฐ๋ฅผ ๋ณด๋ด๊ณ ์ ํ๋ค.
- ์ผ์ฑ ์ํํธ 202ํธ์ ์ฌ๋ ์ฌ๋์ด ํ๋ ์ํํธ 101ํธ์ ์ฌ๋ ์ฌ๋์๊ฒ ํ๋ฐฐ๋ฅผ ๋ณด๋ด๊ณ ์ ํ๋ค.
~> ์ด๋ ๋ณธ์ธ์ด ์ง์ ํ๋ฐฐ๋ฅผ ๋ฐฐ์กํ๋ ๊ฒ์ด ์๋ ์ฐ์ ๊ฒฝ๋น์ค์ ๋ณธ์ธ์ด ๋ณด๋ด๊ณ ์ ํ๋ ํ๋ฐฐ๋ฅผ ์ ๋ฌํ๋ค.
~> ๊ฒฝ๋น์ค์์ ๋ณด๋ด๋ ์ฌ๋๊ณผ ๋ฐ๋ ์ฌ๋์ ํ์ธํ ๋ค, ๋ง์ฝ ๋ ์ฌ๋์ด ์ฌ๋ ์ํํธ๊ฐ ๊ฐ๋ค๋ฉด ๊ฒฝ๋น ์์ ์จ๊ฐ ์ง์
ํ๋ฐฐ๋ฅผ ์ ๋ฌํด์ฃผ๊ณ , ๊ฐ์ง ์๋ค๋ฉด ํ๋ฐฐ ๋ฐฐ์ก์ผํฐ๋ก ํด๋น ํ๋ฐฐ๋ฅผ ์ ๋ฌํ๋ค.
~> ํ๋ฐฐ ๋ฐฐ์ก ์ผํฐ์์ ๋ฐ๋ ์ฌ๋์ด ๊ฑฐ์ฃผํ๋ ์ํํธ์ ๊ฒฝ๋น์ค์ ํ๋ฐฐ๋ฅผ ์ ๋ฌํ๋ฉด, ๊ฒฝ๋น์์ ์จ๊ฐ ์ง์ ํ๋ฐฐ๋ฅผ
์ ๋ฌํด์ค๋ค.
- ์์ ๋ด์ฉ์์ ๊ณ ๊ฐ์ ๋จ๋ง๊ธฐ, ๊ฒฝ๋น์ค์ ์ค์์น, ํ๋ฐฐ ๋ฐฐ์ก์ผํฐ๋ ๋ผ์ฐํฐ, ์ํํธ ๋จ์ง๋ ๋คํธ์ํฌ์ ํด๋นํ๋ค.
# ํต์ ๋ชจ๋ธ

- ๊ฒ์์ ๊ท๋ชจ, ์ฅ๋ฅด, ๊ฐ๋ฐ์์ ์ญ๋์ ๋ฐ๋ผ Protocol์ ์ ํํ๋ ๊ฒ์ด ์ค์ํ๋ค.
~> MMO RPG๋ ์ฃผ๋ก TCP, Latency๊ฐ ์ค์ํ FPS๋ ์ฃผ๋ก UDP๋ฅผ ์ฌ์ฉํ๋ค.
~> TCP๋ ๋๋ฆฐ ์๋์ ๋์ ์ ๋ขฐ์ฑ์ ๊ฐ์ง๊ณ , UDP๋ ๋น ๋ฅธ ์๋์ ๋ฎ์ ์ ๋ขฐ์ฑ์ ๊ฐ์ง๋ค.
# ์์ผ ํ๋ก๊ทธ๋๋ฐ ์ ๋ฌธ #1

- ์๋์ ๋ณธ์ธ์ ํธ๋ํฐ์ ํตํด ์๋น์ ์ ํ๋ฅผ ๊ฑธ์ด ์ ์ฅ์ด ๊ฐ๋ฅํ์ง ๋ฌผ์ด๋ณด๊ณ , ๋ง์ฝ ์ ์ฅ์ด ๊ฐ๋ฅํ ๊ฒฝ์ฐ ๋ณธ์ธ์ ๋๋ฆฌ์ธ์
์๋น์ ์ ์ฅ ์ํจ๋ค. ์๋์ ๋๋ฆฌ์ธ์ ํตํด ์๋น๊ณผ ๋ํ๊ฐ ๊ฐ๋ฅํ๋ค.
- ์๋น์ ์ฐ์ ๋ฌธ์ง๊ธฐ๋ฅผ ๊ณ ์ฉํ๊ณ , ํด๋น ๋ฌธ์ง๊ธฐ์๊ฒ ์๋๋ค์ ์ ์ฅ ๋ฌธ์ ์ ํ๋ฅผ ๋ฐ์ ์ ์๋ ์๋น ๋ฒํธ๋ฅผ ์๋ ค์ค๋ค.
๋ฌธ์ง๊ธฐ๋ ์๋ ๋๋ฆฌ์ธ์ ํตํด ์๋๊ณผ ๋ํ๊ฐ ๊ฐ๋ฅํ๋ค.
- ์ง์ ๊ณ ์ฉ ๋ฐ ๊ต์ก์ด ๋๋ ๊ฒฝ์ฐ ์๋น์ ์์ ๋ฐ ์๋ด๋ฅผ ์์ํ๋ค.

- ์์ ๋ด์ฉ์์ ์๋์ Client, ๋๋ฆฌ์ธ์ Session, ์๋น์ Server, ๋ฌธ์ง๊ธฐ๋ Listener ์์ผ, ๋ฌธ์ง๊ธฐ ๊ต์ก์ Bind,
์์ ์์์ Listen, ์๋ด๋ Accept์ ํด๋นํ๋ค.
# ์์ผ ํ๋ก๊ทธ๋๋ฐ ์ ๋ฌธ #2

- ์ฐ์ Test๋ฅผ ์ํด [ ์๋ฃจ์ ] - [ ์ค๋ฅธ์ชฝ ๋ง์ฐ์ค ] - [ ์์ฑ ] ์ [ ๊ณต์ฉ ์์ฑ ] - [ ์์ ํ๋ก์ ํธ ] ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ๋ค.
Server ์ฝ๋ ๊ตฌํ
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
class Program
{
static void Main(string[] args)
{
// DNS (Domain Name System) : Domain์ IP ๋คํธ์ํฌ์์ ์ฐพ์๊ฐ ์ ์๋ IP๋ก ๋ณํํด ์ค๋ค.
string host = Dns.GetHostName(); // Local Computer์ host ์ด๋ฆ์ ๋ฐํ
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0]; // ip ์ฃผ์๋ฅผ ๋ฐฐ์ด๋ก ๋ฐํ (์๋ฅผ ๋ค์ด Google๊ณผ ๊ฐ์ด Traffic์ด ์ด๋ง๋ฌด์ํ ์ฌ์ดํธ๋ ์ฌ๋ฌ๊ฐ์ ip ์ฃผ์๋ฅผ ๊ฐ์ง ์ ์๊ธฐ ๋๋ฌธ)
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777); // ip ์ฃผ์์ port ๋ฒํธ๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅ
// ๋ฌธ์ง๊ธฐ ๊ณ ์ฉ
Socket listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
// ๋ฌธ์ง๊ธฐ ๊ต์ก
listenSocket.Bind(endPoint);
// ์์
์์
// backlog : ์ต๋ ๋๊ธฐ ์
listenSocket.Listen(10); // backlog๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅ
while (true)
{
Console.WriteLine("Listening...");
// ์๋์ ์
์ฅ์ํจ๋ค
// Accept() ๋ Blocking ํจ์์ด๋ฏ๋ก Client๊ฐ ์
์ฅํ์ง ์์ ๊ฒฝ์ฐ ๋ฌดํํ ๋๊ธฐํ๋ค.
Socket clientSocket = listenSocket.Accept(); // ์ด๋์ clientSocket์ ๋๋ฆฌ์ธ์ ํด๋นํ๋ค.
// ๋ฐ๋๋ค
byte[] recvBuff = new byte[1024];
int recvBytes = clientSocket.Receive(recvBuff); // ๋ช byte๋ฅผ ๋ฐ์๋์ง int๋ก ๋ฐํ
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes); // ์ด๋์์, ์ด๋๋ถํฐ, ์ผ๋งํผ ๋ฐ์์ฌ ๊ฒ์ธ์ง๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅ
Console.WriteLine($"[From Client] {recvData}");
// ๋ณด๋ธ๋ค
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
clientSocket.Send(sendBuff); // ๋๋ฆฌ์ธ์ ํตํด Client์ ๋ํ
// ์ซ์๋ธ๋ค
clientSocket.Shutdown(SocketShutdown.Both); // ์ฐ๊ฒฐ ํด์ ๋ฅผ ๋ฏธ๋ฆฌ ์๊ณ
clientSocket.Close();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}
Client ์ฝ๋ ๊ตฌํ
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace DummyClient
{
class Program
{
static void Main(string[] args)
{
// DNS (Domain Name System) : Domain์ IP ๋คํธ์ํฌ์์ ์ฐพ์๊ฐ ์ ์๋ IP๋ก ๋ณํํด ์ค๋ค.
string host = Dns.GetHostName(); // Local Computer์ host ์ด๋ฆ์ ๋ฐํ
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0]; // ip ์ฃผ์๋ฅผ ๋ฐฐ์ด๋ก ๋ฐํ (์๋ฅผ ๋ค์ด Google๊ณผ ๊ฐ์ด Traffic์ด ์ด๋ง๋ฌด์ํ ์ฌ์ดํธ๋ ์ฌ๋ฌ๊ฐ์ ip ์ฃผ์๋ฅผ ๊ฐ์ง ์ ์๊ธฐ ๋๋ฌธ)
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777); // ip ์ฃผ์์ port ๋ฒํธ๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅ
// ํด๋ํฐ ์ค๋น
Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
// ์
์ฅ ๋ฌธ์
socket.Connect(endPoint);
Console.WriteLine($"Connected To {socket.RemoteEndPoint.ToString()}"); // RemoteEndPoint๋ ๋ณธ์ธ๊ณผ ์ฐ๊ฒฐ๋ ๋์
// ๋ณด๋ธ๋ค
byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World");
socket.Send(sendBuff);
// ๋ฐ๋๋ค
byte[] recvBuff = new byte[1024];
int recvBytes = socket.Receive(recvBuff); // ๋ช byte๋ฅผ ๋ฐ์๋์ง int๋ก ๋ฐํ
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes); // ์ด๋์์, ์ด๋๋ถํฐ, ์ผ๋งํผ ๋ฐ์์ฌ ๊ฒ์ธ์ง๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅ
Console.WriteLine($"[From Server] {recvData}");
// ๋๊ฐ๋ค
socket.Shutdown(SocketShutdown.Both); // ์ฐ๊ฒฐ ํด์ ๋ฅผ ๋ฏธ๋ฆฌ ์๊ณ
socket.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}
# Listener
- ์์ ์ฝ๋์์ Accept() ๋ Blocking ํจ์์ด๋ฏ๋ก Client๊ฐ ์ ์ฅํ์ง ์์ ๊ฒฝ์ฐ ๋ฌดํํ ๋๊ธฐํ๋ค.
~> Accept() ๋ฟ๋ง์ด ์๋๋ผ Send(), Receive() ์ญ์ Blocking ๋ฐฉ์์ผ๋ก ๊ตฌํ ๋์๋ค.
~> ๋ฌด์ํ Client๋ฅผ ๋ฐ๊ธฐ ์ํด Blocking ํจ์๋ฅผ ์ฑํํ๋ ๊ฒ์ ๋ฐ๋์งํ์ง ์์ ์ ์๋ค.
~> ์ด๋ฅผ NonBlocking ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ฌ ํด๊ฒฐํ ์ ์๋ค.
- Blocking ๊ณผ NonBlocking ์ ์ฐจ์ด
~> ๋์๋ก ๋น์ ๋ฅผ ํ์๋ฉด Blocking ๋ฐฉ์์ ๋์๋๋ฅผ ๋์ ธ๋๊ณ , ๋ฌผ๊ณ ๊ธฐ๊ฐ ์กํ๋๊น์ง ์๋ฌด ๊ฒ๋ ํ์ง ์์ผ๋ฉด์ ๋์๋๋ง
๋ท์ด์ ธ๋ผ ์ณ๋ค๋ณด๊ณ ์๋ ๊ฒ์ด๋ค.
~> ๊ทธ๋ฌ๋ NonBlocking ๋ฐฉ์์ ๋์๋๋ฅผ ๋์ ธ๋๊ณ , ๋์ง์๋ง์ ์ ์ง์ด ์ฌ ๊ฒฝ์ฐ ๋ฐ๋ก ๋์๋๋ฅผ ๋์ด ์ฌ๋ฆฌ์ง๋ง
๋์ง ๊ทธ ์๊ฐ์ ๋ฐ๋ก ์ ์ง์ด ์ค์ง ์์ ๊ฒฝ์ฐ ๋ณธ์ธ์ด ํ ๋ค๋ฅธ ์ผ๋ค์ ํ๊ณ ์๋ค๊ฐ ์ ์ง์ด ์ฌ ๊ฒฝ์ฐ ๊ทธ์ ์์ผ ๋์์
๋์๋๋ฅผ ๋์ด์ฌ๋ฆฌ๋ ๊ฒ์ด๋ค.
Accept๋ฅผ NonBlocking ๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ธฐ ์ํ Listener Class ์์ฑ
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
internal class Listener
{
Socket _listenSocket;
Action<Socket> _onAcceptHandler;
public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler) // Socket ํจ์์ ๋งค๊ฐ๋ณ์๋ฅผ ์ํ endPoint, _onAcceptHandler์ event ํจ์๋ก ๋ฑ๋กํ๊ธฐ ์ํ onAcceptHandler (delegate๋ฅผ ํตํด ํจ์๋ฅผ ์ธ์๋ก ๋ฐ์ ๊ฒ)
{
// ๋ฌธ์ง๊ธฐ ๊ณ ์ฉ
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_onAcceptHandler += onAcceptHandler; // onAcceptHandler ํจ์๊ฐ _onAcceptHandler event๋ฅผ ๊ตฌ๋
์ ์ฒญ
// ๋ฌธ์ง๊ธฐ ๊ต์ก
_listenSocket.Bind(endPoint);
// ์์
์์
// backlog : ์ต๋ ๋๊ธฐ ์
_listenSocket.Listen(10); // backlog๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅ
// SocketAsyncEventArgs ๋ ์ผ๊พผ ๊ฐ์ ์น๊ตฌ์ ( Async ํน์ง์ ์ฐ๋ฆฌ๊ฐ ๋น์ฅ ๊ฐ์ ์ถ์ถํ ์ ์์์ผ๋ ์ด๋ฐ ์ ๋ฐ ์ ๋ณด๋ค์ ์ด ์น๊ตฌ๋ฅผ ํตํด ์ ๋ฌ๋ฐ๋ ๊ฒ )
SocketAsyncEventArgs args = new SocketAsyncEventArgs(); // ํ๋ฒ๋ง ๋ง๋ค์ด์ฃผ๋ฉด ๊ณ์ํด์ ์ฌ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค๋ ์ฅ์ ์ ๊ฐ์ง๋ค. ( args ๋ผ๋ event๋ฅผ ์์ฑ )
// AcceptAsync() ๊ฐ ์๋ฃ๋ ๊ฒฝ์ฐ ์ฐ๋ฆฌ์๊ฒ call back ๋ฐฉ์์ผ๋ก ์ฐ๋ฝ์ ์ค ์ ์๋๋ก event ์ฌ์ฉ (๋ด ์ผ์ ํ๊ณ ์๋ค๊ฐ ์
์ง์ด ์ฌ ๊ฒฝ์ฐ args๊ฐ OnAcceptCompleted ํจ์๋ฅผ ์๋์ผ๋ก ํธ์ถ)
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted); // OnAcceptCompleted ๋ฉ์๋๊ฐ args Completed event๋ฅผ ๊ตฌ๋
์ ์ฒญ
// *** ๋์๋๋ฅผ ๋์ง๋ค ***
RegisterAccept(args); // ์ต์ด 1๋ฒ์ ์ง์ ๋ฑ๋กํด์ค๋ค.
}
void RegisterAccept(SocketAsyncEventArgs args) // AcceptAsync ํจ์์ ๋งค๊ฐ๋ณ์๋ฅผ ์ํ args
{
args.AcceptSocket = null; // ์ด๊ธฐํ ํ์ง ์์ ๊ฒฝ์ฐ Crash ๋ฐ์
bool pending = _listenSocket.AcceptAsync(args); // ๋น๋๊ธฐ Accept() ํจ์
// *** ๋์๋๋ฅผ ๋์ง์๋ง์ ๋ฌผ๊ณ ๊ธฐ๊ฐ ์กํ ๊ฒฝ์ฐ ***
if (pending == false) // AcceptAsync๋ฅผ ์คํํ๋ ๋์์ Client๋ก๋ถํฐ ์ ์ ์์ฒญ์ด ์์ pending ์์ด ์๋ฃ๋ ๊ฒฝ์ฐ
OnAcceptCompleted(null, args); // ์ง์ OnAcceptCompleted ํจ์๋ฅผ ํธ์ถ
// ๋ง์ฝ pending์ด true์ธ ๊ฒฝ์ฐ ๋น์ฅ์ ์๋ฃ๋์ง ์์์ง๋ง ๋์ค์๋ผ๋ ์๋ฃ๊ฐ ๋๋ค๋ฉด ์์ args๊ฐ ์๋์ผ๋ก ๋ณธ์ธ์ ๊ตฌ๋
์ ์ฒญํ ๊ตฌ๋
์๋ค์๊ฒ ์๋ ค์ค๋ค. (OnAcceptCompleted ํจ์๋ฅผ ํธ์ถ)
}
void OnAcceptCompleted(object sender, SocketAsyncEventArgs args) // call back ํจ์๋ก ๋ฑ๋กํ๊ธฐ ์ํ ๋งค๊ฐ๋ณ์ sender์ args ( ์ฆ, ํ์์ ๋ง์ถฐ์ฃผ๊ธฐ ์ํ ๊ฒ )
{
// *** ๋์ ๋ฌผ๊ณ ๊ธฐ๋ฅผ ๋นผ๋ธ๋ค ***
if (args.SocketError == SocketError.Success) // Client๋ก๋ถํฐ ์ ์ ์์ฒญ์ด ์์ Accept๊น์ง ์ฑ๊ณต์ ์ผ๋ก ๋ง์น ๊ฒฝ์ฐ
{
_onAcceptHandler.Invoke(args.AcceptSocket); // _onAcceptHandler๊ฐ ๋ณธ์ธ์ ๊ตฌ๋
์ ์ฒญํ ๊ตฌ๋
์๋ค์๊ฒ ์๋ ค์ค๋ค. (onAcceptHandler ํจ์๋ฅผ ํธ์ถ) (args.AcceptSocket ๋ ClientSocket์ ๋ฑ์ด์ค๋ค.)
}
else
Console.WriteLine(args.SocketError.ToString());
// *** ๋ค์ ๋์๋๋ฅผ ๋์ง๋ค ***
RegisterAccept(args); // ์ฌ๊ธฐ๊น์ง ๋๋ฌํ ๊ฒฝ์ฐ ๋ค์ Client๋ฅผ ์ํด์ ๋ค์ ๋ฑ๋กํด์ฃผ๋ ๊ฒ
}
}
}โ
Accept๋ฅผ NonBlocking ๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ธฐ ์ํ Server ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
class Program
{
static Listener _listener = new Listener();
static void OnAcceptHandler(Socket clientSocket) // Client Accept๊ฐ ์๋ฃ๋ ๊ฒฝ์ฐ
{
try
{
// ๋ฐ๋๋ค
byte[] recvBuff = new byte[1024];
int recvBytes = clientSocket.Receive(recvBuff); // ๋ช byte๋ฅผ ๋ฐ์๋์ง int๋ก ๋ฐํ
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes); // ์ด๋์์, ์ด๋๋ถํฐ, ์ผ๋งํผ ๋ฐ์์ฌ ๊ฒ์ธ์ง๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅ
Console.WriteLine($"[From Client] {recvData}");
// ๋ณด๋ธ๋ค
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
clientSocket.Send(sendBuff);
// ์ซ์๋ธ๋ค
clientSocket.Shutdown(SocketShutdown.Both); // ๋ฏธ๋ฆฌ ์๊ณ
clientSocket.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
static void Main(string[] args)
{
// DNS (Domain Name System) : Domain์ IP ๋คํธ์ํฌ์์ ์ฐพ์๊ฐ ์ ์๋ IP๋ก ๋ณํํด ์ค๋ค.
string host = Dns.GetHostName(); // Local Computer์ host ์ด๋ฆ์ ๋ฐํ
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0]; // ip ์ฃผ์๋ฅผ ๋ฐฐ์ด๋ก ๋ฐํ (์๋ฅผ ๋ค์ด Google๊ณผ ๊ฐ์ด Traffic์ด ์ด๋ง๋ฌด์ํ ์ฌ์ดํธ๋ ์ฌ๋ฌ๊ฐ์ ip ์ฃผ์๋ฅผ ๊ฐ์ง ์ ์๊ธฐ ๋๋ฌธ)
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777); // ip ์ฃผ์์ port ๋ฒํธ๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅ
// ์๋์ ์
์ฅ์ํจ๋ค.
_listener.Init(endPoint, OnAcceptHandler); // ๋ฌธ์ง๊ธฐ์๊ฒ endPoint ์ ๋ฌ, ํน์๋ผ๋ ๋๊ตฐ๊ฐ ์ ์ ์์ฒญ ํ Accept ์๋ฃ์ call back์ ๋ฐ๊ธฐ ์ํด OnAcceptHandler๋ฅผ event ํจ์๋ก ๋ฑ๋กํ๊ณ ์ ์ ๋ฌ
Console.WriteLine("Listening...");
while (true)
{
}
}
}
}
+ ์ถ๊ฐ ์ ๋ฆฌ


# Session #1
Receive๋ฅผ NonBlocking ๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ธฐ ์ํ Session Class ์์ฑ
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
class Session
{
Socket _socket;
int _disconnected = 0; // ๋๊น์ ์ฌ๋ถ๋ฅผ ํ๋จํ flag (MultiThread ํ๊ฒฝ์์ Disconnect() ๋ฅผ 2๋ฒ ์ด์ ํธ์ถํ๋ ๊ฒ์ ๋ฐฉ์ง)
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
// ReceiveAsync() ๊ฐ ์๋ฃ๋ ๊ฒฝ์ฐ ์ฐ๋ฆฌ์๊ฒ call back ๋ฐฉ์์ผ๋ก ์ฐ๋ฝ์ ์ค ์ ์๋๋ก event ์ฌ์ฉ (๋ด ์ผ์ ํ๊ณ ์๋ค๊ฐ ๋ฐ์ดํฐ๊ฐ ์ฌ ๊ฒฝ์ฐ recvArgs๊ฐ OnRecvCompleted ํจ์๋ฅผ ์๋์ผ๋ก ํธ์ถ)
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
// ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ธฐ ์ํ Buffer ์ค์
recvArgs.SetBuffer(new byte[1024], 0, 1024);
RegisterRecv(recvArgs);
}
public void Send(byte[] sendBuff)
{
_socket.Send(sendBuff);
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both); // ๋ฏธ๋ฆฌ ์๊ณ
_socket.Close();
}
void RegisterRecv(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args); // ๋น๋๊ธฐ Receive() ํจ์
if (pending == false) // ReceiveAsync๋ฅผ ์คํํ๋ ๋์์ ๋ฐ๊ณ ์ ํ๋ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ์ฌ pending ์์ด ์๋ฃ๋ ๊ฒฝ์ฐ
OnRecvCompleted(null, args); // ์ง์ OnRecvCompleted ํจ์๋ฅผ ํธ์ถ
}
void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success) // ๋ฐ์ดํฐ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ๋ฐ์ ๊ฒฝ์ฐ
{
try
{
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {recvData}");
RegisterRecv(args);
}
catch (Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed {e}");
}
}
else
{
Disconnect();
}
}
}
}โ
Receive๋ฅผ NonBlocking ๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ธฐ ์ํ Server ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
class Program
{
static Listener _listener = new Listener();
static void OnAcceptHandler(Socket clientSocket) // Client Accept๊ฐ ์๋ฃ๋ ๊ฒฝ์ฐ
{
try
{
// ๋ฐ๋๋ค.
Session session = new Session(); // Session Class ๊ฐ์ฒด ์์ฑ
session.Start(clientSocket);
// ๋ณด๋ธ๋ค.
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
session.Send(sendBuff);
// ์ซ์๋ธ๋ค.
session.Disconnect();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
static void Main(string[] args)
{
// DNS (Domain Name System) : Domain์ IP ๋คํธ์ํฌ์์ ์ฐพ์๊ฐ ์ ์๋ IP๋ก ๋ณํํด ์ค๋ค.
string host = Dns.GetHostName(); // Local Computer์ host ์ด๋ฆ์ ๋ฐํ
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0]; // ip ์ฃผ์๋ฅผ ๋ฐฐ์ด๋ก ๋ฐํ (์๋ฅผ ๋ค์ด Google๊ณผ ๊ฐ์ด Traffic์ด ์ด๋ง๋ฌด์ํ ์ฌ์ดํธ๋ ์ฌ๋ฌ๊ฐ์ ip ์ฃผ์๋ฅผ ๊ฐ์ง ์ ์๊ธฐ ๋๋ฌธ)
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777); // ip ์ฃผ์์ port ๋ฒํธ๋ฅผ ๋งค๊ฐ๋ณ์๋ก ์
๋ ฅ
// ์๋์ ์
์ฅ์ํจ๋ค.
_listener.Init(endPoint, OnAcceptHandler); // ๋ฌธ์ง๊ธฐ์๊ฒ endPoint ์ ๋ฌ, ํน์๋ผ๋ ๋๊ตฐ๊ฐ ์ ์ ์์ฒญ ํ Accept ์๋ฃ์ call back์ ๋ฐ๊ธฐ ์ํด OnAcceptHandler๋ฅผ event ํจ์๋ก ๋ฑ๋กํ๊ณ ์ ์ ๋ฌ
Console.WriteLine("Listening...");
while (true)
{
}
}
}
}โ
# Session #2
- Send๋ ์์ ์ด ์ ํด์ ธ ์์ง ์๋ค.
~> Send์ ๋ณด๋ด์ค Buffer์ Msg๋ฅผ ๊ฐ์ด ์ค์ ํด์ค์ผ ํ๋๋ฐ ๋ฏธ๋์ ์ด๋ค Msg๋ฅผ ๋ณด๋ผ์ง ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์
Receive์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์ ๊ทผํ ์ ์๋ค.
- ๋ํ Send๋ OnSendCompleted() ํธ์ถ ํ RegisterSend(sendArgs) ๋ฅผ ํตํด ๋ค์ ๋ฑ๋กํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํ๋ค.
~> ์ด๋ ๋๊ฐ์ ์ ๋ณด๋ฅผ ๋ค์ ๋ณด๋ด๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ด๋ฉฐ, ๋ฐ๋ผ์ ์ฌ์ฌ์ฉ์ด ๋ถ๊ฐ๋ฅํ๋ค.
- ๋ง์ง๋ง์ผ๋ก MultiThread ํ๊ฒฝ์์ ๋์๋ค๋ฐ์ ์ผ๋ก Send() ๋ฅผ ํธ์ถ์ ๋งค๋ฒ RegisterSend() ๊ฐ ํธ์ถ๋์ด SendAsync๋ฅผ
๋งค๋ฒ ํธ์ถํ๊ฒ ๋๋๋ฐ ์ด๋ ๋คํธ์ํฌ ๊ณผ๋ถํ๋ฅผ ์ ๋ฐํ๋ค.
~> ์ด๋ ์ด์์ฒด์ ๊ฐ Kernel์์ ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ด๋ค.
- ์ด๋ฅผ ๋ฐํ์ผ๋ก Send๋ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํํ ์์ ์ด๋ค.
~> _sendArgs๋ฅผ ์ ์ญ๋ณ์๋ก ์ ์ธํ์ฌ ์ฌ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋๋ก ๋ง๋ค ๊ฒ์ด๋ค.
~> SendAsync๋ฅผ ๋งค๋ฒ ํธ์ถํ๋ ๊ฒ์ด ์๋ _sendQueue์ ๋ฐ์ดํฐ๊ฐ ์ด๋์ ๋ ๋ชจ์ผ ๊ฒฝ์ฐ ํธ์ถํ ๊ฒ์ด๋ค.
Send๋ฅผ NonBlocking ๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ธฐ ์ํ Session Class ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
class Session
{
// ...
object _lock = new object();
Queue<byte[]> _sendQueue = new Queue<byte[]>();
bool _pending = false; // pending ์ฌ๋ถ ํ๋จ (true์ธ ๊ฒฝ์ฐ ๋๊ตฐ๊ฐ ๋ณด๋ด๊ณ ์๋ ๊ฒ, false์ธ ๊ฒฝ์ฐ ์๋ฌด๋ ๋ณด๋ด๊ณ ์์ง ์์ ๊ฒ)
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs(); // Send๋ ์ธ์ ํธ์ถ๋ ์ง ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ์ ์ญ ๋ณ์๋ก ์ ์ธ
public void Start(Socket socket)
{
// ...
// SendAsync() ๊ฐ ์๋ฃ๋ ๊ฒฝ์ฐ ์ฐ๋ฆฌ์๊ฒ call back ๋ฐฉ์์ผ๋ก ์ฐ๋ฝ์ ์ค ์ ์๋๋ก event ์ฌ์ฉ (๋ด ์ผ์ ํ๊ณ ์๋ค๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ๊ฒฝ์ฐ _sendArgs๊ฐ OnSendCompleted ํจ์๋ฅผ ์๋์ผ๋ก ํธ์ถ)
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
// ...
}
public void Send(byte[] sendBuff)
{
lock (_lock)
{
// ๋๊ตฐ๊ฐ RegisterSend()๋ฅผ ํ๊ณ ์์ด SendAsync() ๊ฐ ๋๋ ๋ค OnSendCompleted() ๊ฐ ์๋ฃ๋๊ธฐ ์ ๊น์ง๋ ๋ณด๋ด์ง์๊ณ sendBuff๋ฅผ Queue์ ๋ฃ๊ณ ์ข
๋ฃ
// ~> ์ฆ, ๋๊ตฐ๊ฐ ๋ณด๋ด๊ณ ์์ผ๋ฉด _pending ์ true์ด๋ฏ๋ก ๋ณธ์ธ์ ๋ณด๋ด์ง ์๊ณ Queue์ ์ง์ด๋ฃ๊ธฐ๋ง ํ๋ ๊ฒ
_sendQueue.Enqueue(sendBuff);
if (_pending == false) // RegisterSend() ๋ฅผ ๊ฐ์ฅ ๋จผ์ ํธ์ถํ์ฌ ์ ์ก๊น์ง ํ ์ ์๋ ์ํ์ธ ๊ฒฝ์ฐ
RegisterSend();
}
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both); // ๋ฏธ๋ฆฌ ์๊ณ
_socket.Close();
}
void RegisterSend()
{
_pending = true;
byte[] buff = _sendQueue.Dequeue();
_sendArgs.SetBuffer(buff, 0, buff.Length);
bool pending = _socket.SendAsync(_sendArgs); // ๋น๋๊ธฐ Send() ํจ์
if (pending == false) // SendAsync๋ฅผ ์คํํ๋ ๋์์ ๋ฐ๊ณ ์ ํ๋ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ์ฌ pending ์์ด ์๋ฃ๋ ๊ฒฝ์ฐ
OnSendCompleted(null, _sendArgs);
}
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
lock (_lock) // ์ค์ง RegisterSend() ๋ฅผ ํตํด์๋ง OnSendCompleted() ๊ฐ ํธ์ถ๋๋ ๊ฒฝ์ฐ์๋ ํ์ X (๊ทธ๋ฌ๋, ์ฐ๋ฆฐ call back์ ํตํด์๋ ํธ์ถ O)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success) // ๋ฐ์ดํฐ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ๋ณด๋ธ ๊ฒฝ์ฐ
{
try
{
// _pending์ด false์ธ ์ํฉ์์ A Thread๊ฐ Send()์ RegisterSend() ๋ฅผ ํธ์ถํ๊ฒ ๋๋๋ฐ
// ๋ง์ฝ pending์ด true์ฌ์ OnSendCompleted() ๊ฐ ๋ฐ๋ก ํธ์ถ๋๋ ๊ฒ์ด ์๋ call back์ ํตํด ๋์ค์ ํธ์ถ๋ ๊ฒฝ์ฐ๋ฅผ ๊ฐ์ ํ์.
// ์ด๋ฌํ ์ํฉ์์ ๋ง์ฝ ๋ค๋ฅธ Thread๋ค์ด Send()์ _pending์ด true์ด๊ธฐ ๋๋ฌธ์ RegisterSend() ๋ฅผ ํธ์ถํ์ง ์๊ณ Queue์ ์ง์ด๋ฃ๊ธฐ๋ง ํ๋ค.
// ์ด๋ A Thread๊ฐ OnSendCompleted() ํธ์ถ์ ๋ณธ์ธ ๋ฐ์ดํฐ๋ฟ๋ง์ด ์๋ Queue์ ์๋ ๋ฐ์ดํฐ๋ค๊น์ง ์ฒ๋ฆฌํด์ค๋ค.
// ~> ์ฆ, ๊ฐ์ฅ ์ฒซ๋ฒ์งธ ์์ฝ ๋๊ธฐ์๊ฐ ๋ณธ์ธ์ ์์ฝ์ ๊ธฐ๋ค๋ฆฌ๋ ๋์, ๊ทธ ํ์ ์์ฝ์ ๊ฑด ์ฌ๋๋ค์ ๋ชซ๊น์ง ์ฒ๋ฆฌํด์ฃผ๋ ๊ฒ์ด๋ค.
if (_sendQueue.Count > 0) // Queue์ ๋ณด๋ด์ผ ํ ๋ฐ์ดํฐ๊ฐ ๋จ์์๋ ๊ฒฝ์ฐ
RegisterSend(); // ๋ค์ ์ ์ก์ ์ํด RegisterSend() ํธ์ถ
else // Queue์ ๋ณด๋ด์ผ ํ ๋ฐ์ดํฐ๊ฐ ๋จ์์์ง ์์ ๊ฒฝ์ฐ
_pending = false;
}
catch (Exception e)
{
Console.WriteLine($"OnSendCompleted Failed {e}");
}
}
else
{
Disconnect();
}
}
}
// ...
}
}
+ ์ถ๊ฐ ์ ๋ฆฌ
- ์ด์ฐจํผ OnSendCompleted()์์ _sendQueue.Count๊ฐ 0๋ณด๋ค ํฐ ๊ฒฝ์ฐ RegisterSend()๋ฅผ ๊ณ์ํด์ ํธ์ถํ๊ฒ ๋๋๋ฐ
๊ทธ๋ ๊ฒ ๋๋ฉด ๊ฒฐ๊ตญ์๋ ์ฑ๋ฅ์ ์ธ ๊ด์ ์์ ๋ฌ๋ผ์ง ๊ฒ์ ๊ฑฐ์ ์๋ค๊ณ ๋ณผ ์ ์๋ค.
~> 100๋ช ์ ์ฌ๋์ด Send() ๋ฅผ ํธ์ถ์ SendAsync() ๋ ์ธ์ ๊ฐ๋ 100๋ฒ ํธ์ถ ๋๊ธฐ ๋๋ฌธ์ด๋ค.
~> ์ฆ, ์์ ์ฝ๋๊ฐ ์์ ํ ํด๊ฒฐ์ฑ ์ด๋ผ๊ณ ๋ ๋ณด๊ธฐ ์ด๋ ต๋ค.
~> ๋ฐ๋ผ์ ์์ ์ฝ๋๋ 1์ฐจ์ ์ผ๋ก Send() ๋ฅผ Async ๊ณ์ด๋ก ๋ฐ๊ฟจ๋ค๋ ๊ฒ์ ์์๋ฅผ ๊ฐ์ง๋ค.
# Session #3
- ์์ ์ฝ๋์์ ์์ฌ์ด ์ ์ _sendQueue์ ์๋ ๋ฐ์ดํฐ 1๊ฐ๋น SendAsync() ๋ฅผ 1๋ฒ์ฉ ํธ์ถํ๊ณ ์๋ค๋ ๊ฒ์ด๋ค.
~> BufferList๋ฅผ ํตํด ํด๊ฒฐ์ด ๊ฐ๋ฅํ๋ค.
BufferList๋ฅผ ํตํด NonBlocking ๋ฐฉ์์ Send๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํ Session Class ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
class Session
{
// ...
// _pendingList ์ฌ์ฉ์ _pending ๋ณ์๋ ํ์ X
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs(); // Receive๋ Send์ ๋ง์ฐฌ๊ฐ์ง๋ก ์ ์ญ๋ณ์๋ก ์ ์ธํด๋ ๋ฌด๋ฐฉ
List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>(); // ๋๊ธฐ์ค์ธ ๋ชฉ๋ก๋ค์ ๋ด๊ธฐ ์ํ List
// ...
public void Send(byte[] sendBuff)
{
lock (_lock)
{
_sendQueue.Enqueue(sendBuff);
// _pending ๋ณ์ ๋์ _pendingList.Count ์ฌ์ฉ
if (_pendingList.Count == 0) // RegisterSend() ๋ฅผ ๊ฐ์ฅ ๋จผ์ ํธ์ถํ์ฌ ์ ์ก๊น์ง ํ ์ ์๋ ์ํ์ธ ๊ฒฝ์ฐ
RegisterSend();
}
}
// ...
void RegisterSend() // ํด๋น ํจ์๋ก ๋ค์ด์ค๋ ์์ ์ _pendingList ๋ ์ธ์ ๋ null
{
// _sendQueue์ ๋ฐ์ดํฐ๋ค์ List๋ก ์ฐ๊ฒฐํ์ฌ ๋ด์์ฃผ๊ฒ ๋๋ฉด ์ด๋ฅผ SendAsync() ์ ํ๋ฒ์ ๋ณด๋ผ ์ ์๋ค.
// ~> ์ฆ _sendQueue์ ์๋ ๋ฐ์ดํฐ 1๊ฐ๋น SendAsync() ๋ฅผ 1๋ฒ์ฉ ํธ์ถํ๋ ๊ฒ์ ๋ณด์
while (_sendQueue.Count > 0)
{
byte[] buff = _sendQueue.Dequeue();
// _sendArgs.BufferList์ ์ง์ Addํ ๊ฒฝ์ฐ ์ค๋ฅ ๋ฐ์
_pendingList.Add(new ArraySegment<byte>(buff, 0, buff.Length)); // ArraySegment ๋ ์ด๋ค ๋ฐฐ์ด์ ์ผ๋ถ๋ฅผ ๋ํ๋ด๋ ๊ตฌ์กฐ์ฒด์ด๋ค.
}
_sendArgs.BufferList = _pendingList;
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
}
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
lock (_lock)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success) // ๋ฐ์ดํฐ๋ค์ ์ฑ๊ณต์ ์ผ๋ก ๋ณด๋ธ ๊ฒฝ์ฐ
{
try
{
// ์ด๊ธฐํ
_sendArgs.BufferList = null;
_pendingList.Clear();
if (_sendQueue.Count > 0)
RegisterSend();
}
catch (Exception e)
{
Console.WriteLine($"OnSendCompleted Failed {e}");
}
}
else
{
Disconnect();
}
}
}
// ...
}
}โ
+ ์ถ๊ฐ ์ ๋ฆฌ
- ์์ ์ฝ๋ ์ญ์ ์๋ฒฝํ๋ค๊ณ ํ ์ ์๋ค.
~> RegisterSend() ์์ _sendQueue๋ฅผ ๋ฌด์กฐ๊ฑด ๋น์ ๋ชจ๋ ์ ๋ณด๋ฅผ ํ๋ฒ์ ๋ณด๋ด๊ณ ์๋๋ฐ, ์ผ์ ์งง์ ์๊ฐ ๋์ ๋ช Byte๋ฅผ
๋ณด๋๋์ง ์ถ์ ํ์ฌ ๋๋ฌด ๊ณผํ๊ฒ ๋ณด๋ธ๋ค ์ถ์ผ๋ฉด ์ฌ์ด ๊ฐ๋ฉฐ ๋ณด๋ด๋ ๊ฒ์ด ์ฑ๋ฅ์ ์ผ๋ก ๋ ์ข๋ค.
~> ๋ํ ์๋๋ฐฉ์ด ์ ์์ ์ผ๋ก ์๋ฏธ์๋ ์ ๋ณด๋ฅผ ๋ด์ ๋ค์์ ํจํท์ ๋ณด๋ด๋ ๊ฒฝ์ฐ ์ด๋ฅผ ์ฒดํฌํ์ฌ ๋น์ ์์ ์ด๋ค ์ถ์ผ๋ฉด
๋ฐ์ง ์๋๋ก ํ๋ ๊ฒ์ด ์ข๋ค.
~> ๋ํ ๋์ ๋ฐ๋ผ์๋ ํจํท ์์ฒด๋ฅผ 1๋ฒ์ ์์ ๋จ์๋ก ๋ณด๋ด๋ ๊ฒ์ด ์๋ ๋ญ์ณ ๋ณด๋ด์ผ ํ๋ ๊ฒฝ์ฐ๋ ์กด์ฌํ๋ค.
# Session #4
- ์ถํ๋ฅผ ์ํด Engine๊ณผ Content๋ฅผ ๋ถ๋ฆฌํ๊ณ ์ ํ๋ค.
~> event handler ๋ฅผ ํตํ ๋ฐฉ๋ฒ๊ณผ ์์์ ํตํ ๋ฐฉ๋ฒ์ด ์กด์ฌํ๋ค.
~> ์์์ ํตํ ๋ฐฉ๋ฒ์ด ๋ ๊ฐ๋จํ๊ธฐ ๋๋ฌธ์ Session์ ์์ ๋ฐ์ GameSession Class๋ฅผ ์์ฑํ์ฌ
Engine๊ณผ Content๋ฅผ ๋ถ๋ฆฌํด ๋ณผ ๊ฒ์ด๋ค.
- Content ์์๋ Session์ ์์๋ฐ์ GameSession์ ์์ฑํ์ฌ ์คํํ๊ณ ์ ํ๋ ์ฝ๋๋ค์ ์ถ๊ฐํ๊ณ ,
Engine ์์๋ GameSession ๋ด์ ํจ์๋ค์ด ๋ฌด์์ ์คํํ๋์ง๋ ์ ๋ชจ๋ฅด๊ฒ ์ง๋ง ์ ์ ํ ํ์ด๋ฐ์ ํด๋น ํจ์๋ค์
ํธ์ถ๋ง ํด์ฃผ๋ ๊ฒ์ด๋ค.
Engine๊ณผ Content๋ฅผ ๋ถ๋ฆฌํ๊ธฐ ์ํ Session Class ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
abstract class Session
{
// ...
// Session์ ์์๋ฐ์ Class์์ ๊ตฌํํ๋๋ก abstract๋ก ์ ์ธ
// Engine์์ ๊ตฌํํ๋ ๊ฒ์ด ์๋ Content์์ ๊ตฌํํ๋ ๊ฒ์ด๋ค.
// Engine์์๋ ํด๋น ํจ์๋ฅผ ํธ์ถ๋ง ํ๋ค.
public abstract void OnConnected(EndPoint endPoint); // OnConnected() ํจ์๋ ์๋ฐํ ๋งํ๋ฉด Listener Class ์์ ํธ์ถ (์ฆ, Session Class ์์๋ ํธ์ถ X)
public abstract void OnRecv(ArraySegment<byte> buffer);
public abstract void OnSend(int numOfBytes);
public abstract void OnDisconnected(EndPoint endPoint);
// ...
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
OnDisconnected(_socket.RemoteEndPoint); // OnDisconnected() ํจ์ ํธ์ถ (RemoteEndPoint๋ ๋ณธ์ธ๊ณผ ์ฐ๊ฒฐ๋ ๋์)
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
// ...
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
lock (_lock)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
_sendArgs.BufferList = null;
_pendingList.Clear();
OnSend(_sendArgs.BytesTransferred); // OnSend() ํจ์ ํธ์ถ
if (_sendQueue.Count > 0)
RegisterSend();
}
catch (Exception e)
{
Console.WriteLine($"OnSendCompleted Failed {e}");
}
}
else
{
Disconnect();
}
}
}
// ...
void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
OnRecv(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred)); // OnRecv() ํจ์ ํธ์ถ
RegisterRecv();
}
catch (Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed {e}");
}
}
else
{
Disconnect();
}
}
}
}
Engine๊ณผ Content๋ฅผ ๋ถ๋ฆฌํ๊ธฐ ์ํ Server ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using static System.Collections.Specialized.BitVector32;
namespace ServerCore
{
class GameSession : Session // Session Class๋ฅผ ์์ ๋ฐ์ GameSession Class ์์ฑ
{
public override void OnConnected(EndPoint endPoint) // OnConnected() ํจ์ override
{
Console.WriteLine($"OnConnected : {endPoint}");
// ๋ณด๋ธ๋ค.
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!");
Send(sendBuff);
Thread.Sleep(1000);
// ์ซ์๋ธ๋ค.
Disconnect();
}
public override void OnDisconnected(EndPoint endPoint) // OnDisconnected() ํจ์ override
{
Console.WriteLine($"OnDisconnected : {endPoint}");
}
public override void OnRecv(ArraySegment<byte> buffer) // OnRecv() ํจ์ override
{
string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
Console.WriteLine($"[From Client] {recvData}");
}
public override void OnSend(int numOfBytes) // OnSend() ํจ์ override
{
Console.WriteLine($"Transferred bytes : {numOfBytes}");
}
}
class Program
{
static Listener _listener = new Listener();
// ๊ธฐ์กด์๋ OnAcceptHandler() ๋ฅผ call back ํ๋ ๋ฐฉ์์ด์๋ค.
// ~> ์๋ชป๋๊ฑด ์๋์ง๋ง Session Class ๋ฅผ ์์๋ฐ์ ๋ค์ํ Session ๊ฐ๋
๋ฑ์ฅ์ผ๋ก ์์
// ~> OnAcceptHandler() ํจ์ ๋ด์ session.Start(clientSocket) ๋ Content ๋ณด๋จ Engine ์ ์๋๊ฒ์ด ์ ์
static void Main(string[] args)
{
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
// ์๋์ ์
์ฅ์ํจ๋ค.
// ๋ฐ๋ผ์ ์ด๋ค Session์ ๋ง๋ค์ง๋ฅผ Init() ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌํ๋ฉด Engine์์ ์ด๋ฅผ ์ฒ๋ฆฌํด์ฃผ๋ ๋ฐฉ์์ผ๋ก ์์
_listener.Init(endPoint, () => { return new GameSession(); }); // Func<Session> ์ Lambda ์์ผ๋ก ์์ฑ
Console.WriteLine("Listening...");
while (true)
{
}
}
}
}โ
Engine๊ณผ Content๋ฅผ ๋ถ๋ฆฌํ๊ธฐ ์ํ Listener Class ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
internal class Listener
{
Socket _listenSocket;
Func<Session> _sessionFactory; // Session์ ์ด๋ค ๋ฐฉ์์ผ๋ก, ์ด๋ค Session ๋ง๋ค ๊ฒ์ธ์ง
// GameSession Class ๋ Content ์ชฝ์ ์กด์ฌํ๊ธฐ ๋๋ฌธ์
// Listenr์ Session๊ณผ ๊ฐ์ด Engine ์ชฝ์์ new ๋ฅผ ํตํด ์์ฑํ์ง ์๋๋ก
// Init()์ delegate ๋งค๊ฐ๋ณ์๋ฅผ ํตํด ์ด๋ค Session์ ๋ง๋ค์ง์ ๋ํ ํจ์๋ฅผ ์ธ์๋ก ๋ฐ์ ๋ค
// ํด๋น ํจ์๋ฅผ _sessionFactory event ๊ตฌ๋
์ ์ฒญ์ ํตํด ์คํ๋๋๋ก ์์
public void Init(IPEndPoint endPoint, Func<Session> sessionFactory)
{
// ๋ฌธ์ง๊ธฐ ๊ณ ์ฉ
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_sessionFactory += sessionFactory; // sessionFactory ํจ์๊ฐ _sessionFactory event๋ฅผ ๊ตฌ๋
์ ์ฒญ
// ๋ฌธ์ง๊ธฐ ๊ต์ก
_listenSocket.Bind(endPoint);
// ์์
์์
// backlog : ์ต๋ ๋๊ธฐ ์
_listenSocket.Listen(10);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
RegisterAccept(args);
}
void RegisterAccept(SocketAsyncEventArgs args)
{
args.AcceptSocket = null;
bool pending = _listenSocket.AcceptAsync(args);
if (pending == false)
OnAcceptCompleted(null, args);
}
void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
// ๋ฐ๋๋ค.
Session session = _sessionFactory.Invoke(); // _sessionFactory ๋ฅผ ํตํ GameSession ์์ฑ (Content ๋ด์์ ์๊ตฌํ ๋ฐฉ์๋๋ก Session ์์ฑ)
session.Start(args.AcceptSocket);
session.OnConnected(args.AcceptSocket.RemoteEndPoint);
}
else
Console.WriteLine(args.SocketError.ToString());
RegisterAccept(args);
}
}
}โ
# Connector
- ํ์ฌ DummyClient ์์ Connect() ๋ Blocking ํจ์์ด๋ค.
~> ๊ฒ์์์ Blocking ํจ์ ์ฌ์ฉ์ ์ง์ํด์ผํ๋ฏ๋ก ์ด๋ฅผ NonBlocking ๋ฐฉ์์ผ๋ก ๊ตฌํํ์ฌ ํด๊ฒฐํ ์ ์๋ค.
- ๋ถ์ฐ Server ๊ตฌํ์ Server ๋ผ๋ฆฌ ํต์ ํ๊ธฐ ์ํด์๋ ํ์ชฝ์ Listener์ ์ญํ ์, ํ์ชฝ์ Connector์ ์ญํ ์ ํด์ผ๋ง ํ๋ค.
Connect๋ฅผ NonBlocking ๋ฐฉ์์ผ๋ก ๊ตฌํํ๊ธฐ ์ํ Connector Class ์์ฑ
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace ServerCore
{
class Connector
{
Func<Session> _sessionFactory; // Session์ ์ด๋ค ๋ฐฉ์์ผ๋ก, ์ด๋ค Session ๋ง๋ค ๊ฒ์ธ์ง
public void Connect(IPEndPoint endPoint, Func<Session> sessionFactory)
{
// ํด๋ํฐ ์ค์
Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_sessionFactory = sessionFactory;
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += OnConnectCompleted;
args.RemoteEndPoint = endPoint; // ์ฐ๊ฒฐํ๊ธฐ ์ํด ์๋๋ฐฉ์ ์ฃผ์๋ฅผ ๋๊ฒจ์ค๋ค.
args.UserToken = socket; // UserToken์ ํตํด ์ํ๋ ์ ๋ณด๋ฅผ ๋๊ฒจ์ค ์ ์๋ค.
// socket์ ์ ์ญ๋ณ์๋ก ์ ์ธํ๋ ๊ฒ์ด ์๋ UserToken์ ํตํด ์ ๋ณด๋ฅผ ๋๊ฒจ์ฃผ๋ ์ด์ ๋
// Listener ๊ฐ ๊ณ์ํด์ ๋บ๋บ์ด๋ฅผ ๋๋ฉฐ ์ฌ๋ฌ๋ช
์ ๋ฐ์ ์ ์๋ฏ์ด
// Connector ์ญ์ ์ฌ๋ฌ๋ช
์ ๋ฐ์ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
RegisterConnect(args);
}
void RegisterConnect(SocketAsyncEventArgs args)
{
Socket socket = args.UserToken as Socket;
if (socket == null)
return;
bool pending = socket.ConnectAsync(args);
if (pending == false)
OnConnectCompleted(null, args);
}
void OnConnectCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
Session session = _sessionFactory.Invoke(); // _sessionFactory ๋ฅผ ํตํ Session ์์ฑ (Content ๋ด์์ ์๊ตฌํ ๋ฐฉ์๋๋ก Session ์์ฑ)
session.Start(args.ConnectSocket);
session.OnConnected(args.RemoteEndPoint);
}
else
{
Console.WriteLine($"OnConnectCompleted Fail: {args.SocketError}");
}
}
}
}โ
- ํ์ฌ Connector๋ฅผ DummyClient ์์ ์ฌ์ฉํ ์ ์๋ค. (Connector๋ ServerCore์ Class)
~> ๊ทธ๋ ๋ค๊ณ ServerCore์ Connector, Listener, Session Class๋ฅผ ๋ณต์ฌํ์ฌ DummyClient ์ ๋ถ์ฌ๋ฃ๋ ๊ฒ์ ์ธ๋ จ๋์ง X
- ์ถํ์ DummyClient, Server, ServerCore ๋ฅผ ๋์์ ์ฌ์ฉํ๊ฒ ๋๋ค.
~> ์ด๋ ServerCore ๋ ์ค์ ๋ก ์คํ๋๋ ๊ฒ์ด ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก๋ง ์ฌ์ฉํ ์์ ์ด๋ค.
- ServerCore๋ฅผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ์ธํ ํ ๋ค DummyClient์ Server๋ ServerCore ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐธ์กฐํ๋๋ก ์ค์ ํ๋ค.


- ์ฐ์ [ ServerCore ํ๋ก์ ํธ ] - [ ์ค๋ฅธ์ชฝ ๋ง์ฐ์ค ] - [ ์์ฑ ] ์ [ ์ ํ๋ฆฌ์ผ์ด์ ] - [ ์ผ๋ฐ ] ์์ ์ถ๋ ฅ ์ ํ์
[ ์ฝ์ ์ ํ๋ฆฌ์ผ์ด์ ] ์ด ์๋ [ ํด๋์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ] ๋ฅผ ์ ํํ๋ค.
~> ํด๋์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ํ์ ํด๋น ํ๋ก์ ํธ๋ ๋ ๋ฆฝ์ ์ผ๋ก ์คํํ ์ ์๋ค.
~> ์ฆ, ๋ค๋ฅธ ํ๋ก์ ํธ์ ๊ธฐ์ํ์ฌ ๊ฐ์ ์ ์ผ๋ก ์คํ๋ ์ ์๋ค.


- ๊ทธ ํ [ DummyClient / Server ํ๋ก์ ํธ ] - [ ์ค๋ฅธ์ชฝ ๋ง์ฐ์ค ] - [ ์ถ๊ฐ ] - [ ํ๋ก์ ํธ ์ฐธ์กฐ ] ์ [ ํ๋ก์ ํธ ] ์์ ServerCore
๋ฅผ ์ ํํ๋ค.
~> ์ด๋ฅผ ํตํด DummyClient ์ Server ํ๋ก์ ํธ๋ ServerCore ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐธ์กฐํ๋ค.
๋์ด์ ServerCore์ Program Class ๋ ์ฌ์ฉํ์ง ์์ผ๋ฏ๋ก ํด๋น ๋ด์ฉ์ Server์ Program Class๋ก ๋ณต์ฌํ ๋ค ์ญ์
(Server ์ ์ฅ์์ ServerCore๋ ๋ค๋ฅธ ํ๋ก์ ํธ์ด๋ฏ๋ก ServerCore์ Class๋ค์ ๋ณดํธ ์์ค์ public์ผ๋ก ๋ณ๊ฒฝ)
(์ด๋ฅผ ํตํด Server๋ Content, ServerCore๋ Engine์ ์ญํ ์ ๋งก์ ๊ฒ)
using System; using System.Text; using System.Net; using System.Net.Sockets; using ServerCore; // Servercore ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐธ์กฐ namespace Server { class GameSession : Session { public override void OnConnected(EndPoint endPoint) { Console.WriteLine($"OnConnected : {endPoint}"); // ๋ณด๋ธ๋ค. byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server!"); Send(sendBuff); Thread.Sleep(1000); // ์ซ์๋ธ๋ค. Disconnect(); } public override void OnDisconnected(EndPoint endPoint) { Console.WriteLine($"OnDisconnected : {endPoint}"); } public override void OnRecv(ArraySegment<byte> buffer) { string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count); Console.WriteLine($"[From Client] {recvData}"); } public override void OnSend(int numOfBytes) { Console.WriteLine($"Transferred bytes : {numOfBytes}"); } } class Program { static Listener _listener = new Listener(); static void Main(string[] args) { string host = Dns.GetHostName(); IPHostEntry ipHost = Dns.GetHostEntry(host); IPAddress ipAddr = ipHost.AddressList[0]; IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777); // ์๋์ ์ ์ฅ์ํจ๋ค. _listener.Init(endPoint, () => { return new GameSession(); }); Console.WriteLine("Listening..."); while (true) { } } } }โ


- [ ์๋ฃจ์ ] - [ ์ค๋ฅธ์ชฝ ๋ง์ฐ์ค ] - [ ์์ฑ ] ์ [ ๊ณต์ฉ ์์ฑ ] - [ ์์ ํ๋ก์ ํธ ] ์์ [ ํ ๊ฐ์ ์์ ํ๋ก์ ํธ ] ๊ฐ ์๋
[ ์ฌ๋ฌ ๊ฐ์ ์์ ํ๋ก์ ํธ ] ๋ฅผ ์ ํํ ๋ค DummyClient ์ Server ํ๋ก์ ํธ์ ์์ ์ํ๋ฅผ [ ์์ ] ์์ [ ์์ ] ์ผ๋ก,
ServerCore ํ๋ก์ ํธ์ ์์ ์ํ๋ฅผ [ ์์ ] ์์ [ ์์ ] ์ผ๋ก ๋ณ๊ฒฝํ๋ค.
Connector Class ์ฌ์ฉ์ ์ํ Client ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using ServerCore; // Servercore ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐธ์กฐ
namespace DummyClient
{
// GameSession Class ์ถ๊ฐ
class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
// ๋ณด๋ธ๋ค
byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World");
Send(sendBuff);
}
public override void OnDisconnected(EndPoint endPoint)
{
Console.WriteLine($"OnDisconnected : {endPoint}");
}
public override void OnRecv(ArraySegment<byte> buffer)
{
string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
Console.WriteLine($"[From Server] {recvData}");
}
public override void OnSend(int numOfBytes)
{
Console.WriteLine($"Transferred bytes : {numOfBytes}");
}
}
class Program
{
static void Main(string[] args)
{
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
// Connector Class ์ฌ์ฉ
Connector connector = new Connector();
connector.Connect(endPoint, () => { return new GameSession(); });
try
{
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}โ
# TCP vs UDP
- ๊ฒ์์์๋ ํจํท ๋จ์๋ก ํต์ ์ด ์ด๋ฃจ์ด์ง๋ค.


# RecvBuffer
- TCP ํน์ฑ์ (์ผ๋ถ๋ง ๋ณด๋ด๋ ํ๋ฆ/ํผ์ก์ ์ด) Client ์์ ๋ณด๋ธ ํจํท์ด 100 Byte ๋ผ๊ณ ํด์ 100 Byte๊ฐ ์์ ํ ๋์ฐฉํ๋ค๋
๋ณด์ฅ์ ์๋ค.
~> 100 Byte ๋ฏธ๋ง์ ํจํท์ด ๋์ฐฉํ ๊ฒฝ์ฐ ์ด๋ฅผ ๋ฐ๋ก ์ฒ๋ฆฌํ ์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ recvBuffer์ ๋ณด๊ด๋ง ํ๊ณ ์๋ค๊ฐ
์ถํ์ ๋๋จธ์ง ํจํท์ด ๋ง์ ๋์ฐฉํ๋ฉด ์ด๋ฅผ ์กฐ๋ฆฝํ ๋ค ํ๋ฒ์ ์ฒ๋ฆฌํ ์ ์๋๋ก ์์ ํ๋ค.
RecvBuffer ๊ฐ์ ์ ์ํ RecvBuffer Class ์์ฑ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
public class RecvBuffer
{
// byte ๋ฐฐ์ด์ ๋ค๊ณ ์์ด๋ ๋์ง๋ง byte ๋ฅผ ๋ค๊ณ ์๋ ์ด์ ?
// ~> ์์ฒญ ํฐ Byte ๋ฐฐ์ด์์ ๋ถ๋ถ์ ์ผ๋ก ์๋ผ ์ฌ์ฉํ๊ณ ์ถ์ ์๋ ์๊ธฐ ๋๋ฌธ
ArraySegment<byte> _buffer;
// _readPos์ _writePos๋ Buffer์์ ์ปค์ ์ญํ ์ ํ๋ค.
// Buffer์ ํฌ๊ธฐ๋ 8Byte, 1ํจํท์ด 3Byte ๋ผ๊ณ ๊ฐ์ ํ์๋ ์๋์ ์์๋ฅผ ์ดํด๋ณด์.
// [r w] [ ] [ ] [ ] [ ] [ ] [ ] [ ] (์ด๊ธฐ ์ํ)
// [r] [ ] [ ] [w] [ ] [ ] [ ] [ ] (Client๋ก๋ถํฐ 3Byte๋ฅผ ๋ฐ์ ์ํ)
// [ ] [ ] [ ] [r w] [ ] [ ] [ ] [ ] (Content ์ฝ๋์์ 1ํจํท์ ์ฒ๋ฆฌํ ์ํ)
int _readPos;
int _writePos;
// ์์ฑ์
public RecvBuffer(int bufferSize)
{
_buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
}
public int DataSize { get { return _writePos - _readPos; } }
public int FreeSize { get { return _buffer.Count - _writePos; } }
public ArraySegment<byte> ReadSegment // ํ์ฌ๊น์ง ๋ฐ์ ๋ฐ์ดํฐ์ ์ ํจ ๋ฒ์๊ฐ ์ด๋์๋ถํฐ ์ด๋๊น์ง์ธ๊ฐ?
{
get { return new ArraySegment<byte>(_buffer.Array, _buffer.Offset + _readPos, DataSize); }
}
public ArraySegment<byte> WriteSegment // ๋ค์ Recv์ ๋น ๊ณต๊ฐ์ ์ ํจ ๋ฒ์๊ฐ ์ด๋์๋ถํฐ ์ด๋๊น์ง์ธ๊ฐ?
{
get { return new ArraySegment<byte>(_buffer.Array, _buffer.Offset + _writePos, FreeSize); }
}
// Clean ํจ์๋ฅผ ํตํด Buffer๋ฅผ ์ ๋ฆฌํ์ง ์์ ๊ฒฝ์ฐ [ ] [ ] [ ] [ ] [ ] [ ] [r] [w] ์ ๊ฐ์ด _readPos์ _writePos๊ฐ Buffer์ ๋๊น์ง ๋ฐ๋ฆด ์ ์๋ค.
// _readPos์ _writePos์ ์์น๊ฐ ๋ค๋ฅธ ๊ฒฝ์ฐ _readPos ๋ถํฐ _writePos - 1 ๊น์ง์ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํ ๋ค _writePos ๋ฅผ ์ด๋์ํจ๋ค.
// _readPos์ _writePos์ ์์น๊ฐ ๊ฐ์ ๊ฒฝ์ฐ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํ์ง ์๊ณ _readPos ์ _writePos ๋ฅผ Buffer์ ์์ ์์น๋ก ์ด๋์ํจ๋ค.
public void Clean()
{
int dataSize = DataSize;
if (dataSize == 0) // _readPos์ _writePos์ ์์น๊ฐ ๊ฐ์ ๊ฒฝ์ฐ (Client์์ ๋ณด๋ธ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ ์ํ)
{
_readPos = _writePos = 0;
}
else // _readPos์ _writePos์ ์์น๊ฐ ๋ค๋ฅธ ๊ฒฝ์ฐ
{
// ์ด๋ ๋ฐฐ์ด์? ์ด๋์๋ถํฐ? ์ด๋ ๋ฐฐ์ด์? ์ด๋๋ก? ์ผ๋ง๋งํผ?
Array.Copy(_buffer.Array, _buffer.Offset + _readPos, _buffer.Array, _buffer.Offset, dataSize);
_readPos = 0;
_writePos = dataSize;
}
}
public bool OnRead(int numOfBytes) // Content ์ฝ๋์์ ์ฑ๊ณต์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ ๊ฒฝ์ฐ ํด๋น ํจ์๋ฅผ ํตํด ์ปค์ ์์น๋ฅผ ์ด๋์ํจ๋ค.
{
if (numOfBytes > DataSize)
return false;
_readPos += numOfBytes;
return true;
}
public bool OnWrite(int numOfBytes) // Client๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๊ฒฝ์ฐ ํด๋น ํจ์๋ฅผ ํตํด ์ปค์ ์์น๋ฅผ ์ด๋์ํจ๋ค.
{
if (numOfBytes > FreeSize)
return false;
_writePos += numOfBytes;
return true;
}
}
}โ
RecvBuffer Class ์์ฑ์ ๋ฐ๋ฅธ Session Class ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
public abstract class Session
{
// ...
// RecvBuffer Class ์ฌ์ฉ
RecvBuffer _recvBuffer = new RecvBuffer(1024);
// ...
public abstract int OnRecv(ArraySegment<byte> buffer); // ๋ฐํ๊ฐ์ void์์ int๋ก ์์ (์ผ๋ง๋งํผ์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋์ง ๋ฐํํ๋๋ก)
// ...
public void Start(Socket socket)
{
_socket = socket;
_recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
// ์๋ ๋ถ๋ถ ์ญ์
// _recvArgs.SetBuffer(new byte[1024], 0, 1024);
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
RegisterRecv();
}
// ...
void RegisterRecv()
{
// ์๋ ๋ถ๋ถ ์ถ๊ฐ
_recvBuffer.Clean();
ArraySegment<byte> segment = _recvBuffer.WriteSegment;
_recvArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
bool pending = _socket.ReceiveAsync(_recvArgs);
if (pending == false)
OnRecvCompleted(null, _recvArgs);
}
void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
// Write ์ปค์ ์ด๋
if (_recvBuffer.OnWrite(args.BytesTransferred) == false) // BytesTransferred ๋ ์์ ๋ฐ์ Byte
{
Disconnect();
return;
}
// Content ์ชฝ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋๊ฒจ์ฃผ๊ณ ์ผ๋ง๋ ์ฒ๋ฆฌํ๋์ง ๋ฐ๋๋ค.
int processLen = OnRecv(_recvBuffer.ReadSegment);
if (processLen < 0 | _recvBuffer.DataSize < processLen)
{
Disconnect();
return;
}
// Read ์ปค์ ์ด๋
if (_recvBuffer.OnRead(processLen) == false)
{
Disconnect();
return;
}
RegisterRecv();
}
catch (Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed {e}");
}
}
else
{
Disconnect();
}
}
}
}โ
Server ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using ServerCore; // Servercore ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐธ์กฐ
namespace Server
{
class GameSession : Session
{
// ...
// ๋ฐํ๊ฐ์ด void ์์ int ๋ก ์์ ๋จ์ ๋ฐ๋ฅธ ์ฝ๋ ์์
public override int OnRecv(ArraySegment<byte> buffer)
{
string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
Console.WriteLine($"[From Client] {recvData}");
return buffer.Count;
}
// ...
}
// ...
}โ
Client ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using ServerCore; // Servercore ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐธ์กฐ
namespace DummyClient
{
class GameSession : Session
{
// ...
// ๋ฐํ๊ฐ์ด void ์์ int ๋ก ์์ ๋จ์ ๋ฐ๋ฅธ ์ฝ๋ ์์
public override int OnRecv(ArraySegment<byte> buffer)
{
string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
Console.WriteLine($"[From Server] {recvData}");
return buffer.Count;
}
// ...
}
// ...
}โ
# SendBuffer
- Session ๋ง๋ค ์์ ์ ๊ณ ์ RecvBuffer๋ฅผ ๊ฐ์ง๋ค.
~> Client ๊ฐ ๋ณด๋ด๋ ์ ๋ณด๋ ๊ฐ๊ธฐ ๋ค ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ์ด๋ ๋น์ฐํ ์ฌ์ค์ด๋ค.
- ๊ทธ๋ฌ๋ sendBuffer๋ Session ๋ง๋ค ๊ณ ์ ํ์ง ์๊ณ ๋ณด๋ด๋ ์๊ฐ์ ์ธ๋ถ์์ ๋ง๋ค์ด์ง๋ค.
~> ์ด๋ ์ฑ๋ฅ์ ์ด์์ ๊ด๋ จ์ด ์๋ค.
~> ๋ง์ฝ 100๋ช ์ User๊ฐ ๊ฐ์ Zone ์์ ์๋ ๊ฒฝ์ฐ User 1๋ช ์ด ์ด๋์ ํด๋น User์ ์ด๋ ์ ๋ณด๋ฅผ ๋๋จธ์ง 99๋ช ์๊ฒ ์ ๋ถ
๋ณด๋ด์ผ ํ๋ค. ํ์ง๋ง User 1๋ช ๋ง ์ด๋ํ๋ ๊ฒ์ด ์๋๊ธฐ ๋๋ฌธ์ ์ด๋ ํจํท์ด 99 * 100 ๊ฐ๊ฐ ์ ์ก๋์ด์ผ ํ๋ค.
- sendBuffer์ Size๋ ์ด๋ป๊ฒ ๊ฒฐ์ ํด์ผ ํ ๊น?
~> ๋ง์ฝ ๋ณด๋ด๊ณ ์ ํ๋ Class์ ๋ฉค๋ฒ ๋ณ์๋ก string ๊ณผ List ์ ๊ฐ์ด ๊ฐ๋ณ์ ์ธ ๊ธธ์ด๋ฅผ ๊ฐ๋๋ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น?
~> Thread ๋ง๋ค ์์ ๋ง์ Chunk ๋ฅผ ํฌ๊ฒ ํ ๋นํ ๋ค ์ด๋ฅผ ๊ณ์ํด์ ์ชผ๊ฐ์ ์ฌ์ฉํ๋๋ก ๋ง๋ ๋ค.
SendBuffer ๊ฐ์ ์ ์ํ SendBuffer Class ์์ฑ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
public class SendBufferHelper
{
// ThreadLocal : ๋ณธ์ธ์ Thread ์์๋ง ์ฌ์ฉ ๊ฐ๋ฅํ ์ ์ญ ๋ณ์ (Thread ๋ผ๋ฆฌ์ ๊ฒฝํฉ์ ์์ ๊ณ ์ ์ฌ์ฉ)
public static ThreadLocal<SendBuffer> CurrentBuffer = new ThreadLocal<SendBuffer>(() => { return null; });
public static int ChunkSize { get; set; } = 4096 * 100;
public static ArraySegment<byte> Open(int reserveSize)
{
if (CurrentBuffer.Value == null) // ํ๋ฒ๋ ์ฌ์ฉํ์ง ์์ ๊ฒฝ์ฐ
CurrentBuffer.Value = new SendBuffer(ChunkSize); // ์๋ก ์์ฑ
if (CurrentBuffer.Value.FreeSize < reserveSize) // ์ฌ์ฉํ ์ ์ ์์ง๋ง ์๊ตฌํ Size๋ณด๋ค FreeSize๊ฐ ๋ ์์ ๊ฒฝ์ฐ
CurrentBuffer.Value = new SendBuffer(ChunkSize); // ๊ธฐ์กด Chunk ๋ฅผ ๋ ๋ฆฐ๋ค ์๋ก์ด Chunk ๋ก ๊ต์ฒด
return CurrentBuffer.Value.Open(reserveSize);
}
public static ArraySegment<byte> Close(int usedSize)
{
return CurrentBuffer.Value.Close(usedSize);
}
}
public class SendBuffer
{
byte[] _buffer;
// _usedSize๋ Buffer์์ ์ปค์ ์ญํ ์ ํ๋ค.
// Buffer์ ํฌ๊ธฐ๋ 10Byte ๋ผ๊ณ ๊ฐ์ ํ์๋ ์๋์ ์์๋ฅผ ์ดํด๋ณด์.
// [u] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] (์ด๊ธฐ ์ํ)
int _usedSize = 0;
public int FreeSize { get { return _buffer.Length - _usedSize; } }
// ์์ฑ์
public SendBuffer(int chunkSize)
{
_buffer = new byte[chunkSize];
}
public ArraySegment<byte> Open(int reserveSize) // ์ต๋ reserveSize ๋งํผ์ Buffer ๊ณต๊ฐ์ ์ฌ์ฉํ๊ณ ์ ํ๋ค.
{
if (reserveSize > FreeSize)
return null;
return new ArraySegment<byte>(_buffer, _usedSize, reserveSize);
}
public ArraySegment<byte> Close(int usedSize) // ์ค์ ๋ก ์ฌ์ฉํ Buffer ๊ณต๊ฐ์ ๋ฐํํ๊ณ ์ ํ๋ค.
{
ArraySegment<byte> segment = new ArraySegment<byte>(_buffer, _usedSize, usedSize);
_usedSize += usedSize;
return segment;
}
// Send๋ 1๋ช
์ด ์๋ ์ฌ๋ฌ๋ช
์๊ฒ ๋ณด๋ด๋ ๊ฒฝ์ฐ๋ ์๊ธฐ ๋๋ฌธ์ ๋ณธ์ธ์ด Send ํ์๋ค๊ณ ํด์ ๋ง๋ฐ๋ก Clean ํ ์ X
// (์ฎ๊ธฐ๊ณ ์ ํ๋ ๊ณณ์ ๋ค๋ฅธ ๋๊ตฐ๊ฐ๊ฐ ์์ง ์ฐธ์กฐ์ค์ผ ์๋ ์๊ธฐ ๋๋ฌธ)
// ๋ฐ๋ผ์ SendBuffer๋ Clean ํ๋ ๊ฒ์ด ์๋ 1ํ์ฉ์ผ๋ก ์ฌ์ฉํ๋ค.
}
}โ
SendBuffer Class ์์ฑ์ ๋ฐ๋ฅธ Server ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using ServerCore; // Servercore ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐธ์กฐ
namespace Server
{
class Knight
{
public int hp;
public int attack;
public string name;
public List<int> skills = new List<int>();
}
class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
Knight knight = new Knight() { hp = 100, attack = 10 };
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
byte[] buffer = BitConverter.GetBytes(knight.hp);
byte[] buffer2 = BitConverter.GetBytes(knight.attack);
// ์ด๋ ๋ฐฐ์ด์? ์ด๋์๋ถํฐ? ์ด๋ ๋ฐฐ์ด์? ์ด๋๋ก? ์ผ๋ง๋งํผ?
Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
Array.Copy(buffer2, 0, openSegment.Array, buffer.Length, buffer2.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(buffer.Length + buffer2.Length);
Send(sendBuff);
Thread.Sleep(1000);
// ์ซ์๋ธ๋ค.
Disconnect();
}
// ...
}
// ...
}โ
Session ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
public abstract class Session
{
// ...
Queue<ArraySegment<byte>> _sendQueue = new Queue<ArraySegment<byte>>(); // Queue์ ํ์
์ byte[] ์์ ArraySegment<byte> ๋ก ์์
// ...
// ๋งค๊ฐ๋ณ์์ ํ์
์ byte[] ์์ ArraySegment<byte> ๋ก ์์
public void Send(ArraySegment<byte> sendBuff)
{
lock (_lock)
{
_sendQueue.Enqueue(sendBuff);
if (_pendingList.Count == 0)
RegisterSend();
}
}
// ...
void RegisterSend()
{
while (_sendQueue.Count > 0)
{
ArraySegment<byte> buff = _sendQueue.Dequeue(); // buff์ ํ์
์ byte[] ์์ ArraySegment<byte> ๋ก ์์
_pendingList.Add(buff);
}
_sendArgs.BufferList = _pendingList;
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
}
// ...
}
}โ
# PacketSession
- ๊ฒ์์์๋ ํจํท ๋จ์๋ก ํต์ ์ด ์ด๋ฃจ์ด์ง๋ค.
~> ํจํท ์ ์ฉ OnRecv ๋ฅผ ๋ง๋ค ํ์๊ฐ ์๋ค.
Session ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ServerCore
{
// abstract class PacketSession ์ถ๊ฐ
public abstract class PacketSession : Session
{
public static readonly int HeaderSize = 2;
// ํจํท์ ๋ฐ์ ๊ฒฝ์ฐ [ size (2Byte) ] [ packetId (2Byte) ] [ ... ]
public sealed override int OnRecv(ArraySegment<byte> buffer) // sealed๋ ๋ค๋ฅธ Class๊ฐ PacketSession์ ์์ ๋ฐ์ ๋ค OnRecv๋ฅผ override ํ๊ณ ์ ํ ๊ฒฝ์ฐ ์ค๋ฅ ๋ฐ์
{
int processLen = 0; // ๋ด๊ฐ ๋ช Byte ๋ฅผ ์ฒ๋ฆฌํ๋๊ฐ
while (true) // ํจํท์ ์ฒ๋ฆฌํ ์ ์์๋๊น์ง ๊ณ์ํด์ ๋ฐ๋ณต
{
// ์ต์ํ Header ๋ Parsing ํ ์ ์๋์ง ํ์ธ
if (buffer.Count < HeaderSize)
break;
// ํจํท์ด ์์ ์ฒด๋ก ๋์ฐฉํ๋์ง ํ์ธ
ushort dataSize = BitConverter.ToUInt16(buffer.Array, buffer.Offset); // ToUInt16์ Byte ๋ฐฐ์ด์ ushort๋ก ๋ฝ์๋ฌ๋ผ๋ ๊ฒ
if (buffer.Count < dataSize)
break;
// ์ฌ๊ธฐ๊น์ง ์์ผ๋ฉด ํจํท ์กฐ๋ฆฝ ๊ฐ๋ฅ
OnRecvPacket(new ArraySegment<byte>(buffer.Array, buffer.Offset, dataSize)); // ํจํท์ ์ ํจ ๋ฒ์๋ฅผ ๋๊ฒจ์ค๋ค.
processLen += dataSize;
buffer = new ArraySegment<byte>(buffer.Array, buffer.Offset + dataSize, buffer.Count - dataSize);
}
return processLen;
}
public abstract void OnRecvPacket(ArraySegment<byte> buffer);
}
// ...
}โ
Server ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;
namespace Server
{
class Packet // ํจํท ์ค๊ณ์ ์ต๋ํ Size๋ฅผ ์์ถํ์ฌ ๋ณด๋ด๋ ๊ฒ์ด ์ค์ํ๋ค.
{
// Packet์ด ์์ ์ฒด๋ก ์๋์ง? ์๋ ค์ ์๋์ง? ๊ตฌ๋ถํ ์ ์์ด์ผ ํ๋ค.
public ushort size; // ushort๋ 2Byte
public ushort packetId; // ushort๋ 2Byte
}
class GameSession : PacketSession // Session ์ด ์๋ PacketSession ์ ์์ ๋ฐ๋๋ก ์์
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
Packet packet = new Packet() { size = 100, packetId = 10 };
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
byte[] buffer = BitConverter.GetBytes(packet.size);
byte[] buffer2 = BitConverter.GetBytes(packet.packetId);
// ์ด๋ ๋ฐฐ์ด์? ์ด๋์๋ถํฐ? ์ด๋ ๋ฐฐ์ด์? ์ด๋๋ก? ์ผ๋ง๋งํผ?
Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
Array.Copy(buffer2, 0, openSegment.Array, buffer.Length, buffer2.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(buffer.Length + buffer2.Length);
Send(sendBuff);
Thread.Sleep(1000);
// ์ซ์๋ธ๋ค.
Disconnect();
}
// abstract ํจ์์ธ OnRecvPacket override ๋ ํ์
public override void OnRecvPacket(ArraySegment<byte> buffer)
{
ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset); // ToUInt16์ Byte ๋ฐฐ์ด์ ushort๋ก ๋ฝ์๋ฌ๋ผ๋ ๊ฒ
ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + 2);
Console.WriteLine($"RecvPacketID: {id}, Size: {size}");
}
// Sealed ๋ก OnRecv ํจ์๋ฅผ override ํ๋ ๊ฒ์ ๊ธ์ง ์์ผฐ๊ธฐ ๋๋ฌธ์ ์๋ ๋ถ๋ถ ์ญ์
//public override int OnRecv(ArraySegment<byte> buffer)
//{
// string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
// Console.WriteLine($"[From Client] {recvData}");
// return buffer.Count;
//}
// ...
}
// ...
}โ
Client ์ฝ๋ ์์
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using ServerCore; // Servercore ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐธ์กฐ
namespace DummyClient
{
class Packet
{
public ushort size; // ushort๋ 2Byte
public ushort packetId; // ushort๋ 2Byte
}
class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
Packet packet = new Packet() { size = 4, packetId = 7 };
for (int i = 0; i < 5; i++)
{
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
byte[] buffer = BitConverter.GetBytes(packet.size);
byte[] buffer2 = BitConverter.GetBytes(packet.packetId);
// ์ด๋ ๋ฐฐ์ด์? ์ด๋์๋ถํฐ? ์ด๋ ๋ฐฐ์ด์? ์ด๋๋ก? ์ผ๋ง๋งํผ?
Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
Array.Copy(buffer2, 0, openSegment.Array, buffer.Length, buffer2.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
Send(sendBuff);
}
}
// ...
}
// ...
}โ
# ์๋ฒ ํ๋ ์์ํฌ ์์ฝ ์ ๋ฆฌ

