赞
踩
目录
tcp服务器端编程,分为七个步骤,直接上代码
- # 导入socket模块中的所有方法
- from socket import *
-
- # 定义变量存储服务器的IP地址,端口号
- # 这里我们设置IP地址为本机地址,端口号为8080
- IP = '127.0.0.1'
- PORT = 8080
-
- # 1.实例化一个服务器套接字对象,使用socket方法,传入两个参数
- # 第一个参数代表网络层基于IPv4,第二个参数代表传输层基于TCP协议
- listen_socket = socket(AF_INET, SOCK_STREAM)
-
- # 2.实例化的服务器套接字对象,调用bind()方法,绑定端口号和IP地址
- # 一定注意,bind()方法的参数是一个元组,元组里面写有IP地址和端口号
- listen_socket.bind((IP, PORT))
-
- # 3.设置监听,并且设置最大监听数量为5
- # 最大监听数量不超过128
- listen_socket.listen(5)
- # 到这里,服务器就启动成功了,所以我们可以输出一些提示信息如下
- print(f'服务器已启动,在{PORT}端口等待连接')
-
- # 4.服务器套接字对象调用accept()方法,等待客户端/浏览器的连接
- # accept()方法没有参数,但是有两个返回值
- # 返回一个元组,元组中第一个元素是一个新的套接字对象,这个套接字对象用来和客户端进行数据交换
- # 元组中第二个元素是客户端的IP地址和端口号
- # 这里定义两个变量,使用元组拆包的方式进行接收
- # accept()方法,如果没有客户端连接,会一直停留在这一步,有点类似于input()函数
- data_socket, addr = listen_socket.accept()
- # 输出连接的客户端的信息如下
- print(f'{addr}已连接')
-
- # 5.新的套接字对象,调用recv()方法,接收客户端返回的数据
- # 注意这里调用recv()方法的是data_socket对象,而不是listen_socket对象
- # recv()方法有一个参数,1024代表一次接收的字节流长度最大为1024字节
- # 把接收到的数据,使用decode()方法,由字节流转码成字符串,并打印控制台
- recv_data = data_socket.recv(1024).decode('gbk')
- print(recv_data)
-
- # 6.新的套接字对象,调用send()方法,发送数据给客户端
- # 同样注意这里调用send()方法的是data_socket对象,而不是listen_socket对象
- # 把要发送的数据,先使用encode()方法,由字符串转码成字节流,再作为参数传入send()方法中
- send_data = '服务器已收到'.encode('gbk')
- data_socket.send(send_data)
-
- # 7.关闭两个套接字对象
- data_socket.close()
- listen_socket.close()
程序运行以后,如图所示
>>>
服务器已启动,在8080端口等待连接
这里我们使用一个工具来模拟客户端:NetAssist
这个调试助手连接以后,pycharm运行窗口编程这样
>>>
服务器已启动,在8080端口等待连接
('127.0.0.1', 10869)已连接
此时,我们在调试助手窗口数据内容,然后发送给服务器,可以看到服务器自动回复了我们设置的内容
此时,pycharm运行窗口中如下图,由于我们目前没有设置循环,所以连接一次服务器套接字对象就关闭了
>>>
服务器已启动,在8080端口等待连接
('127.0.0.1', 10869)已连接
你好进程已结束,退出代码0
第一步的代码中,我们使用的是cs模型,也就是client客户端-server服务器端模型
接下来我们编写一下bs模型,也就是browser浏览器-server服务器端模型的代码
这里和第一步代码不同的地方在于,回复给浏览器的内容,要符合http响应报文格式
完整代码如下
- # 导入socket模块中的所有方法
- from socket import *
-
- # 定义变量存储服务器的IP地址,端口号
- # 这里我们设置IP地址为本机地址,端口号为8080
- IP = '127.0.0.1'
- PORT = 8080
-
- # 1.实例化一个服务器套接字对象,使用socket方法,传入两个参数
- # 第一个参数代表网络层基于IPv4,第二个参数代表传输层基于TCP协议
- listen_socket = socket(AF_INET, SOCK_STREAM)
-
- # 2.实例化的服务器套接字对象,调用bind()方法,绑定端口号和IP地址
- # 一定注意,bind()方法的参数是一个元组,元组里面写有IP地址和端口号
- listen_socket.bind((IP, PORT))
-
- # 3.设置监听,并且设置最大监听数量为5
- # 最大监听数量不超过128
- listen_socket.listen(5)
- # 到这里,服务器就启动成功了,所以我们可以输出一些提示信息如下
- print(f'服务器已启动,在{PORT}端口等待连接')
-
- # 4.服务器套接字对象调用accept()方法,等待客户端/浏览器的连接
- # accept()方法没有参数,但是有两个返回值
- # 返回一个元组,元组中第一个元素是一个新的套接字对象,这个套接字对象用来和客户端进行数据交换
- # 元组中第二个元素是客户端的IP地址和端口号
- # 这里定义两个变量,使用元组拆包的方式进行接收
- # accept()方法,如果没有客户端连接,会一直停留在这一步,有点类似于input()函数
- data_socket, addr = listen_socket.accept()
- # 输出连接的客户端的信息如下
- print(f'{addr}已连接')
-
- # 5.新的套接字对象,调用recv()方法,接收客户端返回的数据
- # 注意这里调用recv()方法的是data_socket对象,而不是listen_socket对象
- # recv()方法有一个参数,1024代表一次接收的字节流长度最大为1024字节
- # 把接收到的数据,使用decode()方法,由字节流转码成字符串,并打印控制台
- recv_data = data_socket.recv(1024).decode('gbk')
- print(recv_data)
-
- # 6.新的套接字对象,调用send()方法,发送数据给客户端
- # 同样注意这里调用send()方法的是data_socket对象,而不是listen_socket对象
- # 把要发送的数据,先使用encode()方法,由字符串转码成字节流,再作为参数传入send()方法中
-
- # 不同点在于发送内容的格式要符合http响应报文的格式
- # http响应报文 = 响应行 + 响应头 + 空行 + 响应体
- # 我们要回复的内容写在响应体中,空行则只书写\r\n
- # 响应行
- line = 'HTTP/1.1 200 ok\r\n'
- # 响应头
- head = 'Server:webserver\r\n'
- # 响应体
- body = '服务器已收到'
- send_data = line + head + '\r\n' + body
- # 转码
- send_data = send_data.encode('gbk')
- data_socket.send(send_data)
-
- # 7.关闭两个套接字对象
- data_socket.close()
- listen_socket.close()
程序运行之后,我们打开浏览器,在地址栏输入,并回车
浏览器显示内容如下
此时,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
针对上一步的代码,想要返回一个html页面,比如返回一个index.html的导航页面
只需要在body响应体那里,把内容换成相应的文件就可以了,涉及到文件操作三步骤,也可以使用with方法,完整代码如下
- # 导入socket模块中的所有方法
- from socket import *
-
- # 定义变量存储服务器的IP地址,端口号
- # 这里我们设置IP地址为本机地址,端口号为8080
- IP = '127.0.0.1'
- PORT = 8080
-
- # 1.实例化一个服务器套接字对象,使用socket方法,传入两个参数
- # 第一个参数代表网络层基于IPv4,第二个参数代表传输层基于TCP协议
- listen_socket = socket(AF_INET, SOCK_STREAM)
-
- # 2.实例化的服务器套接字对象,调用bind()方法,绑定端口号和IP地址
- # 一定注意,bind()方法的参数是一个元组,元组里面写有IP地址和端口号
- listen_socket.bind((IP, PORT))
-
- # 3.设置监听,并且设置最大监听数量为5
- # 最大监听数量不超过128
- listen_socket.listen(5)
- # 到这里,服务器就启动成功了,所以我们可以输出一些提示信息如下
- print(f'服务器已启动,在{PORT}端口等待连接')
-
- # 4.服务器套接字对象调用accept()方法,等待客户端/浏览器的连接
- # accept()方法没有参数,但是有两个返回值
- # 返回一个元组,元组中第一个元素是一个新的套接字对象,这个套接字对象用来和客户端进行数据交换
- # 元组中第二个元素是客户端的IP地址和端口号
- # 这里定义两个变量,使用元组拆包的方式进行接收
- # accept()方法,如果没有客户端连接,会一直停留在这一步,有点类似于input()函数
- data_socket, addr = listen_socket.accept()
- # 输出连接的客户端的信息如下
- print(f'{addr}已连接')
-
- # 5.新的套接字对象,调用recv()方法,接收客户端返回的数据
- # 注意这里调用recv()方法的是data_socket对象,而不是listen_socket对象
- # recv()方法有一个参数,1024代表一次接收的字节流长度最大为1024字节
- # 把接收到的数据,使用decode()方法,由字节流转码成字符串,并打印控制台
- recv_data = data_socket.recv(1024).decode()
- print(recv_data)
-
- # 6.新的套接字对象,调用send()方法,发送数据给客户端
- # 同样注意这里调用send()方法的是data_socket对象,而不是listen_socket对象
- # 把要发送的数据,先使用encode()方法,由字符串转码成字节流,再作为参数传入send()方法中
-
- # 不同点在于发送内容的格式要符合http响应报文的格式
- # http响应报文 = 响应行 + 响应头 + 空行 + 响应体
- # 我们要回复的内容写在响应体中,空行则只书写\r\n
- # 响应行
- line = 'HTTP/1.1 200 ok\r\n'
- # 响应头
- head = 'Server:webserver\r\n'
- # 响应体
- # 使用with方法操作文件
- # 这里还要注意打开文件的编码,使用utf8编码(因为这个html文件就是使用utf8编码)
- # 同时,encode和decode方法转码,编码就不用指定了,不然可能会产生乱码问题
- with open('./html/index.html', 'r', encoding='utf8') as f:
- body = f.read()
- send_data = line + head + '\r\n' + body
- # 转码
- send_data = send_data.encode()
- data_socket.send(send_data)
-
- # 7.关闭两个套接字对象
- data_socket.close()
- listen_socket.close()
在浏览器输入地址,能够正确链接到指定html文件
这个代码还不够完善,我们想在主页点击连接,就能跳转到指定的html页面或者跳转到指定的超文本内容,还需要做如下的修改
我们先观察一下浏览器发送的http请求报文结构
- 服务器已启动,在8080端口等待连接
- ('127.0.0.1', 14070)已连接
-
- # 请求行
- GET /html1.html 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
在请求行中,出现了我们想要连接到的页面的资源路径,即/html1.html 这个路径,那么我们就可以在浏览器发送了http请求报文以后,把这个资源路径的地址提取出来,存储到变量中,并把这个变量作为文件打开的地址,然后把文件返回给浏览器,
最后再添加循环,就能够实现上述效果
完整代码如下
- # 导入socket模块中的所有方法
- from socket import *
-
- # 定义变量存储服务器的IP地址,端口号
- # 这里我们设置IP地址为本机地址,端口号为8080
- IP = '127.0.0.1'
- PORT = 8080
-
- # 1.实例化一个服务器套接字对象,使用socket方法,传入两个参数
- # 第一个参数代表网络层基于IPv4,第二个参数代表传输层基于TCP协议
- listen_socket = socket(AF_INET, SOCK_STREAM)
-
- # 2.实例化的服务器套接字对象,调用bind()方法,绑定端口号和IP地址
- # 一定注意,bind()方法的参数是一个元组,元组里面写有IP地址和端口号
- listen_socket.bind((IP, PORT))
-
- # 3.设置监听,并且设置最大监听数量为5
- # 最大监听数量不超过128
- listen_socket.listen(5)
- # 到这里,服务器就启动成功了,所以我们可以输出一些提示信息如下
- print(f'服务器已启动,在{PORT}端口等待连接')
-
- # 添加循环
- while True:
-
- # 4.服务器套接字对象调用accept()方法,等待客户端/浏览器的连接
- # accept()方法没有参数,但是有两个返回值
- # 返回一个元组,元组中第一个元素是一个新的套接字对象,这个套接字对象用来和客户端进行数据交换
- # 元组中第二个元素是客户端的IP地址和端口号
- # 这里定义两个变量,使用元组拆包的方式进行接收
- # accept()方法,如果没有客户端连接,会一直停留在这一步,有点类似于input()函数
- data_socket, addr = listen_socket.accept()
- # 输出连接的客户端的信息如下
- print(f'{addr}已连接')
-
- # 5.新的套接字对象,调用recv()方法,接收客户端返回的数据
- # 注意这里调用recv()方法的是data_socket对象,而不是listen_socket对象
- # recv()方法有一个参数,1024代表一次接收的字节流长度最大为1024字节
- # 接收的数据我们先转码成字符串
- recv_data = data_socket.recv(1024).decode()
- print(recv_data)
-
- # 添加是否断开连接的判断
- if not recv_data:
- print(f'{addr}已断开')
- break
- else:
-
- # 然后我们使用字符串的split()方法,按照空格进行切割,返回一个列表
- recv_data = recv_data.split(' ')
- # 通过http请求报文结构,不难发现,这个列表的第二个元素,就是要访问的资源路径地址
- # 把这个地址赋值给变量path
- path = recv_data[1]
-
- # 6.新的套接字对象,调用send()方法,发送数据给客户端
- # 同样注意这里调用send()方法的是data_socket对象,而不是listen_socket对象
- # 把要发送的数据,先使用encode()方法,由字符串转码成字节流,再作为参数传入send()方法中
-
- # 不同点在于发送内容的格式要符合http响应报文的格式
- # http响应报文 = 响应行 + 响应头 + 空行 + 响应体
- # 我们要回复的内容写在响应体中,空行则只书写\r\n
- # 响应行
- line = 'HTTP/1.1 200 ok\r\n'
- # 响应头
- head = 'Server:webserver\r\n'
- # 响应体
-
- # 这里我们添加一个if判断,如果直接输入网址,跳转到主页
- # 因为直接输入网址,http请求报文的资源路径是 / 左斜杠
- if path == '/':
- path = '/index.html'
-
- # 使用with方法操作文件
- # 这里拼接path和./html,组成一个完整的资源路径
- # 同时注意打开方式应该为rb二进制读,编码不指定
- with open('./html' + path, 'rb') as f:
- body = f.read()
- # 这里应该注意,body存储的内容本身就是字节流(二进制流)的格式了,所以不需要再转码
- send_data = line + head + '\r\n'
- # 转码
- send_data = send_data.encode()
- data_socket.send(send_data + body)
-
- # 7.关闭套接字对象,监听套接字对象可以不关闭
- data_socket.close()
然后在浏览器直接输入资源路径,能够实现直接跳转到指定页面
或者只输入网址,不输入资源路径,也能够跳转到主页,同时在主页点击链接,也能够链接到其他页面或者内容中
FileNotFoundError: [Errno 2] No such file or directory: './html/favicon.ico'
解决办法:在html文件夹中添加一个favicon.ico的图标
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。