一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
前言
前几章我们已经完成了一个基本的Http服务器设计,但缺陷也越写越多,本章来完成最基本的一个头信息设置,也就是Content-Length,如果没有他,虽然可以正常返回,但是浏览器不知道文件具体大小,所以下载文件会无进度。
但是前几章也已经也看到了,代码越写越多,所以,这章我们把新的代码抽离出来单独当在一个文件中,在主文件中通过%include 引入即可。
下面来看下主要代码。
utils.asm
getFileSize:
mov ecx ,statStructBuffer
mov ebx,edx
mov eax,195
int 80h
mov eax,[statStructBuffer+11*4]
ret
loadBaseHeaderToBuffer:
mov edi,headersBuffer
mov esi,headers
mov ecx,72
rep movsb
ret
setBodyContentLength:
mov ecx,10
mov edx,0
mov ebx,0
loopdiv:
div ecx
push edx ;余数入栈
inc ebx ;;存取结果是几位数
mov edx,0
mov ecx,10
cmp eax,0 ;;商是否为0
jnz loopdiv
setContentLengthAscii:
mov ecx,ebx
mov eax,0
_loop:
nop
pop edx ;;获取第n位
add edx,48
mov [headersBuffer+72+eax],edx
inc eax ;;偏移
loop _loop
mov edi,headersBuffer
add edi,72
add edi,eax
mov esi,header_end
mov ecx,4
rep movsb
ret
第一个函数getFileSize用来获取文件大小,这需要了解一下Linux中stat结构体以及stat函数,调用stat函数时,传入文件名和stat结构体指针,最终我们要的信息就会被包含在stat结构体中,但是奇怪的是,stat的系统调用号是18,但是当我调用他时,总会返回错误,但是当调用195时候,却是正常的,195的函数名却是stat64,具体原因就不清楚了,而我们要的文件大小信息,会偏移11*4,这个位置以后的4个字节就是我们要的文件大小,所以就有了下面代码。
mov eax,[statStructBuffer+11*4]
loadBaseHeaderToBuffer用来加载基本头信息到buffer中,这个buffer用来和以后的文件大小拼接,并返回给客户端。
setBodyContentLength函数和下面所有函数用来向buffer中追加文件大小,你会发现不就是追加个4字节的东西么,为什么会这么长,原因是我们要把一个int,转换成ascii,比如文件大小是11,可不能吧11直接追加到buffer后面,需要把11转换成ascii码,也就是将4949追加到后面,这样才行。
转换的方法就是不停除10,取出最后一位,也就是将余数入栈,最后遍历所有已经入栈的数字,加上48,变成标准的ascii,并拼接到后面。
主代码如下,下面就不说了,可以参考前几章。
%include "/home/HouXinLin/project/nasm/include/io.inc"
%include "utils.asm"
SECTION .data
headers db 'HTTP/1.1 200 OK', 0Dh, 0Ah,'Content-Type: application/octet-stream',0Dh, 0Ah,'Content-Length:',0h
header_end db 0Dh, 0Ah, 0Dh, 0Ah,0h
notfound_response db 'HTTP/1.1 404 OK', 0Dh, 0Ah, 'Content-Type: text/html', 0Dh, 0Ah, 'Content-Length: 10', 0Dh, 0Ah, 0Dh, 0Ah, 'not found!', 0Dh, 0Ah, 0h
root db '/home/HouXinLin/test', 0h
msg db 'accept',0h
ok db 'ok',0h
fail db 'fail',0h
SO_REUSEADDR db 1,0h
SECTION .bss
headersBuffer resb 4096
fileContents resb 40960
responseBuffer resb 40960
requestBuffer resb 4096
fullPath resb 1024
requestPath resb 1024
socketbuf resb 4
buffer resb 1024
statStructBuffer resb 144
SECTION .text
global CMAIN
CMAIN:
mov ebp, esp; for correct debugging
xor eax, eax
xor ebx, ebx
xor edi, edi
xor esi, esi
socket:
push byte 6
push byte 1
push byte 2
mov ecx, esp
mov ebx, 1
mov eax, 102
int 80h ;;创建Socket
bind:
push eax
mov edi,4
mov esi,SO_REUSEADDR
mov edx,2
mov ecx,1
mov ebx,eax
mov eax,366
int 80h
pop eax
mov edi, eax ;;edi存放server socket描述副
push dword 0x00000000
push word 0x901f ;;端口8080
push word 2
mov ecx, esp
push byte 16
push ecx
push edi
mov ecx, esp
mov ebx, 2
mov eax, 102
int 80h ;;绑定8080端口
cmp eax,0
je listen
jmp exit
listen:
push byte 1
push edi
mov ecx, esp
mov ebx, 4
mov eax, 102
int 80h ;监听
accept:
push byte 0
push byte 0
push edi
mov ecx, esp
mov ebx, 5
mov eax, 102
int 80h
mov esi, eax ;;将客户端描述符保存到esi中
mov eax, 2
int 80h
cmp eax, 0
jz read
jmp accept
read:
mov edx,6
mov ecx,msg
mov ebx,1
mov eax,4
int 80h
mov edx, 4096 ;;读取客户端内容
mov ecx, requestBuffer
mov ebx, esi
mov eax, 3
int 80h
getRequestResourcePath: ;;获取请求资源路径
mov eax,requestBuffer
mov ebx,eax
nextResourceChar:
cmp byte[ebx],32 ;;如果是空格
jz record ;;开始记录
inc ebx ;;下一个字符
jmp nextResourceChar
ret
record:
mov edx,ebx ;;ebx是第一个空格后的位置
sub edx,eax ;存放开始索引
mov ecx, requestBuffer
add ecx,edx ;;从ecx后的位置开始查看地一个空格
mov edx,0
hasEnd:
inc ecx
cmp byte[ecx],32 ;;如果下一个也是空格
jz finishSearch
mov eax, dword[ecx] ;;获取当前字符
mov dword[requestPath+edx],eax
inc edx
jmp hasEnd
finishSearch:
push esi
mov esi,root
mov edi,fullPath
mov ecx,20
rep movsb ;;复制root
mov esi,requestPath
mov edi,fullPath
add edi,20
mov ecx,edx
rep movsb
pop esi
openFile:
mov ecx, 4 ;;打开文件
mov ebx, fullPath
mov eax, 5
int 80h
cmp eax,0
jl notfound
push eax ;;保存文件描述符
write:
push eax
push esi
mov edx,fullPath
call getFileSize ;获取文件大小,结果保存到eax中
call loadBaseHeaderToBuffer ;家在header buffer用来拼接
call setBodyContentLength ;设置body大小
mov edx,eax
add edx,76 ;其中72个字节是头信息,4个字节是body和header之间的分割符号
pop esi
pop eax
mov ecx, headersBuffer ;;首先输出头
mov ebx, esi
mov eax, 4
int 80h
hasNext:
pop eax ;;文件描述符传递给readFile
push eax
call readFile ;;调用之后eax保存读取的大小,fileContents保存文件内容
push eax
mov edx,eax
mov ecx, fileContents ;;输出内容
mov ebx, esi
mov eax, 4
int 80h
pop eax ;;读取的文件字节数
cmp eax,0
call closeFile
jz closeSocket
jmp hasNext
notfound:
mov edx,76
mov ecx, notfound_response ;;输出头
mov ebx, esi
mov eax, 4
int 80h
closeSocket:
;;关闭客户端socket
push 2
push esi
mov ecx, esp
mov ebx, 13
mov eax, 102
int 80h
cmp eax,0
jz exit
mov edx,4
mov ecx,fail
mov ebx,1
mov eax,4
int 80h
jmp exit
readFile:
mov edx, 40960 ;;尝试读取40960个字节到fileContents
mov ecx, fileContents
mov ebx, eax
mov eax, 3
int 80h ;读取
ret
closeFile:
mov ebx,eax
mov eax,6
int 80h
ret
exit:
mov ebx,0
mov eax,1
int 80h