Posts p12 重写linux命令-- web服务器
Post
Cancel

p12 重写linux命令-- web服务器

1. 概览

客户/服务器,交互过程包含下列3个操作:

  1. 服务器设立服务

  2. 客户链接到服务器

  3. 服务器和客户处理事务

1.1 操作1,建立服务器端socket

  1. 创建一个socket socket = socket(PF_INET,SOCK_STREAN,0);

  2. 给socket绑定一个地址 bind(sock,&addr,sizeof(addr))

  3. 监听接入请求 listen(sock,queue_size);

将上述三个步骤合成一个函数: make_serve_socket,调用该函数就可以创建一个服务器端socket: sock = make_serve_socket(int portnum);

1.2 建立到服务器端的链接

  1. 创建一个socket socket = socket(PF_INET,SOCK_STREAM,0)

  2. 使用socket连接到服务器 connect(sock,&serv_addr,sizeof(serv_addr));

将上述两步抽象为一个函数,connetc_to_server,只要调用该函数就可以建立到服务器的链接: fd = connect_to_server(hostname,portnum);

1.3 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#define HOSTLEN 256
#define BACKLOG 1

int make_server_socket_q(int,int);

int make_server_socket(int portnum){
    return make_server_socket_q(portnum,BACKLOG);
}

int make_server_socket_q(int portnum,int backlog){
    struct sockaddr_in saddr;
    struct hostent *hp;
    char hostname[HOSTLEN];
    int sock_id;

    sock_id = socket(PF_INET,SOCK_STREAM,0);
    if (sock_id == -1)
        return -1;
    bzero((void *)&saddr,sizeof(saddr));
    gethostname(hostname,HOSTLEN);
    hp = gethostbyname(hostname);

    bcopy((void *)hp->h_addr,(void*)&saddr.sin_addr,hp->h_length);
    saddr.sin_port = htons(portnum);  // why ?
    saddr.sin_family = AF_INET;   // AF_INET             IPv4 Internet protocols          ip(7)
    if (bind(sock_id,(struct sockaddr *)&saddr, sizeof(saddr)) != 0)
        return -1;

    if (listen(sock_id,backlog) != 0)
        return -1;
    return sock_id;
}


int connect_to_server(char *host,int portnum){
    int sock;
    struct sockaddr_in servadd;
    struct hostent *hp;

    sock = socket(AF_INET,SOCK_STREAM,0);
    if (sock == -1)
        return -1;

    bzero(&servadd,sizeof(servadd));
    hp = gethostbyname(host);
    if (hp == NULL)
        return -1;
    bcopy(hp->h_addr,(struct sockaddr *)&servadd.sin_addr,hp->h_length);
    servadd.sin_port = htons(portnum);
    servadd.sin_family = AF_INET;

    if (connect(sock,(struct sockaddr*)&servadd, sizeof(servadd)) != 0)
        return -1;
    return sock;
}

1.4 操作3: 客户/服务器的会话

一般的客户端

1
2
3
4
5
6
7
8
main(){
    int fd;
    fd = connect_to_server(host,port);
    if (fd == -1)
        exit(1);
    talk_with_server(fd);
    closed(fd);
}

一般的服务器端

1
2
3
4
5
6
7
8
9
10
11
12
13
main(){
    int sock,fd;
    sock = make_server_socket(port);
    if (sock == -1)
        exit(1);
    while(1){
        fd = accept(sock,NULL,NULL);
        if (fd == -1)
            break;
        process_request(fd);
        close(fd);
    }
}

1.5 利用客户/服务器框架,重写 timeserv/timeclnt

1
2
3
4
5
6
7
talk_with_server(fd){
    char buf[LEN];
    int n;

    n = read(fd,buf,LEN);
    write(1,buf,n);
}
1
2
3
4
5
6
7
process_request(fd){
    time_t now;
    char *cp;
    time(&now);
    cp = ctime(&now);
    write(fd,cp,strlen(cp));
}

调用fork实现服务器端

1
2
3
4
5
6
7
8
9
10
process_request(fd){
    int pid = fork();
    switch(pid){
        case -1:return;
        case 0: dup2(fd,1);
            close(fd);
            execl("/bin/date","date",NULL);
        default:wait();
    }
}

思路: 子进程处理,父进程等待。 问题: 父进程的wait() 等待有什么意义? 下一章中说明。

1.7 服务器的设计问题: DIY or 代理

  1. 自己做 服务器接受请求,然后自己处理工作 适合于简单的任务。

  2. 代理 服务器接受请求,然后创建一个新的进程来处理工作。 适合于复杂的任务。

1.8 代理详解

看到这里,终于有点明白,springboot 等网络框架,是怎么并发处理多个请求的。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
main(){
    int sock,fd;
    void child_waiter(int),process_request(int);
    signal(SIGCHED,child_waiter);

    if ((sock = make_server_socket(PROTNUM)) == -1)
        perror("sock error);
    while(1){
        if (fd == -1)
            break;
        process_request(fd);
        close(fd);
    }
}

void child_waiter(int signum){
    wait(NULL);
}

void process_request(int fd){
    if (fork() == 0){
        dup2(fd,1);
        close(fd);
        execlp("data","date",NULL);
        oops("execlp date");
    }
}

问题

  1. 程序运行到信号处理函数跳转时候会终中断系统调用accept 额。。。 这个没太想明白

  2. Unix 如何处理多信号 这里先说一下wait(),wait() 为什么可以将僵尸子进程删除。 父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。 那如果几乎同时,有三个子进程的SIGCHED信号到达,如何处理? 信号处理函数处理一个SIGCHED信号,阻塞一个SIGCHED信号,但是还是会丢失一个SIGCHED 信号,即会产生一个僵尸进程。 调用waitpid()解决。

    1
    2
    3
    4
    
     waitpid(-1,NULL,WNOHANG)
     //-1 : 等待所有子进程
     //第二个值: 获取状态
     //WNOHANG:没有僵尸进程,则不必等待
    

    该循环直到所有退出的子进程都被等待了才停止。

2. web 服务器

2.1 设计

  1. 建立服务器

  2. 接受请求

  3. 读取请求

  4. 处理请求

  5. 发送应答

2.2 web服务器协议

请求和应答的格式在超文本传输协议(HTTP)中有定义。

  1. HTTP 请求:GET telnet 创建了一个socket,并调用了connect来链接到服务器。接下来输入请求: GET /index.html/1.0 参数1:命令。 参数2:参数。 参数3:协议的版本号。

  2. HTTP应答 HTTP/1.1 200 OK 第一串是协议的版本,第二串是返回码。 这里请求的文件是/info.html.而服务器给出的应答是可以找到该文件,如果服务器没有该文件名,返回码将会是404。 接下来服务器会返回附加信息,包括服务器名字,应答时间,服务器所发送的数据类型以及应答链接的类型。

3. 编写web服务器

This post is licensed under CC BY 4.0 by the author.

Contents

Trending Tags