P2P 메신저 – 숏 메시지 송수신 예광탄 구현하기 [C#]

안녕하세요. 언제나 휴일에 언휴예요.

이번 강의는 P2P 메신저 중에서 숏 메시지 송수신 부분을 구현할 거예요.

P2P 프로그램은 Peer와 Peer 사이의 통신을 하는 프로그램을 말하죠.

전통적인 네트워크 프로그래밍에서 사용자가 사용하는 응용을 클라이언트, 서비스 제공하는 응용을 서버라고 부르죠.

그런데 P2P 프로그램에서 사용자가 사용하는 Peer는 특정 네트워크 서비스를 서버를 거치지 않고 Peer끼리 직접 주고 받습니다.

이를 위해 특정 서비스의 서버 부분과 클라이언트 부분이 Peer 부분에 구현합니다.

이번 강의는 이러한 특징을 갖는 P2P 프로그램 중에 Short 메시지를 주고 받는 부분을 구현합니다.

1. 화면 배치

숏 메시지 송수신 예광탄 프로그램에서는 자신의 IP, Port 및 상대방 IP, Port를 입력하게 컨트롤을 배치할게요.

컨트롤 배치
[그림] 컨트롤 배치

2. 이벤트 핸들러 등록

UI 컨트롤 중에 세 개의 버튼에 클릭 이벤트 핸들러를 등록하세요.

3. 숏 메시지 수신 이벤트 인자 및 대리자 소스 코드

using System;
using System.Net;

namespace P2P_예광탄_숏메시지
{
    public delegate void SmsgRecvEventHandler(object sender, SmsgRecvEventArgs e);
    public class SmsgRecvEventArgs:EventArgs
    {
        public IPEndPoint RemoteEndPoint
        {
            get;
            private set;
        }
        public string Msg
        {
            get;
            private set;
        }
        public string IPStr
        {
            get
            {
                return RemoteEndPoint.Address.ToString();
            }
        }
        public int Port
        {
            get
            {
                return RemoteEndPoint.Port;
            }
        }
        public SmsgRecvEventArgs(IPEndPoint remote,string msg)
        {
            RemoteEndPoint = remote;
            Msg = msg;
        }
    }
}

3. 숏 메시지 서버 소스 코드

using System.IO;
using System.Net;
using System.Net.Sockets;

namespace P2P_예광탄_숏메시지
{
    public class SmsgServer
    {
        public event SmsgRecvEventHandler SmsgRecvEventHandler = null;
        public string IPStr
        {
            get;
            private set;
        }
        public int Port
        {
            get;
            private set;
        }
        public SmsgServer(string ipstr, int port)
        {
            IPStr = ipstr;
            Port = port;
        }
        
        Socket sock;
        public bool Start()
        {
            try
            {
                sock = new Socket(AddressFamily.InterNetwork,
                   SocketType.Stream, ProtocolType.Tcp);
                IPEndPoint iep = new IPEndPoint(IPAddress.Parse(IPStr), Port);
                sock.Bind(iep);
                sock.Listen(5);
                AcceptLoopAsync();
                return true;
            }
            catch
            {
                return false;
            }
        }

        delegate void AcceptDele();
        private void AcceptLoopAsync()
        {
            AcceptDele dele = AcceptLoop;
            dele.BeginInvoke(null, null);
        }
        private void AcceptLoop()
        {
            Socket dosock;
            while(true)
            {
                dosock = sock.Accept();
                DoIt(dosock);
            }
        }

        private void DoIt(Socket dosock)
        {
            IPEndPoint remote = dosock.RemoteEndPoint as IPEndPoint;
            byte[] packet = new byte[1024];
            dosock.Receive(packet);
            dosock.Close();
            MemoryStream ms = new MemoryStream(packet);
            BinaryReader br = new BinaryReader(ms);
            string msg = br.ReadString();
            br.Close();
            ms.Close();
            if(SmsgRecvEventHandler != null)
            {
                SmsgRecvEventHandler(this, new SmsgRecvEventArgs(remote,msg));
            }
        }
    }
}

4. 숏 메시지 전송 클라이언트 소스 코드

using System.IO;
using System.Net;
using System.Net.Sockets;

namespace P2P_예광탄_숏메시지
{
    public static class SmsgClient
    {
        public static void SendMsgAsync(string other_ip, int other_port, string text)
        {
            SendDele dele = SendMsg;
            dele.BeginInvoke(other_ip, other_port, text, null, null);
        }

        delegate void SendDele(string other_ip, int other_port, string text);
        public static void SendMsg(string other_ip, int other_port, string text)
        {
            try
            {
                byte[] packet = new byte[1024];
                MemoryStream ms = new MemoryStream(packet);
                BinaryWriter bw = new BinaryWriter(ms);
                bw.Write(text);
                bw.Close();
                ms.Close();

                Socket sock = new Socket(AddressFamily.InterNetwork,
                    SocketType.Stream, ProtocolType.Tcp);
                IPEndPoint iep = new IPEndPoint(IPAddress.Parse(other_ip), other_port);
                sock.Connect(iep);
                sock.Send(packet);
                sock.Close();
            }
            catch
            {
            }
        }
    }
}

5. Form1.cs 소스 코드

using System;
using System.Windows.Forms;

namespace P2P_예광탄_숏메시지
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btn_my_set_Click(object sender, EventArgs e)
        {
            string ip = tbox_my_ip.Text;
            int port = 0;
            if(int.TryParse(tbox_my_port.Text,out port)==false)
            {
                MessageBox.Show("포트를 잘못 입력하셨네요.");
                return;
            }
            SmsgServer sms = new SmsgServer(ip,port);
            sms.SmsgRecvEventHandler += Sms_SmsgRecvEventHandler;
            if(sms.Start()==false)
            {
                MessageBox.Show("숏메시지 서버 가동 실패!");
            }
            else
            {
                tbox_my_ip.Enabled = tbox_my_port.Enabled = btn_my_set.Enabled = false;
            }
        }

        private void Sms_SmsgRecvEventHandler(object sender, SmsgRecvEventArgs e)
        {
            AddMessage(string.Format("{0}:{1}→{2}", e.IPStr, e.Port, e.Msg));
        }

        delegate void MyDele(string msg);
        private void AddMessage(string msg)
        {
            if(lbox_msg.InvokeRequired)
            {
                MyDele dele = AddMessage;
                object[] objs = new object[] { msg };
                lbox_msg.BeginInvoke(dele, objs);
            }
            else
            {
                lbox_msg.Items.Add(msg);
            }
        }

        string other_ip;
        int other_port=10300;
        private void btn_other_set_Click(object sender, EventArgs e)
        {
            other_ip = tbox_other_ip.Text;
            if(int.TryParse(tbox_other_port.Text, out other_port)==false)
            {
                MessageBox.Show("포트 번호를 정수로 변환할 수 없습니다.");
            }
        }

        private void btn_send_Click(object sender, EventArgs e)
        {
            SmsgClient.SendMsgAsync(other_ip, other_port, tbox_msg.Text);
            lbox_msg.Items.Add(string.Format("{0}:{1}←{2}", other_ip, other_port, tbox_msg.Text));
            tbox_msg.Text = "";
        }
    }
}