호우동의 개발일지

Today :

article thumbnail

서버


DNS 및 Socket 연결

string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress address = ipHost.AddressList[0];
IPEndPoint ipEndPoint = new IPEndPoint(address, 7777); // 최종주소


Socket socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);


socket.Connect(ipEndPoint); // 해당 ip주소로 연결 요청

Console.WriteLine($"Connect To {socket.RemoteEndPoint.ToString()}");

DNS

  • www.google.com → 112.123.123.1 이런 식으로 내부적으로 ip주소로 변환되는 것
  • 코딩할 때 서버 주소를 하드코딩 하면 안 되는 이유
    • 서버 이전이나, IP 주소가 바뀌면 일일이 바꿔줘야 함
    • DNS를 통해 자동으로 ip를 찾을 수 있어 DNS로 해두는 게 편함

 

AddressList

  • IPHostEntry.AddressList을 호출하면 IPAddress [] 배열로 반환
    • 구글같이 큰 사이트는 트래픽 관리를 위해 여러 아이피를 가지고 있기 때문
  • 여기서는 그중에서 첫 번째 것만 사용

 

IPEndPoint

  • 최종적인 주소 - 아이피 주소와 포트번호가 들어간다.
    • 포트 번호는 우리가 임의로 정해줘야 함

  • 이 포트 번호는 클라이언트와 서버가 일치해야지 통신이 가능하다.

 

Socket

  • 여기서 소켓을 선언할 때 3가지 매개변수가 들어감
    • 첫 번째 매개변수 : IPv4를 사용할지 IPv6을 사용할지 등을 결정
      → 여기서는 ipEndPoint에 있는 규칙을 따라가기 위해 ipEndPoint.AddressFamily를 사용

    • 두 번째 매개변수, 세 번째 매개변수 → 통신 규약
      • 여기서는 tcp를 사용했기 때문에 한쌍으로 SocektType도 Stream으로 맞춰줌

 

Listen

  • 백로그로 최대 대기할 수 있는 인원수를 정한다.
  • 예를 들어, 백로그가 10이라면 10명만 서버에 대기할 수 있고 나머지는 접속에 실패한다.

 


클라이언트와 상호작용

while (true)
{
    Console.WriteLine("Connect Server");

    // 클라이언트 입장시키기 
    Socket clientSocket = listenSocket.Accept();


    // 데이터 받기

    byte[] recvBuff = new byte[1024]; // 1024바이트까지 받아들일 수 있는 버퍼
    
    // 클라이언트 소켓으로부터 받아 recv에 저장
    int recv = clientSocket.Receive(recvBuff);
    
    // 받은 것은 UTF8 정책에 따라 문자열로 인코딩
    string data = Encoding.UTF8.GetString(recvBuff, 0, recv);
    Console.WriteLine($"[From Client] {data}");


    // 데이터 보내기
    
    // 문자열을 byte로 변환
    byte[] sendBuff = Encoding.UTF8.GetBytes("Hello Client"); 
    clientSocket.Send(sendBuff); // 클라이언트 소켓을 통해 데이터를 보냄 


    // 소켓 연결 끊기
    clientSocket.Shutdown(SocketShutdown.Both);
    clientSocket.Close();
}

 

Accept

  • Accept를 통해 클라이언트를 입장시킬 수 있다.
  • Accept는 반환할 때 Socket을 반환하기 때문에 이런 식으로 clientSocket을 변수로 받는다.
    • 이제 해당 클라이언트와 통신하기 위해서는 해당 소켓으로 통신한다.

 

데이터 받기

  • 데이터는 버퍼를 통해 byte로 들어오기 때문에 이를 받기 위해 recvBuff를 만들어둔다
    • 들어오는 데이터가 얼마나 클지 모르기 때문에 1024byte로 정의해 둔다.

  • 받아들인 byte 배열을 UTF8 정책으로 String 형으로 변환하여 data로 저장한다.
    • 이때 매개변수 0은 시작 인덱스의 위치를 뜻한다.

 

데이터 보내기

  • 데이터 받기와 비슷한 원리로 보낼 데이터를 문자열로 쓰고 이를 byte로 변환하여 저장해 둔다.
  • 그리고 clientSocket을 통해 send 한다.

 

소켓 연결 끊기

  • clientSocket.Close()를 통해 연결을 끊는다. → 사실 이것만 있어도 연결은 끊긴다.
  • 좀 더 안전하고 확실하게 하기 위해 Shutdown을 사용한다.

 


서버 전체 코드

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ServerCore
{
    public class Program
    {
        static void Main(string[] args) {
            string host = Dns.GetHostName(); // 로컬 컴퓨터의 호스트 이름을 가져옴
            
             // 호스트 이름을 통해 IPHostEntry를 가져옴
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            
            // 해당 IPHostEntry에 있던 여러 IP 중 가장 첫 IP를 가져옴
            IPAddress address = ipHost.AddressList[0];
            
            // 최종주소, 7777은 임의로 설정해둔 번호
            IPEndPoint ipEndPoint = new IPEndPoint(address, 7777);


            // listen해줄 소켓을 만듦 매개변수로, AddressFamily, 통신 방법(TCP/UDP)를 정함
            Socket listenSocket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            listenSocket.Bind(ipEndPoint); // 소켓을 해당 방식으로 바인딩시킴

            // 백로그 : 최대 대기자수 10명
            listenSocket.Listen(10);


            // 계속해서 응답을 받음
            while (true)
            {
                Console.WriteLine("Connect Server");

                // 클라이언트 입장시키기 
                Socket clientSocket = listenSocket.Accept();


                // 데이터	받아들이기

                byte[] recvBuff = new byte[1024]; // 1024바이트까지 받아들일 수 있는 버퍼
				
                // 클라이언트 소켓으로부터 받아 recv에 저장
                int recv = clientSocket.Receive(recvBuff);
                
                // 받은 것은 UTF8 정책에 따라 문자열로 인코딩
                string data = Encoding.UTF8.GetString(recvBuff, 0, recv); 
                Console.WriteLine($"[From Client] {data}");


                // 데이터 보내기
                
                // 문자열을 byte로 변환
                byte[] sendBuff = Encoding.UTF8.GetBytes("Hello Client");
                clientSocket.Send(sendBuff); // 클라이언트 소켓을 통해 데이터를 보냄 


                // 소켓 연결 끊기
                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();
            }
        }
    }
}

 

 


클라이언트

  • 클라이언트 쪽은 서버 쪽 코드보다 훨씬 쉽다.
  • 서버 쪽에서 설명한 것이 거의 전부기 때문에 새롭게 나온 것만 설명한다.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ServerCore
{
    public class Program
    {
        static void Main(string[] args)
        {
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress address = ipHost.AddressList[0];
            IPEndPoint ipEndPoint = new IPEndPoint(address, 7777); // 최종주소


            Socket socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);


            socket.Connect(ipEndPoint); // 해당 ip주소로 연결 요청

            Console.WriteLine($"Connect To {socket.RemoteEndPoint.ToString()}");




            // 데이터 보내기
            byte[] sendBuff = Encoding.UTF8.GetBytes("Hello Server");
            socket.Send(sendBuff);


            // 데이터 받
            byte[] recvBuff = new byte[1024];
            int recv = socket.Receive(recvBuff);
            string data = Encoding.UTF8.GetString(recvBuff, 0, recv);

            Console.WriteLine($"[From Server] {data}");

            socket.Shutdown(SocketShutdown.Both);
            socket.Close();

        }
    }
}

 


서버와 연결

Connect

  • 매개변수로 IPEndPoint가 들어간다.
  • 해당 ip 주소로 연결 요청을 하는 것이다.
  • Console.WriteLine에 있는 socket.RemoteEndPoint는 현재 통신하고 있는 상대를 뜻한다.
    • 여기서 현재 통신하고 있는 상대 → 서버

실행 결과

서버 실행 결과
서버 실행 결과

  • ServerCore 쪽에서의 화면이다. 제대로 실행되는 것을 확인할 수 있다.

클라이언트 실행 결과
클라이언트 실행 결과

  • Client 쪽에서도 정상적으로 실행되는 것을 확인할 수 있다.