개발

C#으로 하는 TCP/IP 통신 -2-

워시기 2024. 12. 7. 01:36
반응형

안녕하세요. 워시기 입니다.
한동한 포스팅을 못하였네요.
이번 포스팅에서는 코드를 한번 작성해 보겠습니다.

 

TCP/IP 통신을 하기위해서는 Socket 클래스를 불러오는 방법이 있고 또는 Helper 클래스인 TcpClient, TcpListener, UdpClient 클래스가 있습니다.
저는 보통  Helper 클래스를 많이 씁니다. 예전에는 Socket 클래스를 쓰긴 했는데 TcpClient 클래스 쓰다보니 다른것 못쓰겠네요. 

상황에 따라서 쓰면 됩니다~
일단 간단한 에코 서버를 만들고 클라이언트 서버를 만들고 이후 조금더 고도화 작업을 해보겠습니다.

 

1.서버작업

새 프로젝트 만드시고 저는 윈폼으로 만들겠습니다.
Form1에 Textbox 만드시고 Textbox 이름은 Tbox_Log라고 변경해주세요.
우측 상단 끝에 화살표 모양의 아이콘 클릭하시면 MultiLine 체크박스가 나오는데 체크하셔서 멀티라인으로 변경해주세요.
멀티라인으로 변경해야 텍스트 박스가 커져서 로그창처럼 사용가능합니다.
멀티라인 체크 안되면 1줄짜리 텍스트박스여서 크기가 안커집니다.
그리고 ScrollBars 속성은 None => Vertical로 변경해 주세요.
ReadOnly 속성은 True로 변경해 주세요. 로그창이라서 글이 안써지게 하기 위함입니다.
ReadOnly = True상태면 배경색이 Window에서 Control로 변경이 됩니다.
배경색은 Window로 다시 설정하시면 됩니다.

Tbox 우측 끝 상단 클릭하면 Multiline 변경 체크박스 나옵니다. 체크하여 멀티라인으로 작업하도록 변경하세요.



Textbox 이름 변경

 

ScrollBars 속성 변경
ReadOnly 속성 True
BackColor Window로 설정

 


간단하게 만들어보려다 보니 TableLayoutPanel은 사용하지 않고 바로 Textbox만 가지고 만들었습니다.
이제 코드를 작성하도록 하겠습니다.

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.Net;
using System.Net.Sockets;
using System.Threading;


namespace TCP_Server_Simple
{
    public partial class Form1 : Form
    {
        TcpListener tcpListener;
        Thread serverThread;

        public Form1()
        {
            InitializeComponent();
            Init();
        }
        //=============================================================
        // 초기화 메서드
        //=============================================================
        void Init()
        {
            serverThread = new Thread(ServerProcess);
            serverThread.IsBackground = true;
            serverThread.Start();
        }
        //=============================================================
        // 서버 쓰레드
        //=============================================================
        void ServerProcess()
        {
            tcpListener = new TcpListener(IPAddress.Any, 16795);
            tcpListener.Start();
            LogUpdate($"서버 시작 IP: {IPAddress.Any} Port: 16795");

            while(true)
            {
                try
                {
                    TcpClient tcpClient = tcpListener.AcceptTcpClient();
                    LogUpdate($"접속 확인: {tcpClient.Client.RemoteEndPoint}");

                    NetworkStream stream = tcpClient.GetStream();

                    while (tcpClient.Connected)
                    {
                        byte[] buff = new byte[1024];
                        int cnt = stream.Read(buff, 0, buff.Length);

                        if (cnt > 0)
                        {
                            byte[] receiveBytes = new byte[cnt];
                            Array.Copy(buff, 0, receiveBytes, 0, receiveBytes.Length);
                            LogUpdate("RX " + Encoding.ASCII.GetString(receiveBytes));

                            stream.Write(receiveBytes, 0, receiveBytes.Length);

                            LogUpdate("TX " + Encoding.ASCII.GetString(receiveBytes));
                        }
                    }
                }
                catch(Exception es)
                {
                    MessageBox.Show("Error" + es.ToString());
                }
            }
        }
        //=============================================================
        // 로그 업데이트
        //=============================================================
        void LogUpdate(string msg)
        {
            if(Tbox_Log.InvokeRequired)
            {
                Tbox_Log.Invoke(new MethodInvoker(delegate () {
                    Tbox_Log.AppendText(msg + Environment.NewLine);
                }));
            }
            else
            {
                Tbox_Log.AppendText(msg + Environment.NewLine);
            }
        }
        //=============================================================
    }
}

 

대략 에코서버의 코드는 위와 같습니다. 
네트워크 통신과 쓰레드를 사용하기 위해
 

 - using System.Net;
 - using System.Net.Sockets;
 - using System.Threading;
위의 3가 네임스페이스를 불러옵니다.

폼이 시작하면서 Init 메서드가 시작하면서 수신 메서드가 쓰레드로 돌기 시작합니다.
쓰레드를 사용하지 않으면 서버가 실행이 되고 클라이언트가 접속하기전까지는 대기 상태이므로 접속전까지 UI 는 멈춰있을 것 입니다.
수신 쓰레드는 TcpListener가 시작하고 현재 IP의 16795포트로 클라이언트를 기다리고 있습니다.
클라이언트가 접속하는 순간 tcpListener.AcceptTcpClient(); 통해 클라이언트 객체가 만들어지고 송수신을 위한 stream이 생성됩니다. 이제 데이터가 수신되면 만들어진 버퍼에 데이타가 들어가게 되고 수신데이타의 길이값이 리턴됩니다.
리턴값만큼의 배열을 생성해서 배열에 복사를 해주고 이 배열이 ASCII로 송수신하기로 현재 코드에서는 정해져서 Encoding을 하면 수신데이타를 알 수 있고 동시에 송신을 진행합니다.
수신시, 송신시 해당 데이타를 UI에 표현을 하가위해  LogUpdate 메서드도 만들었습니다.
생각보다 단순해서 보시면 이해가 되실것입니다.

2. 클라이언트

이제 클라이언트 부분도 작성하도록 하겠습니다.


일단 UI는 위와 같이 사용하였습니다.
IP Address 입력과 Port 입력부분은 Textbox를 사용하였습니다. 사용자가 프로그램을 실행하였을때 각 위치를 모를까봐 기본으로 각 창의 목적을 표시하도록 하였고, 이후 해당 Textbox를 클릭하면 사라지고 사용자가 입력하도록 하였습니다.

[Connect] 버튼을 클릭하면 IP주소와 포트를 통해 서버로 접속이 됩니다.
하단에 MSG입력부분이 있고 입력후 [Send] 버튼을 클릭하면 서버로 데이타가 전송이 됩니다.
[Clear Log] 버튼은 중간에 데이터 송수신에 대한 로그창을 만들었는데 그 부분 데이터 삭제시 사용하게 될 버튼입니다.

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.Net;
using System.Net.Sockets;
using System.Threading;


namespace TCP_Client
{
    public partial class Form1 : Form
    {
        TcpClient tcpClient;            
        NetworkStream stream;
        Thread thread;

        public Form1()
        {
            InitializeComponent();
        }
        //=============================================================
        // TBox_IP 클릭이벤트 
        //=============================================================
        private void TBox_IP_MouseClick(object sender, MouseEventArgs e)
        {
            TBox_IP.Text = "";
            TBox_IP.TextAlign = HorizontalAlignment.Left;
        }
        //=============================================================
        // TBox_Port 클릭이벤트
        //=============================================================
        private void TBox_Port_MouseClick(object sender, MouseEventArgs e)
        {
            TBox_Port.Text = "";
            TBox_Port.TextAlign = HorizontalAlignment.Left;
        }
        //=============================================================
        // 연결 버튼
        //=============================================================
        private void Btn_Connect_Click(object sender, EventArgs e)
        {
            IPAddress iPAddress;
            int port = 0;

            bool ipAddrOk =IPAddress.TryParse(TBox_IP.Text, out iPAddress);
            bool portOk = int.TryParse(TBox_Port.Text, out port);


            if(ipAddrOk && portOk)
            {
                tcpClient = new TcpClient();

                try
                {
                    tcpClient.Connect(iPAddress, port);
                    stream = tcpClient.GetStream();

                    ReceiveThreadStart();

                    Btn_Connect.Text = "DisConnect";
                }
                catch(Exception es)
                {
                    MessageBox.Show("연결 실패");
                }
            }
            else
            {
                MessageBox.Show("IP주소, Port 번호 확인해주세요");
            }
        }
        //=============================================================
        // 송신 버튼
        //=============================================================
        private void Btn_Send_Click(object sender, EventArgs e)
        {
            byte[] msg = Encoding.ASCII.GetBytes(Tbox_Msg.Text);

            stream.Write(msg, 0, msg.Length);
            UpdateLog("TX " + Encoding.ASCII.GetString(msg));
        }
        //=============================================================
        // 로그창 삭제 버튼
        //=============================================================
        private void Btn_ClearLog_Click(object sender, EventArgs e)
        {
            Tbox_Log.Clear();
        }
        //=============================================================
        // 로그 업데이트
        //=============================================================
        public void UpdateLog(string msg)
        {
            if (Tbox_Log.InvokeRequired)
            {
                Tbox_Log.Invoke(new MethodInvoker(delegate () {
                    Tbox_Log.AppendText(msg + Environment.NewLine);
                }));
            }
            else
            {
                Tbox_Log.AppendText(msg + Environment.NewLine);
            }

        }
        //=============================================================
        // 수신부 쓰레드 시작
        //=============================================================
        void ReceiveThreadStart()
        {
            thread = new Thread(ReciveProcess);
            thread.IsBackground = true;
            thread.Start();
        }
        //=============================================================
        // 수신부 메서드
        //=============================================================
        void ReciveProcess()
        {
            while(tcpClient.Connected)
            {
                try
                {
                    byte[] buff = new byte[1024];
                    int cnt = stream.Read(buff, 0, buff.Length);

                    if (cnt > 0)
                    {
                        byte[] recBytes = new byte[cnt];

                        Array.Copy(buff, 0, recBytes, 0, recBytes.Length);

                        string recStr = Encoding.ASCII.GetString(recBytes);
                        UpdateLog("RX " + recStr);
                    }
                }
                catch(Exception es)
                {
                    MessageBox.Show("Error" + es.ToString());
                }
              
            }
        }
        //=============================================================
    }
}


코드는 위와 같습니다.


보시면 위에 클릭 이벤트에 대해 각각 Textbox 동작을 정의했습니다.
Connect버튼을 클릭시 IP와 Port를 통해 접속을 하고 접속을 하게 되면 TcpClient객체를 통해 스트림을 생성하고 수신을 위해 수신 쓰레드가 동작하기 시작합니다.
이제 수신부는 쓰레드로 동작하면서 언제 어떤 데이터가 서버를 통해 들어오는지 모르기때문에 수신 대기 상태이며 데이터 수신시 그 내용을 UI 로 표시합니다.

Send 버튼을 클릭하면 송신 스트림을 통해 데이터를 서버로 전송하게 됩니다.

현재 서버는 에코서버 형태로 클라이언트로 데이터를 수신시 해당 클라이언트에게 수신데이터를 그대로 전송하게 되어 있습니다.
클라이언트에서 데이터를 전송하게되면 클라이언트의 로그창에 송신, 수신 데이타가 바로 표시 되게 됩니다.



위처럼 왼쪽이 서버의 기능을 하는 프로그램입니다. 실행시 수신 대기중인 IP 와 Port를 보여주며 실행합니다.
클라이언트에서 접속을 하게 되면 서버 로그창에 접속 IP와 Port 가 표시됩니다.
이후 메세지 창에서 메세지를 보내면 서버와 클라이언트에 송수신 데이타가 표시되는 것을 알 수 있습니다.

이로써 간략한 에코 서버와 클라이언트 프로그램을 완성하였습니다.
하지만 조금더 활용도 있게 하기에는 부족한 점이 보이네요.

다음 시간에는 조금더 활용도 있도록 작성을 하도록 하겠습니다.

여기까지 읽어주셔서 감사합니다.
포스팅 마치겠습니다.



반응형