刚刚学习了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;
};