如何用NASM汇编写一个静态资源服务器?(二)、多进程服务设计

164 阅读4分钟

前言

本文接着上一章,上一章结尾我们说过,还有很多欠缺,比如文件只能读取40960字节,并且只允许请求一次主进程便退出了,这次我们来完成多进程服务器设计,那人家都是多线程,为什么你要多进程呢?

原因是以现在的功力,还没办法在nasm中掌握线程的使用,虽然我已经找到了相关文章,但还需要些时间,这如果使用MASN,那么情况可能会变得更好。

fork

fork函数用来在linux中创建一个进程,子进程和父进程运行在不同的内存中,并且两个内存空间内容是一样的,但是编写他的时候,难于理解的是调用一次他,却返回两次结果,这两次分别在子进程和父进程中。

看一下整体代码

%include "/home/HouXinLin/project/nasm/include/io.inc"

SECTION .data

headers db 'HTTP/1.1 200 OK', 0Dh, 0Ah, 'Content-Type: application/octet-stream', 0Dh, 0Ah, 0Dh, 0Ah

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

fileContents resb 40960

responseBuffer resb 40960

requestBuffer resb 4096

fullPath resb 1024

requestPath resb 1024

socketbuf resb 4



SECTION .text

global CMAIN

CMAIN:

    mov ebp, esp

    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

write:

    push esi



    mov edi,responseBuffer ;;字符复制目的地址

    mov esi,headers ;;字符复制原地址

    mov ecx,59 ;;复制59个字节到响应buffer中

    rep movsb

    call readFile ;;读取文件

    mov edi,responseBuffer+59 ;;偏移59个字节拼接body

    mov esi,fileContents

    mov ecx,eax

    rep movsb ;;在复制n个字节,eax是读取到的字节数量,不固定

    pop esi


    mov edx, eax ;;文件内容长度

    add edx,59 ;;加上头部长度

    mov eax,edx


    mov ecx, responseBuffer ;;输出

    mov ebx, esi

    mov eax, 4

    int 80h


;;关闭客户端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 ecx, 4 ;;打开文件

    mov ebx, fullPath

    mov eax, 5

    int 80h

    mov edx, 40960 ;;尝试读取40960个字节到fileContents

    mov ecx, fileContents

    mov ebx, eax

    mov eax, 3

    int 80h ;读取

    ret

exit:

    mov ebx,0

    mov eax,1

    int 80h

这比上一章并没有多出多少,主要的下面这几句,linux作为一个多进程的系统,把创建进程的系统调用号设置为2也是很好理解,也体现了重要性。

accept:
    mov eax, 2
    int 80h
    cmp eax, 0
    jz read
    jmp accept

int 80h执行后,此时会有两个进程执行下面的语句,一个是在子进程,一个是在父进程,如果eax中是0,那么表示当前环境在子进程中,所以要跳转到读数据的代码下,如果是一个非0的正整数,那么表示还是在父进程中,所以要跳转到accept下重新等待客户端连接,这样就基本实现了一个多进程服务器。