본문 바로가기
C#

tcpclient

by 자바초보자 2023. 1. 12.
728x90

https://unininu.tistory.com/475

 

C# 윈폼 - TCP/IP 네트워크 통신을 뚫어보자


TCP/IP 네트워크 통신은 초보자 입장에서는 다소 복잡한 개념이다.
그러나 서버와 클라이언트가 메세지를 주고받는 간단한 채팅 프로그램만이라도 성공적으로 짜볼 수 있다면,

네트워크 통신의 진입장벽을 낮출 수 있을 것이라 생각한다.
그래서 최대한 짧은 코드로 작성해본 것을 올려본다.

 

왼쪽이 서버, 오른쪽이 클라이언트 실행 화면이다.

폼구조는 서버와 클라이언트 모두 같게 만들었다.

 

 

TCP/IP 네트워크 통신에 필요한 주요한 코드들을 먼저 확인해 보자.

 

1. using 지시문 선언

using System.Threading; // 추가
using System.Net; // 추가
using System.Net.Sockets; // 추가
using System.IO; // 추가

위와 같이 네가지 using 지시문이 먼저 선언되어야 한다.

 

 

2. 쓰레드 실행

private void button1_Click(object sender, EventArgs e)  // '연결하기' 버튼이 클릭되면
{
    Thread thread1 = new Thread(connect); // Thread 객채 생성, Form과는 별도 쓰레드에서 connect 함수가 실행됨.
    thread1.IsBackground = true; // Form이 종료되면 thread1도 종료.
    thread1.Start(); // thread1 시작.
}

button1(연결하기) 버튼을 눌렀을 때 쓰레드를 만들어 connect 함수가 메인폼과는 개별적으로 실행되게 했다. connect 함수는 네트워크 통신을 위한 코드들을 짜놓은 함수다. 별도 쓰레드에서 네트워크 통신을 실행해야 다른 작업들도 동시에 할 수 있게 된다.

 

 

3. TCP/IP 네트워크 통신 코드

private void connect()  // thread1에 연결된 함수. 메인폼과는 별도로 동작한다.
{
    TcpListener tcpListener1 = new TcpListener(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text)); // 서버 객체 생성 및 IP주소와 Port번호를 할당
    tcpListener1.Start();  // 서버 시작
    writeRichTextbox("서버 준비...클라이언트 기다리는 중...");

    TcpClient tcpClient1 = tcpListener1.AcceptTcpClient(); // 클라이언트 접속 확인
    writeRichTextbox("클라이언트 연결됨...");

    streamReader1 = new StreamReader(tcpClient1.GetStream());  // 읽기 스트림 연결
    streamWriter1 = new StreamWriter(tcpClient1.GetStream());  // 쓰기 스트림 연결
    streamWriter1.AutoFlush = true;  // 쓰기 버퍼 자동으로 뭔가 처리..

    while (tcpClient1.Connected)  // 클라이언트가 연결되어 있는 동안
    {
        string receiveData1 = streamReader1.ReadLine();  // 수신 데이타를 읽어서 receiveData1 변수에 저장
        writeRichTextbox(receiveData1); // 데이타를 수신창에 쓰기                  
    }
}

connect 함수 안에 실질적으로 필요한 네트워크 통신 코드들을 작성했다.

위 코드는 서버 코드이고 클라이언트는 약간은 다르지만 대체적으로 비슷하다.

서버기준으로 작성해야 할 코드들을 하나씩 살펴보자.

 

TcpListener는 서버를 준비하기 위한 클래스다. 객체를 만들 때 서버의 IP주소와 Port번호를 지정해줘야 한다.

textBox1에는 자신의 컴퓨터의 IP주소를 찾아 넣어주고, textBox2에는 임의의 Port번호를 넣어주자.

 

TcpClient는 클라이언트에 대한 클래스다. tcpListener.AcceptTcpClient() 는 클라이언트 접속을 확인한다.

 

StreamReader 는 상대방이 보내준 데이터를 읽기 위한 클래스이고,

StreamWriter 는 상대방에게 데이터를 보낼 때 필요한 클래스이다.

 

while문에서 클라이언트가 연결되어 있는 동안, streamReader1을 통해 받은 데이터를 receiveData1변수에 저장해서 writeRichTextbox 함수로 전달한다. writeRichTextbox 함수는 수신 데이터를 화면에 보여주기 위한 함수다.

 

 

4. 수신창에 표시하기

private void writeRichTextbox(string str)  // richTextbox1 에 쓰기 함수
{
    richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.AppendText(str + "\r\n"); }); // 데이타를 수신창에 표시, 반드시 invoke 사용. 충돌피함.
    richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.ScrollToCaret(); });  // 스크롤을 젤 밑으로.
}

writeRichTextbox 함수다. 수신창(richTextBox1)에 받은 데이터를 보여준다.

중요. 여기에서 Invoke 가 사용되었다. Invoke를 사용해야 richTextBox1를 사용하는데에 충돌이 안생긴다.

 

 

5. 데이터 보내기

private void button2_Click(object sender, EventArgs e)  // '보내기' 버튼이 클릭되면
{
    string sendData1 = textBox3.Text;  // testBox3 의 내용을 sendData1 변수에 저장
    streamWriter1.WriteLine(sendData1);  // 스트림라이터를 통해 데이타를 전송
}

button2(보내기) 버튼을 눌렀을 때 textBox3의 내용을 streamWirter1을 통해 상대방에게 전송한다.

 

 

 

전체 코드이다.

1. 서버

서버로 사용할 내컴퓨터의 IP 주소를 미리 textBox1 에 표시했고, 임의의 포트 번호도 texBox2 에 미리 표시했다.

 

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.Threading; // 추가
using System.Net; // 추가
using System.Net.Sockets; // 추가
using System.IO; // 추가

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

        StreamReader streamReader1;  // 데이타 읽기 위한 스트림리더
        StreamWriter streamWriter1;  // 데이타 쓰기 위한 스트림라이터    

        private void button1_Click(object sender, EventArgs e)  // '연결하기' 버튼이 클릭되면
        {
            Thread thread1 = new Thread(connect); // Thread 객채 생성, Form과는 별도 쓰레드에서 connect 함수가 실행됨.
            thread1.IsBackground = true; // Form이 종료되면 thread1도 종료.
            thread1.Start(); // thread1 시작.
        }

        private void connect()  // thread1에 연결된 함수. 메인폼과는 별도로 동작한다.
        {
            TcpListener tcpListener1 = new TcpListener(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text)); // 서버 객체 생성 및 IP주소와 Port번호를 할당
            tcpListener1.Start();  // 서버 시작
            writeRichTextbox("서버 준비...클라이언트 기다리는 중...");

            TcpClient tcpClient1 = tcpListener1.AcceptTcpClient(); // 클라이언트 접속 확인
            writeRichTextbox("클라이언트 연결됨...");

            streamReader1 = new StreamReader(tcpClient1.GetStream());  // 읽기 스트림 연결
            streamWriter1 = new StreamWriter(tcpClient1.GetStream());  // 쓰기 스트림 연결
            streamWriter1.AutoFlush = true;  // 쓰기 버퍼 자동으로 뭔가 처리..

            while (tcpClient1.Connected)  // 클라이언트가 연결되어 있는 동안
            {
                string receiveData1 = streamReader1.ReadLine();  // 수신 데이타를 읽어서 receiveData1 변수에 저장
                writeRichTextbox(receiveData1); // 데이타를 수신창에 쓰기                  
            }
        }

        private void writeRichTextbox(string str)  // richTextbox1 에 쓰기 함수
        {
            richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.AppendText(str + "\r\n"); }); // 데이타를 수신창에 표시, 반드시 invoke 사용. 충돌피함.
            richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.ScrollToCaret(); });  // 스크롤을 젤 밑으로.
        }

        private void button2_Click(object sender, EventArgs e)  // '보내기' 버튼이 클릭되면
        {
            string sendData1 = textBox3.Text;  // testBox3 의 내용을 sendData1 변수에 저장
            streamWriter1.WriteLine(sendData1);  // 스트림라이터를 통해 데이타를 전송
        }
    }
}



2. 클라이언트

 

 

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.Threading;  // 추가
using System.Net; // 추가
using System.Net.Sockets;  // 추가
using System.IO;  // 추가


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

        StreamReader streamReader;  // 데이타 읽기 위한 스트림리더
        StreamWriter streamWriter;  // 데이타 쓰기 위한 스트림라이터 

        private void button1_Click(object sender, EventArgs e)  // '연결하기' 버튼이 클릭되면
        {
            Thread thread1 = new Thread(connect);  // Thread 객채 생성, Form과는 별도 쓰레드에서 connect 함수가 실행됨.
            thread1.IsBackground = true;  // Form이 종료되면 thread1도 종료.
            thread1.Start();  // thread1 시작.
        }

        private void connect()  // thread1에 연결된 함수. 메인폼과는 별도로 동작한다.
        {
            TcpClient tcpClient1 = new TcpClient();  // TcpClient 객체 생성
            IPEndPoint ipEnd = new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));  // IP주소와 Port번호를 할당
            tcpClient1.Connect(ipEnd);  // 서버에 연결 요청
            writeRichTextbox("서버 연결됨...");

            streamReader = new StreamReader(tcpClient1.GetStream());  // 읽기 스트림 연결
            streamWriter = new StreamWriter(tcpClient1.GetStream());  // 쓰기 스트림 연결
            streamWriter.AutoFlush = true;  // 쓰기 버퍼 자동으로 뭔가 처리..

            while (tcpClient1.Connected)  // 클라이언트가 연결되어 있는 동안
            {
                string receiveData1 = streamReader.ReadLine();  // 수신 데이타를 읽어서 receiveData1 변수에 저장
                writeRichTextbox(receiveData1);  // 데이타를 수신창에 쓰기
            }
        }

        private void writeRichTextbox(string data)  // richTextbox1 에 쓰기 함수
        {
            richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.AppendText(data + "\r\n"); }); //  데이타를 수신창에 표시, 반드시 invoke 사용. 충돌피함.
            richTextBox1.Invoke((MethodInvoker)delegate { richTextBox1.ScrollToCaret(); });  // 스크롤을 젤 밑으로.
        }

        private void button2_Click(object sender, EventArgs e)  // '보내기' 버튼이 클릭되면
        {
            string sendData1 = textBox3.Text;  // testBox3 의 내용을 sendData1 변수에 저장
            streamWriter.WriteLine(sendData1);   // 스트림라이터를 통해 데이타를 전송
        }
    }
}

 

 

 

실행순서

 

1. 서버 폼과 클라이언트 폼을 띄우고 자신의 컴퓨터의 IP 주소를 확인해서 서버/클라이언트 모두 똑같이 기입한다.
IP 주소를 가져오는 코드도 있으나 최대한 간단히 하려고 생략했다. 검색하여 자신의 IP 주소를 확인하자.

2. Port 번호를 양쪽 똑같이 기입한다. Port 번호는 임의의 번호다. Port 번호는 똑같아야 한다.
3. 서버쪽에서 연결하기 버튼을 클릭한다. 서버가 먼저 준비가 되어야 에러가 없다.
4. 클라이언트쪽 연결하기 버튼을 클릭한다. 여기까지 하면 메세지를 주고 받을 준비가 된다.

5. 제일 밑 텍스트박스에 보내려는 메세지를 적고 보내기 버튼을 클릭하여 통신한다.

 

 

 

주의사항

위 코드는 단순하게 작성한 코드라서 특정한 상황에서 에러가 반드시 발생한다.
예외 처리도 안되어 있고, 구조적으로도 미흡한 코드일 수 있다.

 

나머지는 하나씩 차근차근 개선해 나간다면 좋을 것 같다.  


<프로젝트 파일 첨부>

728x90