(网络)应用层:实现简易http服务器 (详细代码)

刚刚学习了http协议的数据格式,我们就来趁热打铁,实现一个简易的http服务器吧~

我们说http只是应用层的协议,在传输层使用TCP协议进行传输,http服务器本质上就是一个tcp服务器

搞清楚了之后,我们就可以开始了。

既然本质是tcp服务器,那么我们直接拿到之前实现的tcp服务器进行修改

没看过的小伙伴请猛戳TCP通信流程以及代码实现

回忆一下TCP服务器的通信流程:

创建套接字,绑定地址信息,开始监听,获取新建连接,收发数据,关闭套接字

那么下面就是按照上述流程实现的代码:
与之前的区别就是发送数据的时候要组织http格式数据

#include "tcpsocket.hpp"
#include <sstream>

using namespace std;


int main() {
  TcpSocket lst_sock;
  CHECK_RET(lst_sock.Socket());
  CHECK_RET(lst_sock.Bind("0.0.0.0", 9000));
  CHECK_RET(lst_sock.Listen());
  while(1) {
    //获取新连接
    TcpSocket con_sock;
    lst_sock.Accept(&con_sock);

    //接收信息
    string buf;
    con_sock.Recv(&buf);
    cout << "请求信息: [" << buf << "]" << endl;
    
    string body; //正文
    body = "<html><body><h1>Hello World!~</h1></body></html>";

    string empty_line = "\r\n"; //空行

    stringstream header; //头部
    header << "Content-Type: text/html\r\n";
    header << "Content-Length: " << body.size() << "\r\n";
    header << "Connection: close\r\n";
    //header << "Location: http://www.baidu.com\r\n";

    string first_line;//首行
    first_line = "HTTP/1.1 302 OK\r\n";

    //发送数据
    con_sock.Send(first_line);
    con_sock.Send(header.str());
    con_sock.Send(empty_line);
    con_sock.Send(body);

    con_sock.Close();
  }
  lst_sock.Close();

  return 0;
}

注意我先把头部信息中的Location注掉了,如果不注释,访问服务器会直接跳转到百度首页

上述代码编译后就可以运行了,然后请求服务器会得到如下响应:
在这里插入图片描述

OK,那这样我们就实现了一个简易的http服务器


tcpsocket.hpp是我们自己封装的TCP通信接口,为了方便小伙伴学习,我就直接把代码贴在这了,有兴趣的小伙伴可以看一看~

#include <iostream>
#include <string>
#include <unistd.h>
#include <arpa/inet.h> //地址转换接口头文件
#include <netinet/in.h> // 地址结构类型定义头文件
#include <sys/socket.h> //套接字接口头文件

using namespace std;

#define CHECK_RET(q) if((q)==false){return -1;}

class TcpSocket {
  public:
    TcpSocket() :_sockfd(-1) {}

    bool Socket() {
      _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
      if(_sockfd < 0) {
        perror("socket error");
        return false;
      }
      return true;
    }
    
    //绑定地址信息
    bool Bind(const string& ip, const int port) {
      //定义地址结构
      struct sockaddr_in addr;
      addr.sin_family = AF_INET;
      addr.sin_port = htons(port);
      addr.sin_addr.s_addr = inet_addr(ip.c_str());
      socklen_t len = sizeof(struct sockaddr_in);
      int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
      if(ret < 0) {
        perror("bind error");
        return false;
      }
      return true;
    }

    //服务端开始监听
    bool Listen(int backlog = 5) {
     //int listen(int sockfd, backlog)
     int ret = listen(_sockfd, backlog);
     if (ret < 0) {
       perror("listen error");
       return false;
     }
     return true;
    }

    //客户端请求新连接
    bool Connect(const string& ip, int port) {
      //int connect(int sockfd, sockaddr* srvaddr, int addrlen)
      struct sockaddr_in addr;
      addr.sin_family = AF_INET;
      addr.sin_port = htons(port);
      addr.sin_addr.s_addr = inet_addr(ip.c_str());
      socklen_t len = sizeof(struct sockaddr_in);
     int ret = connect(_sockfd, (struct sockaddr*)&addr, len);
     if (ret < 0) {
       perror("connect error");
       return false;
     }
     return true;
    }

    //服务端获取新连接
    bool Accept(TcpSocket* sock, string* ip = nullptr, int* port = nullptr) {
      //int accept(int sockfd, sockaddr* addr, int* len);
      struct sockaddr_in cli_addr;
      socklen_t len = sizeof(struct sockaddr_in);
      int fd = accept(_sockfd, (struct sockaddr*)&cli_addr, &len);
      if(fd < 0) {
        perror("accept error");
        return false;
      }
      //传入的sock对象, 获取新建的连接套接字
      //fd是与cli_addr地址的客户端进行通信的, 外部通过sock与客户端进行通信
      sock->_sockfd = fd;
      if (ip != nullptr) {
        *ip = inet_ntoa(cli_addr.sin_addr);
      }
      if(port != nullptr) {
        *port = ntohs(cli_addr.sin_port);
      }
      return true;
    }

    //接收数据
    bool Recv(string* buf) {
      //ssize_t recv(int sockfd, char* buf, int len, len)
      //返回值: 错误返回-1, 连接断开返回0, 成功返回实际接收的数据长度
      
      char tmp[4096] = {0};
      int ret = recv(_sockfd, tmp, 4096, 0);
      if (ret < 0) {
        perror("recv error");
        return false;
      }
      else if (ret == 0) {
        printf("connect shutdown");
        return false;
      }
      buf->assign(tmp, ret);
      return true;
    }

    //发送数据
    bool Send(const string& data) {
      //ssize_t send(int sockfd, char* data, int len, int flag)
      size_t total_len = 0; //实际发送的数据长度
      while(total_len < data.size()) {
        int ret = send(_sockfd, data.c_str() + total_len, 
            data.size() - total_len, 0);
        if (ret < 0) {
          perror("send error");
          return false;
        }
        total_len += ret;
      }
      return true;
    }

    //关闭套接字
    bool Close () {
      if(_sockfd < 0) {
        close(_sockfd);
        _sockfd = -1;
      } 
      return true;
    }

  private:
    int _sockfd;
};

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

殇&璃

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

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

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

打赏作者

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

抵扣说明:

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

余额充值