화면 캡쳐/이미지 전송 클라이언트와 이미지 수신 서버 만들기

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

이번 강의는 화면 캡쳐와 이미지를 전송하는 클라이언트 및 이미지를 수신하는 서버를 만들어 볼게요.

원격 제어 프로그램을 만들다가 갑자기 엉뚱한 걸 만든다고 생각하시나요?

7~9강까지 다룬 내용은 ImageClient, RecvImageEventArgs, ImageServer입니다. 이들은 이미지를 전송하고 수신하는 서버를 만들 때 사용할 수 있어요.

이번 강의는 이들을 라이브러리 형태로 만들고 이를 이용하는 프로그램을 만들 거예요. 어떻게 보면 부분적인 테스트를 하는 것이라 볼 수도 있겠죠.

이미지 송수신 라이브러리 만들기

7~9강까지 만든 파일로 라이브러리를 만들어요.

먼저 클래스 라이브러리 프로젝트를 생성하세요..

그리고 이미 작성한 ImageClient.cs, ImageServer.cs, RecvImageEventArgs.cs 파일을 추가합니다.

그리기 요소가 있어서 System.Drawing 어셈블리를 참조 추가해야 합니다.

프로젝트 속성>빌드 로 이동 후에 XML 문서 파일을 체크하세요.

public으로 접근 지정한 모든 멤버에 XML 주석을 작성합니다.

이러한 작업은 라이브러리를 사용할 개발자가 노출한 형식과 멤버가 어떠한 의미를 갖는지 알 수 있게 해 줍니다. Visual Studio는 XML 주석을 작성한 부분을 만나면 세 줄 주석을 작성한 부분을 작은 풍선 도움말 창을 띄워 알려줍니다.

//                                                                                                                                   https://ehpub.co.kr
//                                                                                                                                   원격 제어 프로그램
//                                                                                                                         7. 이미지 전송 클라이언트

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace 원격제어_프로그램
{
    ///
    /// 이미지 전송 클라이언트
    /// 
    public class ImageClient
    {
        Socket sock;
        ///
        /// 연결 메서드
        /// 
        ///연결할 서버 측 IP주소
        ///연결할 서버 측 Port
        public void Connect(string ip,int port)
        {
            sock = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);

            IPAddress ipaddr = IPAddress.Parse(ip);
            IPEndPoint ep = new IPEndPoint(ipaddr, port);
            sock.Connect(ep);
        }

        
        ///
        /// 이미지 전송 메서드
        /// 
        ///전송할 이미지
        /// 전송 성공 여부
        public bool SendImage(Image img)
        {
            if(sock == null)
            {
                return false;
            }
            MemoryStream ms = new MemoryStream();
            img.Save(ms, ImageFormat.Jpeg);
            byte[] data = ms.GetBuffer();
            try
            {
                int trans = 0;
                byte[] lbuf = BitConverter.GetBytes(data.Length);
                sock.Send(lbuf);//전송할 이미지 길이를 먼저 전송

                while(trans <data.Length)
                {                    
                    trans += sock.Send(data, trans, data.Length - trans, SocketFlags.None);
                }
                sock.Close();
                sock = null;
                return true;
            }
            catch
            {
                return false;
            }
        }
        ///
        /// 비동기로 이미지를 전송하는 메서드
        /// 
        ///전송할 이미지
        ///이미지 전송을 완료하였을 때 처리할 콜백
        public void SendImageAsync(Image img, AsyncCallback callback)
        {
            SendImageDele dele = SendImage;
            dele.BeginInvoke(img, callback, this);
        }
        public void Close()
        {
            if(sock != null)
            {
                sock.Close();
                sock = null;
            }
        }
    }
    ///
    /// 이미지를 비동기로 전송하기 위해 정의한 대리자
    /// 
    ///전송할 이미지
    /// 이미지 전송 성공  여부
    public delegate bool SendImageDele(Image img);
}

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

namespace 원격제어_프로그램
{
    public class ImageServer
    {
        Socket lis_sock;
        public event RecvImageEventHandler RecvedImage = null;

        public ImageServer(string ip,int port)
        {
            lis_sock = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);
            IPAddress ipaddr = IPAddress.Parse(ip);
            IPEndPoint ep = new IPEndPoint(ipaddr, port);
            lis_sock.Bind(ep);
            lis_sock.Listen(5);
            lis_sock.BeginAccept(DoAccept, null);
        }
        void DoAccept(IAsyncResult result)
        {
            if(lis_sock == null)
            {
                return;
            }
            try
            {
                Socket dosock = lis_sock.EndAccept(result);
                Recevice(dosock);
                lis_sock.BeginAccept(DoAccept, null);
            }
            catch
            {
                Close();
            }
        }

        public void Close()
        {
            if(lis_sock!=null)
            {
                lis_sock.Close();
                lis_sock = null;
            }
        }

        private void Recevice(Socket dosock)
        {
            byte[] lbuf = new byte[4];
            dosock.Receive(lbuf);

            int len = BitConverter.ToInt32(lbuf, 0);
            byte[] buffer = new byte[len];
            int trans = 0;
            
            while(trans<len)
            {
                trans += dosock.Receive(buffer, trans, len - trans, SocketFlags.None);
            }

            if(RecvedImage != null)
            {
                IPEndPoint ep = dosock.RemoteEndPoint as IPEndPoint;
                RecvImageEventArgs e = new RecvImageEventArgs(ep, ConvertBitmap(buffer));
                RecvedImage(this, e);
            }

        }
        public Bitmap ConvertBitmap(byte[] data)
        {
            MemoryStream ms = new MemoryStream();
            ms.Write(data, 0, (int)data.Length);
            Bitmap bitmap = new Bitmap(ms);
            return bitmap;
        }
    }
}
using System;
using System.Drawing;
using System.Net;

namespace 원격제어_프로그램
{
    ///
    /// 이미지 수신 처리를 위한 대리자
    /// 
    ///이벤트 통보 개체(게시자)
    ///이벤트 처리 인자
    public delegate void RecvImageEventHandler(object sender, RecvImageEventArgs e);
    
    ///
    /// 이미지 수신 이벤트 인자 클래스
    /// 
    public class RecvImageEventArgs:EventArgs
    {
        ///
        /// IP 단말 - 가져오기
        /// 
        public IPEndPoint IPEndPoint
        {
            get;
            private set;
        }
        ///
        /// IP 주소 - 가져오기
        /// 
        public IPAddress IPAddress
        {
            get
            {
                return IPEndPoint.Address;
            }
        }
        ///
        /// IP 주소 문자열 - 가져오기
        /// 
        public string IPAddressStr
        {
            get
            {
                return IPAddress.ToString();
            }
        }
        ///
        /// 포트 - 가져오기
        /// 
        public int Port
        {
            get
            {
                return IPEndPoint.Port;
            }
        }
        ///
        /// 이미지 - 가져오기
        /// 
        public Image Image
        {
            get;
            private set;
        }
        ///
        /// 이미지 크기 - 가져오기
        /// 
        public Size Size
        {
            get
            {
                return Image.Size;
            }
        }
        ///
        /// 이미지 폭 - 가져오기
        /// 
        public int Width
        {
            get
            {
                return Image.Width;
            }
        }
        ///
        /// 이미지 높이 - 가져오기
        /// 
        public int Height
        {
            get
            {
                return Image.Height;
            }
        }
        ///
        /// 이미지 수신 이벤트 인자 클래스
        /// 
        ///상대측 EndPoint
        ///이미지
        public RecvImageEventArgs(IPEndPoint ep, Image image)
        {
            IPEndPoint = ep;
            Image = image;            
        }
        ///
        /// ToString 메서드
        /// 
        /// IP주소 및 이미지 크기를 문자열로 반환
        public override string ToString()
        {
            return string.Format("IP:{0} width:{1} Height:{2}", IPAddressStr, Width, Height);
        }
    }
}

이미지 수신 서버

이미지 수신 서버 프로그램을 만들어 봅시다.

Windows Form 형태로 만들어 수신한 이미지 목록을 확인할 수 있게 구현하기로 할게요.

앞에서 만든 이미지 송수신 라이브러리를 참조 추가하세요.

이미지 수신 서버 프로그램은 수신한 이미지가 있을 때 수신한 일련 번호를 보여주는 ListBox가 있습니다. 그리고 선택한 이미지를 보여줄 PictureBox 컨트롤을 배치하세요.

폼의 Load 이벤트 핸들러를 추가합니다.

ImageServer를 생성하고 IP 주소와 Port 번호를 입력인자로 전달합니다.

디폴트 IP와 10200 포트를 전달하게요.

그리고 이미지 수신 이벤트 핸들러를 등록합니다.

        ImageServer ims;
        int imgcnt = 0;
        private void Form1_Load(object sender, EventArgs e)
        {
            ims = new ImageServer(DefaultIP, 10200);
            ims.RecvedImage += Ims_RecvedImage;
        }

        private void Ims_RecvedImage(object sender, RecvImageEventArgs e)
        {
        }
        static string DefaultIP
        {
            get
            {          
                //호스트 이름 구하기
                string host_name = Dns.GetHostName();
                //호스트 엔트리 구하기
                IPHostEntry host_entry = Dns.GetHostEntry(host_name);
                //호스트 주소 목록 반복
                foreach (IPAddress ipaddr in host_entry.AddressList)
                {
                    //주소 체계가 InterNetwork일 때
                    if (ipaddr.AddressFamily == AddressFamily.InterNetwork)
                    {
                        return ipaddr.ToString();//IP 주소 문자열 반환
                    }
                }
                return string.Empty;//빈 문자열 반환
            }

이미지 수신 이벤트 핸들러에서는 수신한 이미지 일련 번호를 1 증가시킵니다.

이미지 파일을 저장하고 ListBox 항목에 추가합니다.

        private void Ims_RecvedImage(object sender, RecvImageEventArgs e)
        {
            imgcnt++;
            e.Image.Save(string.Format("{0}.bmp", imgcnt));
            lbox_fno.Items.Add(imgcnt);
        }

ListBox 선택 항목 변경 이벤트 핸들러를 등록하세요.

선택 항목을 int 형식 변수로 얻어옵니다. 그리고 PictureBox의 Image 속성을 설정하세요.

        private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            if(lbox_fno.SelectedIndex == -1)
            {
                return;
            }
            int icnt = (int)lbox_fno.SelectedItem;
            pbox.ImageLocation = string.Format("{0}.bmp", icnt);
        }

이미지 서버는 작성이 끝났습니다.

using System;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using 원격제어_프로그램;

namespace 이미지_수신_서버
{
    public partial class Form1 : Form
    {        
        public Form1()
        {
            InitializeComponent();
        }

        ImageServer ims;
        int imgcnt = 0;
        private void Form1_Load(object sender, EventArgs e)
        {
            ims = new ImageServer(DefaultIP, 10200);
            ims.RecvedImage += Ims_RecvedImage;
        }

        private void Ims_RecvedImage(object sender, RecvImageEventArgs e)
        {
            imgcnt++;
            e.Image.Save(string.Format("{0}.bmp", imgcnt));
            lbox_fno.Items.Add(imgcnt);
        }

        private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            if(lbox_fno.SelectedIndex == -1)
            {
                return;
            }
            int icnt = (int)lbox_fno.SelectedItem;
            pbox.ImageLocation = string.Format("{0}.bmp", icnt);
        }
        static string DefaultIP
        {
            get
            {          
                //호스트 이름 구하기
                string host_name = Dns.GetHostName();
                //호스트 엔트리 구하기
                IPHostEntry host_entry = Dns.GetHostEntry(host_name);
                //호스트 주소 목록 반복
                foreach (IPAddress ipaddr in host_entry.AddressList)
                {
                    //주소 체계가 InterNetwork일 때
                    if (ipaddr.AddressFamily == AddressFamily.InterNetwork)
                    {
                        return ipaddr.ToString();//IP 주소 문자열 반환
                    }
                }
                return string.Empty;//빈 문자열 반환
            }
        }
    }
}

화면 캡쳐 및 이미지 전송 클라이언트

Windows Form 프로젝트를 추가하세요.

이미지 송수신 라이브러리를 참조합니다.

IP 정보를 입력할 TextBox와 버튼을 배치합니다.

켑쳐할 화면의 좌표 X1, X2, Y1, Y2를 설정할 TrackBar를 배치합니다.

캡쳐 및 전송 버튼을 배치합니다.

캡쳐 화면을 시각화할 PictureBox 컨트롤을 배치합니다.

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

이미지 Client 개체를 생성합니다.

네 TrackBar 의 최대값을 화면의 폭과 높이로 설정합니다.

        string ip;
        ImageClient ic; 
        private void Form1_Load(object sender, EventArgs e)
        {
            ic = new ImageClient();
            tbar_x1.Maximum = tbar_x2.Maximum = Screen.PrimaryScreen.Bounds.Width;
            tbox_y1.Maximum = tbary2.Maximum = Screen.PrimaryScreen.Bounds.Height;
        }

서버 IP 등록 버튼의 Click 이벤트 핸들러를 등록합니다.

하는 일은 입력한 IP 주소를 멤버 필드에 대입하는 것입니다.

        private void but_setting_ip_Click(object sender, EventArgs e)
        {
            ip = tbox_server_ip.Text;
        }

이미지 Capute 및 전송 버튼의 Click 이벤트 핸들러를 등록하세요.

네 개의 좌표로 left, right, top, bottom 좌표를 구합니다.

        private void but_capture_send_Click(object sender, EventArgs e)
        {
            if(ic == null)
            {
                return;
            }
            int left = tbar_x1.Value;
            int right = tbar_x2.Value;
            if(left>right)
            {
                int temp = left;
                left = right;
                right = temp;
            }
            int top = tbox_y1.Value;
            int bottom = tbary2.Value;
            if(top>bottom)
            {
                int temp = top;
                top = bottom;
                bottom = temp;
            }
            int width = right - left;
            int height = bottom - top;
            if((width == 0)||(height==0))
            {
                return;
            }
            ...이어서 작성...
        }

캡쳐할 크기의 빈 Bitmap 이미지를 생성합니다.

그리고 Bitmap 개체에 그릴 Graphics 개체를 얻어옵니다. Graphics 클래스의 정적 메서드 FromImage를 호출하면 Image 개체에 그릴 개체를 반환합니다.

그리고 Graphics 개체로 화면을 캡쳐합니다. Graphics 클래스의 CopyFromScreen 메서드를 호출하여 캡쳐할 화면 좌표와 Image에 그릴 좌표 및 크기를 입력 인자로 전달합니다.

            Bitmap bitmap = new Bitmap(width, height);
            Graphics gr = Graphics.FromImage(bitmap);
            Size size = new Size(width, height);
            gr.CopyFromScreen(new Point(left, top), new Point(0, 0), size);

서버 측에 연결한 후 이미지를 전송하고 소켓을 닫습니다.

그리고 보낸 이미지를 PictureBox에 설정합니다.

            ic.Connect(ip, 10200);
            ic.SendImage(bitmap);
            ic.Close();
            pbox.Image = bitmap;

다음은 캡쳐 및 이미지 전송 클라이언트의 Form1.cs 소스 코드입니다.

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 원격제어_프로그램;

namespace 이미지_캡쳐_및_전송_클라이언트
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }


        string ip;
        ImageClient ic;
        private void Form1_Load(object sender, EventArgs e)
        {
            ic = new ImageClient();
            tbar_x1.Maximum = tbar_x2.Maximum = Screen.PrimaryScreen.Bounds.Width;
            tbox_y1.Maximum = tbary2.Maximum = Screen.PrimaryScreen.Bounds.Height;
        }

        private void but_setting_ip_Click(object sender, EventArgs e)
        {
            ip = tbox_server_ip.Text;
        }

        private void but_capture_send_Click(object sender, EventArgs e)
        {
            if(ic == null)
            {
                return;
            }
            int left = tbar_x1.Value;
            int right = tbar_x2.Value;
            if(left>right)
            {
                int temp = left;
                left = right;
                right = temp;
            }
            int top = tbox_y1.Value;
            int bottom = tbary2.Value;
            if(top>bottom)
            {
                int temp = top;
                top = bottom;
                bottom = temp;
            }
            int width = right - left;
            int height = bottom - top;
            if((width == 0)||(height==0))
            {
                return;
            }
            Bitmap bitmap = new Bitmap(width, height);
            Graphics gr = Graphics.FromImage(bitmap);
            Size size = new Size(width, height);
            gr.CopyFromScreen(new Point(left, top), new Point(0, 0), size);
            ic.Connect(ip, 10200);
            ic.SendImage(bitmap);
            ic.Close();
            pbox.Image = bitmap;
        }
    }
}

이제 두 개의 프로그램을 실행하여 테스트를 해 보세요.