基于 Socket 和 HTTP协议实现简单的Web服务器 - Go语言中文社区

基于 Socket 和 HTTP协议实现简单的Web服务器


本Web服务器能够实现的基本功能:
1、实现最基本的HTTP/1.0版本的web服务器,客户端能够使用GET、POST方法请求资源 
2、服务器将客户请求的资源以html页面的形式呈现,能够返回一个静态页面,并能够进行差错处理(如:客户请求的资源不存在时,服务器能够返回一个404的页面) 
3、服务器能进行简单的cgi程序·,能够根据 query_string或者 body中的内容动态生成页面。比如当客户在表单中输入数据后,服务器能够将运行结果返回给客户 

一、http服务器实现的基础知识

  • 关于HTTP协议(在前几篇博客《图解http协议》中有详细的介绍,在这就简单地说一下) 
        即超文本传输协议,它是应用层的协议,底层是基于TCP通信的。HTTP协议的工作过程:客户通过浏览器向服务器发送文档请求,浏览器会合成http请求报文格式(若想了解具体报文格式可结合费德勒抓包工具来学习),服务器将请求的资源b包装成http响应报文格式发送给浏览器。
  • 关于URL 
       即统一资源定位符,每个网页都对应一个URL地址(俗称网址),具有全球唯一性。它包含的信息指出所请求文件path,参数等。 一个完整的URL包括协议类型、主机类型、路径和文件名。 
    http协议的URL格式: http: //host[:port][abs_path] ,http表示使用http协议来进行资源定位;host是主机域名或者ip地址;port是端口号,如果用户不填写则有默认的;abs_path代表资源的路径。 
    项目中主要涉及URL的两种格式—URL带参数和不带参数的。(?后边是参数) 下边是标准url的格式。
  • GET方法有时会带参数有时会不带参数,如果带?则问号后边是参数,连接在资源路径后边;POST方法使用的是不带参数的URL,它的参数是通过http请求报文中的body传递给服务器的。
  • 关于HTTP的请求与响应格式 
  • 响应报头中的状态码和状态码描述,例如:当请求的资源不存在时,会收到“404 NotFound”的页面,404就是状态码,“NotFound”就是状态码描述,即请求的文件不存在,服务器无法处理。(关于其它状态码在前边几篇博客中有详细介绍)

二、服务器实现的基本思路

1、http协议是基于TCP通信的协议,因此,实现web服务器的第一步至少要能实现两个主机不同进程之间的TCP通信,这部分可基于socket来实现。服务器端:创建sock->绑定(将sock文件描述符和ip地址端口号绑定在一起)-> 设置服务器为监听模式->accept->服务器创建线程去循环读写(创建一个新线程去单独处理一个客户端的请求)客户端·:创建sock->connect连接到服务器->循环读写。
2、接下来的部分就是比较主要的处理逻辑了,当服务器收到请求后,首先应该从请求报文中读取首行,从首行中分离出method,如果是get请求,则判断是否有query_string,如果有没有,则生成及静态页面,如果有,则触发cgi程序,根据参数不同,生成不同的cgi响应。如果是post请求,也触发cgi程序,根据body处的内容不同,生成不同响应。
3、判断资源是否存在,如果存在,判断这个资源是一个目录、还是文件。如果是目录,则返回该目录下的一个默认文件。例如:index.html.

cgi模式: 
这里写图片描述
     上述这张图描述了运行cgi时的过程,首先服务器要从浏览器上读取参数,然后需要fork出一个子进程进行cgi部分的处理,子进程进行程序替换来执行cgi程序代码。

下面总结出父子进程内部各自需要干的事情: 

1 创建一对管道

2 fork

3 对于父进程:

    a) 请求相关内容写入到管道中,

    b)尝试从管道中读取输出(CGI 程序生成的动态页面)

    c)把结果写回到socket里面,回收子进程

4 对于子进程

     a)设置环境变量(method query_string conent_length),为了让程序替换后的CGI程序能够访问到。

     b)把标准输入标准输出重定向到管道中,所以在CGI程序中,printf scanf就相当于从管道中读取数据和往管道中放数据。

     c)子程序进行程序替换。

三、错误处理

       需要改变响应的消息报头的格式,即改变状态码,状态码描述,以及返回的页面。例如当请求的资源不存在时,服务器需要返回给浏览器一个默认的404页面,告诉客户请求的资源不存在。效果如图: 

四、项目文件

五、测试

从功能上测试:

1 请求资源不存在时-----------预期结果:应出现404页面

 

2 请求资源存在,并且是文本文件时--------预期结果:出现存在的对应文本文件

3 请求资源为图片时:--------预期结果:展示存在的对应图片资源

4 当输入请求的path为一个目录时--------预期结果:展示服务器端wwwroot下的默认文件(index.html)

 

5 如果get请求并且带有query_string.----------预期结果:触发 CGI程序,动态生成页面

6 如果是post请求---------预期结果:触发 CGI 程序,动态生成页面

 

 从兼容性上测试:(在chrome,火狐等等浏览器上分别进行测试,看是否能正确显示)

六、源码:(已托管到Github)

https://github.com/superWangxinrui/hello-world/tree/master/%E5%9F%BA%E4%BA%8EHTTP%E5%8D%8F%E8%AE%AE%E5%AE%9E%E7%8E%B0%E7%AE%80%E6%98%93Web%E6%9C%8D%E5%8A%A1%E5%99%A8

附: 
这里是我遇到的一些问题,粘出来,也可能是你遇到的问题: 
1  页面可以显示出来,但是乱码:

      解决方法:在响应报文生成时 content_type ,设置utf8编码风格。成功解决了上述乱码问题。

      const char* type_line="" Content-Type:"" text/html;charset=utf-8"n";

2  服务器经常显示端口被占用,发现服务器有大量Time_wait状态。表示服务器端在等待最后一次ACK,而没有彻底断开与客户端的连接。

    解决方法:设置,使得可重用Time_wait状态,成功解决上述问题。

     int opt=1;
     setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

3  刚开始,服务器只能处理一个客户端连接的情况,原因是,服务器是单线程,阻塞到了recv,没法循环去accept,导致无法处理另一个客户端连接的情况。

      解决方法:使用多线程的技术来处理,一个客户端连接进来,create一个线程去单独处理一个客户端的情况,主线程进行线程分离,返回执行accept,去处理下一个客户端的请求。

4 关掉终端,就无法访问服务器。

       解决方法:利用守护进程的方式来运行。 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/superwangxinrui/article/details/81142674
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-07 03:24:28
  • 阅读 ( 1590 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢