using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using FUP; // FUP 네임스페이스를 사용합니다.
namespace FileReceiver
{
class MainApp
{
static void Main(string[] args)
{
if (args.Length < 1) // 명령줄 인수가 1개 미만이면 사용법을 출력하고 프로그램을 종료합니다.
{
Console.WriteLine("사용법 : {0} <Directory>",
Process.GetCurrentProcess().ProcessName);
return;
}
uint msgId = 0; // 메시지 ID를 저장할 변수입니다.
string dir = args[0]; // 첫 번째 명령줄 인수를 디렉터리 경로로 사용합니다.
if (Directory.Exists(dir) == false) // 디렉터리가 존재하지 않으면 디렉터리를 생성합니다.
Directory.CreateDirectory(dir);
const int bindPort = 5425; // 서버가 바인딩할 포트 번호입니다.
// 서버 포트는 5425입니다.
TcpListener server = null; // TcpListener 객체를 저장할 변수입니다.
try
{
IPEndPoint localAddress = new IPEndPoint(0, bindPort); // 모든 IP 주소에 대해 bindPort 포트를 사용하는 IPEndPoint 객체를 생성합니다.
// 이처럼 IP 주소를 '0'으로 입력하면, 127.0.0.1 뿐만 아니라,
// OS에 할당되어 있는 어떤 주소로도 서버에 접속할 수 있습니다.
server = new TcpListener(localAddress); // TcpListener 객체를 생성합니다.
server.Start(); // 서버를 시작합니다.
Console.WriteLine("파일 업로드 서버 시작... "); // 서버 시작 메시지를 출력합니다.
while (true) // 클라이언트의 연결 요청을 계속해서 수신합니다.
{
TcpClient client = server.AcceptTcpClient(); // 클라이언트의 연결 요청을 수락합니다.
Console.WriteLine("클라이언트 접속 : {0} ",
((IPEndPoint)client.Client.RemoteEndPoint).ToString()); // 클라이언트의 IP 주소와 포트 번호를 출력합니다.
NetworkStream stream = client.GetStream(); // 클라이언트와 통신할 수 있는 NetworkStream 객체를 가져옵니다.
Message reqMsg = MessageUtil.Receive(stream); // 스트림에서 메시지를 읽어옵니다.
// (클라이언트가 보내온 파일 전송 요청 메시지를 수신합니다)
if (reqMsg.Header.MSGTYPE != CONSTANTS.REQ_FILE_SEND) // 메시지 유형이 REQ_FILE_SEND가 아니면 연결을 닫고 다음 클라이언트를 기다립니다.
{
stream.Close();
client.Close();
continue;
}
BodyRequest reqBody = (BodyRequest)reqMsg.Body; // 메시지 본문을 BodyRequest 타입으로 변환합니다.
Console.WriteLine("파일 업로드 요청이 왔습니다. 수락하시겠습니까? yes/no"); // 사용자에게 파일 업로드 요청 수락 여부를 묻습니다.
string answer = Console.ReadLine(); // 사용자의 입력을 받습니다.
Message rspMsg = new Message(); // 응답 메시지를 생성합니다.
rspMsg.Body = new BodyResponse() // 응답 메시지 본문을 생성합니다.
{
MSGID = reqMsg.Header.MSGID, // 요청 메시지의 ID를 응답 메시지 본문에 저장합니다.
RESPONSE = CONSTANTS.ACCEPTED // 응답 코드를 ACCEPTED로 설정합니다.
};
rspMsg.Header = new Header() // 응답 메시지 헤더를 생성합니다.
{
MSGID = msgId++, // 메시지 ID를 설정하고 증가시킵니다.
MSGTYPE = CONSTANTS.REP_FILE_SEND, // 메시지 유형을 REP_FILE_SEND로 설정합니다.
BODYLEN = (uint)rspMsg.Body.GetSize(), // 메시지 본문의 크기를 설정합니다.
FRAGMENTED = CONSTANTS.NOT_FRAGMENTED, // 메시지가 조각화되지 않았음을 나타냅니다.
LASTMSG = CONSTANTS.LASTMSG, // 메시지가 마지막 메시지임을 나타냅니다.
SEQ = 0 // 메시지 순서를 0으로 설정합니다.
};
if (answer != "yes") // 사용자가 "yes"를 입력하지 않으면
{
rspMsg.Body = new BodyResponse() // 응답 메시지 본문을 생성합니다.
{
MSGID = reqMsg.Header.MSGID, // 요청 메시지의 ID를 응답 메시지 본문에 저장합니다.
RESPONSE = CONSTANTS.DENIED // 응답 코드를 DENIED로 설정합니다.
// (즉, 클라이언트에게 '거부' 응답을 보냅니다.)
};
MessageUtil.Send(stream, rspMsg); // 응답 메시지를 전송합니다.
stream.Close(); // 스트림을 닫습니다.
client.Close(); // 클라이언트 연결을 닫습니다.
continue; // 다음 클라이언트를 기다립니다.
}
else
MessageUtil.Send(stream, rspMsg); // 사용자가 "yes"를 입력하면 응답 메시지를 전송합니다.
// (즉, 클라이언트에게 '승낙' 응답을 보냅니다.)
Console.WriteLine("파일 전송을 시작합니다..."); // 파일 전송 시작 메시지를 출력합니다.
long fileSize = reqBody.FILESIZE; // 요청 메시지 본문에서 파일 크기를 가져옵니다.
string fileName = Encoding.Default.GetString(reqBody.FILENAME); // 요청 메시지 본문에서 파일 이름을 가져옵니다.
FileStream file = new FileStream(dir + "\\" + fileName, FileMode.Create); // 파일을 생성합니다.
// (업로드 파일 스트림을 생성합니다.)
uint? dataMsgId = null; // 데이터 메시지 ID를 저장할 변수입니다.
ushort prevSeq = 0; // 이전 메시지의 순서를 저장할 변수입니다.
while ((reqMsg = MessageUtil.Receive(stream)) != null) // 스트림에서 메시지를 읽어옵니다.
{
Console.Write("#"); // "#" 문자를 출력합니다.
if (reqMsg.Header.MSGTYPE != CONSTANTS.FILE_SEND_DATA) // 메시지 유형이 FILE_SEND_DATA가 아니면 반복문을 종료합니다.
break;
if (dataMsgId == null) // 데이터 메시지 ID가 null이면
dataMsgId = reqMsg.Header.MSGID; // 현재 메시지의 ID를 저장합니다.
else // 데이터 메시지 ID가 null이 아니면
{
if (dataMsgId != reqMsg.Header.MSGID) // 현재 메시지의 ID가 이전 메시지의 ID와 다르면 반복문을 종료합니다.
break;
}
if (prevSeq++ != reqMsg.Header.SEQ) // 이전 메시지의 순서와 현재 메시지의 순서가 일치하지 않으면 반복문을 종료합니다.
// (즉, 메시지 순서가 어긋나면 전송을 중단합니다.)
{
Console.WriteLine("{0}, {1}", prevSeq, reqMsg.Header.SEQ);
break;
}
file.Write(reqMsg.Body.GetBytes(), 0, reqMsg.Body.GetSize()); // 메시지 본문을 파일에 씁니다.
// (전송받은 스트림을 서버에서 생성한 파일에 기록합니다.)
if (reqMsg.Header.FRAGMENTED == CONSTANTS.NOT_FRAGMENTED) // 메시지가 분할 메시지가 아니라면 반복을 한 번만 하고 빠져 나옵니다.
break;
if (reqMsg.Header.LASTMSG == CONSTANTS.LASTMSG) // 메시지가 마지막 메시지이면 반복문을 종료합니다.
break;
}
long recvFileSize = file.Length; // 수신된 파일의 크기를 가져옵니다.
file.Close(); // 파일을 닫습니다.
Console.WriteLine(); // 줄 바꿈을 합니다.
Console.WriteLine("수신 파일 크기 : {0} bytes", recvFileSize); // 수신된 파일의 크기를 출력합니다.
Message rstMsg = new Message(); // 결과 메시지를 생성합니다.
rstMsg.Body = new BodyResult() // 결과 메시지 본문을 생성합니다.
{
MSGID = reqMsg.Header.MSGID, // 요청 메시지의 ID를 결과 메시지 본문에 저장합니다.
RESULT = CONSTANTS.SUCCESS // 결과 코드를 SUCCESS로 설정합니다.
};
rstMsg.Header = new Header() // 결과 메시지 헤더를 생성합니다.
{
MSGID = msgId++, // 메시지 ID를 설정하고 증가시킵니다.
MSGTYPE = CONSTANTS.FILE_SEND_RES, // 메시지 유형을 FILE_SEND_RES로 설정합니다.
BODYLEN = (uint)rstMsg.Body.GetSize(), // 메시지 본문의 크기를 설정합니다.
FRAGMENTED = CONSTANTS.NOT_FRAGMENTED, // 메시지가 조각화되지 않았음을 나타냅니다.
LASTMSG = CONSTANTS.LASTMSG, // 메시지가 마지막 메시지임을 나타냅니다.
SEQ = 0 // 메시지 순서를 0으로 설정합니다.
};
if (fileSize == recvFileSize) // 파일 크기가 일치하면
MessageUtil.Send(stream, rstMsg); // 결과 메시지를 전송합니다.
// (파일 전송 요청에 담겨온 파일 크기와,
// 실제로 받은 파일의 크기를 비교하여,
// 파일 크기가 같으면 '성공' 메시지를 보냅니다.)
else // 파일 크기가 일치하지 않으면
{
rstMsg.Body = new BodyResult() // 결과 메시지 본문을 생성합니다.
{
MSGID = reqMsg.Header.MSGID, // 요청 메시지의 ID를 결과 메시지 본문에 저장합니다.
RESULT = CONSTANTS.FAIL // 결과 코드를 FAIL로 설정합니다.
};
MessageUtil.Send(stream, rstMsg); // 결과 메시지를 전송합니다.
// (파일 크기에 이상이 있다면 '실패' 메시지를 보냅니다.)
}
Console.WriteLine("파일 전송을 마쳤습니다."); // 파일 전송 완료 메시지를 출력합니다.
stream.Close(); // 스트림을 닫습니다.
client.Close(); // 클라이언트 연결을 닫습니다.
}
}
catch (SocketException e) // 소켓 예외 발생 시 처리
{
Console.WriteLine(e); // 예외 정보를 출력합니다.
}
finally // 예외 발생 여부와 관계없이 항상 실행됩니다.
{
server.Stop(); // 서버를 중지합니다.
}
Console.WriteLine("서버를 종료합니다."); // 서버 종료 메시지를 출력합니다.
}
}
}
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using FUP; // FUP 네임스페이스를 사용합니다.
namespace FileReceiver
{
class MainApp
{
static void Main(string[] args)
{
if (args.Length < 1) // 명령줄 인수가 1개 미만이면 사용법을 출력하고 프로그램을 종료합니다.
{
Console.WriteLine("사용법 : {0} <Directory>",
Process.GetCurrentProcess().ProcessName);
return;
}
uint msgId = 0; // 메시지 ID를 저장할 변수입니다.
string dir = args[0]; // 첫 번째 명령줄 인수를 디렉터리 경로로 사용합니다.
if (Directory.Exists(dir) == false) // 디렉터리가 존재하지 않으면 디렉터리를 생성합니다.
Directory.CreateDirectory(dir);
const int bindPort = 5425; // 서버가 바인딩할 포트 번호입니다.
TcpListener server = null; // TcpListener 객체를 저장할 변수입니다.
try
{
IPEndPoint localAddress = new IPEndPoint(0, bindPort); // 모든 IP 주소에 대해 bindPort 포트를 사용하는 IPEndPoint 객체를 생성합니다.
server = new TcpListener(localAddress); // TcpListener 객체를 생성합니다.
server.Start(); // 서버를 시작합니다.
Console.WriteLine("파일 업로드 서버 시작... "); // 서버 시작 메시지를 출력합니다.
while (true) // 클라이언트의 연결 요청을 계속해서 수신합니다.
{
TcpClient client = server.AcceptTcpClient(); // 클라이언트의 연결 요청을 수락합니다.
Console.WriteLine("클라이언트 접속 : {0} ",
((IPEndPoint)client.Client.RemoteEndPoint).ToString()); // 클라이언트의 IP 주소와 포트 번호를 출력합니다.
NetworkStream stream = client.GetStream(); // 클라이언트와 통신할 수 있는 NetworkStream 객체를 가져옵니다.
Message reqMsg = MessageUtil.Receive(stream); // 스트림에서 메시지를 읽어옵니다.
if (reqMsg.Header.MSGTYPE != CONSTANTS.REQ_FILE_SEND) // 메시지 유형이 REQ_FILE_SEND가 아니면 연결을 닫고 다음 클라이언트를 기다립니다.
{
stream.Close();
client.Close();
continue;
}
BodyRequest reqBody = (BodyRequest)reqMsg.Body; // 메시지 본문을 BodyRequest 타입으로 변환합니다.
Console.WriteLine("파일 업로드 요청이 왔습니다. 수락하시겠습니까? yes/no"); // 사용자에게 파일 업로드 요청 수락 여부를 묻습니다.
string answer = Console.ReadLine(); // 사용자의 입력을 받습니다.
Message rspMsg = new Message(); // 응답 메시지를 생성합니다.
rspMsg.Body = new BodyResponse() // 응답 메시지 본문을 생성합니다.
{
MSGID = reqMsg.Header.MSGID, // 요청 메시지의 ID를 응답 메시지 본문에 저장합니다.
RESPONSE = CONSTANTS.ACCEPTED // 응답 코드를 ACCEPTED로 설정합니다.
};
rspMsg.Header = new Header() // 응답 메시지 헤더를 생성합니다.
{
MSGID = msgId++, // 메시지 ID를 설정하고 증가시킵니다.
MSGTYPE = CONSTANTS.REP_FILE_SEND, // 메시지 유형을 REP_FILE_SEND로 설정합니다.
BODYLEN = (uint)rspMsg.Body.GetSize(), // 메시지 본문의 크기를 설정합니다.
FRAGMENTED = CONSTANTS.NOT_FRAGMENTED, // 메시지가 조각화되지 않았음을 나타냅니다.
LASTMSG = CONSTANTS.LASTMSG, // 메시지가 마지막 메시지임을 나타냅니다.
SEQ = 0 // 메시지 순서를 0으로 설정합니다.
};
if (answer != "yes") // 사용자가 "yes"를 입력하지 않으면
{
rspMsg.Body = new BodyResponse() // 응답 메시지 본문을 생성합니다.
{
MSGID = reqMsg.Header.MSGID, // 요청 메시지의 ID를 응답 메시지 본문에 저장합니다.
RESPONSE = CONSTANTS.DENIED // 응답 코드를 DENIED로 설정합니다.
};
MessageUtil.Send(stream, rspMsg); // 응답 메시지를 전송합니다.
stream.Close(); // 스트림을 닫습니다.
client.Close(); // 클라이언트 연결을 닫습니다.
continue; // 다음 클라이언트를 기다립니다.
}
else
MessageUtil.Send(stream, rspMsg); // 사용자가 "yes"를 입력하면 응답 메시지를 전송합니다.
Console.WriteLine("파일 전송을 시작합니다..."); // 파일 전송 시작 메시지를 출력합니다.
long fileSize = reqBody.FILESIZE; // 요청 메시지 본문에서 파일 크기를 가져옵니다.
string fileName = Encoding.Default.GetString(reqBody.FILENAME); // 요청 메시지 본문에서 파일 이름을 가져옵니다.
FileStream file = new FileStream(dir + "\\" + fileName, FileMode.Create); // 파일을 생성합니다.
uint? dataMsgId = null; // 데이터 메시지 ID를 저장할 변수입니다.
ushort prevSeq = 0; // 이전 메시지의 순서를 저장할 변수입니다.
while ((reqMsg = MessageUtil.Receive(stream)) != null) // 스트림에서 메시지를 읽어옵니다.
{
Console.Write("#"); // "#" 문자를 출력합니다.
if (reqMsg.Header.MSGTYPE != CONSTANTS.FILE_SEND_DATA) // 메시지 유형이 FILE_SEND_DATA가 아니면 반복문을 종료합니다.
break;
if (dataMsgId == null) // 데이터 메시지 ID가 null이면
dataMsgId = reqMsg.Header.MSGID; // 현재 메시지의 ID를 저장합니다.
else // 데이터 메시지 ID가 null이 아니면
{
if (dataMsgId != reqMsg.Header.MSGID) // 현재 메시지의 ID가 이전 메시지의 ID와 다르면 반복문을 종료합니다.
break;
}
if (prevSeq++ != reqMsg.Header.SEQ) // 이전 메시지의 순서와 현재 메시지의 순서가 일치하지 않으면 반복문을 종료합니다.
{
Console.WriteLine("{0}, {1}", prevSeq, reqMsg.Header.SEQ);
break;
}
file.Write(reqMsg.Body.GetBytes(), 0, reqMsg.Body.GetSize()); // 메시지 본문을 파일에 씁니다.
if (reqMsg.Header.LASTMSG == CONSTANTS.LASTMSG) // 메시지가 마지막 메시지이면 반복문을 종료합니다.
break;
}
long recvFileSize = file.Length; // 수신된 파일의 크기를 가져옵니다.
file.Close(); // 파일을 닫습니다.
Console.WriteLine(); // 줄 바꿈을 합니다.
Console.WriteLine("수신 파일 크기 : {0} bytes", recvFileSize); // 수신된 파일의 크기를 출력합니다.
Message rstMsg = new Message(); // 결과 메시지를 생성합니다.
rstMsg.Body = new BodyResult() // 결과 메시지 본문을 생성합니다.
{
MSGID = reqMsg.Header.MSGID, // 요청 메시지의 ID를 결과 메시지 본문에 저장합니다.
RESULT = CONSTANTS.SUCCESS // 결과 코드를 SUCCESS로 설정합니다.
};
rstMsg.Header = new Header() // 결과 메시지 헤더를 생성합니다.
{
MSGID = msgId++, // 메시지 ID를 설정하고 증가시킵니다.
MSGTYPE = CONSTANTS.FILE_SEND_RES, // 메시지 유형을 FILE_SEND_RES로 설정합니다.
BODYLEN = (uint)rstMsg.Body.GetSize(), // 메시지 본문의 크기를 설정합니다.
FRAGMENTED = CONSTANTS.NOT_FRAGMENTED, // 메시지가 조각화되지 않았음을 나타냅니다.
LASTMSG = CONSTANTS.LASTMSG, // 메시지가 마지막 메시지임을 나타냅니다.
SEQ = 0 // 메시지 순서를 0으로 설정합니다.
};
if (fileSize == recvFileSize) // 파일 크기가 일치하면
MessageUtil.Send(stream, rstMsg); // 결과 메시지를 전송합니다.
else // 파일 크기가 일치하지 않으면
{
rstMsg.Body = new BodyResult() // 결과 메시지 본문을 생성합니다.
{
MSGID = reqMsg.Header.MSGID, // 요청 메시지의 ID를 결과 메시지 본문에 저장합니다.
RESULT = CONSTANTS.FAIL // 결과 코드를 FAIL로 설정합니다.
};
MessageUtil.Send(stream, rstMsg); // 결과 메시지를 전송합니다.
}
Console.WriteLine("파일 전송을 마쳤습니다."); // 파일 전송 완료 메시지를 출력합니다.
stream.Close(); // 스트림을 닫습니다.
client.Close(); // 클라이언트 연결을 닫습니다.
}
}
catch (SocketException e) // 소켓 예외 발생 시 처리
{
Console.WriteLine(e); // 예외 정보를 출력합니다.
}
finally // 예외 발생 여부와 관계없이 항상 실행됩니다.
{
server.Stop(); // 서버를 중지합니다.
}
Console.WriteLine("서버를 종료합니다."); // 서버 종료 메시지를 출력합니다.
}
}
}
코드 설명
이 C# 코드는 TCP 소켓을 사용하여 파일을 수신하는 서버 애플리케이션을 구현한 예제입니다. 클라이언트로부터 파일 전송 요청을 받고, 사용자의 승인 후 파일을 수신하여 지정된 디렉터리에 저장합니다.
주요 기능
TcpListener를 사용하여 클라이언트의 연결 요청을 수신합니다.핵심 클래스 및 메서드
TcpListener: 클라이언트의 연결 요청을 수신 대기하는 서버 소켓을 나타냅니다.TcpClient: 서버에 연결하는 클라이언트 소켓을 나타냅니다.NetworkStream: 네트워크를 통해 데이터를 주고받는 스트림을 나타냅니다.MessageUtil.Receive(): 스트림에서 메시지를 읽어옵니다.MessageUtil.Send(): 메시지를 스트림에 씁니다.Header: 메시지 헤더를 나타냅니다.BodyRequest: 파일 전송 요청 메시지 본문을 나타냅니다.BodyResponse: 파일 전송 응답 메시지 본문을 나타냅니다.BodyData: 파일 데이터 메시지 본문을 나타냅니다.BodyResult: 파일 전송 결과 메시지 본문을 나타냅니다.코드 흐름
TcpListener를 사용하여 서버 소켓을 생성하고 시작합니다.MessageUtil.Receive() 메서드를 사용하여 클라이언트의 파일 전송 요청 메시지를 수신합니다.참고
Message, Header, BodyRequest, BodyResponse, BodyData, BodyResult 클래스는 사용자 정의 클래스입니다.CONSTANTS 클래스는 메시지 유형을 정의하는 상수를 포함합니다.MessageUtil 클래스는 메시지를 주고받는 데 사용되는 유틸리티 클래스입니다.