Socket 통신

대인공·2022년 8월 8일
0

Network

목록 보기
2/4
post-thumbnail

동기와 비동기

동기

: 메인 코드가 진행이 되고, 함수 호출을 하였을때 다음 코드는 진행이 중지되고 호출된 함수가 실행, 종료되면 나머지 코드가 실행되는것을 말한다. 즉, 한번에 한가지 일만 하는것이다.

비동기

: 메인 코드가 진행이 되고, 비동기 함수를 호출하였을때 코드의 진행이 멈춤 없이 진행된다. 이렇게 보면 메인 코드와 비동기 함수는 동시에 진행되는것 처럼 보이는데 실은 Thread처럼 진행된다. 즉, 작업이 완료되지 않아도 진행이 된다.

  • 서버를 비동기 서버로 만들면 접속요청 받는 일, 데이터 전송, 데이터 수신등 여러가지 일을 한번에 처리할 수 있다.

  • 비동기 함수
    : 일종의 함수 포인터(or delegate)라고 생각하면 된다. 어떠한 이벤트 발생시 호출되고 Thread처럼 실행된다.

    비동기 함수들은 선언시 매개변수로 (IAsyncResult ar)를 같이 선언 해주어야 하는데 해당 이벤트를 발생시킨 정보, 작업정보 등을 담고있다.



동기 구축

동기 서버

  • 구축 순서
  1. 서버 Socket 생성, 버퍼 생성
  2. 서버의 IP와 Port를 생성한 Socket과 바인딩
  3. 클라이언트의 연결요청을 대기
  4. 클라이언트의 연결요청이 있을시 연결후 새로 Socket생성
  5. 연결된 클라이언트와 데이터 송수신
  6. 연결이 종료되면 할당된 소켓 종료
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();
    }
}
  • 기본적인 동기 서버 구조이다.
    - 서버는 한번만 주고 받는 것이 아니라 클라이언트가 접속종료하지 않는 이상 항상 클라이언트와 데이터를 주고 받아야 한다.
    - 코드의 진행순서는 위에서 아래로 흐르는데 서버는 코드 진행 중간에도 클라이언트의 요청을 받아야 한다.
    이때 필요한게 비동기 서버이다.


동기 클라이언트

  • 구축 순서
  1. 서버와 연결할 Socket 생성, 버퍼 생성
  2. 바인딩된 Socket을 사용하여 서버에게 연결 요청
  3. 연결된 서버와 데이터 송수신
  4. 연결이 종료되면 소켓 종료
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를 사용한다.



비동기 구축

  • 비동기 서버 구축을 위한 사용되는 함수
  1. Socket.BeginAccrpt( AsyncCallback? callback, object? state );
    : 클라이언트가 접속하면 callback함수가 호출된다. state는 비동기 작업에 대해 추가정보를 넘겨주는것이다.

  2. - BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback? callback, object? state);
    - BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback? callback, object? state);
    : 데이터를 송수신하고 Callback함수가 호출된다. buffer, offset, size는 송수신할 Buffer, 최소, 최대를 기입하면 된다.

비동기 서버

  • 구축 순서
  1. 서버 Socket 생성, 버퍼 생성
  2. 서버의 IP와 Port를 생성한 Socket과 바인딩
  3. 클라이언트의 연결요청을 대기
  4. 클라이언트와 연결되면 BeginAccept 비동기 함수 실행
  5. 클라이언트와 연결되면 BeginReceive 비동기 함수 실행
  6. 클라이언트와 연결되면 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)함으로써 다른 일반 클라이언트와 처리시간을 비슷하게나마 할 수 있다.
  • 데이터를 처리할 Queue 추가
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();
            // 순차적으로 데이터 처리
        }
    }
}


비동기 클라이언트

  • 구축 순서
  1. 서버와 연결할 Socket 생성, 버퍼 생성
  2. BeginReceive 비동기 함수 실행
  3. 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();
			// 순차적으로 데이터 처리
        }
    }
}
profile
이제 막 시작하는 유니티 클라이언트

0개의 댓글