Туториал по написанию собственного веб-сервера


Введение

Интернет играет в нашей жизни большую роль. Мы берем почту, узнаем свежую информацию, отлавливаем своих знакомых через ICQ… Но что ассоциируется со словом Интернет у большинства людей в первую очередь? Сайты. Многие при слове «Интернет» вспоминают свои любимые сайты, а некоторые (не слишком продвинутые) ставят знак равенства между WWW и Интернетом, хотя в последнем есть много другого интересного: email, IRC, p2p-сети, MUD’ы и так далее. Но World Wide Web играет доминирующую роль.

В основе WWW лежит протокол HyperText Transfer Protocol. Надо сказать, что HTTP может использоваться не только для передачи сайтов, но и для передачи всего чтобы то ни было. С помощью протокола HTTP мы можем скачать, например, недавно вышедший фильм или свежие mp3 :). В p2p-сетях HTTP-протокол применяется именно в этих целях.

В данном пособии мы рассмотрим, как написать простой веб-сервер. Я предполагаю, что вы знакомы с основами программирования winsock и умеете создавать сокеты и коннектиться к чему-нибудь :).

Основы HyperText Transfer Protocol

Идея HTTP довольно проста. Клиент шлет запрос серверу, тот рассматривает его и шлет соответствующий ответ. Ответом может быть запрошенный файл, сообщение о том, что такого файла на сервере нет или что-то еще. Примерная структура запроса следующая:

<метод> <запрашиваемый_ресурс> HTTP/1.1<\n>
<заголовочное_поле>: <значение><\n>
<заголовочное_поле>: <значение><\n>
[..заголовочных полей может быть много..]<\n>
<заголовочное_поле>: <значение><\n>
<\n>

<метод> — вид запроса. Основных два: GET и POST. Друг от друга они отличаются, главным образом, способом передачи дополнительной информации, отсылающейся вместе с запросом. В этом туториале мы рассмотрим только метод GET — функционально он похож на POST, но несколько проще. О методе POST я расскажу во второй части данного туториала, если, конечно, таковая вообще появится на свет :).

<\n> — это два байта 0Dh, 0Ah, несомненно, хорошо знакомые всем ассемблерщикам :).

Таким образом, с методом мы определились. На данный момент запрос, который мы (в качестве клиента) должны будем послать серверу выглядит так:

GET <запрашиваемый_ресурс> HTTP/1.1<\n>
<заголовочное_поле>: <значение><\n>
<заголовочное_поле>: <значение><\n>
[...]
<заголовочное_поле>: <значение><\n>
<\n>

Теперь нам нужно задать <запрашиваемый_ресурс>. Возьмем типичную ссылка на одном из лучших сайтов по программированию в Рунете WASM.RU (немного рекламы не помешает 🙂 ):

http://www.wasm.ru/article.php?article=1016002

Здесь «http://» указывает на то, что используется протокол HTTP, «www.wasm.ru» говорит о том, что необходимо подсоединиться к сайту www.wasm.ru, а все, что идет после знака вопроса — это дополнительные параметры страницы. Последние используются не всегда и не на всех сайтах.

Предположим, что мы подсоединились к www.wasm.ru и хотим получить article.php с необходимыми параметрами. Очевидно, что раз мы уже подсоединились к данному серверу и знаем, что необходимо использовать протокол HTTP, то пересылать «http://www.wasm.ru» не нужно, а значит, мы пошлем только «/article.php?article=1016002». Теперь наш запрос к серверу выглядит так:

GET /article.php?article=1016002 HTTP/1.1<\n>
<заголовочное_поле>: <значение><\n>
<заголовочное_поле>: <значение><\n>
[...]
<заголовочное_поле>: <значение><\n>
<\n>

Теперь осталось разобраться с заголовочными полями. С их помощью клиент передает дополнительную информацию о себе или характере запроса. Например, довольно часто используемым заголовочным полем является ‘User-Agent’. Опера, скажем, шлет следующее:

User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98) Opera 6.02 [en]

Другим еще более важным полем является ‘Host’. В нем задается имя веб-сервера, с которого мы хотим получить документ. «Как же так», — можете сказать вы, — «Ведь мы же уже указывали имя веб-сервера, когда создавали сокет и коннектились к нему!» Да, это так. Когда мы коннектились к серверу, мы вызвали функцию gethostbyname, которой передали имя веб-сервера, а она возвратила нам адрес структуры, содержащей адрес сервера. Дело в том, что одному IP-адресу может соответствовать несколько имен сайтов, поэтому когда веб-сервер получает запрос, он должен знать к какому сайту обращается клиент (один веб-сервер может обслуживать тысячи различных сайтов, которые физически будут расположены на одной машине с одним адресом). Для этого мы пишем в ‘Host’ адрес сайта, к которому обращаемся:

Host: www.wasm.ru

Вот как теперь выглядит наш запрос:

GET /article.php?article=1016002 HTTP/1.1<\n>
User-Agent: ManualSender/1.0 :)<\n>
Host: www.wasm.ru<\n>
<\n>

Надо заметить, что хотя эти и другие заголовочные поля являются очень желательными, но, строго говоря, они не являются обязательными, то есть их может и не быть. Также я хочу обратить ваше внимание на то, что за последним заголовочным файлом следуют _два_ <\n>, а не один. Это важно.

Теперь взглянем на все вышеизложенное с точки зрения веб-сервера (ведь мы же собирались писать веб-сервер, помните? 🙂 ). Он ждет, пока к нему не поступит запрос, обрабатывает его и посылает ответ, который выглядит примерно так:

HTTP/1.1 <код_ответа> <сообщение><\n>
<заголовочное_поле>: <значение><\n>
<заголовочное_поле>: <значение><\n>
[..заголовочных полей может быть много..]<\n>
<заголовочное_поле>: <значение><\n>
<\n>
<тело_документа>

Получив запрос, сервер должен выдать клиенту код ответа (это трехзначное число), а также сопутствующее ему сообщение. Например, если запрошенный документ был найден, первая строка ответа будет примерно следующей:

HTTP/1.1 200 Ok

Если коды ответов жестко заданы стандартом (200 означает, что запрос был успешно обработан и будет возвращен соответствующий документ/информация), то <сообщение> зависит только от вашей фантазии (конечно, по смыслу оно должно совпадать с <кодом_ответа>. Например, вместо «HTTP/1.1 200 Ok» мы можем послать «HTTP/1.1 200 You want it, you’ll get it» :).

В ответе сервера также могут быть заголовочные поля. Они содержат дополнительные сведения об ответе и данных, идущих вместе с ним. Далее я приведу важнейшие (для нас).

Content-Type — этот заголовок задает тип отдаваемых данных. Главнейшие типы заданы в стандарте, и от того, что вы напишите в данном поле, зависит поведение клиента при приеме данных. Например, если вы посылаете браузеру пользователя html-файл, то необходимо указать тип данных ‘text/html’, иначе браузер может отобразить файл неправильно, либо вообще не будет его отображать, а предложит пользователю его скачать.

Content-Length — здесь указывается длина данных (не включая сам ответ с заголовочными данными) в байтах.

Исходя из вышесказанного, ответ сервера будет выглядеть примерно так:

HTTP/1.1 200 Ok<\n>
Content-Type: text/html<\n>
Content-Length: <длина_документа><\n>
<\n>
<тело_документа>

Если мы хотим отослать простой html-файл, то можем сократить ответ до следующего:

HTTP/1.1 200 Ok<\n>
Content-Type: text/html<\n>
<\n>
<тело_документа>

В результате получаем следующее. Клиент шлет запрос на получение документа, лежащего, например, в корне сервера:

GET / HTTP/1.1<\n>
User-Agent: ManualSender/1.0 :)<\n>
Host: www.someoneserver.com<\n>
<\n>

Сервер получает этот запрос, обрабатывает и выдает корневую страницу:

HTTP/1.1 200 Ok<\n>
Content-Type: text/html<\n>
<\n>
<html>
<head><title>Добро пожаловать на HTTP-сервер!</title></head>
<body>Вы находитесь на нашем http-сервере</body>
</html>

Код приложения

;---------------------------------------------------------------------
; http.asm
;---------------------------------------------------------------------

format PE console

entry start

; Подключаемые файлы

include '..\..\include\kernel.inc'
include '..\..\include\user.inc'
include '..\..\include\macro\stdcall.inc'
include '..\..\include\macro\import.inc'
include 'winsock.inc'
include 'macros.inc'

; Используемые значения

WM_SOCKET = WM_USER+100
INBUF_LEN     = 100000

; Секции программы

section '.data' data readable writeable

include 'strings.inc'

  hInstance dd ?
  http_class dd ?
  hwnd dd ?
  msg dd ?
  sock dd ?
  rv dd ?
  wc WNDCLASS
  wsadata WSADATA
  saddr SOCKADDR_TCP
  iaddr SOCKADDR_TCP
  buf rb INBUF_LEN

section '.code' code executable readable

include 'http_window.asm'

start:

      invoke GetModuleHandle, 0
      mov [hInstance], eax

      ; Инициализируем сокеты
      invoke WSAStartup, 101h, wsadata
      test eax, eax
      jnz error.wsanotinit

      ; Создаем окно
      jmp make_http_window

      ; Цикл обработки сообщений
   .msg_loop:
        invoke GetMessage,msg,NULL,0,0
        test eax,eax
        jz .exit
        invoke TranslateMessage,msg
        invoke DispatchMessage,msg
        jmp .msg_loop

   .exit:
      ; Очищаем сокеты
      invoke WSACleanup
      invoke ExitProcess, 0

error:

   .recv_error:

      ccall [printf], _recv_error
      jmp start.exit

   .connection_was_closed:

      ccall [printf], _connection_was_closed
      jmp start.exit

   .wsanotinit:

      ccall [printf], _wsanotinit, eax, wsadata.size
      jmp start.exit

   .bind_error:

      ccall [printf], _bind_error
      jmp start.exit

   .listen_error:

      ccall [printf], _listen_error
      jmp start.exit

   .cant_createsocket:

      invoke WSAGetLastError
      ccall [printf], _cant_createsocket, eax
      jmp start.exit

   .cant_resolve:

      ccall [printf], _fmtstr, _cant_resolve
      jmp start.exit

   .select_error:
      invoke WSAGetLastError
      cinv printf, _select_error, eax
      jmp start.exit

   .accept_error:
      invoke WSAGetLastError
      cinv printf, _accept_error, eax
      jmp start.exit


section '.idata' import data readable writeable

  library kernel32, 'kernel32.dll', \
          winsock, 'ws2_32.dll', \
          msvcrt, 'msvcrt.dll', \
          user32, 'user32.dll'

  kernel32:
  import ExitProcess, 'ExitProcess', \
         GetModuleHandle, 'GetModuleHandleA', \
         GetLastError, 'GetLastError', \
         WriteFile, 'WriteFile', \
         GetStdHandle, 'GetStdHandle', \
         RtlZeroMemory, 'RtlZeroMemory', \
         lstrlen, 'lstrlen', \
         lstrcmp, 'lstrcmp'

  user32:
  import RegisterClass, 'RegisterClassA', \
         DefWindowProc, 'DefWindowProcA', \
         CreateWindowEx, 'CreateWindowExA', \
         DestroyWindow, 'DestroyWindow', \
         GetMessage, 'GetMessageA', \
         TranslateMessage, 'TranslateMessage', \
         DispatchMessage, 'DispatchMessageA', \
         MessageBox, 'MessageBoxA', \
         ShowWindow, 'ShowWindow'

  winsock:
  import WSAStartup, 'WSAStartup', \
         WSACleanup, 'WSACleanup', \
         socket, 'socket', \
         gethostbyname, 'gethostbyname', \
         connect, 'connect', \
         WSAGetLastError, 'WSAGetLastError', \
         recv, 'recv', \
         send, 'send', \
         htons, 'htons', \
         bind, 'bind', \
         listen, 'listen', \
         WSAAsyncSelect, 'WSAAsyncSelect', \
         accept, 'accept', \
         closesocket, 'closesocket'

  msvcrt:
  import printf, 'printf'

;---------------------------------------------------------------------
; http.asm
;---------------------------------------------------------------------
; Создание скрытого окна, которое будет получать сообщения от сокета
make_http_window:

     ; Регистрируем класс окна
     mov eax, [hInstance]
     mov [wc.hInstance], eax
     mov [wc.hIcon], 0
     mov [wc.hCursor], 0
     mov [wc.style], 0
     mov [wc.lpfnWndProc], http_window_proc
     mov [wc.cbClsExtra], 0
     mov [wc.cbWndExtra], 0
     mov [wc.hbrBackground], COLOR_BTNFACE+1
     mov [wc.lpszMenuName], 0
     mov [wc.lpszClassName], _http_window_class
     invoke  RegisterClass, wc
     mov [http_class], eax
     test eax, eax
     jnz .create_http_window
     cinv printf, _fmtstr, _class_not_registered
     jmp start.exit
  .create_http_window:
     ; Создаем окно
     invoke GetModuleHandle, 0
     invoke CreateWindowEx, NULL, [http_class], _http_window_name, \
                            WS_SYSMENU, 100, 100, 100, 100, NULL, NULL, \
                            [hInstance], 0
     mov [hwnd], eax
     test eax, eax
     jnz start.msg_loop
     cinv printf, _fmtstr, _window_not_created
     br
     invoke GetLastError
     cinv printf, _fmtnum, eax

     jmp start.exit

proc http_window_proc, hWnd, wmsg, wparam, lparam
     enter
     push ebx esi edi
     cmp [wmsg], WM_CREATE
     je .wmcreate
     cmp [wmsg], WM_DESTROY
     je .wmdestroy
     cmp [wmsg], WM_SOCKET
     je .wmsocket

  .defwndproc:
     invoke DefWindowProc, [hWnd], [wmsg], [wparam], [lparam]
     jmp .finish

  .wmcreate:

     cinv printf, _start_sockets
     ; Создаем сокет
     invoke socket, AF_INET, SOCK_STREAM, 0
     cmp eax, -1
     je error.cant_createsocket
     mov [sock], eax
     ; Указываем Windows, чтобы она извещала нас о входящих соединениях
     invoke WSAAsyncSelect, [sock], [hWnd], WM_SOCKET, FD_ACCEPT
     cmp eax, -1
     je error.select_error

     ; Получаем адрес localhost
     invoke gethostbyname, _localhost
     test eax, eax
     jz error.cant_resolve
     mov eax, [eax+12]
     mov eax, [eax]
     mov eax, [eax]

     ; Подготавливаем saddr
     mov [saddr.sin_addr], eax
     mov [saddr.sin_family], AF_INET
     invoke htons, 80
     mov [saddr.sin_port], ax
     ; Биндим сокет к локальному адресу
     invoke bind, [sock], saddr, saddr.size
     test eax, eax
     jnz error.bind_error

     ; Начинаем слушать порт
     invoke listen, [sock], 10
     test eax, eax
     jnz error.listen_error

     jmp .done

  .wmdestroy:
     jmp .done

  ; Сообщение от WSAAsyncSelect
  .wmsocket:
     mov eax, [lparam]
     and eax, 0FFFFh
     cmp eax, FD_ACCEPT
     je .fd_accept
     cmp eax, FD_READ
     je .fd_read
     cmp eax, FD_CLOSE
     je .fd_close
     jmp .done

  .fd_accept:
     invoke accept, [wparam], iaddr, 0
     cmp eax, -1
     je error.accept_error
     invoke WSAAsyncSelect, eax, [hwnd], WM_SOCKET, FD_READ + FD_CLOSE
     jmp .done

  .fd_read:
     ; Обнуляем буфер
     invoke RtlZeroMemory, buf, INBUF_LEN
     ; Читаем данные из сокета
     invoke recv, [wparam], buf, INBUF_LEN, 0
     push eax
     invoke GetStdHandle, -11
     pop edx
     invoke WriteFile, eax, buf, edx, rv, 0
     invoke send, [wparam], index, index_size, 0
     invoke closesocket, [wparam]
     jmp .done

  .fd_close:
     invoke closesocket, [wparam]
     jmp .done

  .done:
     xor eax, eax

  .finish:
     pop ebx esi edi
     return
;---------------------------------------------------------------------
; strings.inc
;---------------------------------------------------------------------
_http_window_class db 'http_server_window_class', 0
_http_window_name db 'Noname', 0
_fmtstr db '%s', 0
_fmtnum db '%d', 0
_br db 0Dh, 0Ah, 0
_window_not_created db 'Window is not created', 0
_class_not_registered db 'Class is not registered', 0
_localhost db 'localhost', 0
_cant_resolve db "Can't resolve host", 0
_cant_createsocket db "Can't create socket: %d", 0
_bind_error db 'Error binding to host', 0
_listen_error db 'Error starting listening', 0
_wsanotinit db 'WSA not initialized: %d, %d', 0
_connection_was_closed db 'Connection was closed', 0
_recv_error db 'Receiving error', 0
_select_error db 'WSAAsyncSelect error %d', 0
_accept_error db 'Accept error %d', 0
_start_sockets db 'Starting sockets', 0Dh, 0Ah, 0
_shutdown_sockets db 'Shutdowning sockets', 0
_inbound_connection db 'Inbound connection', 0
_method_get db 'GET', 0

index db 'HTTP/1.1 200 Ok', 0Dh, 0Ah
      db 'Content-type: text/html', 0Dh, 0Ah
      db 0Dh, 0Ah
      db '<html>', 0Dh, 0Ah
      db '<head>', 0Dh, 0Ah
      db '<title>Welcome to HTTP Server!</title>', 0Dh, 0Ah
      db '</head>', 0Dh, 0Ah
      db '<body>', 0Dh, 0Ah
      db '<h2>HTTP Server Online</h2>', 0Dh, 0Ah
      db 'Best http server in the world!', 0Dh, 0Ah
      db '</body>', 0Dh, 0Ah
      db '</html>'
index_size = $-index

Анализ кода

«Веб-сервер», чей исходный код был приведен выше, очень примитивен. Он умеет только принимать запрос и, не проверяя его на правильность, выдавать только приветственную html-страницу.

В http.asm все, я надеюсь, достаточно понятно. Мы инициализируем сокеты, создаем окно (для чего, я поясню позже), входим в цикл обработки сообщений, а перед тем, как завершить работу приложения, вызываем WSACleanup.

Самое интересное находится в http_window.asm. При обработке сообщения WM_CREATE мы создаем сокет:

     ; Создаем сокет
     invoke socket, AF_INET, SOCK_STREAM, 0
     cmp eax, -1
     je error.cant_createsocket
     mov [sock], eax

А потом вызываем следующую функцию:

     ; Указываем Windows, чтобы она извещала нас о входящих соединениях
     invoke WSAAsyncSelect, [sock], [hWnd], WM_SOCKET, FD_ACCEPT

Вот что об этой функции говорит Platform SDK:

[ начало описания функции WSAAsynctSelect ]

WSAAsyncSelect

Функция WSAAsyncSelect указывает Windows посылать сообщения о событиях, касающихся определенного сокета.

int WSAAsyncSelect(
  SOCKET <>,           
  HWND hWnd <>,          
  unsigned int wMsg <>,  
  long lEvent <>

);

Параметры:

s — Дескриптор сокета, о событиях, связанных с которым, будет сообщаться.

hWnd — Хэндл окна, которому будут посылаться эти сообщения.

wMsg — Сообщение, которое будет посылаться.

lEvent — Битовая маска, в которой задаются интересующие события.

Возвращаемые значения

Если вызов функции WSAAsyncSelect прошел успешно, возвращаемое значение будет равно нулю. В противном случае будет возвращено SOCKET_ERROR, а код ошибки можно будет получить, вызвав WSAGetLastError.

[ конец описания ]

Учтите, что после того, как WSAAsyncSelect отошлет вам сообщение о конкретном событии, связанном с сокетом, то пока вы не предпримите определенных действий, нового сообщения о таком же событии вы не получите. Например, если вы получили сообщение FD_ACCEPT (кто-то пытается законнектиться к вам), то сообщения о другой попытки коннекта вы не получите до тех пор, пока не вызовите функцию accept.

Мы задаем WM_SOCKET, определенное в http.asm, в качестве сообщение, которое будет присылаться Windows, когда произойдет интересующее нас сообщение. Необходимая информация будет находиться в wParam (дескриптор сокета, с которым связано событие) и в lParam (в нижнем слове — код события).

Теперь, когда кто-нибудь попытаемся приконнектиться к сокету, наше окно получит соответствующее уведомление от операционной системы. Впрочем, сначала нужно ассоциировать созданный сокет с определенным адресом и портом, к которым и должны будут коннектиться посетители веб-сервера.

     ; Получаем адрес localhost
     invoke gethostbyname, _localhost
     test eax, eax
     jz error.cant_resolve
     mov eax, [eax+12]
     mov eax, [eax]
     mov eax, [eax]

     ; Подготавливаем saddr
     mov [saddr.sin_addr], eax
     mov [saddr.sin_family], AF_INET
     invoke htons, 80
     mov [saddr.sin_port], ax

Веб-сервер будет «висеть» на localhost’е (т.е. на локальной машине) на 80-ом порту, который является стандартным HTTP-портом. Если в адресе сайта прямо не указан порт, то браузер будет обращаться к 80-ому порту.

     ; Начинаем слушать порт
     invoke listen, [sock], 10
     test eax, eax
     jnz error.listen_error

Собственно, в данных строчках и содержится ответ на то, как сделать из приложения сервер (не обязательно web). Это делает функция listen.

[ начало описания функции listen ]

listen

Функция listen устанавливает сокет в состояние, в котором он слушает порт на предмет входящих соединений.

int listen(
  SOCKET <>,    
  int backlog <>

);

Параметры

s — Дескриптор сокета

backlog — Максимальное количество входящих соединений.

Возвращаемые значения

Если во время вызова не произошло никакой ошибки, listen возвратит ноль. В противном случае будет возвращено значение SOCKET_ERROR, а код ошибки можно будет получить с помощью функции WSAGetLastError.

[ конец описания ]

  ; Сообщение от WSAAsyncSelect
  .wmsocket:
     mov eax, [lparam]
     and eax, 0FFFFh
     cmp eax, FD_ACCEPT
     je .fd_accept
     cmp eax, FD_READ
     je .fd_read
     cmp eax, FD_CLOSE
     je .fd_close
     jmp .done

Было получено сообщение WM_SOCKET. Это значит, что произошло какое-то интересующее нас событие, связанное со слушающим сокетом.

  .fd_accept:
     invoke accept, [wparam], iaddr, 0
     cmp eax, -1
     je error.accept_error

Кто-то пытается подсоединиться к нашему веб-серверу. Вызываем функцию accept, чтобы разрешить входящее соединение.

[ начало описания функции accept ]

accept

Функция accept разрешает входящее соединение.

SOCKET accept(
  SOCKET s,
  struct sockaddr FAR *addr,
  int FAR *addrlen

);

Параметры

s — Дескриптор сокета, который ранее был помещен в состояние прослушивания с помощью функции listen. Фактическое соединение осуществляется с помощью сокета, который возвращается accept’ом.

addr — Необязательный указатель на буфер, который получит адрес того, кто пытается подсоединиться к серверу.

addrlen — Необязательный указатель на двойное слово, которое содержит длину addr.

Возвращаемые значения

Если не произошло никакой ошибки, accept возвратит дескриптор нового сокета, через который и будет происходить соединение.

В противном случае будет возвращен INVALID_SOCKET, а код ошибки можно будет получить с помощью функции WSAGetLastError.

Переменная, на которую указывает addrlen, вначале содержит объем, занятый структурой, на которую указывает addr. По возвращении она будет содержать длину возвращенного адреса в байтах.

[ конец описания ]

     invoke WSAAsyncSelect, eax, [hwnd], WM_SOCKET, FD_READ + FD_CLOSE
     jmp .done

Соединение разрешено, и мы вызываем функцию WSAAsyncSelect, чтобы получить соответствующее уведомление, когда можно будет читать из сокета или он будет закрыт.

  .fd_read:
     ; Обнуляем буфер
     invoke RtlZeroMemory, buf, INBUF_LEN
     ; Читаем данные из сокета
     invoke recv, [wparam], buf, INBUF_LEN, 0
     push eax
     invoke GetStdHandle, -11
     pop edx
     invoke WriteFile, eax, buf, edx, rv, 0
     invoke send, [wparam], index, index_size, 0
     invoke closesocket, [wparam]
     jmp .done

Здесь все просто. Пришло сообщение о том, что можно читать из сокета, что мы и делаем. Все считанное мы выводим на консоль (интересно же, что клиент прислал). По-хорошему, здесь мы должны были бы провести синтаксический разбор запроса: выяснить, какой конкретно документ он хочет, отдать его, если такого документа нет, послать сообщение об ошибке и т.п. Но поскольку я минимализировал сервер почти до предела в плане функциональности :), ничего этого здесь нет. Вместо этого мы шлем клиенту приветственный html.

  .fd_close:
     invoke closesocket, [wparam]
     jmp .done

Если сокет был закрыт клиентом, то мы его тоже закрываем со своей стороны.

Дополнительная литература

Для получения подробной информации о протоколе HTTP я рекомендую вам обратиться к RFC 2068.

Заключение

Надеюсь, вы почерпнули из этого туториала какую-нибудь полезную информацию. Напоследок мне хотелось бы сказать, что хотя составлять конкуренцию таким грандам как Apache и IIS без веских на то оснований, возможно, и не стоит, тем не менее, собственный маленький веб-сервер может быть очень полезен. Мне, например, предложили встроить в него механизм самораспространения, «чтобы он сам приходил к людям на дом» и устанавливался «через упрощенную процедуру инсталляции» ака Outlook. Другим, менее чреватым в плане возможных последствий для автора, вариантом может быть создание утилиты удаленного (не обязательно скрытого) администрирования, причем в качестве клиента будет выступать браузер, что весьма удобно, так как отпадет надобность в написании сопутствующей серверу клиентской программы. Возможно, вы найдете еще какое-нибудь применение для http-сервера. Все в ваших руках!

(c) Aquila / Hi-Tech, 2002

[C] Aquila / WASM.RU

 

Источник: wasm.ru /05.08.2002/


Поделиться в соц сетях

Подписаться
Уведомить о
0 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии

Есть идеи, замечания, предложения? Воспользуйтесь формой Обратная связь или отправьте сообщение по адресу replay@sciencestory.ru
© 2017 Истории науки. Информация на сайте опубликована в ознакомительных целях может иметь ограничение 18+