基于 c/c++语言的 socket 实现 httpserve源码实战分析

235 阅读3分钟

这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

想必大家在面试过程中一定或多或少会问到网络相关的知识,其中出现频率最高的热词无疑是TCP、UDP、http,https等内容,为了更好的理解这些概念、了解webserve的工作原理和了解一些c语言在linux上的接口的使用,所以我决定自己做一个基于socket可以解析http请求的httpserve,话不多说开始做!

首先我们必须要理解什么是socket?

  • socket
    • 1.套接字
    • 2. n. (电源)插座;(电器)插口,插孔;(人体的)窝,槽;(高尔夫插球杆的)棒头承口;(用以插入某物使其转动的)承窝,轴孔

网络套接字在计算机科学中是电脑网络中进程间资料流的端点。使用以网际协议为通信基础的网络套接字,称为网际套接字。因为网际协议的流行,现代绝大多数的网络套接字,都是属于网际套接字。 socket是一种操作系统提供进程间通信机制。 在操作系统中,通常会为应用程序提供一组应用程序接口,称为套接字接口。 维基百科

image.png

通俗理解就是如果两个进程如果想要进行数据交换就必须建立连接,socket就是解决这个问题的,socket这个名字也很形象哈,想要传输数据就要连接起来,就需要接口(socket)

想要建立socket连接,咱也要知道和谁建立连接才可以啊。

这就需要 ip部分的知识了。
这部分知识就不多说了 接下来我会再写文章总结
我们现在只需要知道一个ip和一个端口和ipv4/ipv6的协议版本就ok

找到了连接对象以后怎么发信息呢?

tcp和udp 就是我们的信息快递员,他们负责把我们的信息送到我们建立连接的两端 这部分知识在这里也不详细展开

好了到这里 我们一切工作都做好了

但是 怎么实现httpserve呢?

信息 发送过去了 我们要分析信息里的内容啊,如果我们想要实现httpserve,那肯定要对http的报文结构有详细的了解,每一行的信息的内容内涵,报文的结构,

image.png

针对着报文结构 我们可以读取我们想要的信息

下边是源码。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#include <iostream>
using namespace std;


void *thread_fun(void *arg){
	int fdc = *(int *)arg;

	// 5. 开始通信
	while (1)
	{
		char buff[128*1024];
		memset(buff,0,sizeof(buff));
		int len = read(fdc,buff,sizeof(buff));
		char ip[30] = { 0 };
		if(len>0){
			printf("客户端:%s\n",buff);
			
			string http_body = "<h1 >hello web</h1><h1>";
			// 解析报文 生成 http_body
			int i=0;
			while (1)
			{
				if(buff[i] != '\r' && buff[i+1] != '\n' && buff[i] != '\n'){
					http_body.push_back(buff[i]);
					
				}else{
					http_body.append("</h1><h1>");
				}
				i++;
				if(buff[i]=='\r' && buff[i+1]=='\n' && buff[i+2]=='\r' && buff[i+3]=='\n')
				{
					break;
				}
			}
			http_body.append("/h1");

			// HTTP 报文 第一部分 协议状态码 和 协议版本
			string http_message = "HTTP/1.1 200 \r\n";
			// HTTP 报文 第二部分 header
			http_message += "Content-Type: text/html;charset=UTF-8 \r\n";
			http_message += "\r\n\r\n";
			// HTTP 报文 第三部分 body
			http_message += http_body;
			int len2 = http_message.length();
			write(fdc,http_message.c_str(),len2);
			close(fdc);
			
		}
		if(len == 0){
			printf("客户端链接已经断开!!");
			break;
		}
	}

	pthread_exit(NULL);
}

int main()
{
	// 1. 创建监听套接字
	int fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

	if(fd == -1){
		perror("fail to socket");
		exit(1);
	}
	// 2. 绑定本地的ip和端口
	struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));  //每个字节都用0填充

	addr.sin_family = AF_INET;
	// addr.sin_addr.s_addr = inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr.s_addr);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
	addr.sin_port =  htons(9988);
	

	int ret = bind(fd,(struct sockaddr*)&addr,sizeof(addr));
	if(ret == -1){
		perror("fail to bind");
		exit(1);
	}
	// 3. 监听
	ret = listen(fd,128);
	if(ret == -1){
		perror("listen fail");
		exit(-1);
	}



while (1)
{
	// 4. 接收accept(阻塞,等待链接到达)
	struct sockaddr_in caddr;
	socklen_t caddrlen = sizeof(caddr);
	int fdc =  accept(fd,(struct sockaddr *)&caddr,&caddrlen);
	if(fdc == -1){
		perror("accept fail");
		exit(1);
	}

	//创建线程
	pthread_t thread;
	pthread_create(&thread,NULL,thread_fun,&fdc);
	pthread_detach(thread);
	
}	
	// 6. 关闭套接字
	close(fd);
	return 0;
}

其中还有一些内容涉及到阻塞,进程线程的知识,下篇文章总结