본문 바로가기
개인 스터디

TCP 클라이언트

by 태운콩즙 2024. 10. 11.
728x90
반응형

TcpClient 클래스

.NET Framework에서 TCP 클라이언트 프로그램을 개발하기 위해서는 System.Net.Sokets.TcpClient 클래스를 사용 할 수 있다 TcpClient 클래스는 내부적으로 System.Net.Sokets.TcpClient 클래스 기능을 사용하여 TCP 기능을 구현하고 있다 TCP는 기본적으로 IP와 포트를 필요로 하는데 IP가 호스트 까지 연결하는데 비해 TCP 는 호스트내 포트까지 연결하여 해당 포트에서 기다리고 응용프로그램까지 도달한다

TcpClient 사용법

TcpClient 크래스를 어떻게 사용하는지 살펴보기 위해 단순한 예제를 살펴보겠다 아래 예제는 간단한 메세지를 TCP 서버에 보내고 응답을 읽어 화면에 표시하는 간단한 프로그램 이다

using System;
using System.Net.Sockets;
using System.Text;

namespace TcpClient_TEST
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // (1) IP 주소와 포트를 지정하고 TCP 연결
            //TcpClient tcpClient = new TcpClient("127.0.0.1", 8000);
             TcpClient tcpClient = new TcpClient ("localhost", 8080);
            string msg = "Hello World";
            byte[] buffer = Encoding.ASCII.GetBytes(msg);

            // (2) NetworkStream을 얻어옴
            NetworkStream netStream = tcpClient.GetStream();

            //(3) 스트림에 바이트 데이터 전송
            netStream.Write(buffer, 0, buffer.Length);

            //(4) 스트림으로 부터 바이트 데이터 읽기
            byte[] outbuf = new byte[1024];
            int nbytes = netStream.Read(outbuf, 0, outbuf.Length);
            string output = Encoding.ASCII.GetString(outbuf, 0 , nbytes);

            // (5) 스트림과 TcpClient 객체 닫기
            netStream.Close();
            tcpClient.Close();

        }
    }
}

위 예제를 각각 살펴본다면

  • TCP 클라이언트가 서버에 연결하기 위해서는 IP 주소와 포트가 필요하다 위와 같이 TcpClient 생성자 에서 IP 와 포트를 주면 자동으로 TCP Connection을 연결한다 생성자 에 이들 파라미터를 주지 않은 경우 나중에 Connection() 메서드를 호출하여 명시적으로 연결 할 수도 있다 또한 IP 주소대신 호스트명을 주면 이를 DNS 를 통해 IP로 변경해서 연결한다
  • TcpClient 의 GetStream() 메서드는 TCP 네트워크 스트림을 리턴 한다 이 네트워크 스트림을 이용해서 네트워크로 데이터 송수신을 하게 된다
  • NetworkStream의 Write() 메서드를 사용하여 바이트들을 서버로 보낸다 네트워크 데이터 송수신은 기본적으로 바이트 데이터를 사용하는데 따라서 문자열을 보낼 경우 먼저 바이트로 인코딩 한 후 보내게 된다 일반 영문은 ASCII로 인코딩하고 한글 등 비영문 문자열은 UTF 인코딩을 사용 할 수 있다
  • NetworkStream의 Read() 메서드를 사용하여 바이트 데이터를 서버에서 읽어온다 여기서 한가지 주목할 점은 위의 예제는 개념을 설명하기 위해 Read()를 한번만 호출했지만 실무에서는 이렇게 코딩해서는 안된다는 점이다 일반적으로 TCP 에서 데이터를 보내면 비록 코드에서는 한번 호출할 지라도 실제로는 여러 번 나뉘어져 보내질 수도 있다 따라서 보통 루프 를 만들어 모든 데이터를 Read() 읽기 를 끝내게 된다
  • 마지막 스텝은 물론 NetworkStream을 닫고 TcpClient 객체를 닫는 일이다 일반적으로 이 둘을 무두 명시적으로 닫는다 NetworkStream 만 닫으면 네트워크 스트림의 하위 리소스인 TcpClient 를 같이 닫지만 TcpClient 객체만 닫으면 NetworkStream을 닫지 않음으로 주의 해야 한다

TCP 데이터 수신

일반적으로 데이터를 송신 할 때는 몇 바이트를 보내는지 정확히 알 수 있지만 데이터를 수신 할 때는 몇 바이트가 올지 알 수가 없다 또한 데이터를 읽을 때 상대편이 비록 한번 송신을 하더라도 실제 데이터는 여러 조각으로 나뉘어져 올 수도 있다 NetworkStream의 Read() 메서드는 데이터가 도착할 때까지 기다렸다가 최대 버퍼 크기만큼 데이터를 읽어 들이는데 만약 일어 올 데이터가 버퍼보다 크거나 작아도 데이터가 잘게 쪼개져 올 경우 루프를 돌며 Read() 를 하게된다

상대방이 수신할 데이터의 사이즈를 알고 있는 경우

만약 TCP 통신에 있어 상대방과 주고 받는 데이터의 크기 및 구조 등에 대해 규칙(프로토콜)을 가지고 있다면 수신할 데이터의 크기를 미리 알고 그만큼 만 읽어 들이면 된다. 예를 들어, 헤더에 차후 보낼 바이트의 크기를 보낸 다던지, 메시지의 마지막에 End of Message 마크를 찍는 다던지, 일정 시간 아무 데이터 가 없으면 수신 종료 한 다던지 하는 규칙을 가질 수 있다.

상대방이 수신할 데이터의 사이즈를 모르는 경우

수신 데이터 크기를 미리 알 수 없다면, 상대방에서 TCP Connection을 종료했을 때 Read() 메서드가 0 을 리턴 하므로 이를 체크함으로써 데이터 읽기를 종료할 수 있다. 일반적으로 서버가 Connection을 먼저 닫는지, 클라이언트가 Connection을 먼저 닫는지는 프로토콜마다 다르다. 예를 들어, HTTP 프로토콜의 경우 서버가 먼저 TCP Connection을 닫고, 브라우저 클라이언트가 뒤따라 Connection을 닫는다. 아래 예제는 서버가 TCP Connection을 닫을 때까지 계속 데이타를 읽어 들이는 예이다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace TcpClient_TEST
{
    internal class Class1
    {
        static void Main2(string[] args)
        {
            // (1) IP 주소와 포트를 지정하고 TCP 연결             
            TcpClient tc = new TcpClient("localhost", 7000);

            string msg = "Hello World";
            byte[] buff = Encoding.ASCII.GetBytes(msg);

            // (2) NetworkStream을 얻어옴 
            NetworkStream stream = tc.GetStream();

            // (3) 스트림에 바이트 데이터 전송
            stream.Write(buff, 0, buff.Length);

            // (4) 서버가 Connection을 닫을 때가지 읽는 경우
            byte[] outbuf = new byte[1024];
            int nbytes;
            MemoryStream mem = new MemoryStream();
            while ((nbytes = stream.Read(outbuf, 0, outbuf.Length)) > 0)
            {
                mem.Write(outbuf, 0, nbytes);
            }
            byte[] outbytes = mem.ToArray();
            mem.Close();

            // (5) 스트림과 TcpClient 객체 닫기
            stream.Close();
            tc.Close();

            Console.WriteLine(Encoding.ASCII.GetString(outbytes));
        }
    }
}

728x90
반응형

'개인 스터디' 카테고리의 다른 글

무명 메서드 (Anonymous Method)  (0) 2024.10.14
TCP 서버  (0) 2024.10.11
Win Sock 과 .NET 클래스  (1) 2024.10.11
시리얼 통신  (0) 2024.10.10
Delegate 에서 Event  (0) 2024.10.07