실버라이트 소켓통신 관련 PPT

Download Report

Transcript 실버라이트 소켓통신 관련 PPT

소켓(Socket)
RFC 147에 정의
네트워크로 연결되어 있는 컴퓨터의 통신의 접점에 위치한 통신 객체
[ 구성 ]
인터넷 프로토콜 (TCP, UDP, raw IP)
로컬 IP 주소
로컬 포트
원격 IP 주소
원격 포트
OSI 와 TCP/IP

OSI모델 (Open System Interconnection Reference Model)

인터넷 프로토콜 스택(Internet Protocal Stack)
OSI 는 각종 시스템간의 연결을 위하여 ISO 에서 제안한 모델
시스템에 상관없이 서로의 시스템이 연결 될 수 있도록 만들어주는 모델.
TCP/IP를 많이 사용하여 TCP/IP protocol stack이라고도 한다.
실재 인터넷에서는 TCP/IP 4계층으로 단순화 하여 사용
소켓(Socket)은 애플리케이션,전송,네트워크 stack을 제어 할 수 있다.
TCP/IP
인터넷 프로토콜 스택에서 가장 많이 사용하는 프로토콜

IP (Internet Protocol)

TCP (Transmission Control Protocol )

UDP (User Datagram Protocol )
네트워크 계층
호스트의 주소지정과 패킷 분할 및 조립 기능을 담당
패킷 통신 방식의 인터넷 프로토콜
패킷 전달 여부를 보증하지 않는다
패킷을 보낸 순서와 받는 순서가 다를 수 있다.(unreliable datagram service)
전송 조절 프로토콜(전송계층)
IP 위에서 동작하는 프로토콜
데이터의 전달을 보증하고 보낸 순서대로 받게 해준다.
전송계층
UDP는
UDP는
UDP는
UDP는
UDP는
UDP는
TCP보다 속도가 일반적으로 빠르고 오버헤드가 적다.
도착하는 패킷의 순서를 보장하지 않는다.
전송 승인 확인 기능이 없다.
패킷이 도착했는지 확인하여 주지 않는다.
연결 없는 데이터그램 서비스를 제공한다. 따라서 호스트 사이에 세션이 형성되지 않는다.
점 대 점 통신(point-to-point)과 지점 대 다중 지점 간(point-to-multipoint) 통신을 지원한다.
용어) 패킷( packet) : 데이터의 형식화된 블록 ( = datagram )
TCP/IP
- TCP/IP Protocol Stack Sender/receiver
interaction
TCP/IP
- Encapsulation/decapsulation of application
data within a network stack
RAW/STREAM Socket

Stream Socket

Raw Socket
양방향 byte stream을 전송
연결지향형 소켓(일반적으로 말하는 소켓)
오류수정, 전송처리, 데이터순서 보장
패킷 단위로 통신하며 레코드 경계지정은 직접 처리.( 레코드 시작 문자 또는 끝 문자지정 )
TCP, IP헤더를 제거한 소켓 , 전송,네트워크 계층 구현이 없는 소켓
TCP, IP헤더를 개발자가 직접 구현 해야 한다.
RAW/STREAM Socket
- Stream Socket
- Raw Socket
예) SOCKET socket = socket(AF_INET, SOCKET_RAW,protocol);
참고소스 : 0.RawSocket 폴더참고
소켓의 2가지 모드
 블로킹(Blocking)
모드
- 소켓 함수 호출 시 바로 리턴 되지 않고 소유한 Thread가 대기 상태가 되는 모드.
- Send
고정길이 데이터 만큼 전송 후 상대가 그 길이만큼 받을 때 까지 대기 상태가 됨.
- Receive
상대가 데이터를 전송할 때 까지 대기상태가 됨.
- 호출 시 대기상태로 머물게 되므로 스레드 처리가 필요
UI프로그램의 경우 UI가 반응하지 않게됨
- 닷넷의 경우
Socket.Accept(), Socket.Send() , Socket.Receive()메소드
참고소스 : 1.SyncSocket 폴더
넌
-
블로킹(Non-Blocking) 모드
소켓 함수 호출 시 바로리턴 멀티 스레드를 사용하지 않아도 됨
listen_sock이 넌블로킹이면 client_sock도 넌블로킹 소켓이 됨?(확인필요).
ioctlsocket()함수를 호출하여 넌블로킹 소켓모드로 설정(C++, Winsock2기준)
함수 호출후 반드시 WSAGetLastError()함수를 호출하여 반드시 오류코드를 확인 해야함
- 닷넷의 경우
Socket.AcceptAsync(), Socket.SendAsync() , Socket.ReceiveAsync()메소드
실버라이트의 경우 비동기 클라이언트 소켓만 지원
참고소스 : 2.AsyncSocket 폴더
송/수신 패킷정의
송/수신은 소켓의 버퍼가 지원하는 만큼 가능.
네트워크 환경이 나쁜 경우를 고려하여 보통 512 or 1024 byte등 적당한 단위로 송/수신하는것이 좋
다.
1. 패킷정의
[ 헤더 | 메시지종류 | 내용 ] 형식으로 정의
- 헤더 : 보통 4byte(INT)로 잡으며 헤더를 제외한 데이터의 길이를 저장
- 메시지 종류: 채팅내용, 파일전송시작정보, …등(보통 enum으로 정의)
- 내용 : 총 길이를 넘지 않게 데이터를 넣어서 전송.
패킷은 기본적으로 가변길이이며 수신 시 헤드에 정의된 길이만큼 계속 읽어 하나의 패킷으로 인지.
일반 메시지의 경우는 사이즈가 작지만 파일의 경우 소켓의 버퍼보다 사이즈가 크기 때문에 문제발생
그래서 보통 파일서버를 별도로 제작함.
2. 파일전송( 하나의 파일전송을 기준 )
- 송신 측
최초 패킷에 앞으로 전송할 메시지의 총 길이 및 기타정보를 전송(패킷정의 형식)
파일이 전송시작
- 수신 측
최초 패킷를 받으면 그기에 맞는 초기화 작업 및 수신시작플래그를 true로 set
최종길이만큼 수신 시 플래그 false
여러 개의 파일을 동시에 송/수신 시 패킷마다 파일번호를 붙여서 전송해야 해당파일 식별가능.
참고사이트 :
http://msdn.microsoft.com/en-us/magazine/dd315415.aspx#id0090036
http://code.msdn.microsoft.com/mag200901Silverlight/Release/ProjectReleases.aspx?ReleaseId=1938
소켓통신 절차(silverlight 기준)
클라이언트
ConnectAsync
인증서버
서버
<policy-file-request/>
clientaccesspolicy.xml 내용
실재 ConnectAsync 서버호
출
SendAsync
ReceiveAsync
Data전송/수신
AcceptAsync처리 후
ReceiveAsync 수신대기
ReceiveAsync
SendAsync
동시접속이 많은 경우 조각 메시지가 생김
Silverlight는 비동기 소켓통신만 지원(SocketAsyncEventArgs 모델)
소켓통신 Server 기본코드 (C#,비동기)
일반적인 비동기 소켓통신 주요 절차와 동일
동기 통신과 달리 while loop 가 필요없이 함수 제귀호출 구조

Socket 초기화 코드
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), port); //IPAddress.Any
serverSocket.Bind(ipEndPoint);
// backlog : 연결대기큐에 연결요청 100개까지 버퍼링
// Accept시 이 큐에서 하나씩 가져와서 사용
serverSocket.Listen(100);
StartAccept(serverSocket);

Accept 코드
void StartAccept(Socket serverSocket) {
serverSocket.BeginAccept(new AsyncCallback(OnBeginAccept), null);
}
private void OnBeginAccept(IAsyncResult ar) {
Socket clientSocket = serverSocket.EndAccept(ar);
// StateObject : 소켓과 관련된 실시간 정보들을 저장하기 위한 사용자 정의 객체 (보통 Context라고도 함)
StateObject stateObject = new StateObject();
stateObject.workSocket = clientSocket;
clientSocket.BeginReceive(stateObject.buffer, 0, maxBufferLength
, SocketFlags.None, new AsyncCallback(OnBeginReceive), stateObject);
StartAccept(serverSocket); //제귀 Accept
}
소켓통신 Server 기본코드 (C#)

Receive 코드
private void OnBeginReceive(IAsyncResult ar)
{
StateObject stateObject = (StateObject)ar.AsyncState;
Socket clientSocket = stateObject.workSocket;
int iRead = clientSocket.EndReceive(ar);
// 수신 된 byte배열을 string으로 변환하여 처리
string[] arrMessage = TalkPacketHelper.BuildPacket(stateObject.buffer, 0);
TalkCommand command = TalkCommand.Null ;
if (arrMessage != null)
{
command = (TalkCommand)Convert.ToInt32(arrMessage[0]);
switch (command)
{
}
case TalkCommand.Login:
{
// command종류에 따라 응답 }
…
//재귀 Receive
clientSocket.BeginReceive(stateObject.buffer, 0, maxBufferLength, SocketFlags.None,
new AsyncCallback(OnBeginReceive), stateObject);
}
실버라이트 소켓통신 인증서버
단순히 clientaccesspolicy.xml 내용을 응답해주는 서버
일반 소켓서버와 동일한 구조와 동일
참고 소스는 차후에 SocketAsyncEventArg버젼으로 수정필요.
참고
소스
PolicySocketServer.cs
닷넷의 APM(Asynchronous Programming Model
Begin/End model( .Net Ver 2.0)
닷넷에서 소켓통신을 지원하기 위해 급하게 만든 모델
- 장점
비교적 사용하기 쉬움, 비교적 양호한 처리량 ?
- 단점
많은 양의 비동기 소켓작업 시 IAsyncResult객체를 재사용 할 수 없음
이로 인해 연결이 많아질 수록 많은 메모리와 CPU사용량이 증가.
이전 기본코드에서 메시지 잘림현상만 해결하면 끝
SocketAsyncEventArgs model
Begin/End model의 단점들을 없애고 최적의 성능을 제공하기 위해 제공.
- 장점
최적의 처리량
- 단점
사용이 어렵다.
)
Asynchronous Programming Model (APM)
SocketAsyncEventArgs 클래스( . Framework 2.0 SP 1 이상부터 지원 )
하나의 비동기 작업을 대표
- ConnectAsync, AcceptAsync, SendAsync, ReceiveAsync 각각이 하나의 비동기 작업에 해당
비동기 작업 하나당 하나의 SocketAsyncEventArg가 필요
- 클라이언트
Connect, Send, Receive 를 위한 최소 3개의 SocketAsyncEventArgs가 필요
- 서버
Accept하나, 클라이언트 소켓별 Receive,Send 2개 필요
최대 클라이언트 연결을 100개로 제한하면 SocketAsyncEventArgs객체가 201개 필요하다는 말.
주의점
하나의 비동기 Operation, Begin/End패턴에서 매번 IAsyncResult를 생성하는 문제점을 재사용통해서
해결 하지만 재사용에 관련된 코드는 직접 작성해야한다.
SocketAsyncEventArgs을 매번 생성하면서 버퍼를 계속 할당하면 접속이 많은경우 GC가 감당하지 못
하고 OutOfMemoryExceptions을 발생 시키므로 서버가 감당할 수 있는 연결 개수*2 만큼 미리 정의
하고 버퍼의 영역도 미리 정의하여 버퍼를 재사용해야 한다.
참고
메시지 크기는 내부 Windows 소켓 서비스 공급자의 최대 메시지 크기를 초과하면 안됨
.Net framework 3.5 런타임에서 소켓(비동기 연결) 6만개이상의 연결된 소켓유지가 가능하다고 함.
SocketAsyncEventArgs Client Code(silverlight,C#)
Socket 및 SocketAsyncEventArg 초기화, 서버접속
//클라이언트 소켓생성
_endPoint = new DnsEndPoint("127.0.0.1", 4530); //Application.Current.Host.Source.DnsSafeHost
_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
ClientContext clientContext = new ClientContext();
//송신용
_sendAsyncEventArgs = new SocketAsyncEventArgs();
_sendAsyncEventArgs.UserToken = clientContext ;
_sendAsyncEventArgs.Completed +=
new EventHandler<SocketAsyncEventArgs>(SocketAsyncEvent_Completed);
//수신용
_receiveAsyncEventArgs = new SocketAsyncEventArgs();
_receiveAsyncEventArgs.SetBuffer(clientContext.receiveBufferManager.Buffer, 0
, clientContext.receiveBufferManager.Buffer.Length);
_receiveAsyncEventArgs.UserToken = stateObject;
_receiveAsyncEventArgs.Completed +=
new EventHandler<SocketAsyncEventArgs>(SocketAsyncEvent_Completed);
//연결용
SocketAsyncEventArgs connectAsyncEventArg = new SocketAsyncEventArgs();
connectAsyncEventArg.RemoteEndPoint = _endPoint;
connectAsyncEventArg.Completed += new
EventHandler<SocketAsyncEventArgs>(SocketAsyncEvent_Completed);
//연결
bool stateConnect = _clientSocket.ConnectAsync(connectAsyncEventArg);
SocketAsyncEventArgs Client Code(silverlight,C#)
SocketAsyncEvent_Completed()
//단순히 Operation별로 분기
private void SocketAsyncEvent_Completed(object sender, SocketAsyncEventArgs args)
{
switch (args.LastOperation)
{
case SocketAsyncOperation.Connect:
Connect_Completed(args);
break;
case SocketAsyncOperation.Receive:
Receive_Completed(args);
break;
case SocketAsyncOperation.Send:
Send_Completed(args);
break;
default:
throw new Exception("Invalid operation completed");
}
}
SocketAsyncEventArgs Client Code(silverlight,C#)
접속완료 - Connect_Completed()
//단순히 수신시작 호출
private void Connect_Completed(SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
Dispatcher.BeginInvoke(() =>
{
txtStatus.Text = "정상적으로 서버에 접속되었습니다.";
}
);
ReceiveAsync(args);
}
else
{
Dispatcher.BeginInvoke(() =>
{
System.Windows.Browser.HtmlPage.Window.Alert(args.SocketError.ToString());
});
}
}
SocketAsyncEventArgs Client Code(silverlight,C#)
수신시작 / 완료
//수신시작
void ReceiveAsync(args)
{
//수신용 SocketAsyncEventArgs를 이용하여 ReceiveAsync 호출
bool willRaiseEvent = _clientSocket.ReceiveAsync(_receiveAsyncEventArgs);
if (!willRaiseEvent) //동기식으로 끝난경우 .Completed가 호출되지 않으므로 수동으로 호출
{
Receive_Completed(_receiveAsyncEventArgs);
}
}
//수신완료
private void Receive_Completed(SocketAsyncEventArgs args)
{
if (args.BytesTransferred <= 0) return;
ReceiveBufferManager receiveBufferManager
= (args.UserToken as ClientContext).receiveBufferManager;
receiveBufferManager.Offset += args.BytesTransferred;
}
if (receiveBufferManager.IsMessageReceived())
{
ProcessPacket(receiveBufferManager.Buffer);
receiveBufferManager.AdjustBuffer();
}
else
{ //증가된 Offset에서 버퍼끝까지 다시 세팅 ( Buffering 효과 )
args.SetBuffer(receiveBufferManager.Offset, receiveBufferManager.Remaining)
}
ReceiveAsync(args); //계속 수신
SocketAsyncEventArgs Client Code(silverlight,C#)
송신 / 송신완료 – 수신과 달리 송신은 보내는 데이터 길이를 정확히 명시.
public void SendMessage(string message)
{
byte[] byteMessage = TalkPacketHelper.ToByte(message);
SendBufferManager sendBufferManager
= (_sendAsyncEventArgs.UserToken as ClientContext).sendBufferManager;
if (!sendBufferManager.IsMessageSendable(byteMessage.Length)) return;
_sendAsyncEventArgs.SetBuffer(sendBufferManager.Buffer, sendBufferManager.Offset
, byteMessage.Length);
Array.Copy(byteMessage, 0, sendBufferManager.Buffer, sendBufferManager.Offset, byteMessage.Length);
sendBufferManager.Offset += byteMessage.Length;
(_sendAsyncEventArgs.UserToken as ClientContext).sendEvent.WaitOne(); //여러 번 send시 처리
SendAsync(_sendAsyncEventArgs);
}
//송신시작
public void SendAsync(SocketAsyncEventArgs args)
{
(_sendAsyncEventArgs.UserToken as ClientContext).sendEvent.Reset();
if (!_clientSocket.SendAsync(_sendAsyncEventArgs))
Send_Completed(_sendAsyncEventArgs);
}
//송신완료
private void Send_Completed(SocketAsyncEventArgs args)
{
if (args.SocketError != SocketError.Success) return;
if (args.BytesTransferred < args.Count) //덜 보냈을 때
{
args.SetBuffer(args.BytesTransferred, args.Count - args.BytesTransferred);
SendAsync(args);
}
}
(args.UserToken as ClientContext).sendEvent.Set();
SocketAsyncEventArgs Client Code(silverlight,C#)
버퍼관리 및 조각메시지 처리
ReceiveBufferManager주요코드
// 하나의 패킷이상 수신 시 true리턴
public bool IsMessageReceived()
{
if (_offset < TalkPacketHelper.headerLength) //header length는 정수길이(4)
return false;
int sizeToRecieve = BitConverter.ToInt32(this._buffer, 0);
if ((_offset - TalkPacketHelper.headerLength) < sizeToRecieve)
return false;
}
return true;
// 메시지 처리 후 메시지만큼 지우고 이후의 데이터를 처음으로 shift
public void AdjustBuffer()
{
int messageSize = BitConverter.ToInt32( this._buffer, 0);
int lengthToCopy = _offset - TalkPacketHelper.headerLength – messageSize;
Array.Copy(this._buffer, _offset, this._buffer, 0, lengthToCopy);
_offset = lengthToCopy;
}
SocketAsyncEventArgs Server Code(silverlight,C#)
서버의 경우도 SocketAsyncEventArgs동작방식은 클라이언트와 동일.
접속 클라이언트들에 대해 SocketAsyncEventArgs매핑과 각 args들의 버퍼관리가 핵심
코드는 너무 길어 생략(소스참고, 아래 MSDN소스를 참고하여 만든 소스임.)
http://msdn2.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx
구현참고소스
http://msdn.microsoft.com/en-us/magazine/dd315415.aspx#id0090036
[ 주요 코드설명 ]
1. 최대 클라이언트 접속 개수 * 2(송신/수신용) 만큼
- 버퍼(this.buffer)를 할당 ( Int32.MaxValue * clientCount * 2 ) – ServerBufferManager에 존재
- SocketAsyncEventArgs를 생성, 버퍼를 할당하여 SocketAsyncEventArgsPool에 Push
Args하나(비 동기 작업)가 가지는 버퍼의 크기는 Int32.MaxValue가 된다.
ex) args.SetBuffer(this.buffer, this.currentIndex, TalkPacketHelper.maxBufferLength);
이렇게 SetBuffer를 하고나면 args의 Offset이 결정됨. 읽기전용이라 수동으로 세팅못함
2. 클라이언트 접속 시
SocketAsyncEventArgs receiveEventArgs = socketAsyncEventArgsPool.Pop(); //pop 해서 사용
SocketAsyncEventArgs sendEventArgs = socketAsyncEventArgsPool.Pop();
ClientContext clientContext = new ClientContext();
clientContext.receiveBufferManager
= new ReceiveBufferManager(serverBufferManager, receiveEventArgs.Offset);
clientContext.sendBufferManager
= new SendBufferManager(serverBufferManager, sendEventArgs.Offset);
receiveEventArgs.UserToken = clientContext;
receiveEventArgs.AcceptSocket = args.AcceptSocket;
clientContext.receiveAsyncEventArgs = receiveEventArgs;
sendEventArgs.UserToken = clientContext;
sendEventArgs.AcceptSocket = args.AcceptSocket;
clientContext.sendAsyncEventArgs = sendEventArgs;
ReceiveAsync(receiveEventArgs); //수신Args를 넘겨서 수신시작
Server 버퍼및 SocketAsyncEventArg구성
전역 버퍼 : 단순히 byte[Int32.MaxValue * clientCount * 2]
32k단위로 Args에 할당됨
단위 Buffer
Int32.MaxValue(32k)
Client
접속
1:1
Pool : 버퍼가 할당된 Args들을 관리
SocketAsyncEvnetArgs
args = Pool.Pop()을 두번하여 송/수신용
으로 사용
단위버퍼는 Args에 매핑되어 사용되므로 Args역할에 따라 송신/수신으로 이용된다.
클라이언트 접속이 종료 될 때 사용 중이던 송/수신 Args는 Pool.Push(args); 하여 재사용
동기화 객체들
특정자원(들)에 대한 스레드들의 동시 접근으로 인한 문제를 해결하기 위해 운영체제가 제공.
동기화 객체가 신호상태 일 때 임의의 스레드가 특정자원에 접근가능.
1. Critical section
동일 프로세스 내에서 동기화(보통 스레드 간 자원 동기화)에 사용, 유저모드 동기화객체
EnterCriticalSection함수(non-signal) , LeaveCriticlaSection함수(signal)
닷넷
- Monitor.Enter(), Monitor.Exit() , 닷넷에서는 TryEnter()도 제공하여 접근불가 시 코드작성가능
- lock() { … } 문( 단, lock문은 reference type에만 스레드 동기화지원 )
2. Mutex
크리티컬 섹션보다 느리지만 프로세스 경계를 넘어 스레드간 동기화가 가능, 커널모드 동기화 객체
보통 new 시 신호상태로 생성되며 Wait함수(non-signal), ReleaseMutex함수( signal)
닷넷
- Mutex, AutoResetEvent, ManualResetEvent
3. Semaphore
동일 자원이 N개 존재 할 때 N개의 스레드에게 자원을 분배 할 때 사용, 커널모드 동기화 객체
Wait함수( N = N – 1 ) , ReleaseSemaphore함수( N = N + 1)
N == 0 ? Non-signal : signal
닷넷
- Semaphore
기타, Interlocked, ReadWriterLock 등의 동기화 클래스들도 존재.
참고, 쓰레드도 동기화 객체로 사용될 수 있다.(쓰레드가 종료 시 set의 효과)
.NET thread pool
프로세스당 하나가 존재하며 프로세스 내에서 생성, 소멸되는 모든 스레드를 관리.
스레드를 재사용
최소,최대 스레드 개수관리
[ 스레드 풀이 관리하는 스레드 타입 ]
1. Worker Thread
- ThreadPool.QueueUserWorkItem() 호출로 생성되는 일반적인 작업스레드
- 디폴트 최대 500개까지 생성가능, ThreadPool.SetMaxThreads으로 최대 생성가능 스레드 개수 조절가능
2. Completion Port Thread
- 스레드풀은 정확히 하나의 IOCP ( I/O Completion port)를 가짐. (IOCP는 입출력 큐)
- 비동기작업(asynchronous I/O operations)시 IOCP가 signal되기를 기다리는 스레드
- 디폴트 최대 1000개의 IOCP스레드 생성가능, ThreadPool.SetMaxThreads 메소드로 조절가능
- CPU개수보다 적게 설정하면 안됨.
3. Wait Thread
4. Timer Manager Thread
3,4는 스레드 풀이 내부적으로 사용하는 스레드
[ 참고 : RegisterWaitForSingleObject 메소드 ]
public static RegisteredWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject, WaitOrTimerCallback callBack, Object state,
int millisecondsTimeOutInterval, bool executeOnlyOnce
)
- waitObject(동기화객체)가 set or signal되기를 기다리는 callBack을 등록하는 메소드
- set 시 Wait Thread가 IOCP Thread Pool에서 등록된 callBack(풀에 스레드로 등록됨)들 중 하나를 작동.
- 작업자 스레드와 IOCP스레드 개수는 다음함수를 통해 알 수 있다.
int workerThreads, completionPortThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
실버라이트/플랙스 소켓통신
[ Flex Socket ]
- 소켓서버가 클라이언트(.swf)에서 최초 접속 시 보낸 <policy-file-request/>를 받으면
crossdomain.xml의 내용을 send해주어야 한다.
- BIG_ENDIAN (default)
닷넷서버와 통신 시 반드시 ByteArray. endian = Endian.LITTLE_ENDIAN;
[ Silverlight Socket ]
- 비동기 소켓만지원
- 943port에 인증용 소켓서버를 만들어야 한다.(필수)
클라이언트에서 접속 시 서버에게 <policy-file-request/>를 자동으로
보내면 서버는 클라이언트에게 clientaccesspolicy.xml 의 내용을 전송
- 포트범위 제한
4502-4534 범위의 포트만 사용
- LITTLE_ENDIAN
[ 참고 ]
flex와 .net서버간 통신 시 닷넷의 char타입이 flex에는 존재하지 않음.
String.charCodeAt()메소드를 통해 스트링의 특정 문자코드값을 얻을 수 있음.
Endian관련내용 - http://en.wikipedia.org/wiki/Endianness
소켓통신 모델들(C++)

Select 모델

WSAAsyncSelect

WSAEventSelect

Overlapped
- Event
- CallBack
- IOCP
Select 모델


절차
1. 소켓을 non-blocking모드로 전환
2. 소켓셋 초기화
ex) FD_ZERO( &rset )
3. 소켓 셋에 소켓을 넣는다. 최대 개수는 FD_SETSIZE(64) 즉, 동시에 처리가능개수가 64개라는 의미.
ex) FD_SET( listen_sock, &rset );
4. select() 함수를 호출
- 소켓 셋에 포함된 소켓에 대해 해당 작업을 할 수 있을 때 까지 대기
- 소켓 셋에는 준비가 된 소켓만 남고 나머지는 모두 제거
ex) select( 0, &rset, &wset, NULL, NULL )
5. select() 함수가 리턴 하면 소켓 셋에 남아있는 소켓들에 대해 적절한 처리
ex) FD_ISSET( listen_sock, &rset );
6. 2~5를 반복한다.
특징
멀티스레드를 사용하지 않고도 여러 개의 소켓을 처리
모든 소켓은 넌 블로킹 소켓임에도 불구하고 CPU 사용률이 그다지 높지 않다
각 소켓에 필요한 정보를 관리는 애플리케이션이 구현해야 한다
윈도우 및 유닉스에서도 사용 할 수 있다.(이식성이 좋다)
참고
소스
main.cpp.txt
WSAAsyncSelect 모델



비동기적으로 소켓이벤트를 윈도우 프로시져를 통해 처리
절차
1. 호출 시 non-blocking으로 자동전환
2. WSASyncSelect() 함수를 통해 소켓 수신이벤트를 위한 윈도우와 처리할 네트워크 이벤트를 등록
ex) retValue = WSAAsyncSelect(g_ListenSocket, hWnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE);
3. 등록한 수신 이벤트가 발생하면 윈도우 메세지가 발생하고 윈도우 프로시저가 호출됨
이때 accept() 함수가 리턴하는 소켓은 listening socket과 동일한 속성을 가지지만,
accept = FD_READ | FD_WRITE 이벤트를 처리하지 않는다.
accept 가 리턴하는 소켓이 FD_READ | FD_WRITE 이벤트를 처리 하도록
다시 WSAAsyncSelect()함수를 호출하여 속성 변경한다.
ex) WSAAsyncSelect(clientSocket, hWnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);
4. 윈도우 프로시저에서는 받은 메세지의 종류에 따라 소켓 메시지(이벤트)의 경우 적절한 처리
ex) FD_READ이면 버퍼채운 후 PostMessage(hWnd, WM_SOCKET, wParam, FD_WRITE);
특징
윈도우 메세지 형태로 소켓과 관련된 네트워크 이벤트를 처리
소켓의 이벤트(메시지)가 윈도우 프로시저로 전달 되므로 멀티스레드를 사용하지 않아도 됨
(소켓관련 메세지 + 다른 모든 윈도우 메세지 => 서버 성능이 저하)
WSAEWOULDBLOCK 오류코드가 발생 할 수도 있으므로 체크필요
select 모델과 마찬가지로 소켓 정보를 애플리케이션이 관리(윈도우기반)
동시처리 소켓의 최대 개수의 제약은 없다.
참고
소스
Server.cpp
WSAEventSelect모델



이벤트 객체를 통해 네트워크 이벤트를 감지
절차
1. 함수 호출 시 non-blocking으로 자동전환
2. 소켓을 생성할 때 마다 WSACreateEvent() 함수로 소켓에 대응되는 이벤트 객체 생성(1:1)
3. WSAEventSelect() 로 소켓과 이벤트 객체를 매핑, 처리할 네트워크 이벤트 등록
ex) listenSocket에는 FD_ACCEPT | FD_CLOSE 이벤트만 매핑
WSAEventSelect(listenSocket, g_aEventArray[g_nTotalSockets - 1], FD_ACCEPT | FD_CLOSE);
4. WSAWaitForMultipleEvents() 로 이벤트 객체가 신호상태가 되기를 기다림.
5. 4가 리턴시 WSAEnumNetworkEvents() 함수로 네트워크 이벤트를 적절한 처리
ex) if(networkEvents.lNetworkEvents & FD_ACCEPT)
// accept처리
if((networkEvents.lNetworkEvents & FD_READ) || (networkEvents.lNetworkEvents & FD_WRITE))
// 읽기 쓰기처리
특징
소켓에 대해 이벤트 객체를 생성하여, 이 객체를 관찰함으로써 멀티스레드를 사용하지 않음.
Select, WSAAsyncSelect과 마찬가지로 소켓 정보를 애플리케이션에서 관리
Select와 같이 동시에 처리 할 수 있는 최대 소켓 개수가 64개 ( 관찰 할 수 있는 이벤트 객체 = 64 )
참고
소스
WSAEventSelect_Model.cpp
Overlapped 모델



윈도우 운영체제에서 고성능 파일 입출력을 위해 제공하던 기능을 소켓으로 확장
동기 입출력 : 운영체제의 I/O동안 애플리케이션 코드가 정지?(확인필요).
Select, WSAAsyncSelect, WSAEventSelect 모델이 해당.
비동기 입출력( Overlapped모델을 이용 )
WSAOVERLAPPED구조체를 이용하여 비동기 I/O정보를 운영체제에 전달하거나 받을 때 사용
Overlapped 모델 (Event)



소켓 입출력 작업이 완료시 운영체제는 소켓과 연결된 이벤트 객체를 신호상태로 바꾸면 이 이벤트
객체를 관찰함으로써 작업 완료 사실을 감지(WSAEventSelect모델과 이벤트를 관찰하는점은 동일)
절차
1. 비동기 입출력을 지원하는 소켓을 생성(accept용)
ex) WSASocket( PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED );
2. Accept시 연격된 클라이언트에 대응되는 이벤트 객체를 WSACreateEvent()으로 생성
ex) WSAEVENT event = WSACreateEvent();
3. WSAOVERLAPPED구조체에 생성한 이벤트객체를 대입
ex) overlapped.hEvent = event;
4. 비동기 입출력을 지원하는 소켓 함수를 호출, 이때 함수의 매개변수로 WSAOVERLAPPED를 넘김
ex) WSARecv( hSendSock, &dataBuf, 1, &dwRecvBytes, &dwFlags, &overlapped, NULL )
5. WSAWaitForMultipleEvents()를 호출하여 운영체제가 비동기 I/O완료 통보를 기다린다.
6. 5번에서 통보시, WSAGetOverlappedResult()를 호출하여 비동기 입출력 결과를 확인
수신한 데이터를 처리 후 WSASend를 호출하여 계속 클라이언트와 통신.
7. 새로운 클라이언트 접속 시 2~6를, 연결된 소켓의 경우 4~6이 반복되게 구현
특징
소켓에 대해 이벤트 객체를 생성하여, 이 객체를 관찰함으로써 멀티스레드를 사용하지 않음.
동시에 처리 할 수 있는 최대 소켓 개수가 64개 ( 관찰 할 수 있는 이벤트 객체 = 64 )|
운영체제에게 비동기I/O를 요청하므로 동기I/O보다 성능이 좋음.
참고
소스
Server_Event.cpp
Overlapped 모델 (Callback)

소켓 I/O작업 완료 시 운영체제는 소켓과 연결된 콜백(callback)을 호출하여 데이터작업

절차
1. 비동기 입출력을 지원하는 소켓을 생성(accept용)
ex) WSASocket( PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED );
2. 비동기 입출력을 지원하는 소켓 함수를 호출
이때 함수의 매개변수로 WSAOVERLAPPED와 콜백함수(포인터)를 넘김
ex) WSARecv(sock, &wsabuf, 1, &dwRecvBytes, &dwFlags, overlapped, IOCompletionRoutine);
3. 스레드를 alertable wait상태로 만든다? ( 마지막 파라미터에 true)
굳이 필요없는 단계?
ex) WSAWaitForMultipleEvents( 1, &event, FALSE, WSA_INFINITE, TRUE);
4. 2번과 같이 넘기고나면 수신 시 자동으로 IOCompletionRoutine 이 호출.
이 루틴내에서 수신과 송신코드를 만들어 주면됨.

특징
비동기 입출력을 통해 성능이 뛰어나다.
APCQ (Asynchronous Procedure Call Queue)를 이용하므로 동시처리 소켓의 개수제약이 없다.
운영체제에게 비동기 I/O를 요청하므로 동기I/O보다 성능이 좋음.
참고
소스
Server_Completion.cpp
비교 : WSAWaitForMultipleEvents
없이도 잘 돌아가는듯???
Main.cpp
Overlapped 모델 (Callback)

APC Queue ( Asynchronous Procedure Call Queue )

Alertable wait
비동기 입출력 결과 저장을 위해 운영체제가 각 스레드마다 할당하는 메모리 영역
비동기적으로 호출되어야 할 함수들과 매개변수 정보가 저장
모든 스레드는 자신만의 APC Queue라는 것을 가짐( 스레드 별로 독립적 )
스레드가 알림가능(Alertable wait) 상태에 놓이게 될 때에 호출
스레드의 대기 상태(wait state) 중 하나
아래 함수의 마지막 파라미터에 True를 넘기면 alertable wait상태로 됨
ex) WSAWaitForMultipleEvents( 1, &event, FALSE, WSA_INFINITE, TRUE);
비동기 I/O작업을 호출한 스레드가 이 상태에 있을 경우 I/O작업을 마친 운영체제는 APCQ에 저장된
정보(콜백함수,파라미터)를 이용하여 호출한 스레드에서 완료루틴을 호출하게 한다.
Overlapped 모델 (IOCP)

IOCP ( I/O Completion Port )
Win32 커널 오브젝트의 하나
적용범위- File, Named Pipe, Socket 등 다양
주요기능 - IO 감시 및 완료에 대한 통지(Notify)
재사용이 가능한 스레드 풀을 유지(운영체제가 알아서함)
- 생성 ( CreateIoCompletionPort함수호출 )
Device List(hDevice/dwCompletionKey쌍)라는 자료구조 생성
hDevice에는 연결된 소켓핸들, dwCompletionKey에는 클라이언트 식별가능한 구조체를 넣는다.
ex) CreateIoCompletionPort( hSocket, hIOCP, (DWORD)CompletionKey, 0 ); 를 한번더 호출함.

IOCQ( I/O Completion Queue)

APCQ와 비교
운영체제가 I /O완료 시 device list를 검색하여 완료정보를 만들어 넣는 큐.
dwByteTransfer | dwCompletionKey | pOverlapped | dwError 의 단위정보를 가짐
GetQueuedCompletionStatus 라는 API 함수를 이용 이 데이터를 가져와서 처리
- APCQ는 비동기I/O함수를 소유 스레드만 접근가능 한 반면에 IOCP는 여러스레드에서 접근가능
- IOCP는 생성함수로 여러 개 생성가능(보통 하나이용)
- APCQ가 IOCompletionRoutine과 파라미터를 큐에 넣어 때가되면 호출하는 반면
IOCP는 여러스레드가 이 포트(큐?)를 관찰하게 하는 구조이다.
Overlapped 모델 (IOCP)


절차
1. 전역 IOCP생성 및 필요한 만큼 스레드 생성(CPU개수 * 2개가 최적?)
ex) g_hIOCompletionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0 );
ex) g_phWorkerThreads[ii] = CreateThread(0, 0, WorkerThread, (void *)(ii+1), 0, &nThreadID);
2. 비동기 입출력을 지원하는 소켓을 생성(accept용)
ex) listenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)
3. 클라이언트 접속 시 CreateIoCompletionPort()를 다시 호출하여 클라이언트 소켓과 연결 및 최초 수신호출
데이터 송 수신 시 로컬 큐에 저장되게 되는지 확인??? WorkerThread에서 송/수신을 담당???
WSAOVERLAPPED구조체를 이용
ex) CreateIoCompletionPort((HANDLE)socket, g_hIOCompletionPort, (DWORD)pClientContext, 0);
ex) WSARecv(socket, p_wbuf, 1, &dwBytes, &dwFlags, &overlapped, NULL);
or PostQueuedCompletionStatus(m_hCompletionPort, 0, (DWORD) pContext, &pOverlapBuff->m_ol);
4. WorkerThread에서 GetQueuedCompletionStatus()함수로 OS의 I/O작업 완료정보를 저장하는 IOCP를 감
시
ex) GetQueuedCompletionStatus(g_hIOCompletionPort,&dwBytesTransfered,(LPDWORD)&lpContext
,&pOverlapped, INFINITE);
특징
모든 소켓 입출력 모델 중 가장 뛰어난 성능을 제공.
참고소스2링크 : http://www.codeproject.com/KB/IP/iocp_server_client.aspx
참고소스 1
ServerIOCP.cpp
참고소스 2
Iocps.cpp
Overlapped 모델 (IOCP)

넌 블로킹(Non-Blocking) 송/수신 시 메시지 조각발생 처리 필요

패킷 순서제어의 필요
네트워크 트레픽이 심할 경우 메시지의 잘림(조각 메시지)에 대한 처리가 필요
IOCP도 소켓 수준에서의 송/수신은 패킷을 순서대로 처리함(TCP)
순서의 어긋남은 수신된 내용을 큐에서 가져와서 처리하는 스레드에서 발생(스레드 스케쥴링 이슈)
송신/수신 모두 큐를 이용하므로 송신버퍼,수신버퍼 순서제어가 필요.
작업자 스레드가 2개이상이면서 pending read, CPU개수가 많을 수록 빈번
발생경우)
1번 스레드(1번패킷 처리)와 2번 스레드(2번패킷 처리)가 경쟁적으로 돌고 있을 때 1번이 먼저 시작하
더라도 CPU시간을 나눠서 동작하므로 중간에 2번이 실행을 시작했다가 1번이 완료 되기 전에 먼저
완료되는 경우는 얼마든지 발생 가능하다.
용어) Pending read
비 동기 소켓 수신 시 WSARecv()를 여러 번 호출하여 여러 개의 pending read를 하게 함.
패킷 순서제어
수신의 경우만( 송신의 경우 송신버퍼를 두고 수신의 반대로 하면됨)
WSARecv 호출 시마다 버퍼(CIOCPBuffer)에 0부터 순서를 매겨둔다.( 버퍼에는 순서대로 들어감을 명심!!)
Ex) pBuff->SetSequenceNumber(pContext->m_ReadSequenceNumber++);
WSARecv(…..); // IOCP queue에 insert…
ClentContext * lpClientContext; // 소켓 별 context
CIOCPBuffer *pOverlapBuff; // 송/수신버퍼
IOWorkThread
// address, type, field를 넘겨 type 객체(field를 포함하는)의 주소를 가져옴
pOverlapBuff=CONTAINING_RECORD(lpOverlapped, CIOCPBuffer, m_ol);
pThis->ProcessIOMessage(pOverlapBuff, lpClientContext, dwIoSize);
GetNextReadBuffer(ClientContext *pContext, CIOCPBuffer *pBuff)
pContext-> Lock();
pContext->m_CurrentReadSequenceNumber; 현재처리할 순번(0에서 시작 처리 할 때마다 증가)
처리 시 pContext->m_CurrentReadSequenceNumber 가 0번인데 현재스레드에서 읽은 버퍼의 번
호가 1번인 경우, context.m_ReadBufferMap[1] 에 읽은 버퍼를 넣어두고 m_ReadBufferMap[0]번을
읽어와서 처리한다. 0번도 없으면 읽기 작업 계속진행
pContext-> UnLock();
조각패킷 처리(헤더)
Buffer partialPacket; //소켓 context에 저장
data(socket receive buffer) // 수신된 소켓의 data
headerLength = 4, packetSize = 512
패킷처리시작
OS callback or Event Set
received socket data
exist ?
Y
소켓수신
N
Buffer partialMsg = partialPacket;
Buffer.length에서 length는 할
당된 버퍼의 총 길이가 아닌
수신된 길이를 의미
Y
partialMsg exist ?
N
Y
소켓버퍼처리
partialMsg.length < headerLength
N
Y
nNeedForHeader = headerLength – partialMsg .length;
nNeedForHeader > data.length
Y
partialMsg.addAndFlush(data, data.length);
partialMsg.addAndFlush(data, nNeedForHeader); // 헤더 완성에 필요한양
조각내용처리
N
조각패킷 처리(내용)
계속
nDataSize = 현재패킷의 데이터 길이(헤더의 값);
nNeedForData = nDataSize – (partialMsg .length – headerLength) ;
nNeedForData <= data.length
N
Y
partialMsg.addAndFlush(data, nNeedForData);
완성된 partialMsg처리
partialPacket = NULL
partialMsg.addAndFlush(data, data.length);
패킷 전체 처리
소켓수신
소켓버퍼 패킷 처리
계속
Bool isDone = false;
isDone != true
Y
data.length >= headerLength
N
Y
nDataSize = 현재패킷의 데이터 길이(헤더의 값);
nDataSize == (data.length – headerLength)
N
Y
패킷처리
done = true;
Packet 길이만큼 잘라 패킷처리
done = false;
nDataSize < (data.length – headerLength)
N
Y
nDataSize > (data.length – headerLength)
Y
partialPacket = data;
done = true;
소켓수신