项目-天气邮局

2023-11-13

一、项目背景

  1. http协议被广泛使用,从移动端,pc端浏览器,http协议无疑是打开互联网应用窗口的重要协议,http在网络应用层中的地位不可撼动,是能准确区分前后台的重要协议。
  2. 在学习完网络的有关知识后,HTTP服务器无疑是巩固及应用所学知识的最好选择,从技术上更多的理解从上网开始,到关闭浏览器的所有操作中的细节。

二、项目简介

主要功能:

用户可输入服务器网址,服务器响应,返回一个登陆页面,用户通过服务器暴露出来的接口进行注册,注册完毕之后,用户可登陆,添加一些自己的亲朋好友的信息,服务器将其存储到数据库。服务器每天定时爬取全国的天气,根据数据库的信息然后推送给用户的亲朋好友。

实现技术

在开发项目时,我们是在Linux平台下,用到了如下知识:
C/C++,vim编辑器,socket套接字,CGI模型,shell脚本,epoll模型

三、项目流程

下图为主要流程:
这里写图片描述
由图我们可以看到项目共分为如下部分:
(1)实现HTTP服务器
(2)建立数据库
(3)获取天气信息
(4)推送天气

我们分别来分析每一步。


项目实现

一、HTTP服务器

这个是项目中的核心。利用epoll模型处理浏览器发送的请求,服务器响应,执行CGI程序,结果返回给浏览器。
关于HTTP的基础知识,请看博文:HTTP协议基础
1、socket编程
即网络套接字编程,可以参考网络套接字编程
socket编程有很多接口,首先我们要创建套接字,并将其与固定端口号绑定,创建监听套接字。
代码如下:

 static int startup(int port){
     int sock=socket(AF_INET,SOCK_STREAM,0);
     if(sock<0){
        perror("socket");
        exit(2);
    }

    int opt=1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

    struct sockaddr_in local;
    local.sin_family=AF_INET; 
    local.sin_addr.s_addr=htonl(INADDR_ANY);
    local.sin_port=htons(port);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
        perror("bind");
        exit(3);
    }
    if(listen(sock,5)<0){
        perror("listen");
        exit(4);
    }
    return sock;
}                         

2、epoll模型
关于epoll模型请看博文:epoll服务器
epoll是实现IO多路转接的一种模型,它由三个接口实现,如下:

  1. 创建epoll模型
    epoll_create调用会创建一个epoll模型,epoll模型包括三个部分,红黑树,回调机制,就绪队列。
    所以说,创建一个epoll模型,操作系统要做三件事情。
  2. 完成事件注册
    调用epoll_ctl,即将我们关心的文件描述符告诉操作系统,操作系统会将我们要关心的文件描述符及事件添加到红黑树中,至此我们就不需要管理它们了,由操作系统帮我们管理。
  3. 等待文件描述符就绪,检查事件是否就绪
    调用epoll_wait,检查就绪队列是否为空,如果不为空,就绪队列中保存的就是已经就绪的事件;然后操作系统将数据按顺序放置在用户提供的缓冲区,同时将事件数量返回给用户。

在代码中,我们使用epoll来帮我们管理连接请求,代码如下:

void serviceIO(int efd,struct epoll_event* buf,int num,int listen_sock)
{
    int i=0;
    struct epoll_event eve;
    for(i=0;i<num;i++)
    {
        int fd=buf[i].data.fd;
        if((buf[i].events)&EPOLLIN)
        {
            if(buf[i].data.fd==listen_sock)
            {
                struct sockaddr_in client;
                socklen_t len=sizeof(client);
                int newsock=accept(listen_sock,(struct sockaddr*)&client,&len);
                if(newsock<0)
                {
                    print_log("accept failed",FATAL);
                    continue;
                }
                printf("get a connect:%s:%d\n",inet_ntoa(client.sin_addr),\
                    ntohs(client.sin_port));
                eve.events=EPOLLIN;
                eve.data.fd=newsock;
                int ret=epoll_ctl(efd,EPOLL_CTL_ADD,newsock,&eve);
                if(ret<0)
                {
                    print_log("epoll_ctl failed",FATAL);
                    exit(9);
                }
                else
                {
                    eve.events=EPOLLOUT;
                    eve.data.fd=fd;
                    handler_request(fd,efd,eve);
                }
            }
        }
    }
}

3、CGI模型
关于CGI的知识请看:CGI机制与CGI程序

  1. CGI机制
    CGI(common gateway interface)——通用网关接口,是一个web服务器提供信息服务的接口。
  2. CGI程序
    CGI程序就是基于CGI标准所编写的程序,CGI程序必须按照CGI接口规范来写。

我们主要来分析项目中用到的POST方法和GET方法:
(1)GET方法从浏览器传参数给http服务器时,需要将参数跟到URI后面
(2)POST方法从浏览器传参数给http服务器时,需要将参数放到请求正文
(3)GET方法,如果没有传参,http按照一般的方式进行,返回资源即可,如果有参数传入,http就需要按照CGI方式处理参数,并将执行结果(期望资源)返回给浏览器
(4)POST方法,一般都需要使用CGI方式来进行处理

下面我们通过一张图来理解一下HTTP里面CGI模式的运行流程:
这里写图片描述

POST||GET

在上图中,我们首先需要知道是get方法还是post方法。
我们知道,HTTP请求报头由四部分组成,请求行,消息报头,空行及请求正文。而请求行又由方法,URL,版本号组成,由空格隔开。
因此:

  1. 在处理请求报文的时候,我们采取按行读取的方式,这样的话我们可以通过第一行获得方法和URL。
  2. 选择执行方式。由图所示:
    (1)GET方法,则判断其URL中是否有参数,如果有参数,则执行CGI程序,如果没有,服务器就返回其请求资源。
    (2)POST方法,则继续按行读取,直到读取到content_length字段,获取到content_length的值,根据值读取请求正文,执行CGI程序。
父子进程通信

在上图中,我们用父进程读取报头,子进程处理CGI程序,那么?父进程在拿到请求方法和参数后,怎样将数据交给子进程呢?
免不了需要进程间通信。我们知道,进程间通信有很多种,比如管道,共享内存,消息队列,信号量等。在这里我们使用管道实现,因此代码中我们需要创建两个管道。

  1. 父进程:
    要将socket中的内容写出来,关闭读端,close(input[0]);
    要将结果输出到浏览器端,需要关闭写端,close(output[1])
  2. 子进程:
    要读取父进程写入的数据,关闭写端,close(input[1]);
    需要将结果返回给父进程,关闭读端,close(output[0])

CGI程序如下:

int exe_cgi(int sock,char path[],char method[],char *query_string){
    char line[MAX];
    int content_length=-1;

    char method_env[MAX/32];
    char content_length_env[MAX/8];
    if(strcasecmp(method,"GET")==0){
        clear_header(sock);
    }
   else{//post
        do{
             get_line(sock,line,sizeof(line));
             if(strncmp(line,"Content-Length: ",16)==0){
               content_length=atoi(line+16);
           }
        }while(strcmp(line,"\n")!=0);

        if(content_length==-1){
            return 404;
        }
    }
    sprintf(line,"HTTP/1.0 200 OK\r\n");
    send(sock,line,strlen(line),0);
    sprintf(line,"Content-Type:text/html;charset=ISO-8859-1\r\n");
    send(sock,line,strlen(line),0);

    sprintf(line,"\r\n");
    send(sock,line,strlen(line),0);

    int input[2];
    int output[2];

    pipe(input);
    pipe(output);

    pid_t id=fork();
    if(id<0){
        return 404;
    }
    else if(id==0){
        //child
        //method,GET[query_string],POST[content_length]

        close(input[1]);
        close(output[0]);

        dup2(input[0],0);
        dup2(output[1],1);
        sprintf(method,"METHOD_ENV=%s",method);
        putenv(method_env);
        if(strcasecmp(method,"GET")==0){            
        sprintf(query_string,"QUERY_STRING=%s",query_string);
        putenv(query_string);                                                                                         
        }
        else{
            sprintf(content_length_env,"CONTENT_LENGTH=%d",content_length);
            putenv(content_length_env);
        }   

        //execl(...); //mycmd
        execl(path,path,NULL);
        exit(1);
    }else{
        //father
        close(input[0]);
        close(output[1]);

        char c;
        if(strcasecmp(method,"POST")==0){
            int i=0;
            while(i<content_length){
                read(sock,&c,1);
                write(input[1],&c,1);
                i++;
                }
         }

       while(read(output[0],&c,1)>0){
           send(sock,&c,1,0);
       }

       waitpid(id,NULL,0);

       close(input[1]);
       close(output[0]);
    }
    return 200;
}

二、建立数据库

首先,我们需要建立一个数据库,就叫做weather吧。
由开始的流程图我们知道,需要建立三张表:
用户信息表(login table):存放用户登录信息
我们需要几个字段,姓名,邮箱,账号,密码,且以账号为主键,可以唯一确定用户和注册登录信息(主键是唯一的,不能为NULL值)。

表的结构如下:
这里写图片描述
朋友信息表(msg table):存放用户的朋友
用来存放用户好友的信息,其中tel是该用户的电话号码,剩下的信息是该用户的朋友的信息。
我们要发邮件,必须知道该好友的城市及联系方式吧。因此最重要的两列是city和value,city将来要被用来在weather表里面查找天气,value记录的是邮箱或者电话被用于推送信息。

表的结构如下:
这里写图片描述
天气信息表(weather table):存放天气信息
表中存储的是各城市的天气信息,表中必须包含字段城市,日期,天气,温度,风速,风向等情况。根据好友信息表中的城市来匹配天气表中的城市,发送天气信息。

表的内容如下:
这里写图片描述

接口分析

三、获取天气信息

天气信息怎么获得呢?
项目中用了Python爬取了15tianqi.com天气网,得到了天气信息。
在Python中,使用的是scrapy框架进行天气爬取。
这块内容是在网上查的,不太懂,简单总结一下。

scrapy框架

scrapy是一个用python编写的,轻量级,简单轻巧,使用简单的爬虫框架,它使用Twisted异步网络库处理网络通讯。

了解一下它的组件:

  1. Scrapy Engine
    Scrapy的引擎,用来处理整个系统的数据流处理。
  2. Scheduler
    调度器,Scheduler接受从引擎发送过来的请求,压入队列之中,在引擎再次请求的时候返回给引擎。
  3. Downloader
    拿到请求之后,下载网页,并将下载的网页送给Spider进行处理。
  4. Spiders
    Spiders是蜘蛛,主要是解析网页的内容,我们可以在Spiders里面定制解析的规则。
  5. Item Pipeline
    项目管道,主要是用来存储数据以及对数据进行加工处理的。
  6. Downloader Middlewares
    下载器中间件,位于Scrapy引擎和下载器之间的钩子框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
  7. Spider Middlewares
    蜘蛛中间件,介于Scrapy引擎和蜘蛛之间的钩子框架,主要工作是处理蜘蛛的响应输入和请求输出。
  8. Scheduler Middewares
    调度中间件,介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。

数据处理流程

  1. Scrapy的整个数据流是由引擎控制的,引擎打开一个域名,并让蜘蛛处理这个域名
  2. 蜘蛛获取一个需要爬取的URL后,将这个需要爬取URL返回给引擎,引擎再将这个需要爬取的URL放到调度器中。
  3. 接下来引擎再从调度器中取出一个待爬取的URL,将这个URL送给下载器进行下载。
  4. 下载器下载完毕之后,将结果再返回给引擎。引擎再将结果交给蜘蛛进行解析。
  5. 蜘蛛将下载的页面解析为新的需要下载的URL和数据。新的URL发送给引擎。而数据则是交给项目管道进行处理。
  6. 项目管道可以对数据进行处理、加工和存储。

四、推送天气

获取到天气信息,就要发送天气了,那么如何发送天气呢?

  1. 由于天气具有实时性,所以我们必须每天都要进行更新,为此我们可以设置定时任务,每天定时去启动爬虫控制脚本,爬取全国天气信息。
  2. 将天气推送给msg这张表里面的所有人,用msg表里面的city字段的值到weather这张表里面找对应城市的天气,然后用邮件或短信发送。

项目中遇到的问题

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

项目-天气邮局 的相关文章

随机推荐

  • 问题 2020-6-10

    MySQL查考数据表中某一列是否有重复数据 解决 MySQL中 查询表 dat bill 2018 11 中字段 product id 值重复的记录 SELECT product id COUNT AS sumCount FROM dat
  • VUE中替换指定字符

    例子 我想要将变量a中的clazz替换成user 原本a的值为sys clazz 但是通过跳转到另一个页面时我需要把clazz替换成user 因此我可以这样做 this a this route query academicYearTabl
  • pip&conda 搜索包

    1 pip搜索包 pip search已经停用 需要安装pip search包 gt pip install pip search gt pip search numpy https pypi org search q numpy
  • 构造方法私有化

    1 单例设计模式 Singleton 在正常情况下 如果有一个类 那么只有通过产生对象之后才可以操作这个类范例 观察如下代码 class Singleton public void print System out println Hell
  • Web3的未来:7 个潜在的亿级机会

    Web3的未来将不仅仅是PFP的jpegs NFT市场 CEXs DEXs和DeFi协议 为了使Web3成为主流 有许多工具可以围绕一个繁荣的生态系统来构建 这里有7个隐藏的web3亿美元的机会 当下就开始建设 1 钱包整合生态系统 Web
  • 随机生User-Agent代理Ip

    import random import urllib request def url url p 49 235 246 24 8118 proxy support urllib request ProxyHandler http p op
  • opencv2 无法加载RTSP的问题

    最近通过OPENCV3 2 0加载海康摄像头 实现后续的摄像头画面拼接 但是发现拷贝了所有的opencv2的dll文件 仍然无法成功加载rtsp视频流 假设是视频流无法获取 通过VLC工具拉流 可以成功 效果如下 后来试了很多其他办法 感觉
  • linux卸载内核

    查看所安装的所有内核 ls boot 在删除旧内核之前 记住最好留有2个最近的内核 最新的和上一个版本 以防主要的版本出错 现在就让我们看看如何在Ubuntu上清理旧内核 在Ubuntu内核镜像包含了以下的包 linux image 内核镜
  • 【python实现华为OD机试真题】优雅子数组【2023 Q1

    题目描述 如果一个数组Q中出现次数最多的元素出现大于等于K次 被称为k 优雅数组 k也可以被称为优雅阈值只 例如 数组1 2 3 1 2 3 1 它是一个3 优雅数组 因为元素1出现次数大于等于3次 数组 1 2 3 1 2 就不是一一个3
  • UE4 跑酷游戏-得分机制

    得分机制 1 这次我们要做的是得分 根据人物跑过的地板来得取分数 首先进入我们的第三人称游戏模式里面 创建一些变量 2 退出第三人称游戏模式后 进入主地板蓝图后 在销毁地板之前写读取人物跑i过地板块数进行得分
  • Unity 使用Photon Server 联网开发(一)配置连接设置流程

    Photon官网首页 Photon官网 下载配置Pun与Server的教程 Photon Pun与Server的下载与配置教程 1 Pun导入项目后配置PhotonServerSettings Hosting 服务器托管方式 Not Set
  • Date 日期时间 浅层研究

    Date 日期时间 浅层研究 若需查看所有函数及属性常量 请直接翻至尾部 文章目录 Date 日期时间 浅层研究 获取当前时间 奇怪玩法 Date System Calendar 奇怪的问题探究区 查看此资料之前我们需要了解下 native
  • 论文阅读:FMCW雷达生命体征监测(心跳监测)

    论文 基于连续波雷达的非接触式生命体征监测系统设计与实现 1 雷达种类及特点 用于非接触式测量的雷达种类主要有脉冲雷达和连续波雷达两类 脉冲雷达根据发射脉冲与接收脉冲的时间差来计算所测量对象的实际距离 脉冲雷达需要将窄脉冲持续地发出 其优点
  • QVector 容器

    QVector 容器 QVector在相邻的内存中存储给定数据类型T的一组数据 在QVector前部或中间位置插入操作速度都很慢 因会导致内存中大量的数据移动 访问数据可使用下标 也可使用迭代器 继承自QVector类的子类有QPolygo
  • 如何运行后缀名为.ipynb的文件

    打开cmd 输入 pip install jupyter notebook 安装截图 下载之后 输入 jupyter notebook 之后浏览器会弹出一个页面 如图 然后就可以打开电脑里的文件 如图 也可以选择upload你的文件 打开i
  • Android 下拉刷新实践

    1 手动实现一个下拉刷新功能 2 效果图 3 view结构 4 实现思路
  • linux系统调用线程

    1 基础概念 早期unix系统中 没有线程概念 后来才引入线程 linxu 为了迎合 windows引入了线程 linux 上进程是非常优秀了 linux 上用线程和进程的区别不大 老程序都是用进程 gdb不支持线程 因为gdb比线程出现了
  • net core 下的图形验证码

    首先 通过 Nuget 安装 dotnet add package Lazy Captcha Core 注册服务 默认使用了内存存储 AddDistributedMemoryCache builder Services AddCaptcha
  • 什么是IDP?---What Is an Internal Developer Platform (IDP)?

    The modern approach to software delivery is based on cloud native services and the DevOps culture entailing software dev
  • 项目-天气邮局

    一 项目背景 http协议被广泛使用 从移动端 pc端浏览器 http协议无疑是打开互联网应用窗口的重要协议 http在网络应用层中的地位不可撼动 是能准确区分前后台的重要协议 在学习完网络的有关知识后 HTTP服务器无疑是巩固及应用所学知