본문 바로가기

C#/네트워크

네트워크 소켓 통신을 하는 방법

728x90

소켓 통신 요약

프로그램과 프로그램, 그리고 컴퓨터와 컴퓨터끼리 데이터를 주고 받는 것을 통신이라고 한다. 통신을 좀 더 자세히 설명하면, 전송하는 패킷(데이터)이 컴퓨터의 랜 카드를 거쳐 랜 케이블로 나갑니다. 랜 케이블로 나간 데이터는 DNS와 라우터 등을 거쳐 도달하고자 하는 PC의 랜 카드에 들어가고 목표로 하는 프로그램에서 패킷(데이터)를 읽어 서로 간에 데이터를 주고 받는다.

이 때, 우리는 각 단말 간에 데이터 변환이나 장비 간의 통신 규약에 대해서 모둘 개발하지 않는다. 이러한 통신 규약 등은 모두 OS 측에서 설정(OSI 7계층)되고, 우리는 그 위에 꽂아서 쓴다라는 개념으로 Socket 통신 규약을 이용해 통신한다.

 

소켓 통신 규약

먼저 기다리는 측의 PC를 서버라고 하며 Port를 열고 클라이언트의 접속을 기다린다. 그리고 접속하는 측을 클라이언트라고 하며 서버의 IP와 Port에 접속하여 통신이 연결된다.

서버와 클라이언트 간의 통신은 Send, Receive의 형태로 데이터를  주고 받는다. 그리고 서로 통신이 끝나면 Close로 접속을 끊는다.

서버

Socket 클래스로 서버 Socket 서버 인스턴스를 생성하였습니다. Bind 함수를 사용하여 대기 포트를 설정합니다.

Listen으로 동시 접속 대기 설정을 하고 Accept 함수를 통해 클라이언트의 접속을 대기합니다.

프로그램 상에서는 Accept 함수가 호출이 되면 Client 접속이 발생할 때까지 프로세스가 멈추게 됩니다.

그리고 telnet 프로그램으로 접속을 하게 되면 Accept함수를 통해서 클라이언트 Socket 인스턴스가 나오고 Send와 Receive 함수를 통해서 서버와 클라이언트로부터 서로 메시지를 주고 받을 수 있습니다.

멀티 접속을 허용하게 하려면 쓰레드 기능을 넣어야 합니다.

Accept 함수는 클라이언트가 접속하기 전에 쓰레드가 멈추는 형태이기 때문에, 클라이언트로 접속되면 병렬로 다시 Task 쓰레드를 만들고 다시 루프로 Accept로 대기 상태로 들어갑니다.

더보기
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
 
namespace Example
{
  class Program
  {
    // 서버 실행 Task 메소드
    static async Task RunServer(int port)
    {
      // Socket EndPoint 설정(서버의 경우는 Any로 설정하고 포트 번호만 설정한다.)
      var ipep = new IPEndPoint(IPAddress.Any, port);
      // 소켓 인스턴스 생성
      using (Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
      {
        // 서버 소켓에 EndPoint 설정
        server.Bind(ipep);
        // 클라이언트 소켓 대기 버퍼
        server.Listen(20);
        // 콘솔 출력
        Console.WriteLine($"Server Start... Listen port {ipep.Port}...");
        // server Accept를 Task로 병렬 처리(즉, 비동기를 만든다.)
        var task = new Task(() =>
        {
          // 무한 루프
          while (true)
          {
            // 클라이언트로부터 접속 대기
            var client = server.Accept();
            // 접속이 되면 Task로 병렬 처리
            new Task(() =>
            {
              // 클라이언트 EndPoint 정보 취득
              var ip = client.RemoteEndPoint as IPEndPoint;
              // 콘솔 출력 - 접속 ip와 접속 시간
              Console.WriteLine($"Client : (From: {ip.Address.ToString()}:{ip.Port}, Connection time: {DateTime.Now})");
              // 클라이언트로 접속 메시지를 byte로 변환하여 송신
              client.Send(Encoding.ASCII.GetBytes("Welcome server!\r\n>"));
              // 메시지 버퍼
              var sb = new StringBuilder();
              // 종료되면 자동 client 종료
              using (client)
              {
                // 무한 루프
                while (true)
                {
                  // 통신 바이너리 버퍼
                  var binary = new Byte[1024];
                  // 클라이언트로부터 메시지 대기
                  client.Receive(binary);
                  // 클라이언트로 받은 메시지를 String으로 변환
                  var data = Encoding.ASCII.GetString(binary);
                  // 메시지 공백(\0)을 제거
                  sb.Append(data.Trim('\0'));
                  // 메시지 총 내용이 2글자 이상이고 개행(\r\n)이 발생하면
                  if (sb.Length > 2 && sb[sb.Length - 2] == '\r' && sb[sb.Length - 1] == '\n')
                  {
                    // 메시지 버퍼의 내용을 String으로 변환
                    data = sb.ToString().Replace("\n", "").Replace("\r", "");
                    // 메시지 내용이 공백이라면 계속 메시지 대기 상태로
                    if (String.IsNullOrWhiteSpace(data))
                    {
                      continue;
                    }
                    // 메시지 내용이 exit라면 무한 루프 종료(즉, 서버 종료)
                    if ("EXIT".Equals(data, StringComparison.OrdinalIgnoreCase))
                    {
                      break;
                    }
                    // 메시지 내용을 콘솔에 표시
                    Console.WriteLine("Message = " + data);
                    // 버퍼 초기화
                    sb.Length = 0;
                    // 메시지에 ECHO를 붙힘
                    var sendMsg = Encoding.ASCII.GetBytes("ECHO : " + data + "\r\n>");
                    // 클라이언트로 메시지 송신
                    client.Send(sendMsg);
                  }
                }
                // 콘솔 출력 - 접속 종료 메시지
                Console.WriteLine($"Disconnected : (From: {ip.Address.ToString()}:{ip.Port}, Connection time: {DateTime.Now})");
              }
              // Task 실행
            }).Start();
          }
        });
        // Task 실행
        task.Start();
        // 대기
        await task;
      }
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // Task로 Socket 서버를 만듬(서버가 종료될 때까지 대기)
      RunServer(10000).Wait();
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

클라이언트

서버와 Send, Receive 함수는 서버와 비슷합니다만, Bind, Listen 대신에 Connect 함수를 써서 서버에 Socket 접속을 합니다.

클라이언트는 보통 하나의 서버를 접속하기 때문에 따로 병렬 처리를 만들 필요는 없습니다. 있다면 Receive 함수만 Task 쓰레드로 Send, Receive를 분리했습니다.

사양에 따라 클라이언트도 여러 서버를 동시에 접속할 수 있습니다만, 기본적으로는 하나의 서버의 접속을 합니다

더보기
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
 
namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Socket EndPoint 설정
      var ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 10000);
      // 소켓 인스턴스 생성
      using (Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
      {
        // 소켓 접속
        client.Connect(ipep);
        // 접속이 되면 Task로 병렬 처리
        new Task(() =>
        {
          try
          {
            // 종료되면 자동 client 종료
            // 무한 루프
            while (true)
            {
              // 통신 바이너리 버퍼
              var binary = new Byte[1024];
              // 서버로부터 메시지 대기
              client.Receive(binary);
              // 서버로 받은 메시지를 String으로 변환
              var data = Encoding.ASCII.GetString(binary).Trim('\0');
              // 메시지 내용이 공백이라면 계속 메시지 대기 상태로
              if (String.IsNullOrWhiteSpace(data))
              {
                continue;
              }
              // 메시지 내용을 콘솔에 표시
              Console.Write(data);
            }
          }
          catch (SocketException)
          {
            // 접속 끝김이 발생하면 Exception이 발생
          }
          // Task 실행
        }).Start();
        // 유저로부터 메시지 받기 위한 무한 루프
        while (true)
        {
          // 콘솔 입력 받는다.
          var msg = Console.ReadLine();
          // 클라이언트로 받은 메시지를 String으로 변환
          client.Send(Encoding.ASCII.GetBytes(msg + "\r\n"));
          // 메시지 내용이 exit라면 무한 루프 종료(즉, 클라이언트 종료)
          if ("EXIT".Equals(msg, StringComparison.OrdinalIgnoreCase))
          {
            break;
          }
        }
        // 콘솔 출력 - 접속 종료 메시지
        Console.WriteLine($"Disconnected");
      }
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

 

[출처]

https://nowonbun.tistory.com/155

728x90

'C# > 네트워크' 카테고리의 다른 글

소켓  (0) 2023.11.06
C# 네트워크 프로그래밍  (0) 2023.11.06