'웹서버'에 해당되는 글 3건

  1. 2006/05/10 멀티 쓰레드 기반 웹서버
  2. 2006/04/13 웹서버 소스
  3. 2006/03/31 웹서버 작성을 하면서...
posted by 박영창 2006/05/10 17:37
Multi Thread Simple Web Server

가장 단순한 형태의 멀티 쓰레드 기반의 웹 서버를 구현한 프로그램입니다. 윈도우 환경에서 작성하였으며, 가장 기본적인 기능만을 제공합니다. 이 프로그램을 약간 변경해서 단순한 형태의 프락시 서버를 구현하기도 했었습니다.
프로그램은 기본 윈도우 winsock 함수를 C 기반 환경에서 구현하였으며, 작성을 위해서 HTTP 프로토콜의 구조를 이해하기 위해서 RFC 문서를 찾아보면서 만들었습니다. 처음으로 프로그램을 구현하기 위해서 RFC 문서를 직접 찾아본 프로그램 이었습니다.
크리에이티브 커먼즈 라이선스
Creative Commons License
posted by 박영창 2006/04/13 03:53

/************************************************************************
Multithreaded Web Server

written by eternalbleu
************************************************************************/
#include <STDIO.H>
#include <STDLIB.H>
#include <STRING.H>
#include <WINSOCK2.H>
#include <PROCESS.H>

// Constant
#define HUG_BUFFSIZE 2048
#define MID_BUFFSIZE 1024
#define SMA_BUFFSIZE 32
#define MAX_FILENAME 256

// Error Code
#define STATECODE_BAD_REQUEST 400
#define STATECODE_NOT_FOUND 404
#define STATECODE_OK 200

// Send HTTP Replay to Client
void SendReply(SOCKET clnt, UINT statusType, char *filename);
char* GetStatusLine(UINT type = STATECODE_OK);
char* GetErrorContent(UINT type);
int GetContentLength(FILE* fp);
char* GetContentType(char* filename);

void ErrorHandling(const char* message); // Error Message Handling
UINT WINAPI ClientConnect(void* argv); // Thread Proc

int main(int argc, char** argv)
{
WSADATA wsaData;
SOCKET servSock;
SOCKET clntSock;
SOCKADDR_IN servAddr;
SOCKADDR_IN clntAddr;

HANDLE hThread;
DWORD dwThreadID;

if (argc != 2) {
printf("SIMPLE WEBSERVER. written by youngchang. \n USAGE: %s <PORT>\n", argv[0]);
exit(1);
}

// DLL loading
if ( WSAStartup(MAKEWORD(2, 2), &amp;wsaData) != 0 )
ErrorHandling("WSAStartup() error occured.");

// socket creation &amp; initialize
if ( (servSock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET )
ErrorHandling("socket() error occured.");

// 서버 소켓 설정
memset((void*)&amp;servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET; // IPv4 주소 체계 이용
servAddr.sin_addr.s_addr = htonl( INADDR_ANY ); // 서버 주소 할당. 임의의 주소가 될 수 있어야함.
servAddr.sin_port = htons( atoi(argv[1]) ); // 인자로 받은 숫자를 이용 포트를 할당

// server socket setting
if ( bind(servSock, (SOCKADDR*)&amp;servAddr, sizeof(servAddr) ) == SOCKET_ERROR )
ErrorHandling("bind() error occured.");

// wait for request from client
if ( listen(servSock, 5) == SOCKET_ERROR )
ErrorHandling("listen() error occured.");

// requested from client
while(1) {
// 서버 대기 소켓으로 연결 요청이 들어온 클라이언트와의 소켓을 생성
int clntAddrSize = sizeof(clntAddr);
if ( (clntSock = accept(servSock, (SOCKADDR*)&amp;clntAddr, &amp;clntAddrSize)) == INVALID_SOCKET )
ErrorHandling("accept() error occured.");

// 클라이언트의 정보 출력
#ifdef _DEBUG
printf("REQUEST FROM %s : %d\n", inet_ntoa(clntAddr.sin_addr), ntohs(clntAddr.sin_port));
#endif
// 클라이언트 연결 쓰레드 생성
hThread = (HANDLE)_beginthreadex(NULL, 0, ClientConnect, (void*)clntSock, 0, (unsigned*)&amp;dwThreadID);
if (hThread == NULL)
ErrorHandling("_beginthreadex() error occured.");
}

// socket destroy
closesocket(servSock);

// DLL Unloading
WSACleanup();
return 0;
}

/************************************************************************
ClientConnect 합당한 에러메시지를 출력하며 프로그램을 종료한다.

@ argv 쓰레드프로세저의 인자. 이 경우 클라이언트와 연결된 소켓이 인자임.

# return 에러발생시 1, 정상 종료시 0
************************************************************************/
UINT WINAPI ClientConnect(void* argv)
{
SOCKET clntSock = (SOCKET)argv; // 클라이언트 소켓
char request[HUG_BUFFSIZE] = {0,}; // 요청 라인 (string)
char filename[MAX_FILENAME] = {0,}; // 요청 파일 (string)
char method[SMA_BUFFSIZE] = {0,}; // 요청 방식 (METHOD)

recv(clntSock, request, sizeof(request), 0);

if ( strstr(request, "HTTP/") == NULL ) // HTTP 요청인지 검사
{
closesocket(clntSock);
return 1;
}

// HTTP 요청 정보 출력
#ifdef _DEBUG
fputs("[BEG]\n", stdout);
fputs(request, stdout);
fputs("\n[END]\n", stdout);
#endif

strcpy(method, strtok(request, " ")); // HTTP 요청 방식 (METHOD) 얻기
if (strcmp(method, "GET")) // POST 메소드인 경우 에러 처리
SendReply(clntSock, STATECODE_BAD_REQUEST, NULL);

strcpy(filename, strtok(NULL, " ")); // HTTP 요청 파일 얻기

SendReply(clntSock, STATECODE_OK, filename);
return 0;
}

/************************************************************************
SendReply 클라이언트에게 알맞은 정보를 송신한다.

@ clnt 연결된 클라이언트와의 통신 소켓
@ statusType 답신 메시지의 종류를 송신한다
@ filename 클라이언트에게 보낼 파일 이름
************************************************************************/
void SendReply(SOCKET clnt, UINT statusType, char *filename)
{
// HEADER
char statusLine[SMA_BUFFSIZE] = {0,}; // 상태 라인
char contentLength[SMA_BUFFSIZE]= {0,}; // 내용 길이
char contentType[SMA_BUFFSIZE] = {0,}; // 내용 종류
char serverName[] = "Server:Simple Web Server v1.0\r\n"; // 서버 정보

// DATA
FILE* fp; // 전송 파일 파온터
char buffer[HUG_BUFFSIZE] = {0,}; // 전송 버퍼

// pre-process filename
if ( strlen(filename) == 1 &amp;&amp; !strcmp(filename, "/") ) strcpy(filename, "/index.html");
if ( *filename == '/' ) filename++;

// file open
if ( statusType == STATECODE_OK &amp;&amp; (fp = fopen(filename, "rb")) == NULL ) {
statusType = STATECODE_NOT_FOUND;
}
strcpy(statusLine, GetStatusLine(statusType)); // 상태 라인 정보 얻기
strcpy(contentType, GetContentType(filename)); // 내용 종류 얻기
sprintf(contentLength, "Content-length:%d\r\n", GetContentLength(fp)); // 내용 길이 정보 얻기

// Send HTTP Header
send(clnt, statusLine, strlen(statusLine), 0);
send(clnt, serverName, strlen(serverName), 0);
send(clnt, contentLength, strlen(contentLength), 0);
send(clnt, contentType, strlen(contentType), 0);

// Transmit error page data
if (statusType != STATECODE_OK)
{
char tmpbuf[MID_BUFFSIZE] = {0,};
strcpy(tmpbuf, GetErrorContent(statusType));
send(clnt, tmpbuf, strlen(tmpbuf), 0);
closesocket(clnt);
return;
}

// Send HTTP Data
size_t length = 0;
do {
length = fread(buffer, sizeof(char), sizeof(buffer), fp);
send (clnt, buffer, length, 0);
} while(!feof(fp));

closesocket(clnt);
}

/************************************************************************
GetStatusLine
@ type 상태 라인을 얻기위한 상수 값

# return 상태 라인 정보
************************************************************************/
char* GetStatusLine(UINT type)
{
switch(type)
{
case STATECODE_OK:
return "HTTP/1.0 200 OK\r\n";
break;

case STATECODE_BAD_REQUEST:
return "HTTP/1.0 400 Bad Request\r\n";
break;

case STATECODE_NOT_FOUND:
return "HTTP/1.0 404 Not Found\r\n";
break;

default:
return "HTTP/1.0 400 Bad Request\r\n";
break;
}
}

/************************************************************************
GetErrorContent 에러 코드에 따른 페이지 전송

@ type 에러 코드 상수

# return 에러코드에 맞는 페이지의 스트링
************************************************************************/
char* GetErrorContent(UINT type)
{
char buffer[HUG_BUFFSIZE] = {0,};
switch(type)
{
case STATECODE_NOT_FOUND:
strcpy(buffer,
"<FONT size=5>404 NOT FOUND</FONT>
ERROR OCCURED. CHECK FILENAME."
);
break;
case STATECODE_BAD_REQUEST:
strcpy(buffer,
"<FONT size=5>400 BAD REQUEST</FONT>
ERROR OCCURED. CHECK REQ METHOD."
);
break;
default:
strcpy(buffer,
"<FONT size=5>400 BAD REQUEST</FONT>
ERROR OCCURED. CHECK REQ METHOD."
);
break;
}
return buffer;
}

/************************************************************************
GetContentType 파일의 이름을 기반으로 파일의 MIME 타입의 스트링을 리턴한다.

@ filename 타입을 알아야할 파일의 이름

# return 파일의 MIME 타입 스트링
************************************************************************/
char* GetContentType(char* filename)
{
char retvalue[SMA_BUFFSIZE];

for(UINT i = 0; i &lt; strlen(filename); i++)
*(filename+i) = tolower( *(filename+i) );

if ( strstr(filename, ".html") != NULL || strstr(filename, ".htm") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "text/html");

if ( strstr(filename, ".txt") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "text/plain");

if ( strstr(filename, ".jpeg") != NULL || strstr(filename, ".jpg") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "image/jpeg");

if ( strstr(filename, ".png") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "image/pjpeg");

if ( strstr(filename, ".gif") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "image/gif");

if ( strstr(filename, ".bmp") != NULL )
sprintf(retvalue, "Content-type:%s\r\n\r\n", "image/x-xbitmap");

return retvalue;
}

/************************************************************************
GetContentLength 요청 받은 파일의 크기를 바이트 단위로 리턴한다. (in byte)

@ fp 요청 받은 파일의 포인터

# return file size (in byte)
************************************************************************/
int GetContentLength(FILE* fp)
{
if (fp == NULL) return 0;

unsigned int length = 0;
char buf[HUG_BUFFSIZE];
while ( !feof(fp) )
{
length += fread(buf, sizeof(char), sizeof(buf), fp);
}
fseek(fp, 0, SEEK_SET); //rewind(fp);

return length;
}

/************************************************************************
ErrorHandling 합당한 에러메시지를 출력하며 프로그램을 종료한다.

@ message 프로그램 에러 메시지
************************************************************************/
void ErrorHandling(const char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}


윈도우 환경에서 동작하는 서버입니다. 약간의 수정을 하면 리눅스 환경에서도 사용 가능함.
크리에이티브 커먼즈 라이선스
Creative Commons License
posted by 박영창 2006/03/31 17:46

오늘 학교 숙제로 웹서버를 작성했다.

흐음.. -_-;

문제는 정상적인 동작을 하도록 작성을 했음에도 불구하고, 브라우저에서 소켓이 계속 끊긴다는 것이었다. 내용을 받기도 전에말이다. ㅡ,.ㅡ 젝1;;

ethereal 을 이용해서 패킷을 본 결과. 서버측에서 HTTP Reply를 하지 않고 소켓을 닫아버리는 것이었다. --;;

뭐지 이건... (로컬로 보내면 랜카드를 통하지 않기 때문에 학교까지 와서 컴 2대를 놓고서 돌려보고서야 알았다. --;; MSDN에서 send리턴만 조사하면 되는 거였는데...)

결국 알아낸 것은... -.-;; 디버그 메시지를 보여준다고 이전에 짯던 부분에서 메시지를 출력하면서 클라이언트 소켓을 닫아버리는 한줄의 코드!!! ㅡ,.ㅡ;; 역시나 하무하다.

언제쯤 되야지 이런 황당한 프로그램을 안하게 될지... 참 한심하다.


05/04/01/00:25 네트워크 레포트 2. 웹서버 프로그래밍 종료
멀티 쓰레드, GET, text, binary 데이터 전송 가능
크리에이티브 커먼즈 라이선스
Creative Commons License