: 메인 코드가 진행이 되고, 함수 호출을 하였을때 다음 코드는 진행이 중지되고 호출된 함수가 실행, 종료되면 나머지 코드가 실행되는것을 말한다. 즉, 한번에 한가지 일만 하는것이다.
: 메인 코드가 진행이 되고, 비동기 함수를 호출하였을때 코드의 진행이 멈춤 없이 진행된다. 이렇게 보면 메인 코드와 비동기 함수는 동시에 진행되는것 처럼 보이는데 실은 Thread처럼 진행된다. 즉, 작업이 완료되지 않아도 진행이 된다.
- 서버를 비동기 서버로 만들면 접속요청 받는 일, 데이터 전송, 데이터 수신등 여러가지 일을 한번에 처리할 수 있다.
비동기 함수들은 선언시 매개변수로 (IAsyncResult ar)를 같이 선언 해주어야 하는데 해당 이벤트를 발생시킨 정보, 작업정보 등을 담고있다.
- 구축 순서
- 서버 Socket 생성, 버퍼 생성
- 서버의 IP와 Port를 생성한 Socket과 바인딩
- 클라이언트의 연결요청을 대기
- 클라이언트의 연결요청이 있을시 연결후 새로 Socket생성
- 연결된 클라이언트와 데이터 송수신
- 연결이 종료되면 할당된 소켓 종료
using System.Net;
using System.Net.Sockets;
class ServerSocketProgramming
{
Socket Server_SOCKET;
string Server_IP;
int Server_PORT;
byte[] SnedBuffer;
byte[] ReceiveBuffer;
void Awake()
{
// * 1
// 서버의 IP와 PORT 임의지정
Server_IP = "0.0.0.0";
Server_PORT = 0000;
Server_SOCKET = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SnedBuffer = new byte[128];
ReceiveBuffer = new byte[128];
// * 2
IPEndPoint Server_EndIP = new IPEndPoint(IPAddress.Parse(Server_IP), Server_PORT);
Server_SOCKET.Bind(Server_EndIP);
// * 3
Server_SOCKET.Listen(1000);
// * 4
Socket Client_SOCKET = Server_SOCKET.Accept();
// * 5 Sned
string sendData = "클라이언트에게 전송할 문자열 입력";
SnedBuffer = EncodingDefault.GetByte(sendData);
Client_SOCKET.Send(SnedBuffer);
Array.Clear(SnedBuffer, 0, SnedBuffer.Length);
// * 5 Receive
Client_SOCKET.Receive(ReceiveBuffer);
string ReceiveData = Encoding.Default.GetString(ReceiveBuffer);
Array.Clear(ReceiveBuffer, 0, .Length);
// * 6
Client_SOCKET.Close();
}
}
- 기본적인 동기 서버 구조이다.
- 서버는 한번만 주고 받는 것이 아니라 클라이언트가 접속종료하지 않는 이상 항상 클라이언트와 데이터를 주고 받아야 한다.
- 코드의 진행순서는 위에서 아래로 흐르는데 서버는 코드 진행 중간에도 클라이언트의 요청을 받아야 한다.
이때 필요한게 비동기 서버이다.
- 구축 순서
- 서버와 연결할 Socket 생성, 버퍼 생성
- 바인딩된 Socket을 사용하여 서버에게 연결 요청
- 연결된 서버와 데이터 송수신
- 연결이 종료되면 소켓 종료
using System.Net;
using System.Net.Sockets;
class ClientSocketProgramming
{
Socket Client_SOCKET;
string Client_IP;
int Client_PORT;
byte[] SnedBuffer;
byte[] ReceiveBuffer;
void Awake()
{
// * 1
Client_IP = "0.0.0.0";
Client_PORT = 0000;
Client_SOCKET = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SnedBuffer = new byte[128];
ReceiveBuffer = new byte[128];
// * 2
IPEndPoint Server_IP = new IPEndPoint(IPAddress.Parse(Client_IP), Client_PORT);
Client_SOCKET.Connect(Server_IP);
// * 3 Send
string strdata = "서버에게 전송할 문자열 입력";
byte[] SnedBuffer = Encoding.Default.GetBytes(strdata);
Client.Send(tmp);
Array.Clear(SnedBuffer, 0, SnedBuffer.Length);
// * 3 Receive
Client_SOCKET.Receive(ReceiveBuffer);
string Data = Encoding.Default.GetString(ReceiveBuffer);
Array.Clear(ReceiveBuffer, 0, ReceiveBuffer.Length);
// * 4
Client_SOCKET.Close();
}
}
서버와의 큰 차이는 없으나 Bind, Listen, Accept함수를 대신해 Connect를 사용한다.
- 구축 순서
- 서버 Socket 생성, 버퍼 생성
- 서버의 IP와 Port를 생성한 Socket과 바인딩
- 클라이언트의 연결요청을 대기
- 클라이언트와 연결되면 BeginAccept 비동기 함수 실행
- 클라이언트와 연결되면 BeginReceive 비동기 함수 실행
- 클라이언트와 연결되면 BeginSend 비동기 함수 실행
using System.Net;
using System.Net.Sockets;
class ServerSocketProgramming
{
Socket SOCKET;
string Server_IP;
int Server_PORT;
byte[] SnedBuffer;
byte[] ReceiveBuffer;
void Awake()
{
// * 1
Server_IP = "0.0.0.0";
Server_PORT = 0000;
SOCKET = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SnedBuffer = new byte[128];
ReceiveBuffer = new byte[128];
// * 2
IPEndPoint Server_EndIP = new IPEndPoint(IPAddress.Parse(Server_IP), Server_PORT);
SOCKET.Bind(Server_EndIP);
// * 3
SOCKET.Listen(1000);
// * 4
SOCKET.BeginAccept(AcceptUser, null);
}
// * 4
void AcceptUser(IAsyncResult ar)
{
SOCKET = Server_SOCKET.EndAccept(ar);
SOCKET.BeginReceive(ReceiveBuffer, 0, ReceiveBuffer.Length, SocketFlags.None, ReceiveCallBack, SOCKET);
}
// * 5
void ReceiveCallBack(IAsyncResult ar)
{
byte[] tmp = new byte[ReceiveBuffer.Length];
Array.Copy(ReceiveBuffer, tmp, ReceiveBuffer.Length);
Array.Clear(ReceiveBuffer, 0, ReceiveBuffer.Length);
SOCKET.BeginReceive(ReceiveBuffer, 0, ReceiveBuffer.Length, SocketFlags.None, ReceiveCallBack, SOCKET);
}
// * 6
void SendCallBack(IAsyncResult ar)
{
byte[] tmp = new byte[SnedBuffer.Length];
Array.Copy(SnedBuffer, tmp, SnedBuffer.Length);
Array.Clear(SnedBuffer, 0, SnedBuffer.Length);
SOCKET.BeginSend(SnedBuffer, 0, SnedBuffer.Length, SocketFlags.None, SendCallBack, SOCKET);
}
}
- BeginSned와 BeginReceive가 계속 자신을 호출하는 이유?
- 서버는 계속해서 클라이언트와 데이터를 주고 받아야 하기 때문.
- 기본적인 비동기 서버는 이렇게 구성된다. 하지만 이는 '1 : 1'통신만 가능하다. 왜냐하면 '1 : 다수' 통신을 하려면 여러 클라이언트로 부터 많은 데이터가 들어오는데 그것을 저장하고있는 것이 없기 때문이다. 여러 데이터를 순차적으로 처리하려면 자료구조인 Queue<byte[]>를 선언해주고 BeginReceive에서 데이터를 전송받으면Queue에 추가(enQueue)해준다. 이런식으로 구축하면 여러 클라이언트에게 데이터를 전송받아 순차적으로 처리할 수 있다.
- 주의해야 할점은 P2P방식에서는 한 클라이언트가 서버 역할을 같이 해준다는 것인데, 이 클라이언트의 데이터는 일반 클라이언트보다 비교적 빠르게 적용된다는 점이 있다. 왜냐하면 일반 클라이언트들은 데이터를 전송받아 적용하지만, 서버 역할을 하는 클라이언트는 전송받는다는 것이 없다는게 큰 차이가 있다. 이를 해결하기 위해 데이터를 전송할때 다시 Queue에 추가(enQueue)함으로써 다른 일반 클라이언트와 처리시간을 비슷하게나마 할 수 있다.
using System.Net;
using System.Net.Sockets;
class ServerSocketProgramming
{
Socket SOCKET;
string Server_IP;
int Server_PORT;
Queue<byte[]> queue;
byte[] SnedBuffer;
byte[] ReceiveBuffer;
void Awake()
{
Server_IP = "0.0.0.0";
Server_PORT = 0000;
SOCKET = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
queue = new Queue<byte[]>();
SnedBuffer = new byte[128];
ReceiveBuffer = new byte[128];
IPEndPoint Server_EndIP = new IPEndPoint(IPAddress.Parse(Server_IP), Server_PORT);
SOCKET.Bind(Server_EndIP);
SOCKET.Listen(1000);
SOCKET.BeginAccept(AcceptUser, null);
}
void AcceptUser(IAsyncResult ar)
{
SOCKET = Server_SOCKET.EndAccept(ar);
SOCKET.BeginReceive(ReceiveBuffer, 0, ReceiveBuffer.Length, SocketFlags.None, ReceiveCallBack, SOCKET);
}
void ReceiveCallBack(IAsyncResult ar)
{
byte[] tmp = new byte[ReceiveBuffer.Length];
Array.Copy(ReceiveBuffer, tmp, ReceiveBuffer.Length);
Array.Clear(ReceiveBuffer, 0, ReceiveBuffer.Length);
queue.Enqueue(tmp);
SOCKET.BeginReceive(ReceiveBuffer, 0, ReceiveBuffer.Length, SocketFlags.None, ReceiveCallBack, SOCKET);
}
void SendCallBack(IAsyncResult ar)
{
byte[] tmp = new byte[SnedBuffer.Length];
Array.Copy(SnedBuffer, tmp, SnedBuffer.Length);
Array.Clear(SnedBuffer, 0, SnedBuffer.Length);
queue.Enqueue(tmp);
SOCKET.BeginSend(SnedBuffer, 0, SnedBuffer.Length, SocketFlags.None, SendCallBack, SOCKET);
}
void Update()
{
if(queue.Count > 0)
{
byte[] tmp = queue.Dequeue();
// 순차적으로 데이터 처리
}
}
}
- 구축 순서
- 서버와 연결할 Socket 생성, 버퍼 생성
- BeginReceive 비동기 함수 실행
- BeginSend 비동기 함수 실행
```cs
using System.Net;
using System.Net.Sockets;
class ClientSocketProgramming
{
Socket Client_SOCKET;
string Client_IP;
int Client_PORT;
byte[] SnedBuffer;
byte[] ReceiveBuffer;
void Awake()
{
// * 1
Client_IP = "0.0.0.0";
Client_PORT = 0000;
Client_SOCKET = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SnedBuffer = new byte[128];
ReceiveBuffer = new byte[128];
// * 2
IPEndPoint Server_IP = new IPEndPoint(IPAddress.Parse(Client_IP), Client_PORT);
Client_SOCKET.Connect(Server_IP);
}
void SendCalBack(IAsyncResult ar) // SendBuffer를 enQueue해주는 이유는 다른 peer와 시간을 맞춰주기 위해서이다.
{
byte[] tmp = new byte[sendBuffer.Length];
Array.Copy(sendBuffer, tmp, sendBuffer.Length);
Array.Clear(sendBuffer, 0, sendBuffer.Length);
packetQueue.Enqueue(tmp);
Sock.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, ReceiveCalBack, Sock);
}
void ReceiveCalBack(IAsyncResult ar)
{
byte[] tmp = new byte[receiveBuffer.Length];
Array.Copy(receiveBuffer, tmp, receiveBuffer.Length);
Array.Clear(receiveBuffer, 0, receiveBuffer.Length);
packetQueue.Enqueue(tmp);
}
void SetPacket() // 전송할 패킷데이터 조합
{
// 전송할 데이터를 패킷화 한다.
Sock.BeginSend(sendBuffer, 0, sendBuffer.Length, SocketFlags.None, SendCalBack, Sock);
}
void Update()
{
if (//어떠한 이벤트 발생)
{
SetPacket();
}
if (packetQueue.Count > 0)
{
byte[] tmp = packetQueue.Dequeue();
// 순차적으로 데이터 처리
}
}
}