TCP/IP : 인터넷 통신의 기본 규약
소켓 : TCP/IP 통신을 실제로 사용하기 위한 인터페이스(도구) → 통신 도구
프로토콜 : 소켓을 이용해서 통신하는 규칙들
TCP/IP 소켓 통신 : 프로토콜 없이 소켓을 직접 사용해서 통신하는 것
| 프로토콜 | 용도 |
|---|---|
| HTTP / HTTPS | 웹 브라우저에서 웹 페이지 요청 및 전송 |
| FTP | 파일 전송 |
| SMTP | 이메일 전송 |
| SSH | 원격 서버 접속 |
| MQTT | IoT 센서 데이터 전송 |
| WebSocket | 실시간 채팅, 게임 등 실시간 통신 |
| 구분 | 동기(Blocking) 방식 | 비동기(Async) 방식 |
|---|---|---|
| 처리 방식 | 요청 대기 동안 블로킹 | 요청 처리 중에도 다른 작업 가능 |
| Thread 사용 | 클라이언트 1명당 1 Thread | Thread 수 최소화 가능 |
| 장점 | 구현 간단 | 높은 동시 처리 능력 |
| 단점 | 동시 접속 많으면 성능 저하 | 구현 복잡 |
전통적인 TCP 서버는 동기 소켓을 사용.
동작 순서:
클라이언트 접속 없음 → 서버는 AcceptTcpClient()에서 대기
클라이언트 접속 발생 → 연결 수락
데이터 수신 → ReadLine() 등에서 대기
데이터 처리
특징:
간단하고 구현이 쉽지만
한 번에 하나의 작업만 처리 가능 (Blocking)
클라이언트 요청이 많아질 경우, Thread 방식을 사용하면 문제 발생:
클라이언트 수 증가 → Thread 수 폭발
메모리와 CPU 사용량 급증
해결책:
비동기 소켓 / 이벤트 기반 처리 → Thread 폭발 문제 완화
I/O 작업이 끝날 때까지 CPU를 블로킹하지 않음
전체 요약
[서버 실행] → 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 };
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 };
클라이언트에서 메시지 입력 후 Send 버튼 클릭
streamWriter1.WriteLine(sendData1)로 서버에 전송
서버의 streamReader.ReadLine()에서 수신 대기
서버는 streamReader.ReadLine()으로 메시지 수신
수신한 메시지를 RichTextBox 등 UI에 표시
필요 시 서버에서 다시 streamWriter.WriteLine()으로 클라이언트에 응답 전송
streamReader1.ReadLine()으로 수신클라이언트는 streamReader1.ReadLine()으로 서버 응답 수신
수신 데이터를 RichTextBox에 출력
송수신 반복 → 채팅 가능
종료 및 연결해제
streamReader.Close(); streamWriter.Close(); tcpClient.Close();
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 } }