【网络】:HTTP服务器

本文介绍了HTTP服务器的基础知识,包括域名和URL的作用,特殊字符转义,HTTP请求和响应的结构,以及GET和POST方法的区别。还探讨了HTTP状态码、长连接和Cookie在实际应用中的作用。
摘要由CSDN通过智能技术生成

一.预备知识

1.域名

https://www.baidu.com,这是一个域名。在技术角度上,访问一个服务器其实只需要知道它的ip和域名就行了,而域名主要是方便我们进行查看。而域名会经过域名解析器解析为ip。那么端口号呢?其实https的端口号固定是443,http的端口号固定是80,所以在访问时,浏览器会自动加上。

2.url

例如你看到一篇博客很好,分享了链接http://t.csdnimg.cn/2fzXz,像这种链接就被称为url(统一资源定位符),所有网络上的资源都可以通过“字符串”标识并且获取。仔细观察这个url,/是Linux的分隔符,代表它的服务器是Linux,同时也被称为web根目录。

在这里插入图片描述

3.特殊字符

我们在浏览器上都是通过关键字进行搜索的,例如:搜索一个helloword。

在这里插入图片描述

浏览器通过kv的方式进行匹配。但是在我们的搜索词中如果出现了特殊字符,像 / ? : 等这样的字符, 已经被url当做特殊意义理解了,所以为了避免歧义,浏览器就必须先对特殊字符进行转义。

转义的规则如下:

将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式

在这里插入图片描述

“+” 被转义成了 “%2B”

二.HTTP的请求和响应

http请求报文由多行构成。分为请求行,请求报头,请求正文(如果不需要发资源可以不写);每个部分之间用‘\n’或者’\r\n’分割。

请求行:

1.Method:常用的GET和POST。
2.url:请求的资源
3.HTTP version:一般为1.0,1.1或者2.0
4.中间以空格(一般为一个)为分隔符。

请求报头

1.由多行构成。
2.内部大多是key:value结构。
3.内部有一个content length用来记录请求正文的字节数。
4.在请求报头和请求正文之间用一空行表示分离。

在这里插入图片描述

在这里插入图片描述

响应与请求是一样的。

在这里插入图片描述

三.写一个简单的HTTP服务器

由于之前写过sock的封装,这里就直接使用了。这里的服务器是最基本的就不详解了。

HttpServer.hpp

#include "Socket.hpp"


struct ThreadData
{
  int socket;
};

class HttpServer
{
public:
  HttpServer(const uint16_t port=3389):port_(port)
  {}
  ~HttpServer()
  {}

  bool Start()
  {
    listensock_.Socket();
    listensock_.Bind(port_);
    listensock_.Listen();
    while(true)
    {
      std::string clientip;
      uint16_t clientport;
      int sockfd=listensock_.Accept(&clientip,&clientport);
      //创建线程处理任务
      pthread_t tid;
      ThreadData*td=new ThreadData;
      td->socket=sockfd;
      pthread_create(&tid,nullptr,ThreadRun,td);
    }
  }

  static void* ThreadRun(void*args)
  {
    pthread_detach(pthread_self());//执行完毕后,自己释放
    ThreadData*td=static_cast<ThreadData*>(args);
    char buffer[1024];
    ssize_t n=read(td->socket,buffer,sizeof(buffer));
    if(n>0)
    {
      buffer[n]=0;
      std::cout<<buffer<<std::endl;
    }

    close(td->socket);
    delete td;
    return nullptr;
  }
private:
  Sock listensock_;
  uint16_t port_;
};

HttpServer.cc

#include "HttpServer.hpp"


int main(int argc,char*args[])
{
  if(argc>2)
  {
    std::cout<<"参数传递错误"<<std::endl;
    exit(1);
  }
  HttpServer hp;
  hp.Start();
  return 0;
}

Socket.hpp

#pragma once

#include <iostream>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

class Sock
{
public:
  Sock()
  {
  }
  ~Sock()
  {
  }
  void Socket()
  {
    socket_ = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_ < 0)
    {
      std::cout << "create socket error!!!" << std::endl;
      exit(1);
    }
  }
  void Bind(uint16_t port,std::string ip="0.0.0.0")
  {
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    inet_aton(ip.c_str(), &local.sin_addr);

    if (bind(socket_, (struct sockaddr *)&local, sizeof(local)))
    {
      std::cout << "Bind error!!!" << std::endl;
      exit(2);
    }
  }
  void Listen(int backlog = 10)
  {
    if (listen(socket_, backlog) < 0)
    {
      std::cout << "Listen error!!!" << std::endl;
      exit(3);
    }
  }

  int Accept(std::string *clientip, uint16_t *clientport)
  {
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int newfd = accept(socket_, (struct sockaddr *)&client, &len);

    if (socket_ < 0)
    {
      std::cout << "Accept error!!!" << std::endl;
      return -1;
    }
    uint16_t port = ntohs(client.sin_port);
    char ip[64];
    inet_ntop(AF_INET, &(client.sin_addr), ip, sizeof(ip));

    *clientip = ip;
    *clientport = port;
    return newfd;
  }

  bool Connect(const std::string &ip, const uint16_t &port)
  {
    struct sockaddr_in peer;
    memset(&peer, 0, sizeof(peer));
    peer.sin_family = AF_INET;
    peer.sin_port = htons(port);
    inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));

    int n = connect(socket_, (struct sockaddr *)&peer, sizeof(peer));
    if (n == -1)
    {
      std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
      return false;
    }
    return true;
  }

  int Fd()
  {
    return socket_;
  }

  void Close()
  {
    close(socket_);
  }

private:
  int socket_;
};

makefile

HttpServer:HttpServer.cc
	g++ -o $@ $^ -lpthread -std=c++11
.PHONY:clean
clean:
	rm -f HttpServer

在这里插入图片描述

我们主机收到的请求:

第一行:GET,URL,HTTP版本
HOST:代表的主机。
User-Agent:请求的客户端
中间每一行都是key-value结构

四.返回响应

我们可以根据上面的请求格式构建一个响应。

在这里插入图片描述

在这里插入图片描述

五.HTTP方法和状态码

1.HTTP方法

在这里插入图片描述

在平常的搜索过程中,用户是如何将自己的数据提交给服务器的呢?其实是通过表单,也就是搜索框。如果我们通过get进行提参,那么参数就会自动拼接到url后面。而post方法采用请求正文的方式进行提交。由于get方法会显示在url上,所以post方法更私密。

在这里插入图片描述

2.HTTP状态码

在这里插入图片描述

重点说一下3开头的,重定向就是由于某些原因,服务器无法为我们完成服务,它返回给我们一个新的地址,这个新地址由Location(报头内的数据)指定。这时浏览器就会二次发起请求,去访问新的地址。

在这里插入图片描述

3.报头内常见属性

Content-Type: 数据类型(text/html等)
Content-Length: Body的长度
Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
User-Agent: 声明用户的操作系统和浏览器版本信息;
referer: 当前页面是从哪个页面跳转过来的;
location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
Connection:keep-alive 长连接。
Content-Type:请求类型。

长连接:

这次我们写的http其实是短链接,当进行一次链接后就断开,无疑这样是低效的。所以开发者设计了一种长连接,在进行连接时一次发送多个请求,这样就可以进行多次响应。例如:淘宝的主页就有许多照片,每一张照片都有一个链接,而长连接就可以一次读取多张照片以提高效率。

Cookie

我们在登陆一个APP时,即使关闭了它,在短时间内重新登陆也不需要输入密码,这是因为在浏览器内有一个cookie文件,服务器通过Set Cookie响应,把个人的登陆信息储存在cookie文件内,之后的每一次http请求都会自动携带cookie文件内的内容。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咸蛋挞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值