P2P 메신저 – 인증 서비스 구현 – 2. 인증 서버 및 Peer 구현[네트워크 프로그래밍 C#]

안녕하세요. 언제나 휴일입니다.

P2P 메신저 인증 서비스 구현
P2P 메신저 마지막 파트

앞에서 P2P 메신저의 숏메시지와 파일 송수신을 구현하였고 인증 서비스의 공용 라이브러리를 제작했어요.

이번에는 인증 서버 및 Peer 부분을 수정하는 작업을 진행할게요.

1. 인증 서버

인증 서버는 콘솔 응용으로 제작할게요. .NET 리모팅 서비스로 제작할 것이기 때문에 별다른 코드는 필요하지 않죠. ( 이전 강의에서 제작한 공용 라이브러리를 참조 추가합니다.)

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

namespace 인증_서버
{
    class Program
    {
        static void Main(string[] args)
        {
            HttpChannel hc = new HttpChannel(10800);
            ChannelServices.RegisterChannel(hc, false);
            RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(EHAAALib.EHAAA),
                "AAASVC",
                WellKnownObjectMode.Singleton);
            Console.ReadKey();
        }
    }
}

2. Peer 프로그램 – 컨트롤 배치

이전에 만들었던 Form1.cs를 MainForm.cs로 변경하고 StartForm을 추가한 후 컨트롤 배치하세요.

세 개의 버튼에 이벤트 핸들러를 각각 등록합니다.

lbox_user 선택 변경 이벤트 핸들러를 등록합니다.

timer1의 Tick 이벤트 핸들러를 등록합니다.

폼의 Load 이벤트 핸들러를 등록합니다.

MainForm 컨트롤 배치
MainForm 컨트롤 배치

두 개의 버튼에 클릭 이벤트 핸드러를 등록합니다.

StartForm 컨트롤 배치
StartForm 컨트롤 배치

3. Peer 프로그램 – 소스 코드

Program.cs

using System;
using System.Windows.Forms;

namespace P2P_예광탄_숏메시지
{
    static class Program
    {
        ///
        /// 해당 애플리케이션의 주 진입점입니다.
        /// 
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new StartForm());
        }
    }
}

StartForm.cs

using EHAAALib;
using System;
using System.Windows.Forms;

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

        private void btn_join_Click(object sender, EventArgs e)
        {
            if(Eaaa.Join(tbox_id.Text,tbox_pw.Text))
            {
                MessageBox.Show("가입을 축하합니다.");
            }
            else
            {
                MessageBox.Show("가입 실패입니다.");
            }
        }
        EHAAA Eaaa
        {
            get
            {
                return Activator.GetObject(
                    typeof(EHAAA),
                    "http://인증 서버 IP주소:10800/AAASVC"
                    ) as EHAAA;
            }
        }

        private void btn_login_Click(object sender, EventArgs e)
        {
            int re = Eaaa.Login(tbox_id.Text, tbox_pw.Text);
            if(re == 0)
            {
                MainForm mf = new MainForm(tbox_id.Text, tbox_pw.Text);
                mf.FormClosed += Mf_FormClosed;
                this.Visible = false;
                mf.ShowDialog();
            }
            else
            {
                MessageBox.Show(string.Format("로긴 실패 -{0}", re));
            }
        }

        private void Mf_FormClosed(object sender, FormClosedEventArgs e)
        {
            this.Visible = true;
        }
    }
}

MainForm.cs

using EHAAALib;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Windows.Forms;
using 파일_수신_서버;
using 파일_전송_클라이언트;

namespace P2P_예광탄_숏메시지
{
    public partial class MainForm : Form
    {
        string ID
        {
            get;
            set;
        }
        string PW
        {
            get;
            set;
        }
        public MainForm(string id,string pw)
        {
            ID = id;
            PW = pw;
            InitializeComponent();
        }

        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_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 = "";
        }

        private void lbox_msg_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.All;
        }

        private void lbox_msg_DragDrop(object sender, DragEventArgs e)
        {
            FileSendClient fsc = new FileSendClient(other_ip, other_fport);
            fsc.SendFileDataEventHandler += Fsc_SendFileDataEventHandler;
            string[] fs = e.Data.GetData(DataFormats.FileDrop) as string[];
            foreach(string f in fs)
            {
                fsc.SendAsync(f);
                string msg = string.Format("{0}:{1}에게 {2}파일 전송 시작", other_ip, other_fport);
                AddMessage(msg);
            }
        }

        private void Fsc_SendFileDataEventHandler(object sender, SendFileDataEventArgs e)
        {
            if(e.Remain ==0)
            {
                string msg = string.Format("{0}파일 {1}bytes 남음...", e.FileName, e.Remain);
                AddMessage(msg);
            }
        }

        Dictionary<string, FileStream> fsdic = new Dictionary<string, FileStream>();
        private void Frs_RecvFileNameEventHandler(object sender, RecvFileNameEventArgs e)
        {
            string fname = e.FileName;
            int index = fname.LastIndexOf(@"\");
            if(index !=-1)
            {
                fname = fname.Substring(index + 1);
            }
            FileStream fs = File.Create(fname);
            fsdic[e.FileName] = fs;
        }

        private void Frs_FIleLengthRecvEventHandler(object sender, FIleLengthRecvEventArgs e)
        {
            string msg = string.Format("{0}:{1}에서 파일{2},{3}bytes 전송 시작", e.RemoteEndPoint.Address, e.RemoteEndPoint.Port, e.FileName, e.Length);
            AddMessage(msg);
        }

        private void Frs_FileDataRecvEventHandler(object sender, FileDataRecvEventArgs e)
        {
            FileStream fs = fsdic[e.FileName];
            fs.Write(e.Data, 0, e.Data.Length);
            if(e.RemainLength ==0)
            {
                string msg = string.Format("{0}:{1}에서 파일{2} 전송 완료", e.RemoteEndPoint.Address, e.RemoteEndPoint.Port, e.FileName);
                AddMessage(msg);
                fs.Close();
            }
        }

        private void Frs_ClosedEventHandler(object sender, ClosedEventArgs e)
        {
            string msg = string.Format("{0}:{1}파일 전송을 마치고 연결 해제", e.IPStr, e.Port);
            AddMessage(msg);
        }

        private void Frs_AcceptedEventHandler(object sender, AcceptedEventArgs e)
        {
            string msg = string.Format("{0}:{1}파일 전송을 위해 연결", e.IPStr, e.Port);
            AddMessage(msg);
        }

        int other_fport;        
        EHAAA Eaaa
        {
            get
            {
                return Activator.GetObject(
                    typeof(EHAAA),
                    "http://인증 서버 IP주소:10800/AAASVC"
                    ) as EHAAA;
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            Eaaa.KeepAlive(ID);
        }

        private void btn_logout_Click(object sender, EventArgs e)
        {
            Eaaa.Logout(ID);
            timer1.Enabled = false;
            Close();
        }

        private void btn_withdraw_Click(object sender, EventArgs e)
        {
            Eaaa.Withdraw(ID, PW);
            timer1.Enabled = false;
            Close();
        }

        int sport = 10400;
        int fport = 10200;
        int bport = 10600;
        private void MainForm_Load(object sender, EventArgs e)
        {
            timer1.Enabled = true;
            MySSet();
            MyFSet();
            UserInfoCSServer ucbs = new UserInfoCSServer(DefAddress.ToString(), bport);
            ucbs.UserInfoEventHandler += Ucbs_UserInfoEventHandler;
            if(ucbs.Start()==false)
            {
                MessageBox.Show("허거거^^;;");
            }
            bport = ucbs.Port;
            Eaaa.KeepAlive(ID, DefAddress.ToString(), sport, fport, bport);
        }

        private void Ucbs_UserInfoEventHandler(object sender, UserInfoEventArgs e)
        {
            if(e.FPort == 0)
            {
                UserInfoEventArgs ru = null;
                foreach (UserInfoEventArgs uiea in lbox_user.Items)
                {
                    if(uiea.ID == e.ID)
                    {
                        ru = uiea;
                        break;
                    }
                }
                if(ru != null)
                {
                    lbox_user.Items.Remove(ru);
                }                
            }
            else
            {
                lbox_user.Items.Add(e);
            }
        }

        IPAddress DefAddress
        {
            get
            {
                string hname = Dns.GetHostName();
                IPHostEntry ihe = Dns.GetHostEntry(hname);
                foreach(IPAddress ipaddr in ihe.AddressList)
                {
                    if(ipaddr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    {
                        return ipaddr;
                    }
                }
                return IPAddress.Any;
            }
        }

        private void MyFSet()
        {            
            FileRecvServ frs = new FileRecvServ(DefAddress.ToString(), fport);
            frs.AcceptedEventHandler += Frs_AcceptedEventHandler;
            frs.ClosedEventHandler += Frs_ClosedEventHandler;
            frs.FileDataRecvEventHandler += Frs_FileDataRecvEventHandler;
            frs.FIleLengthRecvEventHandler += Frs_FIleLengthRecvEventHandler;
            frs.RecvFileNameEventHandler += Frs_RecvFileNameEventHandler;
            if (frs.Start() == false)
            {
                MessageBox.Show("파일 수신 서버 가동 실패");
            }
            else
            {
                fport = frs.Port;
            }
        }

        private void MySSet()
        {            
            SmsgServer sms = new SmsgServer(DefAddress.ToString(), sport);
            sms.SmsgRecvEventHandler += Sms_SmsgRecvEventHandler;
            if (sms.Start() == false)
            {
                MessageBox.Show("숏메시지 서버 가동 실패!");
            }
            else
            {
                sport = sms.Port;
            }
        }

        private void lbox_user_SelectedIndexChanged(object sender, EventArgs e)
        {
            UserInfoEventArgs uie = lbox_user.SelectedItem as UserInfoEventArgs;
            other_ip = uie.IPStr;
            other_port = uie.SPort;
            other_fport = uie.FPort;
        }
    }
}


SmsgClient.cs

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
            {
            }
        }
    }
}

SmsgRecvEventArgs.cs

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;
        }
    }
}

SmsgServer.cs

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);
                bool check = true;
                while(check)
                {
                    try
                    {
                        sock.Bind(iep);
                        check = false;
                    }
                    catch
                    {
                        Port += 2;
                        iep = new IPEndPoint(IPAddress.Parse(IPStr), Port);
                    }
                }
                
                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));
            }
        }
    }
}

UserInfoCSServer.cs

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

namespace P2P_예광탄_숏메시지
{
    public class UserInfoCSServer
    {
        public event UserInfoEventHandler UserInfoEventHandler = null;
        public string IPStr
        {
            get;
            set;
        }
        public int Port
        {
            get;
            set;
        }

        public UserInfoCSServer(string ipstr, int port)
        {
            IPStr = ipstr;
            Port = port;
        }
        Socket sock = null; 
        public bool Start()
        {
            try
            {
                sock = new Socket(AddressFamily.InterNetwork,
                    SocketType.Stream, ProtocolType.Tcp);
                IPAddress ipaddr = IPAddress.Parse(IPStr);
                IPEndPoint iep = new IPEndPoint(ipaddr, Port);
                bool check = true;
                while(check)
                {
                    try
                    {
                        sock.Bind(iep);
                        check = false;
                    }
                    catch
                    {
                        Port += 2;
                        iep = new IPEndPoint(ipaddr, Port);
                    }
                }
                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();
                DoItAsync(dosock);
            }
        }

        delegate void DoItDele(Socket dosock);
        private void DoItAsync(Socket dosock)
        {
            DoItDele dele = DoIt;
            dele.BeginInvoke(dosock, null, null);
        }
        private void DoIt(Socket dosock)
        {
            byte[] packet = new byte[1024];
            dosock.Receive(packet);
            MemoryStream ms = new MemoryStream(packet);
            BinaryReader br = new BinaryReader(ms);
            string id = br.ReadString();
            string ip = br.ReadString();
            int sport = br.ReadInt32();
            int fport = br.ReadInt32();
            br.Close();
            ms.Close();
            if(UserInfoEventHandler != null)
            {
                UserInfoEventHandler(this, new UserInfoEventArgs(id, ip, sport, fport));
            }
            dosock.Close();
        }
    }
}

UserInfoEventArgs.cs

using System;

namespace P2P_예광탄_숏메시지
{
    public delegate void UserInfoEventHandler(object sender, UserInfoEventArgs e);
    public class UserInfoEventArgs:EventArgs
    {
        public string ID
        {
            get;
            private set;
        }
        public string IPStr
        {
            get;
            private set;
        }
        public int SPort
        {
            get;
            private set;
        }
        public int FPort
        {
            get;
            private set;
        }
        public UserInfoEventArgs(string id, string ipstr, int sport,int fport)
        {
            ID = id;
            IPStr = ipstr;
            SPort = sport;
            FPort = fport;
        }
        public override string ToString()
        {
            return ID;
        }
    }
}

4. 파일 송수신 라이브러리 수정

FileRecvServ.cs 파일 수정 후

/*                                                                                                                                          https://ehpub.co.kr
 *                                                                                                                                                파일 수신 서버   
 */
using System;
using System.Diagnostics.Eventing.Reader;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace 파일_수신_서버
{
    public class FileRecvServ
    {
        const int MAX_PACK_SIZE = 1024;
        public event AcceptedEventHandler AcceptedEventHandler = null;
        public event ClosedEventHandler ClosedEventHandler = null;
        public event RecvFileNameEventHandler RecvFileNameEventHandler = null;
        public event FIleLengthRecvEventHandler FIleLengthRecvEventHandler = null;
        public event FileDataRecvEventHandler FileDataRecvEventHandler = null;
        
        public string IPStr
        {
            get;
            private set;
        }
        public int Port
        {
            get;
            private set;
        }

        public FileRecvServ(string ip,int port)
        {
            IPStr = ip;
            Port = port;
        }

        Socket sock;
        public bool Start()
        {
            try
            {
                sock = new Socket(AddressFamily.InterNetwork,
                    SocketType.Stream, ProtocolType.Tcp);

                IPAddress ipaddr = IPAddress.Parse(IPStr);
                IPEndPoint iep = new IPEndPoint(ipaddr, Port);
                bool check = true;
                while (check)
                {
                    try
                    {
                        sock.Bind(iep);
                        check = false;
                    }
                    catch
                    {
                        Port += 2;
                        iep = new IPEndPoint(ipaddr, Port);
                    }
                }

                sock.Listen(5);
                AcceptLoopAsync();
            }
            catch
            {
                return false;
            }
            return true;
        }

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

        Thread thread;
        private void DoItAsync(object dosock)
        {
            ParameterizedThreadStart pts = DoIt;
            thread = new Thread(pts);
            thread.Start(dosock);
        }
        void DoIt(object osock)
        {
            Socket dosock = osock as Socket;
            IPEndPoint rep = dosock.RemoteEndPoint as IPEndPoint;
            if(AcceptedEventHandler != null)
            {
                AcceptedEventHandler(this, new AcceptedEventArgs(rep));
            }
            string fname = RecvFileName(dosock);
            if(RecvFileNameEventHandler != null)
            {
                RecvFileNameEventHandler(this, new RecvFileNameEventArgs(fname, rep));
            }
            long length = RecvFileLength(dosock);
            if(FIleLengthRecvEventHandler !=null)
            {
                FIleLengthRecvEventHandler(this, new FIleLengthRecvEventArgs(fname, rep, length));
            }
            RecvFile(dosock, fname, length);
            dosock.Close();
            if(ClosedEventHandler != null)
            {
                ClosedEventHandler(this, new ClosedEventArgs(rep));
            }
        }

        private void RecvFile(Socket dosock, string fname, long length)
        {
            IPEndPoint rep = dosock.RemoteEndPoint as IPEndPoint;
            byte[] packet = new byte[MAX_PACK_SIZE];
            while(length>=MAX_PACK_SIZE)
            {
                int rlen = dosock.Receive(packet);
                if(FileDataRecvEventHandler != null)
                {
                    byte[] pd2 = new byte[rlen];
                    MemoryStream ms = new MemoryStream(pd2);
                    ms.Write(packet, 0, rlen);
                    FileDataRecvEventHandler(this, new FileDataRecvEventArgs(fname, rep, length, pd2));
                }
                length -= rlen;
            }
            dosock.Receive(packet, (int)length, SocketFlags.None);
            if (FileDataRecvEventHandler != null)
            {
                byte[] pd2 = new byte[length];
                MemoryStream ms = new MemoryStream(pd2);
                ms.Write(packet, 0,(int)length);
                FileDataRecvEventHandler(this, new FileDataRecvEventArgs(fname, rep, 0, pd2));
            }

        }

        private long RecvFileLength(Socket dosock)
        {
            byte[] packet = new byte[8];
            dosock.Receive(packet);
            MemoryStream ms = new MemoryStream(packet);
            BinaryReader br = new BinaryReader(ms);
            long length = br.ReadInt64();
            br.Close();
            ms.Close();
            return length;
        }

        private string RecvFileName(Socket dosock)
        {
            byte[] packet = new byte[MAX_PACK_SIZE];
            dosock.Receive(packet);
            MemoryStream ms = new MemoryStream(packet);
            BinaryReader br = new BinaryReader(ms);
            string fname =  br.ReadString();
            br.Close();
            ms.Close();
            return fname;
        }
    }
}