python-TCP 通信与 Web 服务器

一 . 实验目的

熟悉基于 Python 进行 TCP 套接字编程的基础知识,理解 HTTP 报文格式,能基于 Python 编写一个可以一次响应一个 HTTP 请求,并返回静态文件的简单 Web 服务器。

二 . 实验内容

利用 Python 开发一个可以一次处理一个 HTTP 请求的 Web 服务器,该服务器可以接受并解析HTTP 请求,然后从服务器的文件系统中读取被 HTTP 请求的文件,并根据该文件是否存在而向客户端发送正确的响应消息。

三 . 实验原理

基于 TCP 的面向客户端/服务器在 Python 实现中的的工作流程是:

1. 首先在服务器端通过调用 s o c k e t ( ) 创建套接字来启动一个服务器;

2. 服务器调用 b i n d ( ) 绑定指定服务器的套接字地址(IP 地址 + 端口号);

3. 服务器调用 l i s t e n ( ) 做好侦听准备,同时规定好请求队列的长度;

4. 服务器进入阻塞状态,等待客户的连接请求;

5. 服务器通过 a c c e p t ( ) 来接收连接请求,并获得客户的 socket 地址。

6. 在客户端通过调用 s o c k e t ( ) 创建套接字;

7. 客户端调用 c o n n e c t ( ) 和服务器建立连接。

8. 连接建立成功后,客户端和服务器之间通过调用 r e a d ( ) 和 w r i t e ( ) 来接收和发送数据。

9. 数据传输结束后,服务器和客户各自通过调用 c l o s e ( ) 关闭套接字。

基于 Python的 TCP 客户端/服务器具体工作流程如图1所示。

 

四 . 实验条件

• 装有 python 环境的电脑一台(使用两个终端模拟两台电脑/进程);

• 局域网环境;

• 部分代码;

• Python 语言参考手册 – TCP 部分;

• HTTP 协议参考手册。

五 . 实验步骤

逐步填充代码中不完善的部分,并完成一个具有以下功能的简单 Web 服务器:

1. 服务器收到请求时能创建一个 TCP 套接字;

2. 可以通过这个 TCP 套接字接收 HTTP 请求;

3. 解析 HTTP 请求并在操作系统中确定客户端所请求的特定文件;

4. 从服务器的文件系统读取客户端请求的文件;

5. 当被请求文件存在时,创建一个由被请求的文件组成的“请求成功”HTTP 响应报文;

6. 当被请求文件不存在时,创建“请求目标不存在”HTTP 响应报文;

7. 通过 TCP 连接将响应报文发回客户端。

六 . 进阶任务

本实验中的 Web 服务器一次只能处理一个 HTTP 请求,请自行查阅线程知识,修改代码,实现一个能够同时处理多个请求的多线程服务器。

七 . 注意事项及说明

客户端发来的 HTTP 请求中,URL 都是相对根“/”的相对路径,因此需要将服务器文件系统中的某个地址映射为这个“根”。为了简化工作,建议将服务器程序存放的路径作为这个根,这样运行服务器程序时,程序会自动从当前运行的路径开始查询文件。

例如:假设将“HelloWorld.html”这个 html 文件放置在服务器程序文件存放目录中,服务器运行主机的 IP 地址为“123.234.12.34”,6789 为服务器监听的端口号。

则从 URL:http;//123.234.12.34:6789/HelloWorld.html 出发可以获取到“HelloWorld.html” 这个文件。

八 . 单一请求

代码说明在代码注释中给出,具体流程如 三、实验原理

服务器 web_server.py代码

from socket import *
import sys
def web_server(ip, port):
    # 服务器端通过调用socket()创建套接字来启动一个服务器
    serverSocket = socket(AF_INET,SOCK_STREAM)
    server_address = (ip, port)#接收传入的ip地址与端口号
    #服务器调用bind()绑定指定服务器的套接字地址(IP 地址 + 端口号)
    serverSocket.bind(server_address) 
    # 服务器调用listen()做好侦听准备,同时规定好请求队列的长度
    Length=1024
    try:
        serverSocket.listen(1)
    except socket.error:
        print("fail to listen on port %s" % error)
        sys.exit(1)
    while True:
        print("Ready to server...")
        #服务器进入阻塞状态,等待客户的连接请求
        #服务器通过accept来接收连接请求,并获得客户的 socket 地址
        connectionSocket, addr = serverSocket.accept()
        break
    while True:
        try:
            #通过TCP 套接字接收 HTTP 请求
            message = connectionSocket.recv(Length)
            msg_de = message.decode('utf-8')
            print('file msg from client: '+msg_de)
            #收到客户端断开连接消息
            if msg_de == 'disconnect':break
            #从服务器的文件系统读取客户端请求的文件
            filename=message
            f=open(filename[0:])
            #被请求文件存在,创建一个由被请求的文件组成的“请求成功”HTTP 响应报文
            output=(msg_de+' has been successfully received.').encode('utf-8')
            #通过 TCP 连接将响应报文发回客户端
            connectionSocket.send(output)
        except IOError:
            #被请求文件不存在,创建“请求目标不存在”HTTP 响应报文
            output=('The request has failed.').encode('utf-8')
            #通过 TCP 连接将响应报文发回客户端
            connectionSocket.send(output)
    print("finish test, close connect")
    #通过close()关闭套接字
    connectionSocket.close()
    serverSocket.close()
if __name__=='__main__':
    web_server('127.0.0.1',6000)

客户端 web_client.py代码

from socket import * 
def web_client(ip,port):
    #在客户端通过调用socket()创建套接字
    clientSocket = socket(AF_INET,SOCK_STREAM)
    failed_count = 0
    while True:
        try:
            print("start connect to server ")
            #客户端调用connect()和服务器建立连接
            clientSocket.connect((ip,port))
            break
        except socket.error:
            failed_count += 1
            print("fail to connect to server %d times" % failed_count)
            if failed_count == 100: return 
    #连接成功后发送消息
    while True:
        print("connect success")
        receive_count = 0
        while True:
            if receive_count%2==0:
                msg = 'iloveu.txt'#存在的文档消息请求
            else:
                msg='1.txt'#不存在的文档消息请求
            #使用send()发送消息
            clientSocket.send(msg.encode('utf-8'))                        
            msg = clientSocket.recv(1024)
            print('msg from server :'+msg.decode('utf-8'))
            receive_count+= 1
            #一共发送14次消息,随后告知服务器将要断开连接
            if receive_count==14:
                msg = 'disconnect'
                clientSocket.send(msg.encode('utf-8'))
                break
        break
    #通过close关闭套接字
    clientSocket.close()      
if __name__=='__main__':
web_client('127.0.0.1',6000)

九 . 多请求

在单一HTTP请求的代码基础上,创建多线程服务器,可以同一时间处理多个HTTP请求,并在客户端使用多线程同时向服务器发送HTTP请求。

客户端在请求时将自己的线程号与请求文件一同发出,服务器在收到客户端的请求后,若请求成功,将自身的线程号一并返回给客户端。

多线程服务器 web_mul_server.py代码

from socket import *
import sys,threading
def web_mul_server(ip, port,num):
    # 服务器端通过调用socket()创建套接字来启动一个服务器
    serverSocket = socket(AF_INET,SOCK_STREAM)
    server_address = (ip, port)#接收传入的ip地址与端口号
    #服务器调用bind()绑定指定服务器的套接字地址(IP 地址 + 端口号)
    serverSocket.bind(server_address)
    # 服务器调用listen()做好侦听准备,同时规定好请求队列的长度
    Length=1024
    try:
        serverSocket.listen(1)
    except socket.error:
        print("fail to listen on port %s" % error)
        sys.exit(1)
    while True:
        print("server "+str(num)+" ready to server...")
        #服务器进入阻塞状态,等待客户的连接请求
        #服务器通过accept来接收连接请求,并获得客户的 socket 地址
        connectionSocket, addr = serverSocket.accept()
        break
    while True:
        try:
            #通过TCP 套接字接收 HTTP 请求
            message = connectionSocket.recv(Length)
            msg_de = message.decode('utf-8')
            print('file msg from '+msg_de)
            #收到客户端断开连接消息
            if msg_de == 'disconnect':break
            #从服务器的文件系统读取客户端请求的文件
            client_num=message.split()[0]
            client_num=client_num.decode('utf-8')
            filename=message.split()[1]
            f=open(filename[0:])
            #被请求文件存在,创建一个由被请求的文件组成的“请求成功”HTTP 响应报文
            output=(msg_de+' from '+client_num+' has been successfully received.--server '+str(num)).encode('utf-8')
            #通过 TCP 连接将响应报文发回客户端
            connectionSocket.send(output)
        except IOError:
            #被请求文件不存在,创建“请求目标不存在”HTTP 响应报文
            output=('The request has failed.').encode('utf-8')
            #通过 TCP 连接将响应报文发回客户端
            connectionSocket.send(output)
    print("finish test, close connect")
    #通过close()关闭套接字
    connectionSocket.close()
    serverSocket.close()
if __name__=='__main__':
    t1=threading.Thread(target=web_mul_server,args=('127.0.0.1',6000,1))
    t2=threading.Thread(target=web_mul_server,args=('127.0.0.1',7000,2))
    t3=threading.Thread(target=web_mul_server,args=('127.0.0.1',8000,3))
    t1.start()
    t2.start()
    t3.start()

多线程客户端 web_mul_client.py代码

from socket import *
import threading
def web_mul_client(ip,port,num):
    #在客户端通过调用socket()创建套接字
    clientSocket = socket(AF_INET,SOCK_STREAM)
    failed_count = 0
    while True:
        try:
            print('client '+str(num)+" start connect to server ")
            #客户端调用connect()和服务器建立连接
            clientSocket.connect((ip,port))
            break
        except socket.error:
            failed_count += 1
            print('client '+str(num)+" fail to connect to server %d times" % failed_count)
            if failed_count == 100: return
    #连接成功后发送消息
    while True:
        print('client '+str(num)+" connect success")
        receive_count = 0
        while True:
            if receive_count%2==0:
                msg ='client'+str(num)+' iloveu.txt'#存在的文档消息请求
            else:
                msg='client'+str(num)+' 1.txt'#不存在的文档消息请求
            #使用send()发送消息
            clientSocket.send(msg.encode('utf-8'))                      
            msg = clientSocket.recv(1024)
            print('msg from server :'+msg.decode('utf-8'))
 
            receive_count+= 1
            #一共发送14次消息,随后告知服务器将要断开连接
            if receive_count==14:
                msg = 'disconnect'
                clientSocket.send(msg.encode('utf-8'))
                break
        break
    #通过close关闭套接字
    clientSocket.close()    
if __name__=='__main__':
    t1=threading.Thread(target=web_mul_client,args=('127.0.0.1',6000,1))
    t2=threading.Thread(target=web_mul_client,args=('127.0.0.1',7000,2))
    t3=threading.Thread(target=web_mul_client,args=('127.0.0.1',8000,3))
    t1.start()
    t2.start()
    t3.start()

 

十 . HTTP请求

在设计好tcp的基础上,在报文中加上相应的http头部即可从url发送文件需求消息。需要注意的是由于tcp的http请求报文存在一定的格式,在server进行接收时需要截取其中有用的消息片段访问文件,在发送消息时也要按照固定的格式进行发送,否则将会出错。

通过url向web发送报文,发送方式为127.0.0.1:4900/iiii.html 即ip:port/文件名。

若从url输入内容127.0.0.1:4040/iloveu.html,在server中收到的信息如

 

需要用split()函数截取出有用的文件名信息进行下一步操作。

在向url返回信息时,需要在发送前发送http头信息

单一http请求web.py代码

 

from socket import *
import sys

def web_server(ip, port):
    # 服务器端通过调用socket()创建套接字来启动一个服务器
    serverSocket = socket(AF_INET,SOCK_STREAM)
    serverSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    server_address = ('', port)#接收传入的ip地址与端口号
    #服务器调用bind()绑定指定服务器的套接字地址(IP 地址 + 端口号)
    serverSocket.bind(server_address)
 
    # 服务器调用listen()做好侦听准备,同时规定好请求队列的长度
    Length=1024
    try:
        serverSocket.listen(5)
    except socket.error:
        print("fail to listen on port %s" % error)
        sys.exit(1)
    while True:
        print("Ready to server...")
        connectionSocket, addr = serverSocket.accept()
        print('accepted!'+str(addr))
        try:
            #通过TCP 套接字接收 HTTP 请求
            message = connectionSocket.recv(Length)
            message=message.split()[1]
            msg_de = message.decode('utf-8')
            print('file msg from client: '+msg_de)
            #收到客户端断开连接消息
            if msg_de == '\disconnect':break
            #从服务器的文件系统读取客户端请求的文件
            filename=message
            f=open(filename[1:])
            text=f.read()
            #被请求文件存在,创建一个由被请求的文件组成的“请求成功”HTTP 响应报文
            output= "HTTP/1.1 200 OK\r\n"
            output+= "\r\n"
            output+='\r\n'
            output+=msg_de+' has been successfully received.\n'
            output=output.encode('utf-8')
            print('output: '+str(output))
            #通过 TCP 连接将响应报文发回客户端
            connectionSocket.send(output)
            for i in range(0,len(text)):
                connectionSocket.send(text[i].encode('utf-8'))
            print('send!')
        except IOError:
            #被请求文件不存在,创建“请求目标不存在”HTTP 响应报文
            output= "HTTP/1.1 200 OK\r\n"
            output+= "\r\n"
            output+='\r\n'
            output+='The request has failed.\n'
            output=output.encode('utf-8')
            print('output: '+str(output))
            #通过 TCP 连接将响应报文发回客户端
            connectionSocket.send(output)
 
    print("finish test, close connect")
    #通过close()关闭套接字
    connectionSocket.close()
    serverSocket.close()
 
if __name__=='__main__':
    web_server('127.0.0.1',8888)

 单一http请求实验结果

 多http请求web_mul.py代码

from socket import *
import sys,threading

def web_mul(ip, port):
    # 服务器端通过调用socket()创建套接字来启动一个服务器
    serverSocket = socket(AF_INET,SOCK_STREAM)
    serverSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    server_address = ('', port)#接收传入的ip地址与端口号
    #服务器调用bind()绑定指定服务器的套接字地址(IP 地址 + 端口号)
    serverSocket.bind(server_address)
 
    # 服务器调用listen()做好侦听准备,同时规定好请求队列的长度
    Length=1024
    try:
        serverSocket.listen(5)
    except socket.error:
        print("fail to listen on port %s" % error)
        sys.exit(1)
    while True:
        print("Ready to server...")
        connectionSocket, addr = serverSocket.accept()
        print('accepted!'+str(addr))
        try:
            #通过TCP 套接字接收 HTTP 请求
            message = connectionSocket.recv(Length)
            message=message.split()[1]
            msg_de = message.decode('utf-8')
            print('file msg from client: '+msg_de)
            #收到客户端断开连接消息
            if msg_de == '\disconnect':break
            #从服务器的文件系统读取客户端请求的文件
            filename=message
            f=open(filename[1:])
            text=f.read()
            #被请求文件存在,创建一个由被请求的文件组成的“请求成功”HTTP 响应报文
            output= "HTTP/1.1 200 OK\r\n"
            output+= "\r\n"
            output+='\r\n'
            output+=msg_de+' has been successfully received.\n'
            output=output.encode('utf-8')
            print('output: '+str(output))
            #通过 TCP 连接将响应报文发回客户端
            connectionSocket.send(output)
            for i in range(0,len(text)):
                connectionSocket.send(text[i].encode('utf-8'))
            print('send!')
        except IOError:
            #被请求文件不存在,创建“请求目标不存在”HTTP 响应报文
            output= "HTTP/1.1 200 OK\r\n"
            output+= "\r\n"
            output+='\r\n'
            output+='The request has failed.\n'
            output=output.encode('utf-8')
            print('output: '+str(output))
            #通过 TCP 连接将响应报文发回客户端
            connectionSocket.send(output)
 
    print("finish test, close connect")
    #通过close()关闭套接字
    connectionSocket.close()
    serverSocket.close()

if __name__=='__main__':
    t1=threading.Thread(target=web_mul,args=('127.0.0.1',4700))
    t2=threading.Thread(target=web_mul,args=('127.0.0.1',4800))
    t3=threading.Thread(target=web_mul,args=('127.0.0.1',4900))
    t1.start()
    t2.start()
    t3.start()

多http请求实验结果

 

 

十一 . 实验分析

实验过程

在单一HTTP请求中打开两个终端分别运行服务器端web_wever.py与客户端web_client.py。其中需要先打开服务器端再运行客户端。

在单一HTTP请求的基础上进行多HTTP请求的编写。其中对于client向server请求的方式可以有多种,可以开多个进程对server发送请求,此处使用较为方便同时也是server端使用的多线程的方法发送请求。

由于TCP是可靠的连接,在一方连接断开时需要通知另一方自己将断开连接,在实验中在client发送了一定次数请求后将关闭连接并通知server使其也关闭的功能。若client关闭了连接却没有通知server导致server在循环中继续通过tcp发送消息将导致程序报错,同理若server断开连接并进行下一次的等待操作,若没有通知client,client将无法通过tcp传输而导致报错,也因为没有断开连接将无法正常进行下一次的连接操作。

还有一种较为常见可行的方法是client接收用户键盘输入的文件请求,关闭操作也可以由接收的键盘输入控制,而后将文件请求通过tcp发送给服务器端,之后的方法大致相同。

在使用url向server发送请求时要注意端口号,以及在处理报文和发送报文时需要按照固定的格式,接收时需要截取特定的报文内容,发送时需要添加固定的格式。

需要注意传输的内容格式需要是byte,发送时需要使用encode()转码,接收时需要使用decode()转成str。

结果分析

在单一HTTP请求与多HTTP请求中都做到了client向server发送文件请求,server在本地目录下查询文件后返回的操作。在执行完后正常而言不应该进行close操作,但为了代码与执行的整洁性与逻辑性,我们仍然选择在完成特定功能后关闭连接。

tcp是可靠的连接,我们在多HTTP请求中可以观察到,当连接确立后,对应的server线程会接收来自对应client发送的消息,随后将消息准确地发回对应的client。

可以通过url向web发送报文,发送方式为127.0.0.1:4900/iiii.html 即ip:port/文件名。

遇到问题

在server进行accept操作时若不使用循环,当没有client时会报错,所以使用循环进行等待。

在server和client断开连接时若操作不当,server或client单方面断开连接很容易导致报错,在这里client断开连接时会向server发送断开连接消息告知server,server随机断开不再进行传输操作。

当连接异常时,较多为server或client单方面中断,另一方若继续通过tcp发送消息,电脑将中断该操作。

在使用url向server发送请求时,在直接使用之前的方式会出现应答出错,需要将单独的while循环的accpet放进整体中才能正常运行。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值