6. ● .NET 플랫폼용 오픈 소스 네트워크 라이브러리
3.5 ~ 4.5까지 지원.
● Windows, Linux, Mono, Azure를 지원한다.
● 비동기 I/O를 지원. TCP, UDP
● SSL/TLS 지원, 확장 기능 등 다양한 기능 지원
● 공식 사이트 http://www.supersocket.net
문서는 http://docs.supersocket.net/
7. ● 현재(2016.09.02) 기준
● nugget 최신 버전은 1.6.6.1
https://www.nugget.org/packages/SuperSocket/
● GitHub 버전은 1. 6.7
https://github.com/kerryjiang/SuperSocket
● 2.0 버전을 준비 중
Visual Studio Code 지원.
어쩌면 .NET Core도 지원?
13. 설치하기 – 소스 코드에서
SuperSocket 소스에 있는 log4net.dll을 포함한다.
SuperSocket.Common, SuperSocket.SocketBase,
SuperSocket.SocketEngine는 왼쪽 그림처럼 dll을 참조에 포함해도 되
고 아니면 프로젝트를 바로 포함해도 된다(아래).
필요한 클래스 라이브러리
16. 서버 실행 – 설정
void InitConfig()
{
m_Config = new ServerConfig
{
Port = 23478,
Ip = "Any",
MaxConnectionNumber = 100,
Mode = SocketMode.Tcp,
Name = "BoardServerNet"
};
}
17. void CreateServer()
{
m_Server = new BoardServerNet();
bool bResult = m_Server.Setup(new RootConfig(),
m_Config,
logFactory: new Log4NetLogFactory()
);
if (bResult == false)
{
}
......
}
class BoardServerNet : AppServer<NetworkSession,
EFBinaryRequestInfo>
{
}
18. // 포트 번호 2012로 설정
if (! m_Server.Setup(2012))
{
return;
}
....
// 네트워크 시작
if (! m_Server.Start())
{
return;
}
....
// 네트워크 중지
m_Server.Stop();
서버 실행 - 네트워크 설정 및 시작/중단
19. 서버 실행 - 핸들러 등록
새로운 클라이언트가 연결되면 호출될 핸들러 등록
appServer.NewSessionConnected += new
SessionHandler<AppSession>(appServer_NewSessionConnected);
....
static void appServer_NewSessionConnected(AppSession session)
{
session.Send("Welcome to SuperSocket Telnet Server");
}
20. 클라이언트가 보낸 데이터를 받으면 호출될 핸들러 등록
appServer.NewRequestReceived += new RequestHandler<AppSession,
StringRequestInfo>(appServer_NewRequestReceived);
....
static void appServer_NewRequestReceived(AppSession session,
StringRequestInfo requestInfo)
{
switch (requestInfo.Key.ToUpper())
{
case("ECHO"):
session.Send(requestInfo.Body);
break;
........
}
21. AppServer와 AppSession 구현
• AppSession
서버에 연결된 Socket의 로직 클래스.
이 클래스를 통해 클라이언트의 연결,끊어짐, 데이터 주고 받기를 한다.
• AppServer
네트워크 서버 클래스. 모든 AppSession 객체를 관리한다.
SuperSocket의 몸통이다.
22. public class TelnetSession : AppSession<TelnetSession>
{
protected override void OnSessionStarted()
{
this.Send("Welcome to SuperSocket Telnet Server");
}
protected override void HandleUnknownRequest(StringRequestInfo requestInfo)
{
this.Send("Unknow request");
}
protected override void HandleException(Exception e)
{
this.Send("Application error: {0}", e.Message);
}
protected override void OnSessionClosed(CloseReason reason)
{
//add you logics which will be executed after the session is closed
base.OnSessionClosed(reason);
}
}
24. 서버 네트워크 옵션 설정하기
name: the name of appServer instance
serverType: the full name of the AppServer your want to run
ip: listen ip
port: listen port
위 설정을 아래와 같이 config 파일에 정의할 수 있다.
<superSocket>
<servers>
<server name="TelnetServer"
serverType="SuperSocket.QuickStart.TelnetServer_StartByConfig.TelnetServer,
SuperSocket.QuickStart.TelnetServer_StartByConfig"
ip="Any" port="2020">
</server>
</servers>
</superSocket>
25. static void Main(string[] args)
{
var bootstrap =
BootstrapFactory.CreateBootstrap(
);
if (!bootstrap.Initialize())
{
return;
}
var result = bootstrap.Start();
if (result == StartResult.Failed)
{
return;
}
//Stop the appServer
bootstrap.Stop();
}
위의 Config 파일 사용 예
26. 서버 네트워크 옵션 설정하기
Config 파일 - 멀티 인스턴스 사용 예
<superSocket>
<servers>
<server name="TelnetServerA"
serverTypeName="TelnetServer"
ip="Any" port="2020">
</server>
<server name="TelnetServerB"
serverTypeName="TelnetServer"
ip="Any" port="2021">
</server>
</servers>
<serverTypes>
<add name="TelnetServer"
type="SuperSocket.QuickStart.TelnetServer_StartByConfig.TelnetServer,
SuperSocket.QuickStart.TelnetServer_StartByConfig"/>
</serverTypes>
</superSocket>
App.Config 파일 이외의 설정 파일을 사용하기 위해서는 아래처럼 파일 이름을 지정한다.
m_Bootstrap = BootstrapFactory.CreateBootstrapFromConfigFile("SuperSocket.config");
27. SuperSocket 라이브러리가 호스트 프로그램이 아닌 다른 프로젝트에서 사용하는 경우의 설정
SuperSocket의 'AppServer'
클래스를 상속한 클래스
29. 네트워크 옵션 파라미터
루트 설정(모든 서버 네트워크에 적용)에 사용하는 파리미터 IRootConfig
maxWorkingThreads: maximum working threads count of .NET thread pool;
minWorkingThreads: minimum working threads count of .NET thread pool;
maxCompletionPortThreads: maximum completion threads count of .NET thread pool;
minCompletionPortThreads: minimum completion threads count of .NET thread pool;
disablePerformanceDataCollector: whether disable performance data collector;
performanceDataCollectInterval: performance data collecting interval (in seconds, default value: 60);
isolation: SuperSocket instances isolation level
None - no isolation
AppDomain - server instances will be isolated by AppDomains
Process - server instances will be isolated by processes
logFactory: the name of default logFactory, all log factories are defined in the child node "logFactories"
which will be introduced in following documentation;
defaultCulture: default thread culture for the global application, only available in .Net 4.5;
30. 서버 인스턴스 옵션 파라미터 IServerconfig
name: the name of the server instance;
serverType: the full name the AppServer's type which you want to run;
serverTypeName: the name of the selected server types, all server types should be defined in
serverTypes node which will be introduced in following documentation;
ip: the ip of the server instance listens. You can set an exact ip, you also can set the below values Any -
all IPv4 address IPv6Any - all IPv6 address
port: the port of the server instance listens;
listenBacklog: the listen back log size;
mode: the socket server's running mode, Tcp (default) or Udp;
disabled: whether the server instance is disabled;
startupOrder: the server instance start order, the bootstrap will start all server instances order by this
value;
31. 서버 인스턴스 옵션 파라미터
sendTimeOut: sending data timeout;
sendingQueueSize: the sending queue's maximum size;
maxConnectionNumber: maximum connection number the server instance allow to connect at the
same time;
receiveBufferSize: receiving buffer size; 세션당
sendBufferSize: sending buffer size; 세션당
syncSend: sending data in sync mode, default value: false;
logCommand: whether log command execution record;
logBasicSessionActivity: whether log the session's basic activities like connected and closed;
clearIdleSession: true or false, whether clear idle sessions, default value is false;
clearIdleSessionInterval: the clearing timeout idle session interval, default value is 120, in seconds;
idleSessionTimeOut: The session timeout period. Default value is 300, in seconds;
security: Empty, Tls, Ssl3. The security option of the socket server, default value is empty;
32. 서버 인스턴스 옵션 파라미터
maxRequestLength: The maximum allowed request length, default value is 1024;
textEncoding: The default text encoding in the server instance, default value is ASCII;
defaultCulture: default thread culture for this appserver instance, only available in .Net 4.5 and cannot
be set if the isolation model is 'None';
disableSessionSnapshot: Indicate whether disable session snapshot, default value is false. 세션 수 기
록
sessionSnapshotInterval: The interval of taking session snapshot, default value is 5, in seconds;
keepAliveTime: The interval of keeping alive, default value is 600, in seconds;
keepAliveInterval: The interval of retry after keep alive fail, default value is 60, in seconds;
33. Commnad-Line Protocol
"rn" 로 끝나는 라인 단위 문자열을 패킷 프로토콜로 사용할 수 있다.
문자열의 인코딩은 기본은 Ascii. UTF-8 등의 다른 인코딩으로 바꿀 수 있다.
public class StringRequestInfo
{
public string Key { get; }
public string Body { get; }
public string[] Parameters { get; }
//Other properties and methods
}
"LOGIN kerry 123456" + NewLine
Key: "LOGIN"
Body: "kerry 123456";
Parameters: ["kerry", "123456"]
public class LOGIN : CommandBase<AppSession, StringRequestInfo>
{
public override void ExecuteCommand( AppSession session, StringRequestInfo requestInfo)
{
//Implement your business logic
}
}
34. 독자적으로 변경하기 "LOGIN:kerry,12345" + NewLine
public class YourServer : AppServer<YourSession>
{
public YourServer()
: base(new CommandLineReceiveFilterFactory(Encoding.Default,
new BasicRequestInfoParser(":", ",")))
{
}
}
public class YourServer : AppServer<YourSession>
{
public YourServer()
: base(new CommandLineReceiveFilterFactory(Encoding.Default,
new YourRequestInfoParser()))
{
}
}
35. AppSession 조작
AppServer에서 세션 찾기
- GetSessionByID 멤버를 사용한다.
var session = appServer.GetSessionByID(sessionID);
if(session != null)
session.Send(data, 0, data.Length);
sessionID는 AppSession 객체를 생성할 때 GUID를 string으로 할당한다.
UDP의 경우 UdpRequestInfo를 사용하면 GUID로 만들고, 아니면 리모
트의 IP와 Port로 만든다.
데이터 보내기
session.Send(data, 0, data.Length);
or
session.Send("Welcome to use SuperSocket!");
36. 연결된 모든 세션에 메시지 보내기
foreach(var session in appServer.GetAllSessions())
{
session.Send(data, 0, data.Length);
}
자작용 Key로 세션들 찾기
- 아래의 CompanyId 처럼 새로운 Key를 사용하여 검색이 가능하다.
var sessions = appServer.GetSessions(s => s.CompanyId == companyId);
foreach(var s in sessions)
{
s.Send(data, 0, data.Length);
}
37. Connection Filter
• IConnectionFilter라는 인터페이스를 통해서 접속한 클라이언트를 접속 허용할
건지 차단할건지 정의할 수 있다. ip 범위대를 지정하여 특정 ip 범위에서만 접속
을 허용할 수 있다.
public class IPConnectionFilter : IConnectionFilter
{
private Tuple<long, long>[] m_IpRanges;
public bool Initialize(string name, IAppServer appServer)
{
Name = name;
var ipRange = appServer.Config.Options.GetValue("ipRange");
string[] ipRangeArray;
if (string.IsNullOrEmpty(ipRange)
|| (ipRangeArray = ipRange.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)).Length <= 0)
{
throw new ArgumentException("The ipRange doesn't exist in configuration!");
}
m_IpRanges = new Tuple<long, long>[ipRangeArray.Length];
for (int i = 0; i < ipRangeArray.Length; i++)
{
var range = ipRangeArray[i];
m_IpRanges[i] = GenerateIpRange(range);
}
return true;
}
38. private Tuple<long, long> GenerateIpRange(string range)
{
var ipArray = range.Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
if(ipArray.Length != 2)
throw new ArgumentException("Invalid ipRange exist in configuration!");
return new Tuple<long, long>(ConvertIpToLong(ipArray[0]), ConvertIpToLong(ipArray[1]));
}
private long ConvertIpToLong(string ip)
{
var points = ip.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
if(points.Length != 4)
throw new ArgumentException("Invalid ipRange exist in configuration!");
long value = 0;
long unit = 1;
for (int i = points.Length - 1; i >= 0; i--)
{
value += unit * points[i].ToInt32();
unit *= 256;
}
return value;
}
public string Name { get; private set; }
39. public bool AllowConnect(IPEndPoint remoteAddress)
{
var ip = remoteAddress.Address.ToString();
var ipValue = ConvertIpToLong(ip);
for (var i = 0; i < m_IpRanges.Length; i++)
{
var range = m_IpRanges[i];
if (ipValue > range.Item2)
return false;
if (ipValue < range.Item1)
return false;
}
return true;
}
}
42. 동적 언어 지원
• 닷넷에서 지원하는 동적언어들은 SuperSocket을 사용할 수 있다.
• 대표적인 닷넷용 동적언어는 IronPython, Ironruby, F# 등이 있다.
43. The Built-in Common Format Protocol
Implementation Templates
● http://docs.supersocket.net/v1-6/en-US/The-Built-in-Common-Format-Protocol-
Implementation-Templates
● TerminatorReceiveFilter - Terminator Protocol
: 특정 지시어를 지정하여 패킷을 구분한다.
● CountSpliterReceiveFilter - Fixed Number Split Parts with Separator Protocol
: 특정 지시어로 구분된 단위의 크기를 숫자로 지정
● FixedSizeReceiveFilter - Fixed Size Request Protocol
: 고정된 바이너리 크기로 패킷을 구분한다.
● BeginEndMarkReceiveFilter - The Protocol with Begin and End Mark
: 시작과 끝을 구분하는 지시어를 사용하여 패킷을 구분한다.
● FixedHeaderReceiveFilter - Fixed Header with Body Length Protocol
: 헤더와 보디로 나누어서 이것들의 크기에 의해서 패킷을 구분한다.
44. Custome 프로토콜 정의(binary 기반)
• SuperSocket 예제를 보면 대부분 string 기반의 프로토콜을 사용하고 있으나
binary 기반의 프로토콜을 정의해서 사용할 수 있다.
1. BinaryRequestInfo 클래스와 FixedHeaderReceuveFilter 클래스를 재 정의 한다.
// 헤더는 4 바이트 정수값으로 key, 그 다음 body byte[]의 크기를 가리키는 4 바이트 정수값
public class EFBinaryRequestInfo : BinaryRequestInfo
{
public int nKey
{
get;
private set;
}
public EFBinaryRequestInfo(int nKey, byte[] body)
: base(null, body)
{
this.nKey = nKey;
}
}
참조: http://www.gamecodi.com/board/zboard.php?id=GAMECODI_Talkdev&no=1981
45. class ReceiveFilter : FixedHeaderReceiveFilter<EFBinaryRequestInfo>
{
public ReceiveFilter()
: base(8)
{
}
protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
{
if (!BitConverter.IsLittleEndian)
Array.Reverse(header, offset + 4, 4);
var nBodySize = BitConverter.ToInt32(header, offset+4);
return nBodySize;
}
protected override EFBinaryRequestInfo ResolveRequestInfo(ArraySegment<byte> header,
byte[] bodyBuffer, int offset, int length)
{
if (!BitConverter.IsLittleEndian)
Array.Reverse(header.Array, 0, 4);
return new EFBinaryRequestInfo(BitConverter.ToInt32(header.Array, 0), bodyBuffer.CloneRange(offset,
length));
}
}
46. 2. 패킷 핸들러 정의
public class PacketData
{
public NetworkSession session;
public EFBinaryRequestInfo reqInfo;
}
public enum PACKETID : int
{
REQ_DUMMY_CHAT = 1,
REQ_LOGIN = 11,
}
public class CommonHandler
{
public void RequestLogin(NetworkSession session, EFBinaryRequestInfo requestInfo)
{
}
public void RequestDummyChat(NetworkSession session, EFBinaryRequestInfo requestInfo)
{
string jsonstring = System.Text.Encoding.GetEncoding("utf-8").GetString(requestInfo.Body);
var deserializedProduct = JsonConvert.DeserializeObject<PK_CHAT>(jsonstring);
session.Send(deserializedProduct.sender + ":" + deserializedProduct.msg);
}
}
public class PK_LOGON
{
public string ID;
public string PW;
}
public class PK_CHAT
{
public string sender;
public string msg;
}
47. 3. 데이터 받기 이벤트 등록 및 프로토콜 해석하기
.....
var HandlerMap = new Dictionary<int, Action<NetworkSession, EFBinaryRequestInfo>>();
CommonHandler CommonHan = new CommonHandler();
.....
public BoardServerNet()
: base(new DefaultReceiveFilterFactory<ReceiveFilter, EFBinaryRequestInfo>())
{
NewSessionConnected += new SessionHandler<NetworkSession>(OnConnected);
SessionClosed += new SessionHandler<NetworkSession, CloseReason>(OnClosed);
NewRequestReceived += new RequestHandler<NetworkSession, EFBinaryRequestInfo>(RequestReceived);
}
public void RegistHandler()
{
HandlerMap.Add( (int)PACKETID.REQ_LOGIN, CommonHan.RequestLogin );
HandlerMap.Add((int)PACKETID.REQ_DUMMY_CHAT, CommonHan.RequestDummyChat);
}
public void StartPacketThread()
{
IsRunningPacketThread = true;
PakcetThread = new System.Threading.Thread(this.ProcessPacket);
PakcetThread.Start();
}
48. public void ProcessPacket()
{
while (IsRunningPacketThread)
{
PacketData packet;
if (PacketQueue.TryDequeue(out packet))
{
var PacketID = packet.reqInfo.nKey;
if (HandlerMap.ContainsKey(PacketID))
{
HandlerMap[PacketID](packet.session, packet.reqInfo);
}
}
System.Threading.Thread.Sleep(1);
}
}
49. 로그 시스템
• log4net 라이브러리를 사용한다.
: 설정 파일에 따로 설정하지 않으면 ‘log4NetLogFactory’ 생성된다.
• 로그 관련 설정 파일은 log4net.config 혹은 log4nte.unix.config 이다. 이 파일
은 'Config' 라는 폴더 안에 있어야 한다.
로그 설정 파일을 솔루션에 등록하고, 출력 디렉토리로 복사하도록 하면
빌드 할 때마다 아래처럼 복사해 준다.
50. ● 로그 시스템 사용을 위해 SuperSocket에 있는 log4net.dll 이 필요하다.
● log4net의 객체를 가져오는 방법
log4net.ILog log = log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
51. public class RemoteProcessSession : AppSession<RemoteProcessSession>
{
protected override void HandleUnknownRequest(StringRequestInfo requestInfo)
{
Logger.Error("Unknow request");
}
}
<appender name="myBusinessAppender">
<!--Your appender details-->
</appender>
<logger name="MyBusiness" additivity="false">
<level value="ALL" />
<appender-ref ref="myBusinessAppender" />
</logger>
var myLogger = server.LogFactory.GetLog("MyBusiness");
● 직접 만든 로그 시스템을 사용하고 싶으면 ILogFactory와 ILog 인터페이스를
구현한다.
52. Azure, Mono, 그 외
• Work Role을 사용하면 손쉽게 Azure에서 사용할 수 있다.
• Mono 기반을 사용하여 Unix/Linux에서 사용할 수 있다. Mono 2.10 이상 필요.
• 1.6 버전에서는 설정에 의해서 클라이언트와 주고 받는 텍스트 메시지의 포맷
을 UTF-8 이외의 것으로 설정할 수 있다.
• 닷넷플랫폼 4.5 이상이라면 각 서버 인스턴스 별로 defaultCulture 설정 가능.
• Process level isolation 에 의해 하나의 애플리케이션에서 복수의 인스턴스를
생성하는 경우 각 인스턴스 별로 프로세스를 만들 수 있다.
53. Ubuntu + mono에서 사용
• Linux에서는 mono를 사용하면 SuperSocket으로 만든 프로그램을 실행 가
능(다만 mono가 지원하는 닷넷 라이브러리를 사용해야 한다)
• 실행 방법
Windows으로 빌드한 파일을 그대로 가져와서 실행한다.
"mono 실행파일 이름.exe"
실행 권한만 있다면 "실행파일 이름.exe" 만으로도 실행 가능
• 클라이언트와 서버간에 CommandText를 사용할 때 에러가 발생할 수 있음.
이유는 Windows와 Linux 간의 개행 코드가 다르기 때문.
Windows는 CRLF, Linux는 LF
SuperSocket을 사용한 SuperWebSocket의 경우
session.SocketSession.SendResponse(responseBuilder.ToString());
->
session.SocketSession.SendResponse(responseBuilder.ToString().Replace(En
vironment.NewLine, "rn"));
SuperSocket를 Mono에서 사용하기 http://blog.livedoor.jp/tukuruyo/tag/SuperWebSocket
55. SuperSocket을 더 잘 이해하려면
혹은 C# 고성능 네트워크 프로그래밍을 하려면
• C# SocketAsyncEventArgs High Performance Socket Code
http://www.codeproject.com/Articles/83102/C-SocketAsyncEventArgs-High-Performance-Socket-Cod
• (e-book)유니티 개발자를 위한 C#으로 온라인 게임 서버 만들기
http://www.hanbit.co.kr/realtime/books/book_view.html?p_code=E6015792502
56. 강연 문서와 예제 코드는 아래에...
https://github.com/jacking75/kgc2016_SuperSocket