基于socket实现一个简易的web服务器

在浏览器输入一个段网址,就会出现你想要的网页或数据,那么这个过程是如何做到的,今天我们来探索这个过程。首先http是基于socket的封装的,那我们就用socket来实现一个简易的web服务器。

首先先说思路,既然想要基于socket实现web服务器,就要知道在输入网址后都发生了什么,所以我们用springboot写个简易的web服务用Wireshark抓个包看看

@Controller
@ResponseBody
public class Test {
    @GetMapping("")
    public String ccc(){
        return "hello word";
    }
}

我们就是实现这样的一个建议的web服务器来探讨socket请求的时候都发生了什么

我们来抓个包:

Wireshark抓包

分析请求的过程

首先是tcp连接的三次握手

建立一个TCP连接时:

  1. 客户端通过调用connect发起打开连接。这时客户端发送SYN(同步)字节,它告诉服务器客户端将在(待连接)连接中发送的数据的初始序号。
  2. 服务器必须确认(ACK)客户端的SYN,同时自己也发个SYN的字节,它含有服务器将在同一连接中发送的数据的初始序列号。
  3. 客户端必须确认服务器的SYN

Wireshark抓包分析握手

发送http请求服务器端的过程

客户端发送服务器端

服务器发返回客户端

这里重点看服务器返回的数据,利用socket仿造一下返回数据就可以

这里可以看到主要的参数有响应头和数据

响应头:

HTTP/1.1 200 /r/n      http 协议 http状态码
Content-Type: text/html;charset=UTF-8 /r/n     类型
Content-Length: 10/r/n    数据长度
Date: Sun, 22 Aug 2021 13:20:36 GMT/r/n   时间
Keep-Alive: timeout=60rn  长连接时间
Connection: keep-alive/r/n 长连接
/r/n 


响应头结束


然后是返回数据

Line-based text data: text/html (1 lines) 
hello word

也就是说服务器主要返回是这几行数据

HTTP/1.1 200 /r/n      
Content-Type: text/html;charset=UTF-8/r/n    
Content-Length: 10/r/n    
/r/n  
 hello word 

Socket连接流程

代码

思路和方法都有了接下来开始写代码

//linux端c++的写法 因为是阻塞式socket所以在读取的时候可能会发生阻塞
#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
using namespace std;

int main(){
	int server_socketfd,client_socketfd; //声明服务器端与客户端的socket
	struct sockaddr_in my_addr,remote_addr; // 定义socket地址的结构体
	int sin_size; 
	char buf[BUFSIZ];  //数据传送的缓冲区
	memset(&my_addr, 0, sizeof(my_addr));
	my_addr.sin_family = AF_INET; //socket为TCP/IP – IPv4
	my_addr.sin_addr.s_addr = INADDR_ANY; //指定0.0.0.0的地址 接受所有地址的tcp请求
	my_addr.sin_port = htons(8080); //绑定端口

	if ((server_socketfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { //定义socket
		perror("socket error");
		return 1;
	}

	if (bind(server_socketfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) < 0) {//绑定端口与地址
		perror("bind error");
		return 1;
	}

	if (listen(server_socketfd, 5) < 0) { //开启监听
		perror("listen error");
		return 1;
	}

	while (true) {
		sin_size = sizeof(struct sockaddr_in);
		if ((client_socketfd = accept(server_socketfd, (struct sockaddr *)&remote_addr, (socklen_t *)&sin_size)) < 0) { //阻塞等待请求
			perror("accept error");
			return 1;
		}
		string data = "hello word"; //构造数据
		string buff = "HTTP/1.1 200 \r\n"; //构造头
		buff += "Content-Type: text/html;charset=UTF-8 \r\n";
		buff += "Content-Length: " + data.length();
		buff += "\r\n\r\n";
		buff += data;
		cout << "accept client:" << inet_ntoa(remote_addr.sin_addr)<< endl; //打印请求客户端IP
		int len=recv(client_socketfd, buf, BUFSIZ, 0); //接受客户端的数据并将其输出
		buf[len] = '';
		cout << buf << endl;
		send(client_socketfd, buff.c_str(), buff.length(), 0); //发送请求头与数据
		close(client_socketfd); //关闭客户端socket
	}
	close(server_socketfd);//关闭服务端socket
	return 0;
}

//java的写法 java总有访问失败的问题怀疑跟socket连接池有关
package com.company;

import java.net.ServerSocket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class Main {

    public static void main(String[] args) throws Exception {
        // write your code here
        ExecutorService executorService= Executors.newCachedThreadPool(); //构造线程池
        ServerSocket serverSocket = new ServerSocket(8080); //绑定端
        while (true) {
            executorService.execute(new Https(serverSocket.accept()));//监听将监听的分配给线程池
        }
    }
}
//线程
package org.company;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class Https extends Thread {
    private Socket socket;

    public Https(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        OutputStream outputStream = null;
        DataOutputStream dataOutputStream = null;
        try {
            outputStream = socket.getOutputStream(); //获取socket输出流
            dataOutputStream = new DataOutputStream(outputStream); //将输出流转换成DataOutputStream
            String data = "hello word"; //构造数据
            String message = String.format("HTTP/1.1 200\r\n" +
                    "Content-Type: text/html;charset=UTF-8\r\n" +
                    "Content-Length: %d\r\n\r\n" +
                    "%s", data.length(), data);//响应头
            dataOutputStream.write(message.toString().getBytes(StandardCharsets.UTF_8)); //写入客户端数据
            dataOutputStream.flush(); //清空缓冲区
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                dataOutputStream.close();
                outputStream.close();
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}

#python的写法
import socket

if __name__ == '__main__':
    socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #定义服务端socket
    socket_server.bind(('127.0.0.1', 8080)) #绑定端口与地址
    socket_server.listen(1) ##监听一个请求
    while 1:
        conn, socket_client = socket_server.accept() #阻塞等待请求并赋值给客户端
        print(socket_client) #打印客户端IP
        data = conn.recv(10240).decode('UTF-8')#解析数据
        print(data)#输出数据
        data = 'hello word'#返回的参数
        buff = 'HTTP/1.1 200 \r\n' 
               'Content-Type: text/html;charset=UTF-8 \r\n' 
               'Content-Length: {}\r\n' 
               '\r\n' 
               '{}'.format(len(data), data)#构造请求头
        conn.send(buff.encode('UTF-8')) #发送数据
        conn.close()#关闭客户端的socket请求
    socket_server.close() #关闭服务端的socket请求

这个简易的web服务器就算写完了但太简单,不可以区分路径,而且都是阻塞的socket。接下来就需要根据不通的请求路径返回不通的参数与结果并利用非阻塞的socket。通过正则可以获取请求的路径与请求的方式,静态文件可以获取文件内容的方式返回给客户端。

非阻塞的socket可以查看我的这篇文章:基于socket实现一个简易的web服务器——非阻塞的模式

参考文章列表:

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值