3. 클라이언트 구현

00·2025년 1월 7일

C#

목록 보기
134/149
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using FUP; // FUP 네임스페이스를 사용합니다.


namespace FileSender
{
    class MainApp
    {
        const int CHUNK_SIZE = 4096; // 파일 전송 시 청크 크기를 4096 바이트로 설정합니다.

        static void Main(string[] args)
        {
            if (args.Length < 2) // 명령줄 인수가 2개 미만이면 사용법을 출력하고 프로그램을 종료합니다.
            {
                Console.WriteLine(
                    "사용법 : {0} <Server IP> <File Path>",
                    Process.GetCurrentProcess().ProcessName);
                return;
            }

            string serverIp = args[0]; // 첫 번째 명령줄 인수를 서버 IP 주소로 저장합니다.
            const int serverPort = 5425; // 서버 포트 번호를 5425로 설정합니다.
            string filepath = args[1]; // 두 번째 명령줄 인수를 파일 경로로 저장합니다.

            try
            {
                IPEndPoint clientAddress = new IPEndPoint(0, 0); // 클라이언트 IP 주소와 포트 번호를 0으로 설정하여 자동으로 할당합니다.
                                                                 // (클라이언트는 OS에서 할당한 IP 주소와 포트에 바인딩합니다.)
                IPEndPoint serverAddress = new IPEndPoint(IPAddress.Parse(serverIp), serverPort); // 서버 IP 주소와 포트 번호를 사용하여 IPEndPoint 객체를 생성합니다.

                Console.WriteLine("클라이언트: {0}, 서버:{1}", clientAddress.ToString(), serverAddress.ToString()); // 클라이언트와 서버의 정보를 출력합니다.

                uint msgId = 0; // 메시지 ID를 0으로 초기화합니다.

                Message reqMsg = new Message(); // 파일 전송 요청 메시지를 생성합니다.
                reqMsg.Body = new BodyRequest() // 메시지 본문에 파일 크기와 파일 이름을 설정합니다.
                {
                    FILESIZE = new FileInfo(filepath).Length,
                    FILENAME = System.Text.Encoding.Default.GetBytes(filepath)
                };
                reqMsg.Header = new Header() // 메시지 헤더를 설정합니다.
                {
                    MSGID = msgId++, // 메시지 ID를 설정하고 1 증가시킵니다.
                    MSGTYPE = CONSTANTS.REQ_FILE_SEND, // 메시지 유형을 파일 전송 요청으로 설정합니다.
                    BODYLEN = (uint)reqMsg.Body.GetSize(), // 메시지 본문의 크기를 설정합니다.
                    FRAGMENTED = CONSTANTS.NOT_FRAGMENTED, // 메시지가 조각화되지 않았음을 나타냅니다.
                    LASTMSG = CONSTANTS.LASTMSG, // 메시지가 마지막 메시지임을 나타냅니다.
                    SEQ = 0 // 메시지 순서를 0으로 설정합니다.
                };

                TcpClient client = new TcpClient(clientAddress); // TcpClient 객체를 생성하고 클라이언트 주소로 초기화합니다.
                client.Connect(serverAddress); // 서버에 연결합니다.

                NetworkStream stream = client.GetStream(); // 네트워크 스트림을 가져옵니다.

                MessageUtil.Send(stream, reqMsg); // 파일 전송 요청 메시지를 서버에 전송합니다.
                                                  // (클라이언트는 서버에 접속 하자마자,
                                                  // 파일 전송 요청 메시지를 서버에 보냅니다.)

                Message rspMsg = MessageUtil.Receive(stream); // 서버의 응답 메시지를 수신합니다.
                                                              // (그리고 서버의 응답을 받습니다.)

                if (rspMsg.Header.MSGTYPE != CONSTANTS.REP_FILE_SEND) // 서버의 응답 메시지 유형이 파일 전송 응답이 아니면,
                                                                      // 오류 메시지를 출력하고 프로그램을 종료합니다.
                {
                    Console.WriteLine("정상적인 서버 응답이 아닙니다.{0}",
                        rspMsg.Header.MSGTYPE);
                    return;
                }

                if (((BodyResponse)rspMsg.Body).RESPONSE == CONSTANTS.DENIED) // 서버가 파일 전송을 거부하면,
                                                                              // 메시지를 출력하고 프로그램을 종료합니다.
                {
                    Console.WriteLine("서버에서 파일 전송을 거부했습니다.");
                    return;
                }

                using (Stream fileStream = new FileStream(filepath, FileMode.Open)) // 파일을 열고 FileStream 객체를 생성합니다.
                                                                                    // (서버에서 전송 요청을 수락했다면,
                                                                                    // 파일 스트림을 열어서 서버로 보낼 준비를 합니다.)
                {
                    byte[] rbytes = new byte[CHUNK_SIZE]; // 파일에서 읽어온 데이터를 저장할 바이트 배열을 생성합니다.

                    long readValue = BitConverter.ToInt64(rbytes, 0); // rbytes 배열을 long 형식으로 변환합니다. (이 부분은 코드에서 사용되지 않습니다.)

                    int totalRead = 0; // 현재까지 읽은 바이트 수를 저장할 변수를 초기화합니다.
                    ushort msgSeq = 0; // 메시지 순서를 0으로 초기화합니다.
                    byte fragmented = (fileStream.Length < CHUNK_SIZE) ? CONSTANTS.NOT_FRAGMENTED : CONSTANTS.FRAGMENTED; // 파일 크기에 따라 메시지 조각화 여부를 결정합니다.

                    while (totalRead < fileStream.Length) // 파일의 끝에 도달할 때까지 반복합니다.
                    {
                        int read = fileStream.Read(rbytes, 0, CHUNK_SIZE); // 파일에서 CHUNK_SIZE 바이트만큼 읽어서 rbytes 배열에 저장합니다.
                        totalRead += read; // 읽은 바이트 수를 totalRead에 더합니다.
                        Message fileMsg = new Message(); // 파일 데이터 전송 메시지를 생성합니다.

                        byte[] sendBytes = new byte[read]; // 읽은 데이터를 저장할 바이트 배열을 생성합니다.
                        Array.Copy(rbytes, 0, sendBytes, 0, read); // rbytes 배열의 내용을 sendBytes 배열에 복사합니다.

                        fileMsg.Body = new BodyData(sendBytes); // 메시지 본문에 파일 데이터를 설정합니다.
                        fileMsg.Header = new Header() // 메시지 헤더를 설정합니다.
                        {
                            MSGID = msgId, // 메시지 ID를 설정합니다.
                            MSGTYPE = CONSTANTS.FILE_SEND_DATA, // 메시지 유형을 파일 데이터 전송으로 설정합니다.
                            BODYLEN = (uint)fileMsg.Body.GetSize(), // 메시지 본문의 크기를 설정합니다.
                            FRAGMENTED = fragmented, // 메시지 조각화 여부를 설정합니다.
                            LASTMSG = (totalRead < fileStream.Length) ? CONSTANTS.NOT_LASTMSG : CONSTANTS.LASTMSG, // 메시지가 마지막 메시지인지 여부를 설정합니다.
                            SEQ = msgSeq++ // 메시지 순서를 설정하고 1 증가시킵니다.
                        };

                        Console.Write("#"); // "#" 문자를 출력하여 전송 진행 상황을 표시합니다.

                        MessageUtil.Send(stream, fileMsg); // 파일 데이터 전송 메시지를 서버에 전송합니다.
                                                           // (모든 파일의 내용이 전송될 때까지,
                                                           // 파일 스트림을 0x03 메시지에 담아서 서버로 보냅니다.)
                    }

                    Console.WriteLine(); // 줄 바꿈을 합니다.

                    Message rstMsg = MessageUtil.Receive(stream); // 서버의 결과 메시지를 수신합니다.
                                                                  // (서버에서 파일을 제대로 받았는지에 대한
                                                                  // 응답을 받습니다.)

                    BodyResult result = ((BodyResult)rstMsg.Body); // 결과 메시지에서 BodyResult 객체를 가져옵니다.
                    Console.WriteLine("파일 전송 성공 : {0}", result.RESULT == CONSTANTS.SUCCESS); // 파일 전송 성공 여부를 출력합니다.
                } // using 블록 종료 시 fileStream.Close() 자동 호출

                stream.Close(); // 스트림을 닫습니다.
                client.Close(); // 클라이언트를 닫습니다.
            }
            catch (SocketException e) // 소켓 예외 발생 시 처리
            {
                Console.WriteLine(e); // 예외 정보를 출력합니다.
            }

            Console.WriteLine("클라이언트를 종료합니다."); // 클라이언트 종료 메시지를 출력합니다.
        }
    }
}
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using FUP; // FUP 네임스페이스를 사용합니다.

namespace FileSender
{
    class MainApp
    {
        const int CHUNK_SIZE = 4096; // 파일 전송 시 청크 크기를 4096 바이트로 설정합니다.

        static void Main(string[] args)
        {
            if (args.Length < 2) // 명령줄 인수가 2개 미만이면 사용법을 출력하고 프로그램을 종료합니다.
            {
                Console.WriteLine(
                    "사용법 : {0} <Server IP> <File Path>",
                    Process.GetCurrentProcess().ProcessName);
                return;
            }

            string serverIp = args[0]; // 첫 번째 명령줄 인수를 서버 IP 주소로 저장합니다.
            const int serverPort = 5425; // 서버 포트 번호를 5425로 설정합니다.
            string filepath = args[1]; // 두 번째 명령줄 인수를 파일 경로로 저장합니다.

            try
            {
                IPEndPoint clientAddress = new IPEndPoint(0, 0); // 클라이언트 IP 주소와 포트 번호를 0으로 설정하여 자동으로 할당합니다.
                IPEndPoint serverAddress = new IPEndPoint(IPAddress.Parse(serverIp), serverPort); // 서버 IP 주소와 포트 번호를 사용하여 IPEndPoint 객체를 생성합니다.

                Console.WriteLine("클라이언트: {0}, 서버:{1}", clientAddress.ToString(), serverAddress.ToString()); // 클라이언트와 서버의 정보를 출력합니다.

                uint msgId = 0; // 메시지 ID를 0으로 초기화합니다.

                Message reqMsg = new Message(); // 파일 전송 요청 메시지를 생성합니다.
                reqMsg.Body = new BodyRequest() // 메시지 본문에 파일 크기와 파일 이름을 설정합니다.
                {
                    FILESIZE = new FileInfo(filepath).Length,
                    FILENAME = System.Text.Encoding.Default.GetBytes(filepath)
                };
                reqMsg.Header = new Header() // 메시지 헤더를 설정합니다.
                {
                    MSGID = msgId++, // 메시지 ID를 설정하고 1 증가시킵니다.
                    MSGTYPE = CONSTANTS.REQ_FILE_SEND, // 메시지 유형을 파일 전송 요청으로 설정합니다.
                    BODYLEN = (uint)reqMsg.Body.GetSize(), // 메시지 본문의 크기를 설정합니다.
                    FRAGMENTED = CONSTANTS.NOT_FRAGMENTED, // 메시지가 조각화되지 않았음을 나타냅니다.
                    LASTMSG = CONSTANTS.LASTMSG, // 메시지가 마지막 메시지임을 나타냅니다.
                    SEQ = 0 // 메시지 순서를 0으로 설정합니다.
                };

                TcpClient client = new TcpClient(clientAddress); // TcpClient 객체를 생성하고 클라이언트 주소로 초기화합니다.
                client.Connect(serverAddress); // 서버에 연결합니다.

                NetworkStream stream = client.GetStream(); // 네트워크 스트림을 가져옵니다.

                MessageUtil.Send(stream, reqMsg); // 파일 전송 요청 메시지를 서버에 전송합니다.

                Message rspMsg = MessageUtil.Receive(stream); // 서버의 응답 메시지를 수신합니다.

                if (rspMsg.Header.MSGTYPE != CONSTANTS.REP_FILE_SEND) // 서버의 응답 메시지 유형이 파일 전송 응답이 아니면 오류 메시지를 출력하고 프로그램을 종료합니다.
                {
                    Console.WriteLine("정상적인 서버 응답이 아닙니다.{0}",
                        rspMsg.Header.MSGTYPE);
                    return;
                }

                if (((BodyResponse)rspMsg.Body).RESPONSE == CONSTANTS.DENIED) // 서버가 파일 전송을 거부하면 메시지를 출력하고 프로그램을 종료합니다.
                {
                    Console.WriteLine("서버에서 파일 전송을 거부했습니다.");
                    return;
                }

                using (Stream fileStream = new FileStream(filepath, FileMode.Open)) // 파일을 열고 FileStream 객체를 생성합니다.
                {
                    byte[] rbytes = new byte[CHUNK_SIZE]; // 파일에서 읽어온 데이터를 저장할 바이트 배열을 생성합니다.

                    long readValue = BitConverter.ToInt64(rbytes, 0); // rbytes 배열을 long 형식으로 변환합니다. (이 부분은 코드에서 사용되지 않습니다.)

                    int totalRead = 0; // 현재까지 읽은 바이트 수를 저장할 변수를 초기화합니다.
                    ushort msgSeq = 0; // 메시지 순서를 0으로 초기화합니다.
                    byte fragmented = (fileStream.Length < CHUNK_SIZE) ? CONSTANTS.NOT_FRAGMENTED : CONSTANTS.FRAGMENTED; // 파일 크기에 따라 메시지 조각화 여부를 결정합니다.

                    while (totalRead < fileStream.Length) // 파일의 끝에 도달할 때까지 반복합니다.
                    {
                        int read = fileStream.Read(rbytes, 0, CHUNK_SIZE); // 파일에서 CHUNK_SIZE 바이트만큼 읽어서 rbytes 배열에 저장합니다.
                        totalRead += read; // 읽은 바이트 수를 totalRead에 더합니다.
                        Message fileMsg = new Message(); // 파일 데이터 전송 메시지를 생성합니다.

                        byte[] sendBytes = new byte[read]; // 읽은 데이터를 저장할 바이트 배열을 생성합니다.
                        Array.Copy(rbytes, 0, sendBytes, 0, read); // rbytes 배열의 내용을 sendBytes 배열에 복사합니다.

                        fileMsg.Body = new BodyData(sendBytes); // 메시지 본문에 파일 데이터를 설정합니다.
                        fileMsg.Header = new Header() // 메시지 헤더를 설정합니다.
                        {
                            MSGID = msgId, // 메시지 ID를 설정합니다.
                            MSGTYPE = CONSTANTS.FILE_SEND_DATA, // 메시지 유형을 파일 데이터 전송으로 설정합니다.
                            BODYLEN = (uint)fileMsg.Body.GetSize(), // 메시지 본문의 크기를 설정합니다.
                            FRAGMENTED = fragmented, // 메시지 조각화 여부를 설정합니다.
                            LASTMSG = (totalRead < fileStream.Length) ? CONSTANTS.NOT_LASTMSG : CONSTANTS.LASTMSG, // 메시지가 마지막 메시지인지 여부를 설정합니다.
                            SEQ = msgSeq++ // 메시지 순서를 설정하고 1 증가시킵니다.
                        };

                        Console.Write("#"); // "#" 문자를 출력하여 전송 진행 상황을 표시합니다.

                        MessageUtil.Send(stream, fileMsg); // 파일 데이터 전송 메시지를 서버에 전송합니다.
                    }

                    Console.WriteLine(); // 줄 바꿈을 합니다.

                    Message rstMsg = MessageUtil.Receive(stream); // 서버의 결과 메시지를 수신합니다.

                    BodyResult result = ((BodyResult)rstMsg.Body); // 결과 메시지에서 BodyResult 객체를 가져옵니다.
                    Console.WriteLine("파일 전송 성공 : {0}", result.RESULT == CONSTANTS.SUCCESS); // 파일 전송 성공 여부를 출력합니다.
                } // using 블록 종료 시 fileStream.Close() 자동 호출

                stream.Close(); // 스트림을 닫습니다.
                client.Close(); // 클라이언트를 닫습니다.
            }
            catch (SocketException e) // 소켓 예외 발생 시 처리
            {
                Console.WriteLine(e); // 예외 정보를 출력합니다.
            }

            Console.WriteLine("클라이언트를 종료합니다."); // 클라이언트 종료 메시지를 출력합니다.
        }
    }
}

코드 설명

이 C# 코드는 TCP 소켓을 사용하여 파일을 전송하는 클라이언트 애플리케이션입니다.

  • CHUNK_SIZE: 파일 전송 시 데이터를 나누는 단위인 청크 크기를 4096 바이트로 설정합니다.
  • Main 메서드:
    • 명령줄 인수에서 서버 IP 주소와 파일 경로를 가져옵니다.
    • 클라이언트와 서버의 IP 주소와 포트 번호를 사용하여 IPEndPoint 객체를 생성합니다.
    • TcpClient 객체를 생성하고 서버에 연결합니다.
    • NetworkStream을 사용하여 서버와 데이터를 주고받습니다.
    • 파일 전송 요청 메시지를 생성하고 서버에 전송합니다.
    • 서버의 응답 메시지를 수신하고, 응답이 파일 전송 거부이면 프로그램을 종료합니다.
    • 파일을 열고 CHUNK_SIZE 크기의 바이트 배열을 사용하여 파일 데이터를 읽습니다.
    • 읽은 데이터를 BodyData 객체에 저장하고, 메시지 헤더를 설정하여 파일 데이터 전송 메시지를 생성합니다.
    • 파일 데이터 전송 메시지를 서버에 전송합니다.
    • 서버의 결과 메시지를 수신하고, 파일 전송 성공 여부를 출력합니다.
    • 스트림과 클라이언트를 닫습니다.
    • 예외 처리를 통해 소켓 예외를 처리합니다.

추가 설명

  • Message, Header, BodyRequest, BodyResponse, BodyData, BodyResult, MessageUtil, CONSTANTS는 사용자 정의 클래스 및 유틸리티입니다.
  • FRAGMENTED는 파일을 여러 개의 메시지로 나누어 전송할 때 사용됩니다.
  • LASTMSG는 현재 메시지가 마지막 메시지임을 나타냅니다.
  • SEQ는 메시지의 순서를 나타냅니다.

이 코드는 파일 전송 프로토콜을 구현하는 데 필요한 기본적인 기능을 보여줍니다.

0개의 댓글