지난번에 1:1 소켓프로그래밍으로 채팅 프로그램을 만들어보았습니다.


참고 : http://yeolco.tistory.com/31


이번 시간에는 1:n 비동기 방식 채팅프로그램을 만들어보겠습니다.


각 클라이언트 소켓마다 쓰레드를 만들어서 통신하는 방법입니다.






실행화면


3명의 사용자가 동시에 사용했을때




잘 작동하는 화면입니다.





사용자 1명이 나가고 새로운 사용자가 서버에 접속하는 화면입니다.




◎ Server쪽 소스코드입니다.


using System;

using System.Collections.Generic;

using System.Net;

using System.Net.Sockets;

using System.Text;

using System.Threading;

using System.Windows.Forms;


namespace SIMT_CHATTING_SERVER

{

    public partial class Form1 : Form

    {

        TcpListener server = null; // 서버

        TcpClient clientSocket = null; // 소켓

        static int counter = 0; // 사용자 수

        string date; // 날짜 

        // 각 클라이언트 마다 리스트에 추가

        public Dictionary<TcpClient, string> clientList = new Dictionary<TcpClient, string>();


        public Form1()

        {

            InitializeComponent();

            // 쓰레드 생성

            Thread t = new Thread(InitSocket);

            t.IsBackground = true;

            t.Start();

        }


        private void InitSocket()

        {

            server = new TcpListener(IPAddress.Any, 9999); // 서버 접속 IP, 포트

            clientSocket = default(TcpClient); // 소켓 설정

            server.Start(); // 서버 시작

            DisplayText(">> Server Started");


            while (true)

            {

                try

                {

                    counter++; // Client 수 증가

                    clientSocket = server.AcceptTcpClient(); // client 소켓 접속 허용

                    DisplayText(">> Accept connection from client");


                    NetworkStream stream = clientSocket.GetStream();

                    byte[] buffer = new byte[1024]; // 버퍼

                    int bytes = stream.Read(buffer, 0, buffer.Length);

                    string user_name = Encoding.Unicode.GetString(buffer, 0, bytes);

                    user_name = user_name.Substring(0, user_name.IndexOf("$")); // client 사용자 명


                    clientList.Add(clientSocket, user_name); // cleint 리스트에 추가


                    SendMessageAll(user_name + " 님이 입장하셨습니다.", "", false); // 모든 client에게 메세지 전송


                    handleClient h_client = new handleClient(); // 클라이언트 추가

                    h_client.OnReceived += new handleClient.MessageDisplayHandler(OnReceived);

                    h_client.OnDisconnected += new handleClient.DisconnectedHandler(h_client_OnDisconnected);

                    h_client.startClient(clientSocket, clientList); 

                }

                catch (SocketException se) { break; }

                catch (Exception ex) { break; }

            }


            clientSocket.Close(); // client 소켓 닫기

            server.Stop(); // 서버 종료

        }


        void h_client_OnDisconnected(TcpClient clientSocket) // cleint 접속 해제 핸들러

        {

            if (clientList.ContainsKey(clientSocket))

                clientList.Remove(clientSocket);

        }


        private void OnReceived(string message, string user_name) // cleint로 부터 받은 데이터

        {

            if(message.Equals("leaveChat")) {

                string displayMessage = "leave user : " + user_name;

                DisplayText(displayMessage);

                SendMessageAll("leaveChat", user_name, true);

            }

            else {

                string displayMessage = "From client : " + user_name + " : " + message;

                DisplayText(displayMessage); // Server단에 출력

                SendMessageAll(message, user_name, true); // 모든 Client에게 전송

            }

        }


        public void SendMessageAll(string message, string user_name, bool flag)

        {

            foreach (var pair in clientList)

            {

                date = DateTime.Now.ToString("yyyy.MM.dd. HH:mm:ss"); // 현재 날짜 받기


                TcpClient client = pair.Key as TcpClient;

                NetworkStream stream = client.GetStream();

                byte[] buffer = null;


                if (flag)

                {

                    if(message.Equals("leaveChat"))

                        buffer = Encoding.Unicode.GetBytes(user_name + " 님이 대화방을 나갔습니다.");

                    else

                        buffer = Encoding.Unicode.GetBytes("[ " + date + " ] " + user_name + " : " + message);

                }

                else

                {

                    buffer = Encoding.Unicode.GetBytes(message);

                }


                stream.Write(buffer, 0, buffer.Length); // 버퍼 쓰기

                stream.Flush();

            }

        }


        private void DisplayText(string text) // Server 화면에 출력

        {

            if (textBox1.InvokeRequired)

            {

                textBox1.BeginInvoke(new MethodInvoker(delegate

                {

                    textBox1.AppendText(text + Environment.NewLine);

                }));

            }

            else

                textBox1.AppendText(text + Environment.NewLine);

        }

    }

}




◎ Client쪽 소스코드입니다.


using System;

using System.Net.Sockets;

using System.Text;

using System.Threading;

using System.Windows.Forms;


namespace Chatting

{

    public partial class Form2 : Form

    {

        TcpClient clientSocket = new TcpClient(); // 소켓

        NetworkStream stream = default(NetworkStream); 

        string message = string.Empty;


        public Form2()

        {

            InitializeComponent();

        }


        private void Form2_Load(object sender, EventArgs e)

        {

            try

            {

                clientSocket.Connect("192.168.0.31", 9999); // 접속 IP 및 포트

                stream = clientSocket.GetStream();

            }

            catch (Exception e2)

            {

                MessageBox.Show("서버가 실행중이 아닙니다.", "연결 실패!");

                Application.Exit();

            }


            message = "채팅 서버에 연결 되었습니다.";

            DisplayText(message);


            byte[] buffer = Encoding.Unicode.GetBytes(Form1.nickname + "$");

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

            stream.Flush();


            Thread t_handler = new Thread(GetMessage);

            t_handler.IsBackground = true;

            t_handler.Start();

        }


        private void button1_Click(object sender, EventArgs e) // 메세지 보내기

        {

            textBox2.Focus();

            byte[] buffer = Encoding.Unicode.GetBytes(textBox2.Text + "$");

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

            stream.Flush();

            textBox2.Text = "";

        }


        private void GetMessage() // 메세지 받기

        {

            while (true)

            {

                stream = clientSocket.GetStream();

                int BUFFERSIZE = clientSocket.ReceiveBufferSize;

                byte[] buffer = new byte[BUFFERSIZE];

                int bytes = stream.Read(buffer, 0, buffer.Length);


                string message = Encoding.Unicode.GetString(buffer, 0, bytes);

                DisplayText(message);

            }

        }


        private void DisplayText(string text) // Server에 메세지 출력

        {

            if (textBox1.InvokeRequired)

            {

                textBox1.BeginInvoke(new MethodInvoker(delegate

                {

                    textBox1.AppendText(text + Environment.NewLine);

                }));

            }

            else

                textBox1.AppendText(text + Environment.NewLine);

        }


        private void textBox2_KeyUp(object sender, KeyEventArgs e)

        {

            if (e.KeyCode == Keys.Enter) // 엔터키 눌렀을 때

                button1_Click(this, e);

        }


        private void Form2_FormClosing(object sender, FormClosingEventArgs e) // 폼 닫을 때 실행

        {

            byte[] buffer = Encoding.Unicode.GetBytes("leaveChat" + "$");

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

            stream.Flush();

            Application.ExitThread();

            Environment.Exit(0);

        }

    }

}





정보가 유익하셨다면 아래 공감버튼 눌러주시면 감사하겠습니다.

질문사항은 댓글로 달아주시면 성의껏 답변해드리겠습니다.




TCP/IP 는 패킷 통신 방식으로 IP(인터넷 프로토콜)와 TCP(전송제어 프로토콜)로 구성되어있습니다.

이번에는 C#으로 TCP/IP 통신 프로그램을 이용하여 간단한 채팅프로그램을 만들어보겠습니다.




* 개발 환경

개발 툴 : Visual Studio 15.0(2017)

개발 언어 : C#


* 소스 코드(서버)


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 Form1_Load(object sender, EventArgs e)

{

     Thread ListenThread = new Thread(new ThreadStart(Listen)); // 서버 시작

     ListenThread.Start();

}


private void button1_Click(object sender, EventArgs e) // 보내기 버튼

{

     textBox1.AppendText("Me : " + textBox2.Text + "\r\n"); // 화면에 출력

     Writer.WriteLine(textBox2.Text); // 보내버리기

     Writer.Flush();

     textBox2.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 Listen() // 클라이언트와 연결하기

{

     AddTextDelegate AddText = new AddTextDelegate(textBox1.AppendText);

     IPAddress addr = new IPAddress(0); // 서버 ip

     int port = 8080; // 서버 포트

     Server = new TcpListener(addr, port);

     Server.Start(); // 서버 시작

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

     Client = Server.AcceptTcpClient(); // 클라이언트 연결 수락

     Connected = true;

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

     stream = Client.GetStream(); // 클라이언트 스트림 값 받아오기

     Reader = new StreamReader(stream);

     Writer = new StreamWriter(stream);

     ReceiveThread = new Thread(new ThreadStart(Receive)); // 값을 받기 위한 쓰레드

     ReceiveThread.Start();

}


private void Receive() // 클라이언트에게 받기

{

     AddTextDelegate AddText = new AddTextDelegate(textBox1.AppendText);

     while(Connected)

     {     

          Thread.Sleep(1);

          if(stream.CanRead) // 받아온 데이터가 있다면 출력

          {

               string tempStr = Reader.ReadLine();

               if(tempStr.Length > 0)

               {

                    Invoke(AddText, "You : " + tempStr + "\r\n");

               }

          }

     }

}



* 소스 코드(클라이언트) - 서버와 거의 비슷함


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 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 button1_Click(object sender, EventArgs e) // 보내기 버튼

{

     textBox1.AppendText("Me : " + textBox2.Text + "\r\n");

     Writer.WriteLine(textBox2.Text); // 보내버리기

     Writer.Flush();

     textBox2.Clear();

}


private void Form1_Load(object sender, EventArgs e) // 폼 실행

{

     String IP = "192.168.x.x"; // 접속 할 서버 아이피를 입력

     int port = 8080; // 포트

     Client = new TcpClient();

     Client.Connect(IP, port);

     stream = Client.GetStream();

     Connected = true;

     textBox1.AppendText("Connected to Server!" + "\r\n");

     Reader = new StreamReader(stream);

     Writer = new StreamWriter(stream);

     ReceiveThread = new Thread(new ThreadStart(Receive));

     ReceiveThread.Start();

}


private void Receive() // 서버로 부터 값 받아오기

{

     AddTextDelegate AddText = new AddTextDelegate(textBox1.AppendText);

     while (Connected)

     {

          Thread.Sleep(1);

          if (stream.CanRead)

          {

               string tempStr = Reader.ReadLine();

               if (tempStr.Length > 0)

               {

                    Invoke(AddText, "You : " + tempStr + "\r\n");

               }

          }

     }

}



* 실행 화면


서버(좌측), 클라이언트(우측) 실행화면




오류나 질문사항은 댓글로 달아주세요!!




to Top