'TCP'에 해당되는 글 1건

  1. 2017.12.20 [WPF] C# 파일전송 파일수신 예제 6
2017. 12. 20. 15:46

FTP.zip

첫 글 WPF클라이언트, C#서버의 파일송수신 예제입니다. (소켓통신)


예제라기보단 필요에 의해 구현한 코드를 공개하는것이구요, 기존 가지고있던 예제 샘플에서 필요한부분을 가져오고 검색해가면서 구현했습니다.


제가 초보 개발자이고, 또다른 저같은 초보자를위해 정리해둔 파일로써, 거의 한줄한줄에 주석을달아놓아 간단한기본지식, 약간의 구글검색하면하면 이해하기 쉬울겁니다.

참고로 주석에 나름 설명을 써뒀는데, 저도 완벽한지식이 아니고, 부족한점이 많아 틀린내용이 많을수도있습니다.


클래스가 여러개여도 프로그램의 흐름을따라보면 이해가될것입니다.


그래도 이해를 돕기위해 어느정도 설명을 하겠습니다.


먼저 공유기를 사용한다면 4038, 1010포트를 개방해주어야합니다.

프로그램 시작시 서버가 가동됩니다.(포트 4038)

서버는 보내줄 파일들의 위치를 설정할수있는 폴더경로설정 Dialog가 뜹니다.

그 폴더안의 파일들을 보낼수있습니다.


클라이언트의 아이디는 cwc 비밀번호는 1234입니다.

먼저 서버가 구동중인 아이피를 입력해주어야합니다.

EX 123.456.7.89


클라이언트가 서버에 접속 성공시 (포트 4038)

클라이언트는 서버에 파일목록을 달라는 메세지를 보냅니다.

서버는 처음 지정해둔 폴더를 스캔하여 파일목록을 전송합니다.


클라이언트가 리스트뷰에서 선택후 다운로드버튼을 누르면 어디에 다운로드시킬껀지 Dialog가 뜹니다.

클라이언트가 파일을 받을수있는 서버를 가동합니다.(포트 1010)

클라이언트가 서버에 파일을 보내라는 메세지와 어떤파일인지 보내줍니다.


서버는 파일을 보내라는 메세지를 받을시 클라이언트서버(포트1010)에 접속할수있는 클라이언트가 됩니다.

서버클라이언트는 파일을 전송합니다.


이런 흐름입니다.



먼저 서버의 솔루션입니다.

Visual Studio 2017의 Visual C# -> 콘솔 앱(.Net Framework)를 사용했습니다.

일단 참조에 System.Windows.Forms를 추가해줍시다.


저는 모든 작동은 Control을 통해 각각의 Server. Packet, FileOut에 요청하는 방식을 사용했습니다.



Main이 있는 Program.cs 입니다.

//Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FTPServer
{
    class Program
    {
        [STAThreadAttribute]        //쓰레드에서 Dialog를 사용하기위해 필요  --(파일경로설정)
        static void Main(string[] args)
        {            
            Control con = new Control();
        }
    }
}


통제를 담당하는 Control.cs 입니다.

/*
 *  Control.cs
 *  모든 동작은 Control을 경유하여 작동하도록 만듦 
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace FTPServer
{
    class Control
    {
        private Server server;      // 서버의 역할을 할 클래스
        private Packet packet;      // 메세지를 바이트화시키는 클래스
        private FileOut fileout;    // 파일 전송을 처리하는 클래스
       
        private List<string> itemlist = new List<string>();     //파일 목록을 저장할

        string dirPath = "";        //경로를 받을 공간.

        public Control()
        {
            server = new Server(this);      //서버 클래스에서 컨트롤의 함수를 호출할경우가 생길수있으니 this로 Control을 넘겨준다.
            server.ServerStart(4038);       //서버의 포트는 4038  궂이이 안건들여도됨.
        }

        public byte[] RecvData(byte[] msg, ref int type, Socket sock, string path)      //클라이언트에서 서버로 메세지를(byte) 보냈을때
                                                                                        //그 메세지를 문자열로 변환시켜 콜백함
        {        
            string str = Encoding.Default.GetString(msg);
            string[] token = str.Split('\a');       //split 기능을 찾아보면 이해가쉬움
            switch (token[0].Trim())
            {
                case "GiveMeData":      //문자가 데이터 명단 요청일때
                    dirPath = path;     //경로를 지정받음
                    type = 1;           //메세지를 보낸 클라이언트에게 다시재전송할것이라는 표시
                    str = FileScanSend();  //함수의 값을 문자열로 리턴받음
                    Console.WriteLine("파일스캔");
                    break;              //break됨으로 아래의 RecvData 함수를 리턴함
                case "GiveMeFile":      //문자가 파일을 보내라를 요청일때
                    type = 1;
                    fileout = new FileOut(token[1], sock, dirPath);     //파일을 보낸다. 1. 원하는파일 2. 클라이언트소켓 3. 파일경로
                    return null;

            }
            return Encoding.Default.GetBytes(str);      //문자열 리턴
        }

        public string FileScanSend()
        {            
            itemlist.Clear();           //리스트를 초기화
            //============================================================================== 폴더 디렉토리의 파일들의 이름을 리스트에 저장
            if (System.IO.Directory.Exists(dirPath))
            {
                System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(dirPath);
                foreach(var item in di.GetFiles())
                {
                    itemlist.Add(item.Name);
                }                
            }
            //==============================================================================
            packet = new Packet();
            return packet.HereIsData(itemlist);     //리스트를 패킷클래스에 문자열을 만든뒤 그값을 리턴시킴
        }
    }
}


데이터(메세지) 수신을 담당하고있는 Server.cs 입니다.

/*
서버에 관련된 통신서버 역할을 수행함
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;     //요놈이 있어야 아래의 FolderBrowserDialog()를 사용할수있다. 참조 에서 System.Windows.Forms를 추가한뒤 using처리를 하자.

namespace FTPServer
{
    class Server
    {
        private Socket server;      // 소캣 생성
        private Thread Thread;      // 쓰래드 생성
        private Control con;        // Server의 생성자에 넘겨온 Control을 받을수있는 Control생성

        private List<socket> socklist = new List<socket>();     //접속한 사람들의 소켓을 리스트에 저장한다.

        string dirPath = "";        // 경로를 받을 공간.

        public Server(Control conn)
        {
            con = conn;         //Control을 받는다.       
        }
        
        public void ServerStart(int port)//서버가동 Control에서 포트값을 받아옴
        {
            SocketInit(port);            //소캣 초기성정
            Thread = new Thread(new ThreadStart(AcceptThread));     //쓰레드 설정
            Thread.Start();     
        }

        private void SocketInit(int port)
        {
            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, port);      //아무 ip나받을수있게 만든다.
            server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);       //소캣의 타입을 결정함. 인터넷통신, 스트림(바이트화) TCP통신
            server.Bind(ipep);      //바인드            소캣의 정보를 가져옴?
            server.Listen(20);      //Listen            사용 대기상태로 만듦?

            Console.WriteLine("서버시작");

            //================================================== 폴더의 경로를 지정할수있게한다.
            using (var dialog = new FolderBrowserDialog())
            {
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    dirPath = dialog.SelectedPath;      //경로를 할당
                }
            }
            //==================================================
        }

        private void AcceptThread()
        {
            while (true)        //여러번 받을수있게 While문으로 항상돌린다.
            {
                //1. 클라이언트 수신
                Socket client = server.Accept();        //클라이언트의 소켓을 받는 Accept

                IPEndPoint ip = (IPEndPoint)client.RemoteEndPoint;
                Console.WriteLine("{0}주소, {1}포트 접속", ip.Address, ip.Port);

                //2. 소켓 리스트에 저장
                socklist.Add(client);

                //3. 통신스레드
                Thread thread = new Thread(new ParameterizedThreadStart(WorkThread));   //실제 통신을 담당하는 쓰레드 생성
                thread.Start(client);       //ParameterizedThreadStart를 사용하면 매개변수를 넘길수있다. -> 클라이언트 소켓을 넘김
            }
        }

        private void WorkThread(object obj)     //실제 통신을 담당하는 쓰레드
        {
            Socket sock = (Socket)obj;      //클라이언트의 소켓
            int sendtype = 0; //0이면 전체용도, 1이면 개인용도
            while (true)
            {
                byte[] msg = ReceiveData(sock);     //함수의 반환값을 받음
                if (msg == null)//받은 데이터가 0이면 종료
                    break;

                //Console.WriteLine("수신 데이터: " + Encoding.Default.GetString(msg));      //수신한 데이터가 뭔지를 볼수있다.
                byte[] str = con.RecvData(msg, ref sendtype, sock, dirPath);        //control의 RecvData 함수를 사용함  1. 메세지 2. 누구에게보낼지 타입 3. 클라이언트소켓 4. 경로
                if (str != null)                    //ref를 사용하면 저쪽함수에서 값을 바꾸면 그대로 바뀜
                {
                    if (sendtype == 0)      
                    {
                        foreach (Socket s in socklist)      //소켓 리스트의 모든 소켓에게 전송(전체전송)
                        {
                            SendData(s, str);
                        }
                    }
                    else if (sendtype == 1)     //해당 소켓에만 전송
                    {
                        SendData(sock, str);
                    }
                }
            }

            IPEndPoint ip = (IPEndPoint)sock.RemoteEndPoint;
            Console.WriteLine("{0}주소, {1}포트 접속종료", ip.Address, ip.Port);

            socklist.Remove(sock);
        }
        private void SendData(Socket sock, byte[] data)     //메세지를 보내는 부분
        {
            try
            {
                int total = 0;
                int size = data.Length;
                int left_data = size;
                int send_data = 0;

                // 전송할 데이터의 크기 전달
                byte[] data_size = new byte[4];     //전송할 데이터의 크기가 이만큼이다.
                data_size = BitConverter.GetBytes(size);
                send_data = sock.Send(data_size);       //그것을 한번 미리보낸다.

                // 실제 데이터 전송
                while (total < size)
                {
                    send_data = sock.Send(data, total, left_data, SocketFlags.None);        //그뒤 실제데이터를 보낸다
                    total += send_data;
                    left_data -= send_data;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        private byte[] ReceiveData(Socket sock)     //클라이언트 소켓으로부터 데이터를 얻어냄
        {
            try
            {
                int total = 0;
                int size = 0;
                int left_data = 0;
                int recv_data = 0;

                // 1) 수신할 데이터 크기 알아내기 
                byte[] data_size = new byte[4];
                recv_data = sock.Receive(data_size, 0, 4, SocketFlags.None);        //소켓으로부터 전송받을 데이터크기를 먼저받음
                size = BitConverter.ToInt32(data_size, 0);
                //================================================

                left_data = size;       //전송받아야할 실제 바이트크기
                byte[] data = new byte[size];

                // 실제 데이터 수신
                while (total < size)
                {
                    recv_data = sock.Receive(data, total, left_data, 0);            //소켓으로부터 실제 데이터를 전송받음
                    if (recv_data == 0) break;
                    total += recv_data;
                    left_data -= recv_data;
                }
                //한번에 전송되는게 아니라 '총 받아야할 크기' - '받고있는 크기' 를 반복함

                return data;//이게 받은 데이터이다.
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }
    }
}


메세지를 만들어주는 Packet.cs 입니다.

/*
 메세지를 합치는 역할을함
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FTPServer
{
    class Packet
    {
        public Packet()
        {

        }
        public string HereIsData(List stringlist)       //문자열 리스트를 받음
        {
            string str = null;          //리턴시킬 빈 

            str += "HereIsData" + "\a";     //클라이언트에 보낼 메세지
            str += stringlist.Count.ToString() + "@";       //파일 몰록의 개수가 몇개인지
            for(int i = 0; i < stringlist.Count; i++)       //stringlist에서 파일목록을 하나씩 불러와 문자열에 길게늘임
            {
                str += stringlist[i] + "#";
            }
            return str;
        }
    }
}


파일 송신을 담당하는 FileOut.cs 입니다.

/*
 * 파일 전송의 모든것
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Threading;
using System.Net.Sockets;
using System.Net;

namespace FTPServer
{
    class FileOut
    {        
        private string dirPath = "";        // 경로를 받을 공간.

        private Socket sock;        //클라소켓을 받을 소켓
        private IPEndPoint ip;      //아이피를 받아올 공간

        public FileOut(string str,Socket socket, string path)
        {
            dirPath = path + "\\";      //경로에 \를 붙힌다.
            dirPath = dirPath + str;    //그뒤에 파일명을 붙혀서 보낼파일의 경로를 설정. EX @"C:\temp\image.jpg
            sock = socket;  //클라소켓을 받음
            ip = (IPEndPoint)sock.RemoteEndPoint;   //그것으로 아이피를 추출
            Run(dirPath);   //파일전송시작
        }

        public void Run(string filepath)
        {
            Thread t = new Thread(new ParameterizedThreadStart(sendthread));        //쓰레드 생성
            t.IsBackground = true;
            t.Start(filepath);
        }

        private void sendthread(object o)
        {
            try
            {
                string filepath = (string)o;        //보낼 파일의 경로
                string[] p = filepath.Split('\\');
                string filename = p[p.Count() - 1];     //보낼 파일의 이름
                Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);        //소켓의 타입을 정한다.

                socket.Connect(IPAddress.Parse(ip.Address.ToString()), 1010);       //소켓 접속요청 포트번호 1010

                FileStream fileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read);           //filepath의 파일을 불러옴

                // 파일 크기 전송
                int fileLength = (int)fileStream.Length;        //파일 크기를 구함
                byte[] fileBuffer = BitConverter.GetBytes(fileLength);      //그걸 바이트화 시킨다.
                socket.Send(fileBuffer);        //파일 크기를 보낸다.

                // 파일 이름 크기 전송
                int fileNameLength = (int)filename.Length;          //파일 이름의 크기를 구한다.
                fileBuffer = BitConverter.GetBytes(fileNameLength);     //그걸 바이트화 시킨다.
                socket.Send(fileBuffer);        //파일 이름의 크기를 보낸다

                // 파일 이름 전송
                fileBuffer = Encoding.UTF8.GetBytes(filename);      //파일의 이름을 바이트화시킨다.
                socket.Send(fileBuffer);       //파일 이름을 보낸다

                // 파일 전송
                int count = fileLength / 1024 + 1;

                BinaryReader reader = new BinaryReader(fileStream);

                for (int i = 0; i < count; i++)
                {
                    fileBuffer = reader.ReadBytes(1024);

                    socket.Send(fileBuffer);
                }
                reader.Close();
                socket.Close();
            }
            catch(Exception)
            {
                Console.WriteLine("파일 전송 오류.");
            }
        }
    }
} 


이번엔 클라이언트 의 솔루션입니다.


무작정 다운받아서 사용하는걸 방지하려고 로그인창을 만들었습니다.


샤용한것은 Visual C# -> WPF 앱(.NET Framework) 입니다.

구조는 전과같이 Control에서 다른 클래스에 요청을하는 방식입니다.


로그인과 서버주소를 입력하는 Login.xaml.cs입니다.

using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace FTPProtocol
{
    /// 
    /// Login.xaml에 대한 상호 작용 논리
    /// 
    public partial class Login : Window
    {
        string DBID = "cwc";        //DB쓰기귀찮아서 만듬
        string DBPW = "1234";

        public Login()
        {
            InitializeComponent();
        }

        private void LoginButton_Click(object sender, RoutedEventArgs e)
        {
            string id = LoginId.Text;
            string pw = LoginPw.Text;
            string ip = IPbox.Text;         //서버의 ip를 받는다.
            if (DBID == id && DBPW == pw)
            {
                MainWindow mw = new MainWindow(ip);
                mw.Show();
                this.Close();
            }
            else
            {
                MessageBox.Show("아이디 비밀번호가 틀렸습니다.");
            }
        }
    }
}


로그인에서 MainWindow.xaml.cs를 호출합니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace FTPProtocol
{
    /// 
    /// MainWindow.xaml에 대한 상호 작용 논리
    /// 
    public partial class MainWindow : Window
    {
        private Control con;
        private ItemList itemlist = null;       //바인딩시킬 리스트명단초기화

        private string SelectedItem="";         //리스트에서 선택항목
        public string ip="";                    //서버ip 를 받을 공간

        public MainWindow(string ipp)
        {
            InitializeComponent();
            ip = ipp;            //서버ip 받음
            con = new Control(this);    //Control에서 Mainwindow의 함수변환 혹은 UI간섭을 할수있음으로 넘겨준다.
            itemlist = (ItemList)FindResource("itemlist");      //이 처리를 해줘야 MainWIndow.xaml에서 항목의값을 찾아와 넣게된다.
            //바인딩을하면 리스트가변동되면 그대로 자멜코드에 적용되고 그것을 다시불러오는구조이다.
        }

        public void ListUpdate(string number, string str)
        {
            string[] token = str.Split('#');
            
            for (int i = 0; i < int.Parse(number); i++)
            {
                int indexs = i + 1;
                try
                {            
                    Dispatcher.Invoke(new Action(() => { itemlist.Add(new Item() { Index = indexs, Items=token[i] }); }));                    
                }
                catch(Exception)
                {
                    MessageBox.Show("리스트파일 추가에러");
                }
            }
        }

        private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)      //리스트뷰의 항목이 바뀔때
        {
            SelectedItem = itemlist[listview1.SelectedIndex].Items;         //아이템리스트에서 그항목의 index번째의 아이템(파일명)을 가져옴
            //MessageBox.Show(itemlist[listview1.SelectedIndex].Items);
        }

        private void Window_Closed(object sender, EventArgs e)
        {
            con.Stop();//쓰레드 소켓등을 전부닫게만들 코드
        }

        private void DownloadButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {                
                con.Send(SelectedItem);     //다운로드를 요청한다.
            }
            catch(Exception)
            {
                MessageBox.Show("다운로드 실패");
            }
        }
    }
}


동작명령을 담당하는 Control.cs입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;

namespace FTPProtocol
{
    class Control
    {
        private Client client;
        private FileInput fileinput;

        string ip = "";     //서버 아이피 정보
        const int port = 4038;      //접속할 서버 포트번호

        private MainWindow mw;      //넘겨받은 MainWindow를 담을 공간

        public Control(MainWindow mww)
        {
            mw = mww;       //메인윈도우 넘겨받음
            ip = mw.ip;     //아이피 넘겨받음
            client = new Client(RecvData);      //클라이언트에서 Control의 RecvData함수를 콜백함수로 사용하기위해 넘겨줌
            client.ClientStart(ip, port);       //서버에 접속 시작(클라이언트 시작)
            fileinput = new FileInput();        
            GiveMeData();                       //클라가 생성되면 파일목록(데이터)를 보내달라 요청한다.
        }

        public void GiveMeData()
        {
            byte[] value = Packet.GiveMeData();         //패킷에서 메세지를 만들어 바이트화시킨다.
            client.SendData(value);                     //서버에 메세지를 보냄
        }

        #region 콜백
        public void RecvData(byte[] msg)
        {
            string str = Encoding.Default.GetString(msg);           //서버가 보낸 메세지를 문자열화 시킴
            string[] token = str.Split('\a');
            switch (token[0].Trim())
            {
                case "HereIsData":      //데이터를 받았다면
                    Split(token[1]);
                    break;
            }
        }
        #endregion

        public void Split(string str)
        {
            string[] token = str.Split('@');
            mw.ListUpdate(token[0], token[1]);      //token[0]엔 파일목록의 개수, token[1]엔 파일의 이름이 나열되어있다.
        }
      
        public void Stop()
        {
            client.ClientStop();        //클라이언트의 소켓을 닫음
            fileinput.Stop();           //파일 받는 서버의 소켓과 쓰레드를 닫음
        }
        
        public void Send(string SelectedItem)       //다운로드 요청을 한다.
        {
            fileinput.Start();                      //파일을 받을 서버 시작
            byte[] value = Packet.GiveMeFile(SelectedItem);     //SelectedItem 파일을 보내달라 요청하는 메세지를 바이트화시킨다. 
            client.SendData(value);                 //서버에 그 메세지를 보냄
        }
    }
}


서버와의 클라이언트역할을 담당하는 Client.cs입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows;

namespace FTPProtocol
{
    public delegate void RecvDataDel(byte[] str);       //델리게이트를 만들어서 컨트롤의 함수를 받는다. 델리게이트는 그함수와 매개변수가 일치해야함

    class Client
    {
        private Socket sock;
        private Thread Thread;
        private RecvDataDel RecvData;       //델리게이트 선언

        public Client(RecvDataDel del)
        {
            RecvData = del; //델리게이트로 RecvData함수를 받음
        }

        public void ClientStart(string ip, int port)        //받은 ip와 포트를  기반으로 클라이언트 시작
        {
            SockInit(ip, port);         //소켓 초기화

            Thread = new Thread(new ThreadStart(WorkThread));       //실제 일하는 메세지 수신쓰레드
            Thread.Start();

        }
        private void SockInit(string ip, int port)
        {
            try
            {
                IPEndPoint ipep = new IPEndPoint(IPAddress.Parse(ip), port);

                sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);     //소켓 종류 설정

                sock.Connect(ipep);    //서버에 연결을 요청한다.
            }
            catch (Exception)
            {
                MessageBox.Show("서버 연결에 실패했습니다. 아이피를 확인해주세요. EX 192.123.0.44");
            }
        }

        private void WorkThread()
        {
            while (true)
            {
                byte[] msg = ReceiveData(sock);             //서버소켓으로부터 메세지를 받음
                if (msg == null)                //아무내용없으면 종료
                    break;
                //Console.WriteLine("수신 데이터: " + Encoding.Default.GetString(msg));
                RecvData(msg);
            }
            Console.WriteLine("서버가 종료되었습니다");
        }

        private byte[] ReceiveData(Socket sock)     //서버소켓으로부터 메세지를 받는곳
        {
            try
            {
                int total = 0;
                int size = 0;
                int left_data = 0;
                int recv_data = 0;

                // 1) 수신할 데이터 크기 알아내기 
                byte[] data_size = new byte[4];
                recv_data = sock.Receive(data_size, 0, 4, SocketFlags.None);
                size = BitConverter.ToInt32(data_size, 0);
                //================================================

                left_data = size;           //전송받아야할 실제 받아야할 크기
                byte[] data = new byte[size];

                // 실제 데이터 수신
                while (total < size)
                {
                    recv_data = sock.Receive(data, total, left_data, 0);            //소켓으로부터 실제 데이터를 전송받음
                    if (recv_data == 0) break;
                    total += recv_data;
                    left_data -= recv_data;
                }
                //한번에 전송되는게 아니라 '총 받아야할 크기' - '받고있는 크기' 를 반복함
                return data;        //받은 데이터 내용(바이트)
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return null;
            }
        }

        public void SendData(byte[] msg)
        {
            SendData(sock, msg);        //서버에 메세지를 보내는함수 실행
        }

        private void SendData(Socket sock, byte[] data)
        {
            try
            {
                int total = 0;
                int size = data.Length;
                int left_data = size;
                int send_data = 0;

                // 전송할 데이터의 크기 전달
                byte[] data_size = new byte[4];
                data_size = BitConverter.GetBytes(size);
                send_data = sock.Send(data_size);       //전송해야할 데이터

                // 실제 데이터 전송
                while (total < size)
                {
                    send_data = sock.Send(data, total, left_data, SocketFlags.None);
                    total += send_data;
                    left_data -= send_data;
                }                
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public void ClientStop()
        {
            sock.Close();       //클라이언트 소켓을 닫음
        }
    }
}


파일 수신서버역할을 담당하는 FileInput.cs입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using Microsoft.Win32;
using System.Windows.Forms;
namespace FTPProtocol
{
    class FileInput
    {
        Thread t;
        Socket listensock = null;
        Socket socket = null;
        BinaryWriter writer = null;
        Socket sock = null;

        string selected = "";

        public FileInput()
        {
            SockInit();         //소켓 초기화
        }
        public void Start()     //다운로드 버튼을눌러 다운받는 서버가 열리면
        {
            using (var dialog = new FolderBrowserDialog())      //어디다 저장할지 저장위치를 검색할 수 있게한다.
            {
                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    selected = dialog.SelectedPath;
                }
            }

            t = new Thread(new ThreadStart(AcceptThread));      //소켓 수신쓰레드
            t.Start();
        }
        public void SockInit()
        {
            listensock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 1010);          //아무나들어올수있는 1010포트의 서버를 연다.

            listensock.Bind(endPoint);
            listensock.Listen(10);
        }    
        private void AcceptThread()
        {
            while (true)
            {
                socket = listensock.Accept();       //수신한 서버의 파일전송 소켓
                Thread thread = new Thread(
                    new ParameterizedThreadStart(RecvThread));
                thread.Start(socket);
            }
        }

        private void RecvThread(object obj)
        {
            sock = (Socket)obj;     //수신한 서버의 파일전송 소켓
            try
            {        
                    // 파일 사이즈
                    byte[] buffer = new byte[4];
                    sock.Receive(buffer);       //파일 크기를 받는다.
                    int fileLength = BitConverter.ToInt32(buffer, 0);

                    // 파일 이름 사이즈
                    buffer = new byte[4];
                    sock.Receive(buffer);       //파일 이름의 크기를 받는다.
                    int fileNameLength = BitConverter.ToInt32(buffer, 0);

                    // 파일 이름
                    buffer = new byte[fileNameLength];
                    sock.Receive(buffer);       //파일 이름을 받는다.
                    string fileName = Encoding.Default.GetString(buffer);
                    //MessageBox.Show("  [2] 파일이름 " + fileName);

                    // 파일
                    buffer = new byte[1024];
                    int totalLength = 0;
                    //파일이름 = 디렉토리 + 파일이름

                    string dirName = selected + "\\" + fileName;        //위에서 선택한 경로에 파일이름을 붙힘
                    FileStream fileStream = new FileStream(
                        dirName, FileMode.Create, FileAccess.Write);        //파일 생성모드

                    writer = new BinaryWriter(fileStream);

                    while (totalLength < fileLength)        //전체 파일을 받는다.
                    { 
                        int receiveLength = sock.Receive(buffer);

                        writer.Write(buffer, 0, receiveLength);

                        totalLength += receiveLength;
                    }

                MessageBox.Show(" 파일 수신 완료 ");                
                }
                catch (Exception ex)
                {
                    MessageBox.Show("[에러]" + ex.Message);
                    MessageBox.Show(" 파일 수신 오류 ");
                }
                finally                                                                                                                                                                                                                                                                                    
                {
                    if (writer != null) writer.Close();     //1회성이니 마지막에 혹시모를 소켓과 writer를 닫는다.
                    if (sock != null) sock.Close();     
                }
            
        }
        public void Stop()
        {           //윈도우 폼 종료시(소멸자처리) 관련된 모든것을 중지시킨다.
            if (writer != null) writer.Close();
            if (sock != null) sock.Close();
            if (socket != null) socket.Close();
            if (listensock != null) listensock.Close();
            t.Abort();      //쓰레드 종료
        }
    }
}



Xaml코드나 사용한 Item클래스등은 별 의미가없으니 직접다운받아서 보는것을 추천합니다.


도움이되셨다면 댓글꾺










Posted by 하루나비꿀