当前位置:   article > 正文

一步一步实现一个简单的web服务器_c# 实现一个简单web

c# 实现一个简单web

目录

一.实现一个tcp/ip服务器

二.实现一个web服务器,并且能够返回指定内容

三.实现一个web服务器,并且能够返回某个html页面

四.实现一个web服务器,并且能够返回多种内容

五.报错解决


一.实现一个tcp/ip服务器

tcp服务器端编程,分为七个步骤,直接上代码

  1. # 导入socket模块中的所有方法
  2. from socket import *
  3. # 定义变量存储服务器的IP地址,端口号
  4. # 这里我们设置IP地址为本机地址,端口号为8080
  5. IP = '127.0.0.1'
  6. PORT = 8080
  7. # 1.实例化一个服务器套接字对象,使用socket方法,传入两个参数
  8. # 第一个参数代表网络层基于IPv4,第二个参数代表传输层基于TCP协议
  9. listen_socket = socket(AF_INET, SOCK_STREAM)
  10. # 2.实例化的服务器套接字对象,调用bind()方法,绑定端口号和IP地址
  11. # 一定注意,bind()方法的参数是一个元组,元组里面写有IP地址和端口号
  12. listen_socket.bind((IP, PORT))
  13. # 3.设置监听,并且设置最大监听数量为5
  14. # 最大监听数量不超过128
  15. listen_socket.listen(5)
  16. # 到这里,服务器就启动成功了,所以我们可以输出一些提示信息如下
  17. print(f'服务器已启动,在{PORT}端口等待连接')
  18. # 4.服务器套接字对象调用accept()方法,等待客户端/浏览器的连接
  19. # accept()方法没有参数,但是有两个返回值
  20. # 返回一个元组,元组中第一个元素是一个新的套接字对象,这个套接字对象用来和客户端进行数据交换
  21. # 元组中第二个元素是客户端的IP地址和端口号
  22. # 这里定义两个变量,使用元组拆包的方式进行接收
  23. # accept()方法,如果没有客户端连接,会一直停留在这一步,有点类似于input()函数
  24. data_socket, addr = listen_socket.accept()
  25. # 输出连接的客户端的信息如下
  26. print(f'{addr}已连接')
  27. # 5.新的套接字对象,调用recv()方法,接收客户端返回的数据
  28. # 注意这里调用recv()方法的是data_socket对象,而不是listen_socket对象
  29. # recv()方法有一个参数,1024代表一次接收的字节流长度最大为1024字节
  30. # 把接收到的数据,使用decode()方法,由字节流转码成字符串,并打印控制台
  31. recv_data = data_socket.recv(1024).decode('gbk')
  32. print(recv_data)
  33. # 6.新的套接字对象,调用send()方法,发送数据给客户端
  34. # 同样注意这里调用send()方法的是data_socket对象,而不是listen_socket对象
  35. # 把要发送的数据,先使用encode()方法,由字符串转码成字节流,再作为参数传入send()方法中
  36. send_data = '服务器已收到'.encode('gbk')
  37. data_socket.send(send_data)
  38. # 7.关闭两个套接字对象
  39. data_socket.close()
  40. listen_socket.close()

程序运行以后,如图所示

>>>

服务器已启动,在8080端口等待连接

这里我们使用一个工具来模拟客户端:NetAssist

这个调试助手连接以后,pycharm运行窗口编程这样

>>>
服务器已启动,在8080端口等待连接
('127.0.0.1', 10869)已连接

此时,我们在调试助手窗口数据内容,然后发送给服务器,可以看到服务器自动回复了我们设置的内容

 此时,pycharm运行窗口中如下图,由于我们目前没有设置循环,所以连接一次服务器套接字对象就关闭了

>>>
服务器已启动,在8080端口等待连接
('127.0.0.1', 10869)已连接
你好

进程已结束,退出代码0

这样我们就实现了一个非常基础简单的tcp服务器端

二.实现一个web服务器,并且能够返回指定内容

第一步的代码中,我们使用的是cs模型,也就是client客户端-server服务器端模型

接下来我们编写一下bs模型,也就是browser浏览器-server服务器端模型的代码

这里和第一步代码不同的地方在于,回复给浏览器的内容,要符合http响应报文格式

完整代码如下

  1. # 导入socket模块中的所有方法
  2. from socket import *
  3. # 定义变量存储服务器的IP地址,端口号
  4. # 这里我们设置IP地址为本机地址,端口号为8080
  5. IP = '127.0.0.1'
  6. PORT = 8080
  7. # 1.实例化一个服务器套接字对象,使用socket方法,传入两个参数
  8. # 第一个参数代表网络层基于IPv4,第二个参数代表传输层基于TCP协议
  9. listen_socket = socket(AF_INET, SOCK_STREAM)
  10. # 2.实例化的服务器套接字对象,调用bind()方法,绑定端口号和IP地址
  11. # 一定注意,bind()方法的参数是一个元组,元组里面写有IP地址和端口号
  12. listen_socket.bind((IP, PORT))
  13. # 3.设置监听,并且设置最大监听数量为5
  14. # 最大监听数量不超过128
  15. listen_socket.listen(5)
  16. # 到这里,服务器就启动成功了,所以我们可以输出一些提示信息如下
  17. print(f'服务器已启动,在{PORT}端口等待连接')
  18. # 4.服务器套接字对象调用accept()方法,等待客户端/浏览器的连接
  19. # accept()方法没有参数,但是有两个返回值
  20. # 返回一个元组,元组中第一个元素是一个新的套接字对象,这个套接字对象用来和客户端进行数据交换
  21. # 元组中第二个元素是客户端的IP地址和端口号
  22. # 这里定义两个变量,使用元组拆包的方式进行接收
  23. # accept()方法,如果没有客户端连接,会一直停留在这一步,有点类似于input()函数
  24. data_socket, addr = listen_socket.accept()
  25. # 输出连接的客户端的信息如下
  26. print(f'{addr}已连接')
  27. # 5.新的套接字对象,调用recv()方法,接收客户端返回的数据
  28. # 注意这里调用recv()方法的是data_socket对象,而不是listen_socket对象
  29. # recv()方法有一个参数,1024代表一次接收的字节流长度最大为1024字节
  30. # 把接收到的数据,使用decode()方法,由字节流转码成字符串,并打印控制台
  31. recv_data = data_socket.recv(1024).decode('gbk')
  32. print(recv_data)
  33. # 6.新的套接字对象,调用send()方法,发送数据给客户端
  34. # 同样注意这里调用send()方法的是data_socket对象,而不是listen_socket对象
  35. # 把要发送的数据,先使用encode()方法,由字符串转码成字节流,再作为参数传入send()方法中
  36. # 不同点在于发送内容的格式要符合http响应报文的格式
  37. # http响应报文 = 响应行 + 响应头 + 空行 + 响应体
  38. # 我们要回复的内容写在响应体中,空行则只书写\r\n
  39. # 响应行
  40. line = 'HTTP/1.1 200 ok\r\n'
  41. # 响应头
  42. head = 'Server:webserver\r\n'
  43. # 响应体
  44. body = '服务器已收到'
  45. send_data = line + head + '\r\n' + body
  46. # 转码
  47. send_data = send_data.encode('gbk')
  48. data_socket.send(send_data)
  49. # 7.关闭两个套接字对象
  50. data_socket.close()
  51. listen_socket.close()

程序运行之后,我们打开浏览器,在地址栏输入,并回车

http://127.0.0.1:8080/

浏览器显示内容如下

 此时,pycharm运行栏显示内容如下

>>>
服务器已启动,在8080端口等待连接
('127.0.0.1', 12074)已连接
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

进程已结束,退出代码0
 

这样我们就实现了一个非常基础简单的web服务器

三.实现一个web服务器,并且能够返回某个html页面

针对上一步的代码,想要返回一个html页面,比如返回一个index.html的导航页面

只需要在body响应体那里,把内容换成相应的文件就可以了,涉及到文件操作三步骤,也可以使用with方法,完整代码如下

  1. # 导入socket模块中的所有方法
  2. from socket import *
  3. # 定义变量存储服务器的IP地址,端口号
  4. # 这里我们设置IP地址为本机地址,端口号为8080
  5. IP = '127.0.0.1'
  6. PORT = 8080
  7. # 1.实例化一个服务器套接字对象,使用socket方法,传入两个参数
  8. # 第一个参数代表网络层基于IPv4,第二个参数代表传输层基于TCP协议
  9. listen_socket = socket(AF_INET, SOCK_STREAM)
  10. # 2.实例化的服务器套接字对象,调用bind()方法,绑定端口号和IP地址
  11. # 一定注意,bind()方法的参数是一个元组,元组里面写有IP地址和端口号
  12. listen_socket.bind((IP, PORT))
  13. # 3.设置监听,并且设置最大监听数量为5
  14. # 最大监听数量不超过128
  15. listen_socket.listen(5)
  16. # 到这里,服务器就启动成功了,所以我们可以输出一些提示信息如下
  17. print(f'服务器已启动,在{PORT}端口等待连接')
  18. # 4.服务器套接字对象调用accept()方法,等待客户端/浏览器的连接
  19. # accept()方法没有参数,但是有两个返回值
  20. # 返回一个元组,元组中第一个元素是一个新的套接字对象,这个套接字对象用来和客户端进行数据交换
  21. # 元组中第二个元素是客户端的IP地址和端口号
  22. # 这里定义两个变量,使用元组拆包的方式进行接收
  23. # accept()方法,如果没有客户端连接,会一直停留在这一步,有点类似于input()函数
  24. data_socket, addr = listen_socket.accept()
  25. # 输出连接的客户端的信息如下
  26. print(f'{addr}已连接')
  27. # 5.新的套接字对象,调用recv()方法,接收客户端返回的数据
  28. # 注意这里调用recv()方法的是data_socket对象,而不是listen_socket对象
  29. # recv()方法有一个参数,1024代表一次接收的字节流长度最大为1024字节
  30. # 把接收到的数据,使用decode()方法,由字节流转码成字符串,并打印控制台
  31. recv_data = data_socket.recv(1024).decode()
  32. print(recv_data)
  33. # 6.新的套接字对象,调用send()方法,发送数据给客户端
  34. # 同样注意这里调用send()方法的是data_socket对象,而不是listen_socket对象
  35. # 把要发送的数据,先使用encode()方法,由字符串转码成字节流,再作为参数传入send()方法中
  36. # 不同点在于发送内容的格式要符合http响应报文的格式
  37. # http响应报文 = 响应行 + 响应头 + 空行 + 响应体
  38. # 我们要回复的内容写在响应体中,空行则只书写\r\n
  39. # 响应行
  40. line = 'HTTP/1.1 200 ok\r\n'
  41. # 响应头
  42. head = 'Server:webserver\r\n'
  43. # 响应体
  44. # 使用with方法操作文件
  45. # 这里还要注意打开文件的编码,使用utf8编码(因为这个html文件就是使用utf8编码)
  46. # 同时,encode和decode方法转码,编码就不用指定了,不然可能会产生乱码问题
  47. with open('./html/index.html', 'r', encoding='utf8') as f:
  48. body = f.read()
  49. send_data = line + head + '\r\n' + body
  50. # 转码
  51. send_data = send_data.encode()
  52. data_socket.send(send_data)
  53. # 7.关闭两个套接字对象
  54. data_socket.close()
  55. listen_socket.close()

在浏览器输入地址,能够正确链接到指定html文件

这样,这个web服务器就能够返回指定的html页面(主页)

四.实现一个web服务器,并且能够返回多种内容

这个代码还不够完善,我们想在主页点击连接,就能跳转到指定的html页面或者跳转到指定的超文本内容,还需要做如下的修改

我们先观察一下浏览器发送的http请求报文结构

  1. 服务器已启动,在8080端口等待连接
  2. ('127.0.0.1', 14070)已连接
  3. # 请求行
  4. GET /html1.html HTTP/1.1
  5. Host: 127.0.0.1:8080
  6. Connection: keep-alive
  7. sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="101", "Google Chrome";v="101"
  8. sec-ch-ua-mobile: ?0
  9. sec-ch-ua-platform: "Windows"
  10. Upgrade-Insecure-Requests: 1
  11. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36
  12. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
  13. Sec-Fetch-Site: none
  14. Sec-Fetch-Mode: navigate
  15. Sec-Fetch-User: ?1
  16. Sec-Fetch-Dest: document
  17. Accept-Encoding: gzip, deflate, br
  18. Accept-Language: zh-CN,zh;q=0.9

在请求行中,出现了我们想要连接到的页面的资源路径,即/html1.html 这个路径,那么我们就可以在浏览器发送了http请求报文以后,把这个资源路径的地址提取出来,存储到变量中,并把这个变量作为文件打开的地址,然后把文件返回给浏览器,

最后再添加循环,就能够实现上述效果

完整代码如下

  1. # 导入socket模块中的所有方法
  2. from socket import *
  3. # 定义变量存储服务器的IP地址,端口号
  4. # 这里我们设置IP地址为本机地址,端口号为8080
  5. IP = '127.0.0.1'
  6. PORT = 8080
  7. # 1.实例化一个服务器套接字对象,使用socket方法,传入两个参数
  8. # 第一个参数代表网络层基于IPv4,第二个参数代表传输层基于TCP协议
  9. listen_socket = socket(AF_INET, SOCK_STREAM)
  10. # 2.实例化的服务器套接字对象,调用bind()方法,绑定端口号和IP地址
  11. # 一定注意,bind()方法的参数是一个元组,元组里面写有IP地址和端口号
  12. listen_socket.bind((IP, PORT))
  13. # 3.设置监听,并且设置最大监听数量为5
  14. # 最大监听数量不超过128
  15. listen_socket.listen(5)
  16. # 到这里,服务器就启动成功了,所以我们可以输出一些提示信息如下
  17. print(f'服务器已启动,在{PORT}端口等待连接')
  18. # 添加循环
  19. while True:
  20. # 4.服务器套接字对象调用accept()方法,等待客户端/浏览器的连接
  21. # accept()方法没有参数,但是有两个返回值
  22. # 返回一个元组,元组中第一个元素是一个新的套接字对象,这个套接字对象用来和客户端进行数据交换
  23. # 元组中第二个元素是客户端的IP地址和端口号
  24. # 这里定义两个变量,使用元组拆包的方式进行接收
  25. # accept()方法,如果没有客户端连接,会一直停留在这一步,有点类似于input()函数
  26. data_socket, addr = listen_socket.accept()
  27. # 输出连接的客户端的信息如下
  28. print(f'{addr}已连接')
  29. # 5.新的套接字对象,调用recv()方法,接收客户端返回的数据
  30. # 注意这里调用recv()方法的是data_socket对象,而不是listen_socket对象
  31. # recv()方法有一个参数,1024代表一次接收的字节流长度最大为1024字节
  32. # 接收的数据我们先转码成字符串
  33. recv_data = data_socket.recv(1024).decode()
  34. print(recv_data)
  35. # 添加是否断开连接的判断
  36. if not recv_data:
  37. print(f'{addr}已断开')
  38. break
  39. else:
  40. # 然后我们使用字符串的split()方法,按照空格进行切割,返回一个列表
  41. recv_data = recv_data.split(' ')
  42. # 通过http请求报文结构,不难发现,这个列表的第二个元素,就是要访问的资源路径地址
  43. # 把这个地址赋值给变量path
  44. path = recv_data[1]
  45. # 6.新的套接字对象,调用send()方法,发送数据给客户端
  46. # 同样注意这里调用send()方法的是data_socket对象,而不是listen_socket对象
  47. # 把要发送的数据,先使用encode()方法,由字符串转码成字节流,再作为参数传入send()方法中
  48. # 不同点在于发送内容的格式要符合http响应报文的格式
  49. # http响应报文 = 响应行 + 响应头 + 空行 + 响应体
  50. # 我们要回复的内容写在响应体中,空行则只书写\r\n
  51. # 响应行
  52. line = 'HTTP/1.1 200 ok\r\n'
  53. # 响应头
  54. head = 'Server:webserver\r\n'
  55. # 响应体
  56. # 这里我们添加一个if判断,如果直接输入网址,跳转到主页
  57. # 因为直接输入网址,http请求报文的资源路径是 / 左斜杠
  58. if path == '/':
  59. path = '/index.html'
  60. # 使用with方法操作文件
  61. # 这里拼接path和./html,组成一个完整的资源路径
  62. # 同时注意打开方式应该为rb二进制读,编码不指定
  63. with open('./html' + path, 'rb') as f:
  64. body = f.read()
  65. # 这里应该注意,body存储的内容本身就是字节流(二进制流)的格式了,所以不需要再转码
  66. send_data = line + head + '\r\n'
  67. # 转码
  68. send_data = send_data.encode()
  69. data_socket.send(send_data + body)
  70. # 7.关闭套接字对象,监听套接字对象可以不关闭
  71. data_socket.close()

然后在浏览器直接输入资源路径,能够实现直接跳转到指定页面

或者只输入网址,不输入资源路径,也能够跳转到主页,同时在主页点击链接,也能够链接到其他页面或者内容中

这样,就搭建了一个简单版本的web服务器 

五.报错解决

FileNotFoundError: [Errno 2] No such file or directory: './html/favicon.ico'

解决办法:在html文件夹中添加一个favicon.ico的图标

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/数据科学灵魂/article/detail/61661
推荐阅读
相关标签
  

闽ICP备14008679号