그냥 가끔의 기록장

winform Tcp/IP 채팅 통신 예제 [c#] 본문

기타 개발

winform Tcp/IP 채팅 통신 예제 [c#]

the phoenix 2021. 12. 21. 14:11

난 분명 안드로이드 하던 사람인데 9월에 입사한 회사에선 c#과 winform으로 반도체 제어, 신뢰성 검사하는 SW들을 만들고 있다. 정신 차려보니 안드로이드, Kotlin, Java, Python 다 까먹은 바보가 되어 있음..흑흑 입사하고 3개월 동안 수습 통과하겠다고 딴짓하고 공부안했더니 좀 심각하다. 오늘은 마침 사수님이 휴가라 할 일이 없어서 C# Winform으로 TCP/IP 통신, 채팅 프로그램을 연습해봤다. 

 

사실 c#도 모르고 TCP/IP 통신도 잘 몰라서 https://yeolco.tistory.com/31?category=757612 이분 코드를 99%로 참고하였다.. 

 

채팅 프로그램 코드 올리는 것도 좋지만, devExpress써서 간단한 프로젝트 생성하고 폼 만드는 것도 상당히 오래 헤맸기에 기록하고자 전 과정을 올려보려 한다...

 

TCP/IP 통신

※ TCP/IP, OSI 7Layer, Socket에 대해 정리하고 참조로 걸어놓을 예정! 허나 아는 내용이 뒤죽박죽이라 일단 공부 더 하고, 정리하고 올리려 함.. (2022년 2월까지 완성하는걸 목표로)

 

WinForm TCP/IP 채팅 프로그램 [C#] 예제 

 

1. DevExpress v18.2로 프로젝트 생성하기

(1) Visual Studio 초기화면 > 새 프로젝트 만들기 클릭

혹은, 이미 프로젝트 창이 열려 있고 새로 프로젝트를 만들고 싶다면 상단바에서 파일 > 새로 만들기 > 프로젝트 누르면 됨!

 

(2) DevExpress v 18.2 Template Gallery 클릭 후 다음

(3) 프로젝트 명 입력하기 (굳이 다른건 안 입력해도 되고, 파일이 저장될 위치 바꾸고 싶으면 자유롭게 변경 가능)

(4) Template 형식 선택 (필자는 Blank 선택 후 Create Project 버튼 클릭함)

 

※ 참고: 필자 회사에서는 DevExpress를 쓰는데, 이게 굉장히 비싼걸로 알고 있다. 보통 학생들이나 회사에서 DevExpress 안 쓰는 개발자들은 Winform을 쓸거 같은데, 그 경우엔 아래 사진 참고하기! 

 

(2-1) Winform 검색 후, Windows Forms 앱 클릭 (언어 c#, Visual Basic 등등 다양하고 .NET Framework도 다른거니까 주의해서 만들기)

(3-1) 프로젝트 명 입력 (마찬가지로 파일 저장할 위치 선택 가능)

 

(이후엔 창 캡처를 못했는데, 대상 프레임워크 선택하는 창이 나온다. 필자는 .NET 5.0으로 함)

 

2. Form 디자인 생성하기 (Server, Client)

※참고: 필자 Form은 DevExpress Tool로 만든 것이므로, 기본 WinForm 컴포넌트와는 다름! 디자이너 코드 올리면 오히려 복붙하다 100프로 에러 날 것 같아서.. 디자인에서 Tool 선택하고 GUI에서 디자인 수정하는 걸 추천함. 

 

 

Server, Client 모두 동일하게 아주 아주 간단하게 생겼다. panelControl을 Form 위에 올린 후, TextBox 2개랑 SimpleButton 1개로 구성했다. 큰 TextBox는 채팅창, 밑에 작은 TextBox는 입력창 역할을 한다. 

3. Form 코드 (Server)

[Form1.cs] - SocketChatting 프로젝트 내 Server 코드

 

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.Text;
using System.Threading;
using System.Windows.Forms;

namespace SocketChatting
{
    public partial class Form1 : DevExpress.XtraEditors.XtraForm
    {
        TcpListener Server;
        TcpClient Client; 

        StreamReader Reader;
        StreamWriter Writer;
        NetworkStream stream; 

        Thread ReceiveThread;

        bool Connected;

        private delegate void AddTextDelegate(string strText);

        public Form1()
        {
            InitializeComponent();
        }
        private void Listen()
        {
            //1. chatting room에 text 추가 delegate 함수
            AddTextDelegate AddText = new AddTextDelegate(txt_server_chat.AppendText);

            //2. Server Setting
            IPAddress addr = IPAddress.Parse("127.0.0.1"); // Server Socket에 bind할 IP주소, 필자는 루프백 주소인 127.0.0.1 사용함
            int port = 8080;
            Server = new TcpListener(addr, port);
            Server.Start();

            Invoke(AddText, "Server Start"+"\r\n");

            //3. Client Setting
            Client = Server.AcceptTcpClient();
            Connected = true;

            Invoke(AddText, "Connected to Client" + "\r\n");

            //4. Stream create
            stream = Client.GetStream();
            Reader = new StreamReader(stream);
            Writer = new StreamWriter(stream);

            //5. Receive thread start
            ReceiveThread = new Thread(new ThreadStart(Receive));
            ReceiveThread.Start();
        }

        private void Receive()
        {
            AddTextDelegate AddText = new AddTextDelegate(txt_server_chat.AppendText);

            while (Connected)
            {
                // stream에 data 있을 경우 
                if (stream.CanRead)
                {
                    string receiveChat = Reader.ReadLine();
                    if (receiveChat!=null && receiveChat.Length > 0)
                        Invoke(AddText, "You: "+receiveChat+"\r\n");
                }
            }
        }

        private void button_server_send_Click(object sender, EventArgs e)
        {
            //char room에 sender message 추가
            txt_server_chat.AppendText("Me: "+txt_server_send.Text+"\r\n");

            //Client에 chat send
            Writer.WriteLine(txt_server_send.Text);
            Writer.Flush();

            txt_server_send.Clear();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            Connected = false;
            if (Reader != null) Reader.Close();
            if (Writer != null) Writer.Close();
            if (Server != null) Server.Stop();
            if (Client != null) Client.Close();
            if (ReceiveThread != null) ReceiveThread.Abort();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Thread ListenThread = new Thread(new ThreadStart(Listen));
            ListenThread.Start();
        }
    }
}

 코드를 간단히 설명하자면, Form을 load할때 Form1_Load 메서드를 호출하여 Listen 메서드를 thread에서 돌리고 있다. 즉, Server에서는 Form을 생성하고 로드하는 순간 Listen 메서드가 Thread에서 실행되고 있는 것이다. 

 

Listen 메서드 에서는 다음과 같은 순서를 진행한다.

 

  1. chatting 화면에 Text를 추가하기 위해 delegate 함수를 선언한다.

 

  2. Server를 Setting한다.

   Port번호와 IP 주소를 넣어서 Server 객체를 생성하고 Start한다. 

 

  3. Client를 Setting한다.

  Client는 Server.AcceptTcpClient()로 초기화시켜준 후, chatting 화면에도 client와 연결되었다는 Text를 띄워준다.

 

  4. Stream을 생성한다.

  이 stream은 Server와 Client가 작성하는 Message를 쓰고 읽기 위해 생성한 것이다. 

 

  5. Receive 메서드를 ReceiveThread라는 Thread에서 Start한다. 

 

Listen 메서드에서 Thread로 실행시키는 Receive 메서드는 Client로부터 메세지가 오면, chatting 화면에 출력해주기 위해 실행시키는 메서드다. client와 Server가 연결되어 있는 동안, stream을 계속 모니터링하다가 stream.CanRead가 true가 되면 (Client가 메세지를 보내 stream에 메시지가 들어온 상황) Reader가 해당 메시지를 읽어와서 chatting 화면에 출력해준다. 

 

Server에서 Client로 메세지를 보내고 싶은 경우, Send 버튼을 클릭하면 된다. 해당 Send 버튼에는 클릭 이벤트 핸들러를 추가하여 Server가 보낸 메시지를 chatting 화면에 출력하고, Client의 Stream이 읽을 수 있도록 Writer.WriteLine();을 호출한다.

 

4. Form 코드 (Client)

[Form1.cs] - SocketChattingClient 프로젝트 내 Client 코드

 

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

namespace SocketChatting
{
    public partial class Form1 : DevExpress.XtraEditors.XtraForm
    {
        TcpClient Client; 

        StreamReader Reader;
        StreamWriter Writer;
        NetworkStream stream; 

        Thread ReceiveThread;

        bool Connected;

        private delegate void AddTextDelegate(string strText);

        public Form1()
        {
            InitializeComponent();
        }

        private void button_client_send_Click(object sender, EventArgs e)
        {
            txt_client_chat.AppendText("Me: " + txt_client_send.Text + "\r\n");
            Writer.WriteLine(txt_client_send.Text);
            Writer.Flush();
            txt_client_send.Clear();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            string IP = "127.0.0.1"; // 연결할 target(Server) IP 주소, 필자는 루프백 주소인 127.0.0.1 사용함
            int port = 8080;

            Client = new TcpClient();
            Client.Connect(IP, port);

            stream = Client.GetStream();
            Connected = true;

            txt_client_chat.AppendText("Connected to Server" + "\r\n");
            Reader = new StreamReader(stream);
            Writer = new StreamWriter(stream);

            ReceiveThread = new Thread(new ThreadStart(Receive));
            ReceiveThread.Start();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            Connected = false;

            if (Reader != null) Reader.Close();
            if (Writer != null) Writer.Close();
            if (Client != null) Client.Close();
            if (ReceiveThread != null) ReceiveThread.Abort();
        }

        private void Receive()
        {
            AddTextDelegate AddText = new AddTextDelegate(txt_client_chat.AppendText);

            while (Connected)
            {
                if (stream.CanRead)
                {
                    string ReceiveData = Reader.ReadLine();
                    if (ReceiveData != null && ReceiveData.Length > 0)
                        Invoke(AddText, "You: " + ReceiveData + "\r\n");
                }
            }
        }
    }
}

Client 코드도 Server 코드와 매우 유사하다.

 

다만, Client에서는 Form을 load하는 부분이 크게 3단계로 구성된다.

 

  1. Client 객체를 생성한다.

  연결할 Target IP주소, port 번호를 Client.Connect(IP, port); 에 넣어 Target으로 연결 요청을 보낸다. 

 

  2. stream 객체를 생성한다. 

  stream 객체를 Client.GetStream()으로 초기화한 후, connected 변수를 true로 바꾼다. 이후 Reader와 Writer에 stream을 이용해 초기화해준다.

 

  3. Receive 메서드를 ReceiveThread라는 Thread에서 Start한다.

  마찬가지로 이 Receive 메서드는 Server가 보내는 메세지를 chatting 화면에 출력하기 위해 stream을 계속 모니터링 하는 메서드이다. 

 

5. visual studio에서 프로그램 여러개 동시 실행하기 

이건..그냥 편의상 프로젝트 내에 여러 프로그램이 있을 경우 이들을 동시에 실행시키는 방법이다. Client와 Server Form을 동시에 띄워서 채팅 주고받는걸 확인하려고 설정해보았다. 

 

(1) 최상위 프로젝트 우클릭 > 속성 탭 들어가기

(2) 공용 속성 > 시작 프로젝트 > 여러 개의 시작 프로젝트 버튼 선택 > 프로젝트별로 작업을 '시작'으로 변경

저렇게 설정 후, 확인까지 누르면 프로젝트 시작하자마자 여러 개의 프로그램이 동시에 시작된다.

(개인적으로 은근...꿀팁이었다)

 

6. 결과 

(맥북만 쓰다가 지금 회사라 윈도우로 결과화면 녹화하는데 윈도우는 내장된 프로그램쓰면 한 application에 대해서만 녹화가 된다...ㅠㅠ 그래서 어쩔 수 없이 녹화대신 캡처를 택했다.)

 

그림과 같이 Client, Server가 연결이되면 채팅창에 연결되었다는 Text가 출력되며, Server에서 보낸 내용이 즉각적으로 Client에 전달되어 화면에 출력된다. 

 

Reference

https://yeolco.tistory.com/31?category=757612

 

 

'기타 개발' 카테고리의 다른 글

[CS] REST API 바로 알기  (0) 2022.07.31
3. Kotlin+셀레니움 웹 크롤링  (0) 2021.07.21
2. LRU  (0) 2021.07.21
1. 쉘 스크립트 기본 명령어  (0) 2021.07.20
Comments