TCP/IP 소켓 통신

이동근·2026년 3월 16일

C#

목록 보기
8/9

TCP/IP 소켓 통신

  • TCP/IP : 인터넷 통신의 기본 규약

    • 소켓 : TCP/IP 통신을 실제로 사용하기 위한 인터페이스(도구) → 통신 도구

    • 프로토콜 : 소켓을 이용해서 통신하는 규칙들

  • TCP/IP 소켓 통신 : 프로토콜 없이 소켓을 직접 사용해서 통신하는 것

    • HTTP, MQTT 같은 프로토콜보다 자유롭지만 규칙을 직접 정해야함

TCP/IP를 이용하는 프로토콜

프로토콜용도
HTTP / HTTPS웹 브라우저에서 웹 페이지 요청 및 전송
FTP파일 전송
SMTP이메일 전송
SSH원격 서버 접속
MQTTIoT 센서 데이터 전송
WebSocket실시간 채팅, 게임 등 실시간 통신

서버 사용 방식

구분동기(Blocking) 방식비동기(Async) 방식
처리 방식요청 대기 동안 블로킹요청 처리 중에도 다른 작업 가능
Thread 사용클라이언트 1명당 1 ThreadThread 수 최소화 가능
장점구현 간단높은 동시 처리 능력
단점동시 접속 많으면 성능 저하구현 복잡

기본 TCP 서버: 동기(Blocking) 방식

  • 전통적인 TCP 서버는 동기 소켓을 사용.

  • 동작 순서:

    • 클라이언트 접속 없음 → 서버는 AcceptTcpClient()에서 대기

    • 클라이언트 접속 발생 → 연결 수락

    • 데이터 수신 → ReadLine() 등에서 대기

    • 데이터 처리

  • 특징:

    • 간단하고 구현이 쉽지만

    • 한 번에 하나의 작업만 처리 가능 (Blocking)


실제 서버: 비동기(Asynchronous) 방식 사용

  • 클라이언트 요청이 많아질 경우, Thread 방식을 사용하면 문제 발생:

    • 클라이언트 수 증가 → Thread 수 폭발

    • 메모리와 CPU 사용량 급증

  • 해결책:

    • 비동기 소켓 / 이벤트 기반 처리 → Thread 폭발 문제 완화

    • I/O 작업이 끝날 때까지 CPU를 블로킹하지 않음


TCP/IP 소켓 통신 흐름

전체 요약

[서버 실행] → TcpListener.Start() → AcceptTcpClient() 대기
       ↑
[클라이언트 실행] → TcpClient.Connect()
       ↓
[클라이언트] 메시지 전송 → streamWriter
[서버] 메시지 수신 → streamReader → 로그 표시
[서버] 응답 전송 → streamWriter
[클라이언트] 응답 수신 → streamReader → 로그 표시
       ↑
[반복]
       ↓
[종료] → Stream 및 TcpClient 종료
  • 프로젝트를 실행하여 폼 화면에 Server, Client 버튼 존재

    • Server 버튼 → Server 실행 폼 생성

    • Client 버튼 → Client 실행 폼 생성


서버 실행 및 대기

  • 서버 폼 실행 → 연결 버튼 클릭

TcpListener 생성 및 포트 열기

TcpListener tcpListener = new TcpListener(IPAddress.Parse(txt_IP.Text), int.Parse(txt_PORT.Text));
tcpListener.Start();

클라이언트 연결 대기 (AcceptTcpClient()) → Blocking

TcpClient tcpClient = tcpListener.AcceptTcpClient();

연결되면 StreamReader와 StreamWriter를 통해 데이터 송수신 준비

streamReader = new StreamReader(tcpClient.GetStream());
streamWriter = new StreamWriter(tcpClient.GetStream()) { AutoFlush = true };

클라이언트 실행 및 연결

  • 클라이언트 폼 실행 → Connect 버튼 클릭

TcpClient로 서버 IP/Port에 연결

TcpClient tcpClient1 = new TcpClient();
tcpClient1.Connect(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));

서버와 연결되면 StreamReader와 StreamWriter를 통해 송수신 준비

streamReader1 = new StreamReader(tcpClient1.GetStream());
streamWriter1 = new StreamWriter(tcpClient1.GetStream()) { AutoFlush = true };

메시지 전송 (Client → Server)

  • 클라이언트에서 메시지 입력 후 Send 버튼 클릭

    • streamWriter1.WriteLine(sendData1)로 서버에 전송

    • 서버의 streamReader.ReadLine()에서 수신 대기


서버에서 메시지 수신 및 응답

  • 서버는 streamReader.ReadLine()으로 메시지 수신

  • 수신한 메시지를 RichTextBox 등 UI에 표시

  • 필요 시 서버에서 다시 streamWriter.WriteLine()으로 클라이언트에 응답 전송

    • 클라이언트에서 streamReader1.ReadLine()으로 수신

클라이언트에서 서버 응답 수신

  • 클라이언트는 streamReader1.ReadLine()으로 서버 응답 수신

  • 수신 데이터를 RichTextBox에 출력

  • 송수신 반복 → 채팅 가능


종료 및 연결 해제

  • 클라이언트 또는 서버 종료 시 Stream과 TcpClient 종료

종료 및 연결해제

streamReader.Close();
streamWriter.Close();
tcpClient.Close();
  • 서버의 경우 TcpListener.Stop()으로 포트 해제 가능

프로젝트 예시 코드

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btn_Server_Click(object sender, EventArgs e)
        {
            Server serverPop = new Server();
            serverPop.ShowDialog();
        }

        private void btn_Client_Click(object sender, EventArgs e)
        {
            Client clientPop = new Client();
            clientPop.ShowDialog();
        }
    }
}

Server.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Net;

namespace WindowsFormsApp1
{
    public partial class Server : Form
    {
        public Server()
        {
            InitializeComponent();
        }

        StreamReader streamReader;
        StreamWriter streamWriter;

        #region "연결 이벤트"
        private void BTN_CONN_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(connect);

            // 메인 스레드 종료 시 같이 종료
            thread.IsBackground = true;
            thread.Start();
        }
        #endregion

        #region"보내기 이벤트"
        private void BTN_SEND_Click(object sender, EventArgs e)
        {
            string sendData = txt_INPUT.Text;
            streamWriter.WriteLine(sendData);
        }
        #endregion

        #region "TCP/IP 소켓 통신"
        private void connect()
        {
            try
            {
                // IP, PORT 할당
                TcpListener tcpListener = new TcpListener(IPAddress.Parse(txt_IP.Text), int.Parse(txt_PORT.Text));

                tcpListener.Start();
                common.writeRichTextBox(richbox, "서버 준비.. 클라이언트 기다리는 중...");

                // 클라이언트 연결 확인
                TcpClient tcpClient = tcpListener.AcceptTcpClient();
                common.writeRichTextBox(richbox, "클라이언트 연결됨..");

                streamReader = new StreamReader(tcpClient.GetStream());
                streamWriter = new StreamWriter(tcpClient.GetStream());
                streamWriter.AutoFlush = true;

                while (true)
                {
                    string receiveDate = streamReader.ReadLine();

                    if (receiveDate == null)
                    {
                        break;
                    }

                    common.writeRichTextBox(richbox, receiveDate);
                }
            }
            catch (Exception ex)
            {
                common.writeRichTextBox(richbox, "오류 : " + ex.Message);
            }
        }
        #endregion
    }
}

Client.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Remoting.Contexts;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;

namespace WindowsFormsApp1
{
    public partial class Client : Form
    {
        public Client()
        {
            InitializeComponent();
        }

        StreamReader streamReader1;
        StreamWriter streamWriter1;

        #region " 클릭 이벤트 "
        private void btn_Connet_Click(object sender, EventArgs e)
        {
            Thread thread1 = new Thread(connect); // Thread 객체 생성
            thread1.IsBackground = true; // Form이 종료되면 thread1도 종료
            thread1.Start();
        }

        private void btn_Send_Click(object sender, EventArgs e)
        {
            string sendData1 = textBox3.Text;  // testBox3 의 내용을 sendData1 변수에 저장
            streamWriter1.WriteLine(sendData1);  // 스트림라이터를 통해 데이터를 전송
        }
        #endregion

        #region " 서버 연결 "
        private void connect()
        {
            TcpClient tcpClient1 = new TcpClient();
            tcpClient1.Connect(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text)); // 서버에 접속
            common.writeRichTextBox(richTextBox1, "서버 연결됨");

            streamReader1 = new StreamReader(tcpClient1.GetStream());  // 읽기 스트림 연결
            streamWriter1 = new StreamWriter(tcpClient1.GetStream());  // 쓰기 스트림 연결
            streamWriter1.AutoFlush = true;

            while (tcpClient1.Connected)  // 서버 연결되어 있는 동안
            {
                string receiveData1 = streamReader1.ReadLine();  // 수신 데이터를 읽어서 receiveData1 변수에 저장
                common.writeRichTextBox(richTextBox1, receiveData1); // 데이터를 수신창에 쓰기                  
            }
        }
        #endregion 
    }
}
profile
안녕하세요

0개의 댓글