원격 제어 프로그램 7. 원격 제어 – 화면 전송

이번에는 원격 제어를 허용한 호스트의 전체 화면을 제어하는 컨트롤러에게 주기적으로 화면을 전송해야 합니다. 이 부분을 담당하는 ImageClient를 작성합시다.

public class ImageClient
{

전송에 사용할 소켓을 멤버 필드로 선언하세요.

    Socket sock;

생성자에서는 이미지를 수신할 컨트롤러의 IP와 포트 번호를 입력 인자로 받습니다.

    public ImageClient(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();//메모리 스트림 개체 생성

이미지 정보를 JPEG 포멧으로 스트림 개체에 저장합니다.

        //이미지 정보를 JPEG 포멧으로 메모리 스트림에 저장
        img.Save(ms, ImageFormat.Jpeg);

그리고 메모리 스트림에 이미지를 저장한 버퍼를 얻어옵니다.

        //메모리 스티림의 버퍼를 가져오기
        byte[] data = ms.GetBuffer();
        try
        {
            int trans = 0;

소켓으로 전송할 이미지 버퍼의 길이를 byte 배열 개체로 만드세요.

            //버퍼의 크기를 구하여 바이트 배열로 변환
            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
        {

예외가 발생하면 응용을 끝내기로 할게요. 이 프로그램은 원격 제어 프로그램을 만드는 방법을 다루고 있으면 상품 수준으로 작성한 것은 아닙니다. 상품 수준으로 만들기 위해서는 다양한 조건으로 테스트를 수행하고 비정상적인 버그들을 잡는 과정이 필요합니다.

            Application.Exit();//응용 끝내기
            return false;
        }
    }

이미지를 보내는 부분은 비동기로 처리할 수 있게 합시다. 이를 위해 대리자를 정의할게요.

    //비동기로 이미지를 보내는 메서드의 대리자
    public delegate bool SendImageDele(Image img);
    //이미지를 비동기로 전송 메서드
    public void SendImageAsync(Image img, AsyncCallback callback)
    {

대리자 개체를 생성하여 비동기로 수행할 수 있게 BeginInvoke 메서드를 호출하세요.

        //비동기로 이미지 보내는 대리자 생성
        SendImageDele dele = new SendImageDele(SendImage);
        //비동기로 이미지 전송
        dele.BeginInvoke(img, callback, this);
    }

이미지 전송 클라이언트를 닫는 메서드도 제공합시다.

    public void Close()
    {
        if (sock != null)
        {
            sock.Close();//소켓 닫기
            sock = null;
        }
    }
}

다음은 이번 실습에서 작성한 소스 코드입니다.

//ImageClient.cs

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

namespace 원격제어기
{
    /// <summary>
    /// 이미지 전송 클라이언트
    /// </summary>
    public class ImageClient
    {
        Socket sock;

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="ip">컨트롤러의 IP 주소</param>
        /// <param name="port">컨트롤러의 포트</param>
        public ImageClient(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);//연결 요청
        }

        /// <summary>
        /// 이미지 전송 메서드
        /// </summary>
        /// <param name="img">전송할 이미지</param>
        /// <returns>전송 성공 여부</returns>
        public bool SendImage(Image img)
        {
            if (sock == null)  //소켓이 없을 때
            {
                return false; 
            }

            MemoryStream ms = new MemoryStream();//메모리 스트림 개체 생성
            img.Save(ms, ImageFormat.Jpeg);//이미지 개체를 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
            {
                Application.Exit();//응용 끝내기
                return false;
            }
        }

        /// <summary>
        /// 비동기로 이미지를 보내는 메서드의 대리자
        /// </summary>
        /// <param name="img">전송할 이미지</param>
        /// <returns>이미지 전송 성공 여부</returns>
        public delegate bool SendImageDele(Image img);//비동기로 이미지를 보내는 메서드의 대리자

        /// <summary>
        /// 이미지를 비동기로 전송하는 메서드
        /// </summary>
        /// <param name="img">전송할 이미지</param>
        /// <param name="callback">이미지 전송을 완료할 때 처리할 콜백</param>
        public void SendImageAsync(Image img, AsyncCallback callback)//이미지를 비동기로 전송
        {
            SendImageDele dele = new SendImageDele(SendImage);//비동기로 이미지 보내는 대리자 생성
            dele.BeginInvoke(img, callback, this);//비동기로 이미지 전송
        }

        /// <summary>
        /// 이미지 클라이언트 닫기 메서드
        /// </summary>
        public void Close()
        {
            if (sock != null)
            {
                sock.Close();//소켓 닫기
                sock = null;
            }
        }
    }
}