菜单开关

周梦康 发表于 2022-05-08 28 次浏览 标签 : 原创《Java Web 最佳学习路径》

前言

回顾自己的 Java 学习之路,最初跟着网络上学习 Java 教程,虽然说语法和基础知识掌握了个大概,再后来开始用 netty 做 web 容器开发项目,后来凭借之前开发经验结合 netty 的 api 还总结成了个一个轻量级的的框架 https://github.com/zhoumengkang/netty-restful-server

优势是足够的小,基本秒启动,而且性能不错,适合小型项目使用;劣势不够开放只能自己运转,不符合 Servlet 标准。

但是总感觉离没有入门,我想肯定很多人和我一样有同样的困惑,经过不断的东冲西撞最终摸索出一条比较适合有其他 web 语言开发经验的人学习 Java 的路径,所以计划总结成一个系列,自己巩固知识的同时也分享给其他人

目前 SpringBoot 是 Java Web 开发和 Java 微服务标准,Spring 是建立在 Servlet 协议标准之上的。毫不夸张的说Servlet 技术是 Java Web 开发的原点,所以我决定从这里开始讲起。

用 C/C++ 或者其他脚本语言做 Web 开发时,通常会以 CGI 或者 FastCGI 协议标准来做后端开发,web 服务器和 CGI 程序不是同一个进程, Java 做 Web 开发时,则是 Servlet ,Web 服务器进程和执行业务逻辑的 Servlet 程序在同一个进程。

为了做类比,我觉得有必要先说下 CGI 的开发模式。

学习 CGI

这里参考我之前的博客 https://mengkang.net/668.html

有兴趣的朋友可以看下 CGI 1.1 协议 ,这里不仅定义了很多标准的环境变量,URI 规则、请求的方法(GET/POST等)返回值 header 值等,可以说我们的所有 Web 开发的基石。

那么 CGI 程序是怎么提供服务的呢?

客户端访问某个 URL 地址之后,通过 GET/POST/PUT 等方式提交数据,并通过 HTTP 协议向 Web 服务器发出请求,服务器端的 HTTP Daemon(守护进程)将 HTTP 请求里描述的信息通过标准输入 stdin 和环境变量(environment variable)传递给主页指定的 CGI 程序,并启动此应用程序进行处理(包括对数据库的处理),处理结果通过标准输出 stdout 返回给 HTTP Daemon 守护进程,再由 HTTP Daemon 进程通过 HTTP 协议返回给客户端。

上面的这段话理解可能还是比较抽象,下面我们就通过一次GET请求为例进行详细说明。

Web Server

下面用代码来实现图中表述的功能。Web 服务器启动一个 socket 监听服务,然后在本地执行 CGI 程序。后面有比较详细的代码解读。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
    
#define SERV_PORT 9003
 
char* str_join(char *str1, char *str2);
char* html_response(char *res, char *buf);
   
int main(void)
{
    int lfd, cfd;
    struct sockaddr_in serv_addr,clin_addr;
    socklen_t clin_len;
    char buf[1024],web_result[1024];
    int len;
    FILE *cin;
  
    if((lfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
        perror("create socket failed");
        exit(1);
    }
      
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(SERV_PORT);
  
    if(bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
    {
        perror("bind error");
        exit(1);
    }
  
    if(listen(lfd, 128) == -1)
    {
        perror("listen error");
        exit(1);
    }
     
    signal(SIGCLD,SIG_IGN);
   
    while(1)
    {
        clin_len = sizeof(clin_addr);
        if ((cfd = accept(lfd, (struct sockaddr *)&clin_addr, &clin_len)) == -1)
        {
            perror("接收错误\n");
            continue;
        }
 
        cin = fdopen(cfd, "r");
        setbuf(cin, (char *)0);
        fgets(buf,1024,cin); //读取第一行
        printf("\n%s", buf);
 
        //============================ cgi 环境变量设置演示 ============================
         
        // 例如 "GET /user.cgi?id=1 HTTP/1.1";
 
        char *delim = " ";
        char *p;
        char *method, *filename, *query_string;
        char *query_string_pre = "QUERY_STRING=";
 
        method = strtok(buf,delim);         // GET
        p = strtok(NULL,delim);             // /user.cgi?id=1 
        filename = strtok(p,"?");           // /user.cgi
         
        if (strcmp(filename,"/favicon.ico") == 0)
        {
            continue;
        }
 
        query_string = strtok(NULL,"?");    // id=1
        putenv(str_join(query_string_pre,query_string));
 
        //============================ cgi 环境变量设置演示 ============================
 
        int pid = fork();
  
        if (pid > 0)
        {
            close(cfd);
        }
        else if (pid == 0)
        {
            close(lfd);
            FILE *stream = popen(str_join(".",filename),"r");
            fread(buf,sizeof(char),sizeof(buf),stream);
            html_response(web_result,buf);
            write(cfd,web_result,sizeof(web_result));
            pclose(stream);
            close(cfd);
            exit(0);
        }
        else
        {
            perror("fork error");
            exit(1);
        }
    }
   
    close(lfd);
       
    return 0;
}
 
char* str_join(char *str1, char *str2)
{
    char *result = malloc(strlen(str1)+strlen(str2)+1);
    if (result == NULL) exit (1);
    strcpy(result, str1);
    strcat(result, str2);
   
    return result;
}
 
char* html_response(char *res, char *buf)
{
    char *html_response_template = "HTTP/1.1 200 OK\r\nContent-Type:text/html\r\nContent-Length: %d\r\nServer: mengkang\r\n\r\n%s";
 
    sprintf(res,html_response_template,strlen(buf),buf);
     
    return res;
}

如上代码中的重点:

  1. 66~81行找到CGI程序的相对路径(我们为了简单,直接将其根目录定义为Web程序的当前目录),这样就可以在子进程中执行 CGI 程序了;同时设置环境变量,方便CGI程序运行时读取;
  2. 94~95行将 CGI 程序的标准输出结果写入 Web 服务器守护进程的缓存中;
  3. 97行则将包装后的 html 结果写入客户端 socket 描述符,返回给连接Web服务器的客户端。

CGI 程序(user.c)

#include <stdio.h>
#include <stdlib.h>
// 通过获取的 id 查询用户的信息
int main(void){
 
    //============================ 模拟数据库 ============================
    typedef struct 
    {
        int  id;
        char *username;
        int  age;
    } user;
 
    user users[] = {
        {},
        {
            1,
            "mengkang.zhou",
            18
        }
    };
    //============================ 模拟数据库 ============================
 
 
    char *query_string;
    int id;
 
    query_string = getenv("QUERY_STRING");
     
    if (query_string == NULL)
    {
        printf("没有输入数据");
    } else if (sscanf(query_string,"id=%d",&id) != 1)
    {
        printf("没有输入id");
    } else
    {
        printf("用户信息查询<br>学号: %d<br>姓名: %s<br>年龄: %d",id,users[id].username,users[id].age);
    }
     
    return 0;
}

将上面的 CGI 程序编译成gcc user.c -o user.cgi,放在上面web程序的同级目录。

代码中的第28行,从环境变量中读取前面在Web服务器守护进程中设置的环境变量,是我们演示的重点。

如果觉得上面代码看着比较生硬的朋友,我写了一份 PHP 代码,公众号回复123即可。麻烦给个关注吧,需要点动力哟。

原创博客,如需转载,请联系 i@mengkang.net
链接://mengkang.net/1513.html

👇 下面是我的公众号,高质量的博文我会第一时间同步到公众号,给个关注吧!

评论列表