본문 바로가기

IndianPoker

[06] TCP, 클라이언트 만들기

[05]에서 만든 서버 쪽 기초 내용은 아래와 같다.

using UnityEngine;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

public class TCPTutorial : MonoBehaviour
{
    public const int BUFFER_SIZE = 1024;

    private Socket serverSocket;
    private IPAddress ipaddress;
    private string ipaddressStr;
    private int port;
    private IPEndPoint endpoint;

    //Socket -> Bind -> listen -> accept -> receive -> send -> receive -> close
    //Creat Socket
    public void StartServer()
    {
        //서버 소켓 생성
        serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        //IP 주소와 포트 설정. 그리고 바인드
        SetIPAndBind(ipaddressStr, port);
        //Listen, 클라이언트 연결 요청 대기.
        ListenSocket();
        //ClientSocket to Thread. and HandleClient.
        while (true)
        {
            Socket clientSocket = serverSocket.Accept(); //Todo, 비동기적 메서드인 BeginAccept와 비교
            Debug.Log("새 사용자 접속!");

            Thread clientThread = new Thread(() => HandleClient(clientSocket));
            clientThread.Start();
        }

    }
    // IP 주소와 포트 설정 + Bind. Socekt을 ipaddress와 port를 사용해서 바인딩
    public void SetIPAndBind(string ipAddressStr, int port)
    {
        ipaddress = IPAddress.Parse(ipAddressStr);
        endpoint = new IPEndPoint(ipaddress, port);
        serverSocket.Bind(endpoint);
    }
    //listen, 클라이언트를 기다린다.
    private void ListenSocket()
    {
        serverSocket.Listen(5); // 대기 큐 크기를 지정
        Debug.Log("서버 시작. 접속을 기다립니다...");

    }
    private void HandleClient(Socket clientSocket)
    {
        // 버퍼 생성, 읽을 버퍼 개수
        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead; 

        while ((bytesRead = clientSocket.Receive(buffer)) > 0)
        {
            //버퍼 인코딩, 클라이언트로부터 데이터 수신 (바이트 -> String)
            string data = Encoding.ASCII.GetString(buffer, 0, bytesRead);
            Debug.Log("Client Say... : " + data);

            // 데이터 가공
            string response = "Server response: " + data;

            //버퍼 디코딩
            byte[] responseBytes = Encoding.ASCII.GetBytes(response);
            clientSocket.Send(responseBytes);
        }
        clientSocket.Close();
        Debug.Log("Client 연결 끊김.");
    }

}

서버는 준비되었지만, 들어 올 클라이언트가 없다. 클라이언트를 만들어보자.

(클라이언트의 과정은 Socket -> connect -> send -> receive -> close 순서) 

어떤 서버에 접속해야하는지 정보가 필요하다. Server의 IP와 포트 번호가 있어야 접속할 수 있다.

접속을 시도하는 메소드를 하나 만들어보자.

    public void StartClient(string serverIP, int serverPort)
    {
    	IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);
    }

클라이언트는 이 메소드를 사용하여 서버에 접속할 예정이다. 그러기 위해서 IPEndPoint를 입력받은 serverIP와 포트로 세팅한다. 다음의 내용으로는 세팅한 IPEndPoint를 통해 접속하는 것이지만, 문제가 있다.

 

바로 네크워크 접속은 다양한 원인에 의해 실패할 수 있다는 것이다. 시간이 오래 걸려서 일 수도 있고, 네트워크 속도가 느려서 일 수도 있다. 즉, 실패에 대한 예외 처리가 필요한 시점이다. 이걸 try-catch 구문을 사용해서 만들자.

    public void StartClient(string serverIP, int serverPort)
    {
        try
        {
            IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);
			//Socket -> connect -> send -> receive
        }
        catch (Exception e)
        {
        	//에러 원인 출력
            Console.WriteLine("Error: " + e.Message);
        }
    }

try - catch 구문을 통해서 문제가 일어나면 그 에러의 원인을 출력하고 프로그램은 계속 돌아간다.

이제 잘못될 경우를 적었으니, 제대로 진행되는 경우인 소켓 -> 연결 부분을 만들 차례다. 

 

※Using 구문을 사용해야하는가?

using (Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) 을 사용하여 리소스 관리를 해줘야 한다는 글이 있다. 일단은 잘 모르기도 하고, 튜토리얼 느낌으로 진행하고 있기에, 효율과 안정성 관련된 using을  사용하지는 않겠다.

 

소켓 -> 연결로 이어지는 부분과 그 이후들은 전부 서버에서 했던 것과 똑같다. 소켓 명만 바꿔주면 바로 된다.

        //세팅과 소켓 생성
        IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);
        Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientSocket.Connect(serverEndPoint);
        try
        {
            //Connect Server
            clientSocket.Connect(serverEndPoint);
            Debug.Log("Connected to server.");

            //Data 인코딩, 전송
            byte[] data = Encoding.ASCII.GetBytes("Hello, Server!");
            clientSocket.Send(data);
            Debug.Log("Sent: Hello, Server!");

            //데이터 초기화, 데이터 받기
            data = new byte[1024];
            int bytesRead = clientSocket.Receive(data);

            //인코딩해서 받기
            string response = Encoding.ASCII.GetString(data, 0, bytesRead);
            Debug.Log("Received: " + response);
        }
        catch (Exception e)
        {
            Debug.Log("Error: " + e.Message);
        }

거의 완벽히 똑같다.

다음에는 유니티로 호환을 시켜볼 생각이다. 그리고 이 단점이 하나 있는데, 바로 한 번만 통신을 한다는 것이다. 계속해서 통신을 유지하면서 채팅을 서로 주고받을 수 있는 시스템을 유니티를 통해 만들어보자.