나. 제어 화면 송수신

이번에는 원격 제어를 허용한 호스트의 전체 화면을 제어하는 호스트에게 주기적으로 전송하고 수신하는 부분을 작성합시다.

먼저 이미지를 전송하는 ImageClient 클래스를 작성합시다.

public class ImageClient
{

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

  Socket sock;

생성자에서는 상대 IP와 포트 번호를 입력 인자로 받습니다.

    public ImageClient(string ip, int port)
    {

입력 인자로 받은 정보로 IPEndPoint 개체를 생성합니다.

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

그리고 TCP 소켓을 생성하고 상대 호스트 EndPoint에 연결 요청합니다.

        sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        sock.Connect(ep);
    }

이미지를 전송하는 메서드를 정의합시다.

    public bool SendImage(Image img)
    {

만약 소켓이 null이면 메서드를 종료합니다.

        if (sock == null) { return false; }

이미지를 선형적으로 보내기 위해 메모리 스트림 개체를 생성합니다.

        MemoryStream ms = new MemoryStream();

이미지를 메모리 스트림에 저장합니다.

        img.Save(ms, ImageFormat.Jpeg);

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

        byte[] data = ms.GetBuffer();
        try
        {

소켓으로 전송할 이미지 버퍼의 길이를 byte 배열 개체로 만들어서 전송합니다.

            byte[] lbuf = BitConverter.GetBytes(data.Length);
            sock.Send(lbuf);

그리고 이미지를 전송합니다.

            int trans = 0;
            while (trans < data.Length)
            {
                trans += sock.Send(data,trans,data.Length-trans,SocketFlags.None);
            }
            sock.Close();
            return true;

전송이 끝나면 소켓을 닫습니다.

            sock.Close();
            return true;
        }

예외가 발생하면 응용을 끝냅니다.

        catch{    Application.Exit();    return false;    }
    }

비동기로 이미지를 전송하는 메서드도 제공합시다. 이 부분은 대리자를 이용할게요.

    public delegate bool SendImageDele(Image img);
    public void SendImageAsync(Image img, AsyncCallback callback)
    {

SendImage 메서드를 인자로 대리자를 생성하여 대리자를 비동기로 수행합니다.

        SendImageDele dele = new SendImageDele(SendImage);
        dele.BeginInvoke(img, callback, this);
    }

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

    public void Close()
    {
        sock.Close();
        sock = null;
    }
}

이미지 수신 서버를 구현합시다. 이미지 수신 서버에서는 이미지를 수신할 때마다 폼에 이를 알려주어야 합니다. 이 부분을 위해 이벤트 처리를 할 것입니다. 먼저 이미지를 수신하였을 때 이벤트를 처리하기 위해 대리자와 이벤트 인자 클래스를 정의합시다. RecvImageEventArgs 이름의 클래스를 추가하세요.

public class RecvImageEventArgs : EventArgs
{

이벤트 인자로 상대측 IPEndPoint를 가져오기 할 수 있는 속성을 제공합시다.

    public IPEndPoint IPEndPoint
    {
        get;    private set;
    }

상대측 IP 주소 개체와 IP 주소 문자열과 포트 정보를 가져오기 할 수 있는 속성도 제공합시다.

    public IPAddress IPAddress
    {
        get{    return IPEndPoint.Address;    }
    }
    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;    }
    }

생성자에서는 상대측 IPEndPoint 정보와 이미지 개체를 입력 인자로 받아 속성을 설정합니다.

    internal RecvImageEventArgs(IPEndPoint remote_iep, Image image)
    {
        IPEndPoint = remote_iep;
        Image = image;
    }

ToString 메서드를 재정의하여 상대측 IP 정보와 이미지의 폭과 높이를 문자열로 형성하여 제공합시다.

    public override string ToString()
    {
        return string.Format("IP:{0} width:{1} height:{1}", IPAddressStr, Width, Height);
    }
}

이미지를 수신할 때 처리를 위한 대리자를 정의합시다.

public delegate void RecvImageEventHandler(object sender, RecvImageEventArgs e);

이제 이미지 서버 클래스를 정의합시다.

public class ImageServer
{

먼저 Listen 소켓과 연결 요청을 대기하고 수용하는 부분을 위한 스레드를 멤버로 선언합시다.

    Socket lis_sock;
    Thread accept_thread = null;

이미지를 수신할 때 이벤트 처리를 위해 RecvImageEventHandler 형식의 이벤트를 선언합시다.

    public event RecvImageEventHandler RecvedImage = null;

생성자에서는 IP와 포트 정보를 받습니다.

    public ImageServer(string ip, int port)
    {

먼저 전달받은 정보로 IPEndPoint 개체를 생성합니다.

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

그리고 TCP 소켓을 생성하여 네트워크 인터페이스와 결합하고 백로그 큐를 설정합니다.

        lis_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        lis_sock.Bind(ep);
        lis_sock.Listen(1);

클라이언트 연결 요청을 대기하고 수락하는 스레드를 생성하고 시작합니다.

        ThreadStart ts = new ThreadStart(AcceptLoop);
        accept_thread = new Thread(ts);
        accept_thread.Start();
    }
    void AcceptLoop()
    {
        try
        {

클라이언트 연결 요청을 대기하고 수락하는 부분은 무한 반복합니다.

            while (true)
            {

Listen 소켓의 Accept 메서드를 호출합니다.

                Socket do_sock = lis_sock.Accept();

이미지를 수신합니다. 이 부분은 별도의 메서드로 정의합시다.

                Receive(do_sock);
            }
        }
        catch{    Close();    }
    }
    void Receive(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);
        }

만약 이미지 수신 이벤트 핸들러가 있으면 이벤트 인자를 생성하여 이벤트를 전송합니다. 이벤트 인자를 생성하기 위해 수신한 byte 배열을 이미지로 변환하는 부분은 별도의 메서드를 만들기로 합시다.

        if (RecvedImage != null)
        {
            IPEndPoint iep = dosock.RemoteEndPoint as IPEndPoint;
            RecvImageEventArgs e = new RecvImageEventArgs(iep, ConvertBitmap(buffer));
            RecvedImage(this, e);
        }
        dosock.Close();
    }
    public Bitmap ConvertBitmap(byte[] data)
    {

byte 배열을 이미지로 변환하기 위해 메모리 스트림 개체를 생성합니다.

        MemoryStream ms = new MemoryStream();

메모리 스트림 개체에 수신한 이미지가 있는 버퍼의 내용을 기록합니다.

        ms.Write(data, 0, (int)data.Length);

메모리 스트림 개체를 입력 인자로 Bitmap 개체를 생성하여 반환합니다.

        Bitmap bitmap = new Bitmap(ms);
        return bitmap;
    }

이미지 서버를 닫는 메서드도 제공합시다.

    public void Close()
    {

스레드와 Listen 소켓을 해제하는 작업을 수행합니다.

       if (accept_thread != null){    accept_thread = null;    }
       if (lis_sock != null){    lis_sock.Close();    lis_sock = null;    }
    }
using System;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Drawing;

namespace 원격제어기
{
    public class ImageClient
    {
        Socket sock;
        public ImageClient(string ip, int port)
        {
            IPAddress ipaddr = IPAddress.Parse(ip);
            IPEndPoint ep = new IPEndPoint(ipaddr, port);
            sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
                                     ProtocolType.Tcp);
            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
            {
                Application.Exit();
                return false;
            }
        }
        public delegate bool SendImageDele(Image img);
        public void SendImageAsync(Image img, AsyncCallback callback)
        {
            SendImageDele dele = new SendImageDele(SendImage);
            dele.BeginInvoke(img, callback, this);
        }
        public void Close()
        {
            if (sock != null)
            {
                sock.Close();
                sock = null;
            }
        }
    }
}

[소스 9.4] ImageClient.cs

using System;
using System.Net;
using System.Drawing;
namespace 원격제어기
{
    public class RecvImageEventArgs : EventArgs
    {
        public IPEndPoint IPEndPoint
        {
            get;
            private set;
        }
        public IPAddress IPAddress
        {
            get
            {
                return IPEndPoint.Address;
            }
        }
        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;
            }
        }
        internal RecvImageEventArgs(IPEndPoint remote_iep, Image image)
        {
            IPEndPoint = remote_iep;
            Image = image;
        }
        public override string ToString()
        {
            return string.Format("IP:{0} width:{1} height:{1}", IPAddressStr, Width, Height);
        }
    }
    public delegate void RecvImageEventHandler(object sender, RecvImageEventArgs e);
}

[소스 9.5] RecvImageEventArgs.cs

using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Drawing;
using System.IO;
using System;
namespace 원격제어기
{
    public class ImageServer
    {
        Socket lis_sock;
        Thread accept_thread = null;
        public event RecvImageEventHandler RecvedImage = null;
        public ImageServer(string ip, int port)
        {
            IPAddress ipaddr = IPAddress.Parse(ip);
            IPEndPoint ep = new IPEndPoint(ipaddr, port);
            lis_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, 
                                 ProtocolType.Tcp);
            lis_sock.Bind(ep);
            lis_sock.Listen(100);
            ThreadStart ts = new ThreadStart(AcceptLoop);
            accept_thread = new Thread(ts);
            accept_thread.Start();
        }        
        void AcceptLoop()
        {
            try
            {
                while (lis_sock != null)
                {
                    Socket do_sock = lis_sock.Accept();
                    Receive(do_sock);
                }
            }
            catch{    Close();    }
        }

        void Receive(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 iep = dosock.RemoteEndPoint as IPEndPoint;
                RecvImageEventArgs e = new RecvImageEventArgs(iep, ConvertBitmap(buffer));
                RecvedImage(this, e);
            }
            dosock.Close();
        }
        public Bitmap ConvertBitmap(byte[] data)
        {
            MemoryStream ms = new MemoryStream();
            ms.Write(data, 0, (int)data.Length);
            Bitmap bitmap = new Bitmap(ms);
            return bitmap;
        }
        public void Close()
        {
            if (accept_thread != null){    accept_thread = null;    }
            if (lis_sock != null){    lis_sock.Close();    lis_sock = null;    }
        }
    }
}

[소스 9.6] ImageServer.cs