2008년 07월 22일
[MFC] Personal Web File Server
1. 개발 동기 및 필요성.
PC의 보급이 늘어나고 인터넷의 활용이 늘어나면서 집에 있는 PC의 자료를 밖에서 다운로드 받거나 다른 사람에게 보내줘야 하는 경우가 많아졌다. 예전에는 FTP 서버 프로그램을 사용하여 파일전송이 가능하긴 하지만 FTP Client 프로그램이 설치하여야 하기 때문에 불편한 점이 있다. 프로그램을 따로 설치하지 않고도 파일공유와 다운로드가 가능하도록 Client프로그램을 Web Browser로 하여 집에 있는 PC로 접속할 수 있도록 동작하는 서버 프로그램을 만들고자 한다.
Client와 Server간에 HTTP를 사용하는데, HTTP의 기능이 방대하고 HTML의 경우 Web Browser같의 호환이 안되는 부분이 있어서, 본 프로젝트에서는 현재 가장 많이 사용하고 있는 Web Brower인 Internet Explorer 6.5에 최적화된 HTTP와 HTML을 사용하는 서버를 만들고자 한다.
2. 관련 내용 연구.
2.1 HTTP ( Hyper Text Transfer Protocol )
HTTP는 WWW(World Wide Web) 상의 데이터에 접근하기 위해 사용되는 프로토콜이다. 이 프로토콜은 텍스트 기반의 데이터, Hypertext, 오디오, 비디오 등의 데이터를 전송하고, 하나의 페이지에서 다른 페이지로 빠르게 이동하는 Hypertext 환경에서 효과적으로 사용되므로 HTTP라고 불린다. HTTP는 TCP 포트 80번을 사용하여 서비스를 제공한다.
HTTP는 요청 메시지와 응답 메시지의 두가지 Message 형태를 가진다. 요청 Message는 아래 그림과 같이 요청 라인(Request line), 헤더(Header), 공백(Blank), 본문(Body) 부분으로 나뉜다. 이에 반해 응답 메시지는 요청 라인 대신 상태 라인(State line)을 포함하고, 헤더, 공백, 본문 부분으로 나뉜다.
요청 라인 |
헤더 (일반헤더 | 요청 헤더 | 엔터티 헤더) |
공백 라인 |
엔터티 본문 (요청 메시지) |
【 요청 Message Format 】
헤더 부분은 일반 헤더, 요청/응답 헤더, 엔터티 헤더로 나뉘고, 필요에 따라 하나 혹은 여러 개의 헤더 부분이 추가되어 전달된다. 본문 부분 또한 항상 포함되는 것이 아니라 필요에 따라 추가된다. 공백 라인은 ‘CR', 'LF' 문자를 이용하여 헤더 부분과 본문 부분을 구별하고 별도의 기능은 수행하지 않는다. 또한 모든 라인은 'CR(0x0d)', 'LF(0x0a)' 문자로 종료한다.
상태 라인 |
헤더 (일반헤더 | 응답 헤더 | 엔터티 헤더) |
공백 라인 |
엔터티 본문 (응답 메시지) |
【 응답 Message Format 】
3. 개발 내용.
여러명의 Client가 접속을 했을때 각각의 Client마다 Thread를 생성하여 Concurrent한 처리가 가능하다.
Client가 Directory의 경로정보를 주면 Server에서 해당 Directory에 있는 모든 파일의 파일명, 파일크기, 최근 수정날짜에 대한 정보와 Sub Directory에 대한 정보를 Html 형식으로 Coding하여 Client에 전송한다.
Client에 보여지는 파일리스트 화면에서 Directory를 선택했을 경우 서버에서는 선택된 Directory에 있는 파일에 대한 파일리스트를 다시 전송한다.
Client에 보여지는 파일리스트 화면에서 파일을 선택했을 경우 서버에서는 해당 파일에 대한 정보를 HTML 헤더에 넣어서 Client에 보내주고, 파일의 Binary정보를 읽어서 Client에 전송함으로써 다운로드가 가능하게 한다.
관리자 모드와 사용자 모드를 구분하여 각각의 모드에 따른 최대 Traffic사용 정도를 Kb/s 단위로 하여 설정할수 있게 하고, 초과 Traffic이 발생하지 않도록 파일전송 Thread를 동기화 한다.
최초 접속시 로그인 화면을 전송하여 비밀번호를 입력 받는다. 입력받은 비밀번호가 설정한 비밀번호와 같을 경우는 HTTP의 Set-Cookie 기능을 사용하여 랜덤으로 생성된 8Byte의 세션값을 보내준다. Client의 Web Brower에서는 이후의 접속에 대해서는 세션값을 자동으로 서버에 보내주게 되므로 서버에서는 세션값을 검사하여 허가된 사용자 여부를 검사할 수 있다.
서버 자체의 상태정보와 Client로 부터의 접속,파일리스트 요청, 파일전송요청 상태를 알수 있다. 또한 전송의 성공/실패 여부와 서버동작중의 오류에 대한 내용도 알수 있다.
3.1 설계 및 구현 사항.
(1) 전체 Thread 구조
프로그램이 시작되면 Listen Thread와 Update Thread를 생성한다. Listen Thread는 Client가 접속하면 Client Thread를 생성하여 Client의 명령어를 처리하게끔 하고, 자신은 다시 다른 Client의 접속을 받게 된다. Update Thread는 Log창에 서버의 상태를 표시하는 역할과 전송속도 제한과 관련된 기능을 수행한다. Client Thread는 Client로부터 직접적으로 메시지를 받고 그에 따른 처리를 하게 된다.
【 전체 Thread 구조 】
프로그램에서 각각의 기능이 어떤 Message의 교환을 통해 구현되는지 설명하겠다. HTTP의 실제 헤더부분중 기능구현에 중요한 부분 위주로 설명하겠다.
(2) 기능별 Message 구조
루트 Path에 마침표 ‘.’ 를 앞에 넣음으로써 일반적인 파일의 다운로드가 아니라 특수한 기능을 하게끔 하였다. 웹 브라우저를 통해 처음 서버에 접속할때는 비밀번호를 입력하여야 한다. 이때 비밀번호의 값은 GET 방식으로 /.login 이라는 파일에 넘겨주게 된다. 아래 그림의 XXXX 부분은 서버의 프로그램에서 미리설정된 비밀번호 값이 된다. 비밀번호를 제대로 입력했을 경우 서버에서는 Set-Cookie 명령어를 통해 현재 접속이 유효한 세션 값을 쿠키로 지정하게 된다. 이후 Client에서는 페이지를 요청할 때 이 세션값을 같이 보냄으로써 인증된 사용자임을 알려준다.

【 로그인 과정시의 Message 교환 】

로그아웃은 단순히 루트 Path의 특수기능인 /.logout 페이지를 요청함으로써 구현된다. 요청 Message에 현재 사용 중인 세션 값을 보내게 되는데 서버에서는 이 세션값을 무효화 시킨다.


페이지 요청시에 /temp/ 와 같이 마지막이 ‘/’ 로끝나는 경우 Server에서는 파일리스트 요청으로 인식하여 해당 디렉토리에 있는 파일과 서브 디렉토리에 대한 정보를 HTML형식으로 Coding하여 응답한다.

【 파일 리스트 요청시의 Message 교환 】

파일리스트에서 파일 부분을 클릭했을 경우 이미 HTML에서 파일이름에 다운로드 링크가 되어있으므로 서버측에 파일 다운로드 요청을 하게 된다. 서버에서는 파일 다운로드 요청에 대해 두 개의 Message를 응답하는데 첫 번째 Message에는 파일의 크기를 Content-Lenth 항목을 통해 알려주고 두 번째 Message에는 실제적인 파일의 Binary Data를 전송하게 된다.

◆ 파일 업로드
파일 업로드가 다른 기능과 다른점은 POST 방식을 통해 Data를 전송한다는 점이다. POST방식은 보내고자 하는 데이터를 두 번에 나눠서 보내게 되는데 첫 번째로 보내는 Message에는 다음에 보낼 Message의 길이와 서버의 어떤 파일로 전송하는지 알려주고, 두 번째로 보내는 Message에는 실제적인 데이터를 전송한다. 첫 번째로 보내는 Message의 Content-Length항목에서 알려주는 두 번째 Message의 길이 정보는 실제 업로드할 파일의 크기에 대한 값이 아니므로 Server측에서 파일을 저장할 때 파일길이를 잘 계산해서 저장해야 한다.


파일삭제는 루트 Path의 특수기능인 /.delete 페이지에 GET 방식으로 삭제할 파일의 Path값을 넘겨줌으로써 구현된다. 파일 삭제 기능은 관리자 모드로 로그인 했을 경우만 사용가능하며 파일리스트 화면에서 각 파일의 오른쪽에는 ‘(파일)삭제하기’ 링크가 되어있다. 실수로 삭제하기 링크를 누를 경우를 대비하여 정말로 삭제할 것인지 다시한번 물어보는 화면이 뜨고 다시한번 ‘삭제하기’ 링크를 누르게 되면 파일을 삭제한다.


(3) Thread 관련 함수 설명
◆ static UINT Thread_UpdateData(LPVOID arg)
Update Thread에서 실행하는 함수이다. Log창에 메시지를 출력하는 역할과 최대 Traffic 제한관련 기능을한다. Log창에는 일반적인 전송상태등의 내용도 표시되지만 프로그램 전체적으로 에러에 관한 상태도 표시되어야 한다. 에러는 어떤 Thread에서 일어날지 알 수 없기 때문에 Log정보를 저장하는 변수에 대해서 동기화가 필요하다.
◆ static UINT Thread_Listen(LPVOID arg)
Listen Thread에서 실행하는 함수이다. 기본적인 소켓 초기화작업을 진행하고 난후 Client로부터 접속을 받는다. Client가 접속을 하면 새로 Thread를 생성하고 새로 생성된 Thread에서 Client로부터 메시지를 받고 처리를 한다.
◆ static UINT Thread_Client_Message_Processing(LPVOID arg)
Client로부터 메시지를 받아서 유효한 세션값을 갖고 있는지 검사한다. 세션값이 아예없거나 유효하지 않은 값을 갖고 있을때는 로그인을 할수 있는 페이지를 보내준다.
(4) Login 관련 함수
void MakeKey(char *key)
랜덤으로 세션 키 값을 생성하는 함수이다.
◆ UINT CheckSession(char *key, SOCKADDR_IN ip)
Client에서 보낸 세션값이 유효한 세션 값인지 검사하는 기능을 한다. 인자 값으로는 Client로부터 받은 키 값과 Client의 IP주소가 있다. 다른 컴퓨터에서 세션값을 속여서 접속하는 일이 없도록 하기위해서 세션값이랑 IP주소를 같이 검사한다.
◆ void DeleteSession(char *key, SOCKADDR_IN ip)
Logout을 했을 경우 세션을 해제하는 기능을 한다.
◆ void InsertSession(char *key,SOCKADDR_IN ip)
MakeKey()함수로 받아온 랜덤키 값을 세션관리 배열에 등록하는 함수이다.
◆ void LogoutProcess(SOCKET client_sock,SOCKADDR_IN client_addr, char *buf, char *key)
Logout을 했을 경우 실행하는 함수이다. 내부에서 DeleteSession()함수를 호출하여 세션을 해제한다.
◆ void LoginProcess(SOCKET client_sock,SOCKADDR_IN client_addr, char *buf)
Login페이지에서 비밀번호를 입력 했을때 실행하는 함수이다. 입력된 비밀번호가 설정한 비밀번호와 비교를 하고, 관리자로 로그인을 했는지 사용자모드로 로그인을 했는지도 구분하여 모드에 맞는 세션을 등록한다.
(5) Html Page 전송 관련함수
◆ void SendLogin(SOCKET client_sock,char *buf)
로그인을 할 수 있는 페이지를 전송한다. 페이지의 내용은 고정되어있기 때문에 미리 저장되어있던 login.html파일의 내용을 전송한다.
◆ void SendGoHome(SOCKET client_sock, char *buf)
로그인을 하지 않은상태에서 다른 Directory의 파일리스트를 보려고 하거나, 파일을 다운로드를 하고자 할때는 경로를 홈으로 이동하게끔 한다. 로그인을 하지 않은상태에서 홈 Directory를 열게 되면 로그인 화면을 보내게 된다.
◆ void SendDeleteConfirm(char* path)
파일삭제를 요청했을때 진짜삭제를 할것인지 물어보는 페이지를 전송한다.
◆ void SendDeleteFile(char* path)
파일 삭제를 확인했을때 실제 파일을 지우는 함수이다.
◆ void SendUploadForm(SOCKET client_sock,char *buf)
파일을 업로드 할때 업로드 페이지를 전송하는 함수이다.
(6) 파일전송 관련함수
◆ void FileTransfer(SOCKET client_sock, char* path, char *buf,UINT auth_level)
파일을 전송할 때 호출하는 함수이다. 파일크기에 대한 정보를 읽어와서 HTTP헤더에 Conetnt-Length에 지정한다.
◆ void SendFileList(SOCKET client_sock,SOCKADDR_IN client_addr, char* path, char *buf,UINT auth_level)
특정 Directory의 파일리스트를 요청했을때 호출하는 함수이다. 해당 디렉토리의 파일의 이름, 크기, 최근 수정날짜 정보를 읽어오고 파일이름 부분에는 다운로드가 가능하도록 링크 HTML 로 코딩한다.
3.2 구현 사항.
서버를 시작하기 전에 Port번호와 Home Directory를 설정해야한다. 비밀번호와 전송속도 설정은 서버를 동작시킨후에 Apply 버턴을 통해서 변경가능하다. Current Traffic 항목에서 현재 서버에서 사용하고 있는 Traffic을 모니터링 가능하고 Log창을 통해서 서버의 동작상태, 에러, 파일전송요청 등을 알 수 있다.

4. 작품 후기.
새로운 시스템에 대한 프로토콜을 설계하여 프로토콜에 맞게 프로그램을 작성하는 것도 의미 있겠지만, 기존 프로토콜에 맞춰 프로그램을 작성하는 것도 의미가 있을 것으로 생각하여 HTTP에 맞춰 프로그램을 작성해 보았다. 파일 다운로드 부분을 구현할 때 파일전송 중간에 중단되는 버그가 있었다. 파일을 open 할때 text 모드로 설정해서 fread로 파일 내용을 읽는중 Ctrl-Z를 나타내는 0x1A 앞에서 중단 되었다. fopen(path,"r") 부분을 fopen(path,"r+b")로 수정하여 파일전송기능을 완벽하게 구현하였다.
이번 프로젝트를 통해 소켓 프로그래밍 뿐만 아니라 HTML을 사용한 웹프로그래밍 까지 공부할수 있었던 계기가 되었다. 현재까지 구현된 기능은 비록 단순하지만 윈도우 서버의 특징을 잘 살려서 윈도우에서만 할 수 있는 기능을 원격으로 컨트롤 할수 있도록 한다면 좀더 강력한 프로그램이 될 수도 있을 것 같다. HTTP를 전혀 모른 상태에서 프로젝트를 빨리 끝내려다보니 코드 정리가 되지 않아 많이 아쉽다.
PS. 업로드기능을 할때 로컬 파일의 경로에 한글이있을경우 페이지없다는 에러가 난다.
# by | 2008/07/22 10:47 | │ C언어 예제 | 트랙백 | 덧글(9)







☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
천천히 만들어봐야겠어여;
실행시켜봤는데 home path는 어떻게 잡아야되나요??
그냥 아무곳이나 잡아서 스타트 해봤더니 옵션값이 적용되었다고 하고
서버가 정상적으로 시작 되었습니다. 라고 만 나타납니다.
admin , user password는 수정 안해도 되는건지요??
그리고 윗 그림 처럼 personal web file server이 브라우저로는 실행이 안되네요??
exe 파일을 오픈해도 source파일이랑 똑같은 결과값이 나타나고요.
실행방법에 대해서 좀 부탁드려도 될까요??ㅠ
Start를 누르세여 그리고 익스플로어를 열고 아래 주소로 접속해 보세여
http://127.0.0.1:5656/
같은 IP주소를 입력하고 접속하면 안되더라고요...
그부분에 대해서는 소스를 수정하면 가능한가요??
아니면 개인 웹 파일 서버를 만든거라서 안되는건가요??
자신의 IP주소를 보는 방법은
시작->실행 에서 cmd를 누른후
콘솔창이 뜨면
ipconfig 를 치면 됩니다.
그리고 난후에 위의 링크된 127.0.0.1을 IP주소로 바꿔서 접속하시면 됩니다.
단 공유기를 사용하시는 중이라면 외부에서 접속이 안될수도 있습니다.
근데 이거 릴리즈로 컴파일을 하면 웹 서버가 동작 하는데
디버그에서는 동작을 안하는건 왜 그런가요??
이번 과제가 "소스프로그램 작성 메뉴 등의 자료를 다운받아서 현재 우리가 배우고 있는 MFC
내용과 연관성을 비교 분석하기" 인데.
제가 적은시간을 들여서 소스 프로그램을 찾았는데 제가 원하고, 관심있는게 없었는데
4시간 정도 찾다가 이 곳을 찾게 되었습니다.
MFC에 대해 거의 몰라서 프로그램 직접 보고 만들지는 못했고
있는 소스를 가지고 실행하고 어떤 원리로 실행하는지만 알수 있었습니다.
이제 과제를 7시간정도 하고 정리 하면서 도움이 너무 많이 되서 글을 올리게됐습니다.
이런 자료를 제가 보고 알수있게 해주셔서 감사합니다.
코드 스타일이 저만 알아보기 작성해서 이해하기 힘드셨을텐데
고생하셨네여 ㅎㅎ