Upload
lactrious
View
1.302
Download
1
Embed Size (px)
DESCRIPTION
implements game server using c# features such as async, await, dynamic, linq, reflection, attribute, ienumerable.
Citation preview
C# Server 만들기
2013. 06. 07.
최재영
Why?
• 빌드속도
• 표현력
2
asyncawait
extension method
linq
Observable
TPL
DynamicObject
Reflection
Attribute
IEnumerable
흐름3
Network
Datasheet
Database
Logic
async, awaitTaskCompletionSource
ReflectionAttribute
DynamicXmlLinq
IEnumerable
Network
• 빠른패킷 처리를위해 비동기 IO 사용
4
var socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.IPv4);
// preprocess socket
var buffer = new byte[4096];
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None,
result => {
var received = socket.EndReceive(result);
// process packet
}, null /* state */);
TaskCompletionSource
• C++ Future + C# Task Awaitable Future
5
Network
static Task<int> ReceiveAsync(this Socket sock, byte[] buf, int off, int size)
{
var source = new TaskCompletionSource<int>(sock);
sock.BeginReceive(buf, off, size, SocketFlags.None, state =>
{
try
{
source.SetResult(socket.EndReceive(state));
}
catch (Exception e)
{
source.SetException(e);
}
}, source);
return source.Task;
}
async, await
• Task.Result 비동기대기(await), 그런코드가있는함수(async)
6
Network
static async Task<byte[]> ReceiveAsync(this Socket socket, int count)
{
var buffer = new byte[count];
var length = 0;
do
{
var num = await ReceiveAsync(socket, buffer, length, count);
if (num == 0)
break;
length += num;
count -= num;
} while (count > 0);
if (length != buffer.Length) throw new IOException("packet is truncated.");
return buffer;
}
async, await7
Network
async void ReceiveLoop(Socket socket)
{
while (true)
{
var lengthBytes = await socket.ReceiveAsync(sizeof (int));
var packetBytes = await socket.ReceiveAsync(
BitConverter.ToInt32(lengthBytes, 0));
// process packet
var packet = ReadPacket(packetBytes);
_handlerMap[packet.GetType()](packet);
}
}
await하는 지점에 아직 IO signal이 없다면, 해당 Task는 잠시 멈추고,
가용한 다른 Task를 찾아 수행함
Listener (Server)
• ClientSocket을비동기로 Accept해서,
• 각 Socket마다비동기로 Packet을대기해서처리함
8
Network
var listener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
var localEndPoint = new IPEndPoint(IPAddress.Any, Port);
listener.Bind(localEndPoint);
listener.Listen(100);
while (true)
{
var clientSocket = await listener.AcceptAsync();
ReceiveLoop(clientSocket);
}async method
Summary
• Socket의 Async 계열 함수를사용 (.NET 내부는 IOCP로 처리)
• async, await은 TaskContinuation의 Syntax sugar
• async, await Keyword로 Callback 없이 편하게 Network 코드작성
• 그러면서도 Thread Pool에 의한효율적으로수행됨
(.NET Thread Pool도내부에서 IOCP로 관리)
9
Network
Datasheet
• dynamic을사용한일반적인 Xml 읽기
• code generator를사용
10
Xml 작성Xml Model
구현Xml Parser
구현
자동 생성 자동 생성
code generator
DynamicObject
XmlDefinition (자동 생성)
general-loader
DynamicObject
• RuntimeType으로동적으로멤버접근 가능
• 올바른 Type의값을 미리준비해야하므로 XmlDefinition 필요
11
Datasheet
public class XmlObject : DynamicObject
{
private readonly Dictionary<string, object> _attributes;
private readonly Dictionary<string, IEnumerable<XmlObject>>
_multipleChildren;
private readonly Dictionary<string, XmlObject> _singleChildren;
public override bool TryGetMember(GetMemberBinder binder,
out object result)
{
return TryGetValue(_attributes, binder.Name, out result) ||
TryGetValue(_singleChildren, binder.Name, out result) ||
TryGetValue(_multipleChildren, binder.Name, out result);
}
DynamicObject
• Attribute를읽을때 Type 변환을미리수행
• dynamic으로접근하여 Model 없이접근가능
12
Datasheet
_attributes = element.Attributes.OfType<XmlAttribute>()
.ToDictionary(e => e.Name,
e => defNode.SelectAttribute(e.Name)
.ReadValue(e.Value));
<?xml version="1.0" encoding="utf-8" ?>
<World>
<Config port="40123"/>
</World>
World.Config@port : int
dynamic world = XmlObject.Load("World.xml", _def);
_listener.Port = world.Config.port;
Summary
• dynamic을사용하여코딩시간단축(Model, Parser 작성불필요)
• 오타로인한접근위반은 Runtime에확인가능
• XmlDefinition이 필요함(자동생성 가능)
• 보다빠른 속도를원할 경우에는 Model, Parser를 Generate
(IVsSingleFileGenerator)
13
Datasheet
Database
• Reflection과 Attribute 사용으로일반적인 Bind 구현
• scheme 작업이불필요할경우 model 작성만으로모든구현해결 가능
14
Scheme 작성
DataModel 작성
Bind 구현
nosql or generator Reflection
Reflection
• Runtime에 model의 type정보로 scheme를구축
• 각 데이터의 Serialize/Deserialize 구현필요 (String과 object 상호 변환)
• 모든 Model 객체를 Xml로 변환
15
Database
new XElement("Objects",
_gameObjects.Values.Select(
obj =>
new XElement("Object",
obj.GetType().GetProperties()
.Where(e => e.CanRead && e.CanWrite)
.Select(e => new XAttribute(e.Name,
SerializeValue(e.PropertyType, e.GetValue(obj, null))
)))));
모든 Property에 대해 출력
출력할 때에는 string으로, 읽을 때에는 다시 object로
Attribute
• Runtime에접근가능한 metadata를코드에주입
16
Database
[CommandHandler("npc", "새로운 Npc를 생성합니다")]
internal bool SpawnNpc(Entity admin,
[CommandArgument("Npc의 이름")] string npcName,
[CommandArgument("Npc의 X 위치", 0)] double newX,
[CommandArgument("Npc의 Y 위치", 0)] double newY)
{
if (!admin.Has<Pos>())
return false;
var npc = EntityManager.Instance.Create(EntityTemplate.Ids.Npc);
npc.Get<Motion>().Dir = admin.Get<Motion>().Dir;
npc.Get<Nameplate>().Name = npcName;
npc.Get<Pos>().Assign(new Pos {X = newX, Y = newY});
명령어와 설명을 코드에 기록
인자 설명과 기본 값, type을 코드에 기록
Summary
• model 객체의 type 정보를최대한사용
• Attribute를부여하여가능한많은 정보를코드에주입
(DSL, 문서, 주석 등 외부 정보는 추가 유지보수가 필요함)
• Dirty나 Lazy를사용하여최적화가능
• 역시보다 빠른속도를원할경우에는 Code Generate를사용
(partial class를사용하여사용자 코드와혼합 가능)
17
Database
Logic
• yield return을 사용하여 State Machine 제거
• context 유지를 위한 별도 코딩이 필요 없음
18
행동A
행동B
행동C
1초 뒤
3초 뒤
5초 뒤LogicEngine
Logic #1 Logic #2
IEnumerable (Coroutine)
• IEnumerable을반환 type으로설정하여 yield return 사용
• 다음로직 수행까지의대기시간을반환
19
Logic
public IEnumerable<int> RegenerateEntry()
{
while (true)
{
var newNpc = _context.NewGameObject(ObjectType.Npc);
_context.AddGameObject(newNpc);
_context.BroadcastPacket(newNpc.ToSpawnPacket());
var newAi = new EachAi(this, newNpc);
_context.AddEntry(newAi.AiLogicEntry);
var nextInterval = _random.Next(interval) + base;
yield return nextInterval;
}제어권이 호출자에게 넘어감
다음 호출 시 이 지점부터 수행
IEnumerator (Coroutine)
• IEnumerator의 MoveNext() 함수로코드 실행
• Current로 yield return 결과 값 확인
20
Logic
var enumerator = RegenerateEntry().GetEnumerator();
while (enumerator.MoveNext())
{
Thread.Sleep(enumerator.Current);
}
IEnumerable로부터 IEnumerator를 가져옴
MoveNext()로 yield 사이 구간 코드 수행
yield return 반환 값을 얻음. 대기 시간만큼 쉼
여러 IEnumerator를 관리하고 Thread.Sleep()을 보다 작은 단위로 수행
LogicEngine (Coroutine)21
Logic
public void EntryLoop()
{
var prev = DateTime.Now;
while (true)
{
var now = DateTime.Now;
var delta = (now - prev).Milliseconds;
foreach (var newOne in _newLogicEntries)
{
var newEntry = new LogicEntry
{
Enumerator = newOne().GetEnumerator(),
SleepTime = 0
};
_logicEntries.Add(newEntry);
}
_newLogicEntries.Clear();
새로 추가된 Entry로부터
IEnumerator객체를 생성
LogicEngine (Coroutine)22
Logic
var removals = new List<LogicEntry>();
foreach (var each in _logicEntries)
{
each.SleepTime -= delta;
if (each.SleepTime >= 0)
continue;
if (!each.Enumerator.MoveNext())
removals.Add(each);
else each.SleepTime = each.Enumerator.Current;
}
_logicEntries.RemoveAll(removals.Contains);
prev = now;
const int logicInterval = 16;
Thread.Sleep(logicInterval);
}
}
수행 가능한 IEnumerator 집합
수행할 시간이 된 로직을 찾아서 실행
로직 함수가 return되어 완료되면 MoveNext()가 fasle를 반환
수행이 완료된 로직 삭제
Summary
• IEnumerable과 yield return의조합으로 coroutine 구현
• 간단한 coroutine이지만많은 boiler plate 코드작성회피 가능
• yield return으로많은 정보를전달하여다양한활용 (Unity3D Engine)
• 여러 Thread가 LogicEngine을수행하여 Entry 수행분산 가능
(여러 Thread가수행할경우 Lfe 등 객체별 수행 동기화고려가 필요함)
• Script를 C#으로 작성 시 도움이될 듯(?)
(c# script + roslyn + linqpad + nuget)
23
Logic
Summary
• async, await을사용한동기적 Network(IO) 프로그래밍
• dynamic을사용한 Runtime type dispatch
• Reflection, Attribute를사용한 boiler plate 코드줄이기
• Attribute로 metadata를코드로기록하여유지보수 비용줄이기
• coroutine을사용한동기적로직프로그래밍
24
보다 적은, 그리고 직관적인(동기적) 코딩으로 유지 보수 비용 줄이기
높은 표현력
성능 문제는 회로의 발전이 해결해 줄 것입니다 [...]
How much faster is C++ than C#?25
C# may not be faster, but it makes
YOU/ME faster. That's the most
important measure for what I do. :)
http://stackoverflow.com/questions/138361/how-much-faster-is-c-than-c