python高级编程之网络编程

端口

端口就好似是一座房子,端口号就貌似该房子的门牌号,范围0~65535
用于在网络上标记某一个程序的位置,当从计算机在网络上接受到一个数据通过数据中包含的端口号信息判断发送到某一个程序。

端口分类

知名端口

知名端口就是经常使用的端口,该端口都是规定好的一些服务,好比110治安报警,119火灾报警等等,该类端口范围从0~1023,
例如:
88端口分配给HTTP服务
21端口分配给ftp服务

动态端口

动态端口是不固定的,他一般不固定分配给哪一个服务,而是动态分配,
动态分配是指当某一个计算机程序需要网络通信时,它向主机申请端口,主机在可用的端口中动态的分配一个端口给它,当网络通信结束时该端口也被主机收回,

查看端口

netstat -an :查看端口状态
lsof -i [tcp/udp]:端口号

socket简介

电脑上进程之间的通信

进程:运行的程序以及运行时用到的资源这个整体称为进程
进程间到通信:运行的程序之间的数据共享
要想了解进程间到通信首先要了如何标示一个进程,
在计算机中通过进程号(PID)可以标示一个进程
在网络中则需要通过利用ip地址、协议、端口来标示进程。

什么是socket

socket(简称 套接字)是进程间通信的一种方式,它与其他进程间通信到一个主要不同是:它能实现不同主机间到进程通信,我们网络上各种各样的服务大多数基于Socket来完成通信的,例如我们每天浏览到网页、微信、qq等

创建socket

在Python中使用socket模块中的socket函数创建

import socket	#导入socket模块
socket.socket(AddressFamily,Type)   #创建一个socket

说明:
函数socket创建一个socket,该函数包含两个参数:

  • Address Family:可以选择AF_INET(用于internet进程间通信)或者AF_UNIX(用于同一台机器进程间通信),一般使用AF_INET参数
  • Type:socket类型,SOCK_STREAM(用于tcp协议),SOCK_DGRAM(用于UDP协议)
    创建一个tcp套接字
# 导入模块
import socket
#创建一个ftp套接字
FTP_Socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM);

#这里使用套接字功能
#......

#关闭套接字
FTP_Socket.close();

创建一个UDP套接字

# 导入模块
import socket
#创建一个UDP套接字
FTP_Socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM);

#这里使用套接字功能
#......

#关闭套接字
FTP_Socket.close();

创建套接字流程

  1. 创建套接字
  2. 使用套接字接受/发送数据
  3. 关闭套接字

使用UDP套接字发送数据

#导入模块
from socket import *;

#创建UDP套接子
UDP_Socket = socket(AF_INET,SOCK_DGRAM);
#sendto(data,tuple)函数
#该函数中有两个参数,第一个参数是一个byte类型的数据,表示需要发送的数据,
#					第二个参数是一个元组,该元组中有两个数据第一个是字符串		#					类型表示目的ip,第二个是目的端口

data = input("请输入要发送的数据");
UDP_Socket.sendto(data.encode("utf-8"),("192.168.42.156",8088));
#因为发送的数据需要是byte类型,该数据为字符串类型所以需要对该数据进行解码
#utf-8国际通用的编码规范

#关闭套接字
UDP_Socket.close();

使用UDP套接字接受数据

#导入模块
from socket import *;
#创建套接字
UDP_Socket = socket(AF_INET,SOCK_DGRAM);
#接受数据时需要绑定本地信息,Ip和端口号

#设置一个元组用于保存ip、端口号

local_addr = ('',8088);  #''表示本机任意一个ip
#bind(tuple)函数用于绑定本地信息
UDP_Socket.bind(local_addr);

#接受信息
#recvfrom()函数用于接受数据
data = UDP_Socket.recvfrom(1024);		#1024表示本次接受的最大字节数

#因为接受到的数据是一个:(b"",())	类型
#第一个数据是接受到的数据是一个byte类型,第二个元组包含的是该数据来自的主#机的信息

#显示数据
#所以输出信息时就需要对该数据进行解码,如果信息来自windows系统发送过来的
#则需要设置解码规则为gbk,因为windows的编码为gbk
#解码函数:decode();

print(data[0].decode('gbk'));

#关闭套接字
UDP_Socket.close();	

TCP协议socket应用

特性

tcp简称传输控制协议,它是一个比较安全到传输协议。不会出现掉包的情况
传输过程:客户端向服务器发送一个请求消息,服务器接收到会给客户端回应一个包告诉客户端他已经受到请求,如果客户端没有受到回应,客户端会再次询问服务端,服务端会再次向客户端回应一个包

socket客户端

客户端就是用户使用的一些服务,例如本地软件qq就属于qq客户端,向腾讯的qq服务器端访问
客户端创建
1、创建套接字

tcp_client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM);

2、设置服务端ip/port

server_ip = input("请输入服务器的ip:")
server_port = int(input("请输入服务器的port:"))
server_addr = (server_ip,server_port);

3、连接服务器端

# 要想向服务器发送请求,首先要在客户机和服务器之间建立一个通道
# 这就需要客户机使用connect()函数连接服务器
tcp_client_socket.connect(server_addr);

4、向服务器端发送请求

# 向服务器发送请求,使用send()函数,因为该客户机已经和服务器建立通道所以不腻使用sendto()指定地址
send_data = input("请输入你想要发送的数据:")
tcp_client_socket.send(send_data.encode("utf-8"));	# 编码:win使用gbk;linux使用utf-8

5、接受服务器端的响应

server_data = tcp_client_socket.recvfrom(1024);
print(server_data)

6、关闭套接字

tcp_client_socket.close();

socket服务器端

是为客户端提供服务的
创建流程
这个创建流程呢,首先使用一个例子来讲解一下:
这个就好比我们想让我们朋友在其他地方联系你,朋友怎么联系呢?通过电话嘛,对不对,
那么我们应该首先买一台电话机,那么有电话机了还不行,还需要电话号,所以要装一个电话卡
那么然后我们就把那个电话搞成只能接不能打,因为我们想让朋友打来电话嘛,然后就在那等朋友打电话打来?
1、创建套接字(买一台电话机)

tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

2、绑定ip/port(安装电话卡)
因为服务器端的ip和port是不允许变的,就好像我们玩游戏没玩一次,都需要向游戏公司打电话问它ip和端口号是多少吧,这是不行的

server_port = int(input("请输入你要绑定的端口号"))
tcp_server_socket.bind('',server_port);

3、设置为监听模式(只接不打)

# 设置为监听模式使用listen(128) 一般默认设置为128
tcp_server_socket.listen(128)

4、等待客户端连接(等待朋友打电话来)

# 使用accept()函数,该函数等待客户机发送请求过来,当客户机发信息过来,该函数会有进行堵塞,并返回两个值
# 第一个值为一个套接字,第二个是一个元祖数据,包含了客户机的ip和端口号
"""
我给你分析一下这里面的过程:
举个例子:就好比我们给10086打电话,我们要查询一些服务是不是要进行人工服务,当人工服务接通俺哥瞬间我们已经脱
离了10086这个大的会话,进入了一个另一个会话,
这个也是这样,accept()函数在等待客户机,当就到客户机这一瞬间,该函数就在服务器上另开了一个端口就是生成了一
个对该客户机进行一对一服务的人员,然后就将客户机和总服务套接字进行了断开并成功链接这一个新的套接字,那个总
套接字就去等待其他客户机的到来,所以这第一个参数就是对该客户机进行服务的新服务端

这样的好处就是可以增加工作效率,如果总服务端一直被这一个客户机占着,其他用户就会无法链接到服务器
"""
# 我们一般使用解包的方式接受该数据

new_socket,client_addr = tcp_server_socket.accept()

6、接受请求并给出响应

# 因为客户机已经和新端口建立通道,故使用新套接字进行响应操作
recv_data = new_socket.recv(1024) # 因为已经建立连接,故不需要对方的信息了,所以使用recv接受
# recv接受的数据只包含请求信息
print(recv_data)
# 给出相应
new_socket.send("------ok-------");

7、关闭套接字

new_socket.close();
tcp_server_socket.close();

TCP注意点

1、一般TCP服务器都需要绑定端口,否则客户端可能就会找不到该端口
2、TCP客户端一般不需要绑定端口,因为是客户端主动连接服务器,所以只要确认好服务器ip、port等信息,本地客户端随机就可以。
3、tcp服务器中通过listen可以将socket创建出来的套接字变为被动的,这是tcp服务器必须做的。
4、当服务器需要连接服务器时,就需要使用connect进行连接,udp不需要连接就可以直接通信,但tcp必须建立连接,只有建立连接成功才可以通信。
5、当tcp客户端需要连接到服务器时,服务器端会生成一个新的套接字,这个套接字用来标记该客户端
6、listen后的套接字是被动套接字,所以该套接字只能用来进行接收客户端发送的请求,而accept返回的套接字使用来标记该用户的,
7、关闭listen后的套接字意味着被动套接字关闭了,会导致客户端不能够成功连接到服务器,但已经建立的连接可以正常通信。
8、关闭accept返回的套接字,意味着这个客户端的服务已经完成
9、当客户端的套接字调用close关闭后,服务器端recv会解堵塞,并且返回的长度为0,因此服务端可以通过返回数据的长度来区别客户端是否下线。

正则表达式

简介

正则表达式通俗的说就是一种匹配规则,用于判定字符串是否符合某一种规则。当字符串的格式符合这个规则该字符串就匹配,否则就不匹配。
正则表达式由一系列字符组成,这每一个字符都用于表示某一类数据。

匹配单个字符

  • \d:匹配数字,即0-9只匹配一个
    例:
    正 则:\d
    字符串:“1”
    结 果:匹配
    正 则:\d
    字符串:“12”
    结 果:不匹配
    因为\d表示只匹配一个数字而“12”是由1 2 组成
  • \D:表示不匹配数字,即\d可以匹配的,它都不匹配
  • \w:匹配a-z,A-Z,0-9任意一个字符
  • \W:匹配非\w匹配的字符
  • \s:匹配空格、tab键
  • \S:匹配非空白
  • .:匹配任意一个字符(除\n(换行))
  • []:匹配[]中列举的字符
    例:
    正则:[abc123]
    字符:“a”
    结果:匹配
    正则:[abc123]
    字符:“2”
    结果:匹配
    正则:[abc123]
    字符:“5”
    结果:不匹配

匹配多个字符

  • :表示该字符前面的那个字符可以在字符串中匹配0或多次
    例:
    正则:a

    字符:“aaa”
    结果:匹配
    正则:a*
    字符:“a”
    结果:匹配
    正则:ba*
    字符:“baaa”
    结果:匹配
    正则:ba*
    字符:“b”
    结果:匹配

  • +:匹配一次或多次
    的用法一样但+前面的字符必须出现一次
    正则:ba

    字符:“b”
    结果:不匹配

  • ?:匹配前一个字符可以出现一次或零次

  • {m}:表示匹配前面的字符必须出现m次

  • {n,m}:表示前面的字符最少出现n次最多出现m次

匹配开头和结尾

  • ^ :表示匹配字符串必须以该符号后面的那个字符开头
  • $:表示匹配的字符串必须匹配到结尾,完全和匹配规则相同

分组

  • |:功能类似于“或”,将正则表达式以|为中分成两个,满足任意一个都行
  • ( | ):表示字符串满足正则表达式中和|右边语句结合的语句也可以匹配,满足左边的也行
    例:
    正则:ningning@(163|qq).com
    字符:“ningnign@163.com”
    结果:匹配
    正则:ningning@(163|qq).com
    字符:“ningnign@qq.com”
    结果:匹配
  • ():用于将正则进行分组
    用法1:可以使用函数指定下标,将指定括号中的数据取出来
    例:
    正则:aaa(bb)dddd(fff)
    字符串:aaabbddddfff;
    结果:可以匹配
    正则对象.group(1)
    结果:bb
    正则对象.group(2)
    结果:fff
    用法2: 可以使用“\下标值”将该括号中的数据完全一样的作为匹配规则使用。表示前面的字符匹配成什么,后面调用的时候也必须是什么。
    正则:<(\w*)>.</\1>
    字符:<h1>fjafv<h1>
    结果:匹配
    正则:<(\w
    )>.*</\1>
    字符:"<h1>fjafv<h2>"
    结果:不匹配
  • (?P):分组起别名,用法和分组第二种一样,就是调用时使用(?P=name)使用,name:需要设置的名字

python中正则使用

在python中要想使用正则表达式,首先要先导入re包

  • match(正则表达式,字符串)
    用于判断在字符串是否是否在匹配规则,符合返回一个正则对象,可以使用group()方法将匹配的字符串取出来。

  • search(正则,字符串)

  • 在字符串中查找符合该正则规则的字串并返回一个正则对象,使用group()可以将匹配到的数据取出来

  • findall(正则,字符串)
    将字符串中的所有符合该匹配规则的字串全部取出来并保存到一个列表中

  • sub(正则,参数,字符串)
    参数:可以是一个字符,也可以是一个返回值是字符的函数
    将在字符串中匹配到的所有字串都替换为该参数,并返回一个全部替换过的字符串,

  • split(正则,字符串)
    在字符串中按照符合匹配规则的字符进行切割,返回值是一个列表

HTTP协议

HTTP简介

在web应用中服务器将网页传递给浏览器,实则是将一个HTML文件传递给浏览器,浏览器通过解析HTML代码最终将页面显示在浏览器,而浏览器和服务器之间遵循的规则就是HTTP协议。

浏览器向服务器发送请求数据包的数据头包括:

请求方式 请求的网页地址 HTTP协议版本
GET / HTTP/1.1
请求服务器的域名或者地址端口
Host: www.baidu.com
表示请求的方式,超链接
Connection: keep-alive
Upgrade-Insecure-Requests: 1
支持的系统版本
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36
支持的文件格式
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
支持的压缩格式
Accept-Encoding: gzip, deflate, sdch, br
支持的语言编码
Accept-Language: zh-CN,zh;q=0.8
表示该浏览记录用户的一些爱好
Cookie:

服务器相应给浏览器的应答

  • 头部Headers

浏览器的版本 响应状态
HTTP/1.1 200 OK
Bdpagetype: 1
Bdqid: 0xff90574800000ada
Cache-Control: private
Connection: keep-alive
表示响应的数据的压缩格式
Content-Encoding: gzip
文件类型 编码格式
Content-Type: text/html;charset=utf-8
时间
Date: Tue, 17 Nov 2020 02:21:22 GMT
Expires: Tue, 17 Nov 2020 02:20:55 GMT
服务器名称
Server: BWS/1.1
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=32809_1429_32857_33121_33058_31660_33098_33101_32962_26350_22160; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1605579682238956673018415314843067222746
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked

http协议验证

模拟HTTP服务器,无论浏览器发送什么请求,服务器都响应"hahaha


# -*- codeing = utf-8 -*-
# @Time : 2020/11/16 9:55
# @Author : King
# @File : 服务器模拟器.py
# @software : PyCharm
#解释:模拟HTTP服务器,无论浏览器发送什么请求,服务器都响应"hahaha"
import socket


# 服务器的响应
# str = """HTTP/1.1 200 OK
#
# <h1>hahaha</h1>"""
str = "HTTP/1.1 200 OK\r\n"
str += "\r\n";
str += "hahaha";

def main():
"""模拟HTTP服务器"""
    # 创建套接字
    tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 绑定ip/port
    server_addrs = ("127.0.0.1",8088);
    tcp_socket.bind(server_addrs);
    # 设置监听模式
    tcp_socket.listen(128);
    # 循环接受数据并给出相应
    while True:
        # 等待浏览器连接
        new_socket, client_add = tcp_socket.accept();
        # 接受浏览器发送请求
        data = new_socket.recv(1024);
        #打印浏览器发送的请求
        print(data.decode("gbk"));

		# 向浏览器回应数据
        new_socket.send(str.encode("gbk"))
        new_socket.close();

    new_socket.close();

    # 关闭套接字


if __name__ == "__main__":
    main();

TCP的3次捂手和4次挥手

因为HTPP是遵循TCP协议的,所以HTTP协议必须也的有。

TCP的3次捂手

第一次捂手
当浏览器想要向服务器发送请求时,浏览器会首先向服务器发送一个探索包,查看浏览器时候在线且可以建立连接并且提醒浏览器做好资源的准备。
流程
浏览器准备一个并包以syn格式发送该包
包中准备一个数据 syn 数据
为了可以区分请求和应答,使用syn标记该类型数据为请求
第二次捂手
当浏览器收到请求,会在浏览器发送的那个数据做一点改变比如加1,表示该数据舞已经收到了,此时服务器会将该数据重新封装并以ack的格式相应给客户端。
流程
服务器收到包
将包中的数据加1并重新封装,并以ack的方式相应给客户端。
第三次捂手
当服务器将响应包发送完成后,自己也会封装一个探索包,查看客户端是否可以收到并让浏览器做好准备。
流程
服务器准备一个包并以syn格式发送该包
包中准备一个数据 syn 数据
第四次捂手
浏览器收到服务器响应后,会立刻收到一个来自服务器的探索包,浏览器也会将探索包中的数据加1并重新封装并将该包以ack的方式发送给浏览器
流程
浏览器收到包
将包中的数据加1并重新封装,并以ack的方式响应给服务器。
TCP捂手

组合
在第二次和第三次时都是浏览器发送是数据,一般为了节省时间服务器会将响应包和探索包组合在一起,也就是在第二次捂手时服务器将响应给浏览器的包做完改变并在该包中重新添加一个syn方式数据发送给浏览器。

TCP三次捂手

TCP的4次挥手

四次挥手是在客户端和服务器断开连接的时候进行的,
第一次挥手
当客户端调用close函数关闭套接字时,客户端会发送一个包告诉浏览器它要断开连接,然后客户端就会关闭send发送端,因为socket套接字是全双工的因此recv接受端不会关闭。
第二次挥手
当服务器接受到客户端发送过来的数据时,服务器会告诉recv解堵塞,然后关闭recv接受端。
第三次挥手
当服务器关闭了recv接受端后,此时recv返回的数据为空,然后判断数据为空后,服务器端也会调用close然后,然后也会向客户端发送一个数据包告诉客户端它要关闭。此时服务器端关闭send发送
第四次挥手
当客户端的recv接受端收到数据后,也会回应服务器端一个数据然后关闭recv接受服务。
注意:这第二次挥手和第三次挥手是不能合并的,因为第三次挥手是当服务器端调用close而产生的,而close调不调用是不确定的,而客户端会一直在等待服务器的第二次挥手,
谁先调用close
一般都是客户端先调用close,因为谁先调用close谁最后会等待两分钟,如果客户端先调用close如果该服务器突然出现意外断开比如关闭,这就会很尴尬,因为当想再次启动该服务时就会起动不开因为服务器都是固定的端口,因为是服务器先调用的close所以该端口还在被占用中所以就会出现启动不起来的现象。
客户端的端口都是随机分配的所以不会出现此尴尬的问题。
解决:解决服务器的尴尬问题,在程序开头添加 套接字.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

服务器的长连接和短连接

短连接当浏览器发送一个请求时,服务器将该请求给响应完毕后,就会断开连接,如果浏览器还需要一个数据就需要再次向服务器连接,并发送请求,HTTP 1.0版本为短连接
长连接当浏览器和服务器建立连接后,只有服务器给浏览器的所有请求响应完毕后,浏览器自动断开连接,服务再断开连接。HTTP 1.1 版本为长连接

不过我们之前创建的都属于短连接,但现在HTTP版本都是1.1,如果想要创建长连接只需要在响应头中加入:Content-Length:数据长度,

案例:返回浏览器需要的页面-HTTP服务器

# -*- codeing = utf-8 -*-
# @Time : 2020/11/16 9:55
# @Author : King
# @File : 服务器模拟器.py
# @software : PyCharm


import socket
import re


def recv_data(new_socket):

    """接受并对客户进行响应"""

    print("-------------")

    #接受用户的请求
    data = new_socket.recv(1024).decode("gbk");

    # 将请求以行分割,用于提取用户请求的文件
    data_list = data.splitlines();

    # 'GET / HTTP/1.1',
    # 使用正则提取文件数据
    ret = re.match("[^/]+(/[^ ]*)",data_list[0])

    # 判断数据是否提取出来
    if ret :
        file_name = ret.group(1);
        #如果用户后面文件名没有加则返回主页
        if file_name == "/":
            file_name = "/text.html"
    print(file_name);

    # 开始拼装文件名
    try:
        f = open("./第十六次作业"+file_name,"rb");
    except: # 打开文件失败
        respans = "HTTP/1.1 404 NOT FOUND\r\n";
        respans += "\r\n";
        respans +="------你的文件没找到————————";
        new_socket.send(respans.encode("gbk"));
    else:   # 打开文件成功
        html_content = f.read(1024);
        f.close();
        respans = "HTTP/1.1 200 OK\r\n";
        respans += "\r\n";
        new_socket.send(respans.encode("gbk"));
        new_socket.send(html_content);
		# 关闭套接字
		new_socket.close();
		 

    print(">>>>>>>>>>>>")

def main():

    # 创建套接字
    tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 绑定ip/port
    server_addrs = ("127.0.0.1",8088);
    tcp_socket.bind(server_addrs);
    # 设置监听模式
    tcp_socket.listen(128);
    # 循环接受数据并给出相应
    while True:
        # 等待连接
        new_socket, client_add = tcp_socket.accept();
        # 处理数据
        recv_data(new_socket)
        
    # 关闭套接字
    tcp_socket.close();


if __name__ == "__main__":
        main();

案例:返回浏览器需要的页面-HTTP服务器——使用多进程实现

# -*- codeing = utf-8 -*-
# @Time : 2020/11/21 11:19
# @Author : King
# @File : 服务器模拟——使用多线程实现.py
# @software : PyCharm


# -*- codeing = utf-8 -*-
# @Time : 2020/11/16 9:55
# @Author : King
# @File : 服务器模拟器.py
# @software : PyCharm


import socket
import re
import multiprocessing


def recv_data(new_socket):

    """接受并对客户进行响应"""

    print("-------------")

    #接受用户的请求
    data = new_socket.recv(1024).decode("gbk");

    # 将请求以行分割,用于提取用户请求的文件
    data_list = data.splitlines();

    # 'GET / HTTP/1.1',
    # 使用正则提取文件数据
    ret = re.match("[^/]+(/[^ ]*)",data_list[0])

    # 判断数据是否提取出来
    if ret :
        file_name = ret.group(1);
        #如果用户后面文件名没有加则返回主页
        if file_name == "/":
            file_name = "/text.html"
    print(file_name);

    # 开始拼装文件名
    try:
        f = open("./第十六次作业"+file_name,"rb");
    except: # 打开文件失败
        respans = "HTTP/1.1 404 NOT FOUND\r\n";
        respans += "\r\n";
        respans +="------你的文件没找到————————";
        new_socket.send(respans.encode("gbk"));
    else:   # 打开文件成功
        html_content = f.read(1024);
        f.close();
        respans = "HTTP/1.1 200 OK\r\n";
        respans += "\r\n";
        new_socket.send(respans.encode("gbk"));
        new_socket.send(html_content);



    new_socket.close();

    print(">>>>>>>>>>>>")

def main():

    # 创建套接字
    tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 绑定ip/port
    server_addrs = ("127.0.0.1",8085);
    tcp_socket.bind(server_addrs);
    # 设置监听模式
    tcp_socket.listen(128);
    # 循环接受数据并给出相应
    while True:
        # 等待连接
        new_socket, client_add = tcp_socket.accept();
        # 创建进程处理数据
        p = multiprocessing.process(target = recv_data, args = (new_socket,))
        p.start();
        
        new_socket.close();

    # 关闭套接字
    tcp_socket.close();


if __name__ == "__main__":
        main();

案例:返回浏览器需要的页面-HTTP服务器——使用多线程实现

# -*- codeing = utf-8 -*-
# @Time : 2020/11/21 11:19
# @Author : King
# @File : 服务器模拟——使用多线程实现.py
# @software : PyCharm


# -*- codeing = utf-8 -*-
# @Time : 2020/11/16 9:55
# @Author : King
# @File : 服务器模拟器.py
# @software : PyCharm


import socket
import re
import multiprocessing
import threading


def recv_data(new_socket):

    """接受并对客户进行响应"""

    print("-------------")

    #接受用户的请求
    data = new_socket.recv(1024).decode("gbk");

    # 将请求以行分割,用于提取用户请求的文件
    data_list = data.splitlines();

    # 'GET / HTTP/1.1',
    # 使用正则提取文件数据
    ret = re.match("[^/]+(/[^ ]*)",data_list[0])

    # 判断数据是否提取出来
    if ret :
        file_name = ret.group(1);
        #如果用户后面文件名没有加则返回主页
        if file_name == "/":
            file_name = "/text.html"
    print(file_name);

    # 开始拼装文件名
    try:
        f = open("./第十六次作业"+file_name,"rb");
    except: # 打开文件失败
        respans = "HTTP/1.1 404 NOT FOUND\r\n";
        respans += "\r\n";
        respans +="------你的文件没找到————————";
        new_socket.send(respans.encode("gbk"));
    else:   # 打开文件成功
        html_content = f.read(1024);
        f.close();
        respans = "HTTP/1.1 200 OK\r\n";
        respans += "\r\n";
        new_socket.send(respans.encode("gbk"));
        new_socket.send(html_content);



    new_socket.close();

    print(">>>>>>>>>>>>")

def main():

    # 创建套接字
    tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 绑定ip/port
    server_addrs = ("127.0.0.1",8085);
    tcp_socket.bind(server_addrs);
    # 设置监听模式
    tcp_socket.listen(128);
    # 循环接受数据并给出相应
    while True:
        # 等待连接
        new_socket, client_add = tcp_socket.accept();
        # 创建进程处理数据
        p = threading.Thread(target=recv_data, args=(new_socket,))
        p.start();

        #new_socket.close();

    # 关闭套接字
    # tcp_socket.close();


if __name__ == "__main__":
        main();

案例:返回浏览器需要的页面-HTTP服务器——gevent实现

# -*- codeing = utf-8 -*-
# @Time : 2020/11/21 11:19
# @Author : King
# @File : 服务器模拟——使用多线程实现.py
# @software : PyCharm


# -*- codeing = utf-8 -*-
# @Time : 2020/11/16 9:55
# @Author : King
# @File : 服务器模拟器.py
# @software : PyCharm


import socket
import re
import gevent
from gevent import monkey


def recv_data(new_socket):

    """接受并对客户进行响应"""

    print("-------------")

    #接受用户的请求
    data = new_socket.recv(1024).decode("gbk");

    # 将请求以行分割,用于提取用户请求的文件
    data_list = data.splitlines();

    # 'GET / HTTP/1.1',
    # 使用正则提取文件数据
    ret = re.match("[^/]+(/[^ ]*)",data_list[0])

    # 判断数据是否提取出来
    if ret :
        file_name = ret.group(1);
        #如果用户后面文件名没有加则返回主页
        if file_name == "/":
            file_name = "/text.html"
    print(file_name);

    # 开始拼装文件名
    try:
        f = open("./第十六次作业"+file_name,"rb");
    except: # 打开文件失败
        respans = "HTTP/1.1 404 NOT FOUND\r\n";
        respans += "\r\n";
        respans +="------你的文件没找到————————";
        new_socket.send(respans.encode("gbk"));
    else:   # 打开文件成功
        html_content = f.read(1024);
        f.close();
        respans = "HTTP/1.1 200 OK\r\n";
        respans += "\r\n";
        new_socket.send(respans.encode("gbk"));
        new_socket.send(html_content);



    new_socket.close();

    print(">>>>>>>>>>>>")

def main():

    # 将解堵塞全部修改为gevent的解堵塞
    monkey.patch_all();
    # 创建套接字
    tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 绑定ip/port
    server_addrs = ("127.0.0.1",8085);
    tcp_socket.bind(server_addrs);
    # 设置监听模式
    tcp_socket.listen(128);
    # 循环接受数据并给出相应
    while True:
        # 等待连接
        new_socket, client_add = tcp_socket.accept();
        # 创建写协程处理数据
        gevent.spawn(recv_data, new_socket);

        #new_socket.close();

    # 关闭套接字
    # tcp_socket.close();


if __name__ == "__main__":
        main();

使用单进程、单线程、非解堵塞实现

# -*- codeing = utf-8 -*-
# @Time : 2020/11/21 12:03
# @Author : King
# @File : 单进程、单线程、非解堵塞.py
# @software : PyCharm

# -*- codeing = utf-8 -*-
# @Time : 2020/11/16 9:55
# @Author : King
# @File : 服务器模拟器.py
# @software : PyCharm


import socket
import re
import time


def server_repans(new_socket,data):

    """接受并对客户进行响应"""

    print("-------------")

    #接受用户的请求
    #data = new_socket.recv(1024).decode("gbk");

    # 将请求以行分割,用于提取用户请求的文件
    data_list = data.splitlines();

    # 'GET / HTTP/1.1',
    # 使用正则提取文件数据
    ret = re.match("[^/]+(/[^ ]*)",data_list[0])

    # 判断数据是否提取出来
    if ret :
        file_name = ret.group(1);
        #如果用户后面文件名没有加则返回主页
        if file_name == "/":
            file_name = "/text.html"
    print(file_name);

    # 开始拼装文件名
    try:
        f = open("./第十六次作业"+file_name,"rb");
    except: # 打开文件失败
        respans = "HTTP/1.1 404 NOT FOUND\r\n";
        respans += "\r\n";
        respans +="------你的文件没找到————————";
        new_socket.send(respans.encode("gbk"));
    else:   # 打开文件成功
        html_content = f.read(1024);
        f.close();
        respans = "HTTP/1.1 200 OK\r\n";
        respans += "\r\n";
        new_socket.send(respans.encode("gbk"));
        new_socket.send(html_content);



    new_socket.close();

    print(">>>>>>>>>>>>")

def main():

    # 创建套接字
    tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 防止服务器先调用close出错
    tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 设置非解堵塞
    tcp_socket.setblocking(False)
    # 绑定ip/port
    server_addrs = ("127.0.0.1",8088);
    tcp_socket.bind(server_addrs);
    # 设置监听模式
    tcp_socket.listen(128);

    socket_list = list();
    # 循环接受数据并给出相应
    while True:
        # time.sleep(0.5)
        # 等待连接
        # 捕捉等待数据时的异常,没有数据请求会产生
        try:
            new_socket, client_add = tcp_socket.accept();
        except Exception as ret :
            # print("----没有客户端请求-----");
            pass;
        else:
            # print("----新的客户端连接---");
            # 将新客户设置为非堵塞
            new_socket.setblocking(False);
            # 将新套接字加入套接字列表
            socket_list.append(new_socket);

        # 遍历套接字列表,循环服务每位客户
        for client_socket in socket_list:

            # 捕捉接受异常,没有数据请求会产生
            try:
                recv_data = client_socket.recv(1024);
            except Exception as ret :
                #print(ret);
                pass;
            else:
                # 判断客户是否请求完毕
                # 如果有数据,但数据为空,表示客户调用close
                if recv_data:
                    server_repans(client_socket,recv_data.decode("gbk"));
                else:
                    client_socket.close();
                    socket_list.remove(client_socket);

        # 处理数据



    # 关闭套接字
    tcp_socket.close();


if __name__ == "__main__":
        main();

实现长连接

# -*- codeing = utf-8 -*-
# @Time : 2020/11/21 13:16
# @Author : King
# @File : 长连接实现.py
# @software : PyCharm
# -*- codeing = utf-8 -*-
# @Time : 2020/11/21 12:03
# @Author : King
# @File : 单进程、单线程、非解堵塞.py
# @software : PyCharm

# -*- codeing = utf-8 -*-
# @Time : 2020/11/16 9:55
# @Author : King
# @File : 服务器模拟器.py
# @software : PyCharm


import socket
import re
import time


def server_repans(new_socket,data):

    """接受并对客户进行响应"""

    print("-------------")

    #接受用户的请求
    #data = new_socket.recv(1024).decode("gbk");

    # 将请求以行分割,用于提取用户请求的文件
    data_list = data.splitlines();

    # 'GET / HTTP/1.1',
    # 使用正则提取文件数据
    ret = re.match("[^/]+(/[^ ]*)",data_list[0])

    # 判断数据是否提取出来
    if ret :
        file_name = ret.group(1);
        #如果用户后面文件名没有加则返回主页
        if file_name == "/":
            file_name = "/text.html"
    print(file_name);

    # 开始拼装文件名
    try:
        f = open("./第十六次作业"+file_name,"rb");
    except: # 打开文件失败
        respans = "HTTP/1.1 404 NOT FOUND\r\n";
        respans += "\r\n";
        respans +="------你的文件没找到————————";
        new_socket.send(respans.encode("gbk"));
    else:   # 打开文件成功
        html_content = f.read(1024);
        f.close();
        
        # 封装body部分
        respans_body = html_content;
        # 封装header部分
        respans_headr = "HTTP/1.1 200 OK\r\n";
        respans_headr += "Content-Length:%d\r\n" % len(respans_body);
        respans_headr += "\r\n";

        respans = respans_headr.encode("gbk") + respans_body;

        new_socket.send(respans)



    #new_socket.close();

    print(">>>>>>>>>>>>")

def main():

    # 创建套接字
    tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 防止服务器先调用close出错
    tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 设置非解堵塞
    tcp_socket.setblocking(False)
    # 绑定ip/port
    server_addrs = ("127.0.0.1",8088);
    tcp_socket.bind(server_addrs);
    # 设置监听模式
    tcp_socket.listen(128);

    socket_list = list();
    # 循环接受数据并给出相应
    while True:
        # time.sleep(0.5)
        # 等待连接
        # 捕捉等待数据时的异常,没有数据请求会产生
        try:
            new_socket, client_add = tcp_socket.accept();
        except Exception as ret :
            # print("----没有客户端请求-----");
            pass;
        else:
            # print("----新的客户端连接---");
            # 将新客户设置为非堵塞
            new_socket.setblocking(False);
            # 将新套接字加入套接字列表
            socket_list.append(new_socket);

        # 遍历套接字列表,循环服务每位客户
        for client_socket in socket_list:

            # 捕捉接受异常,没有数据请求会产生
            try:
                recv_data = client_socket.recv(1024);
            except Exception as ret :
                #print(ret);
                pass;
            else:
                # 判断客户是否请求完毕
                # 如果有数据,但数据为空,表示客户调用close
                if recv_data:
                    server_repans(client_socket,recv_data.decode("gbk"));
                else:
                    client_socket.close();
                    socket_list.remove(client_socket);

        # 处理数据



    # 关闭套接字
    tcp_socket.close();


if __name__ == "__main__":
        main();

epoll技术

我们一般的程序是属于应用程序单独一块内存空间,操作系统一块内存空间,相互之间无法访问,所以说,当我们的套接字需要判断是否有数据到来时,需要进程copyt一份给操作系统操作系统判断然后告诉该进程,又因为同时启动的进程不只一个所以就会非常影响效率
epoll技术解决了这个copy的问题,epoll使用内存映射技术,创建一个应用程序和操作系统共享的内存空间,这样进程将套接字放入到这一空间中就不需要拷贝,而操作系统可以自己访问该空间检查。
不过这样也有一个弊端如果当套接字非常多的时候遍历一遍非常消耗时间,这样一遍一遍的遍历的方式称为轮询,还有一种方式是事件通知
事件通知是如果该套接字有事件发生自己找操作系统,操作系统给你向进程响应,这样就大大的提高了效率

使用epoll

首先导入select莫快递
创建epoll:select.epoll();
取epoll中的数据epoll对象.poll();
将epl对象中数据取出来,取出来的是一个列表,列表中的元素是一个元组(fd,事件)
将数据加入epoll epoll对象.register(fd,事件)
fd:资源注册符,使用fileno()获取,
事件:EPOLLIN 接受事件
EPOLLOUT 发送事件
删除epoll对象.unregister();

epoll技术实现

# -*- codeing = utf-8 -*-
# @Time : 2020/11/16 9:55
# @Author : King
# @File : 服务器模拟器.py
# @software : PyCharm


import socket
import re
import select



def server_repans(new_socket,data):

    """接受并对客户进行响应"""

    print("-------------")

    #接受用户的请求
    #data = new_socket.recv(1024).decode("gbk");

    # 将请求以行分割,用于提取用户请求的文件
    data_list = data.splitlines();

    # 'GET / HTTP/1.1',
    # 使用正则提取文件数据
    ret = re.match("[^/]+(/[^ ]*)",data_list[0])

    # 判断数据是否提取出来
    if ret :
        file_name = ret.group(1);
        #如果用户后面文件名没有加则返回主页
        if file_name == "/":
            file_name = "/text.html"
    print(file_name);

    # 开始拼装文件名
    try:
        f = open("./第十六次作业"+file_name,"rb");
    except: # 打开文件失败
        respans = "HTTP/1.1 404 NOT FOUND\r\n";
        respans += "\r\n";
        respans +="------你的文件没找到————————";
        new_socket.send(respans.encode("gbk"));
    else:   # 打开文件成功
        html_content = f.read(1024);
        f.close();

        respans_body = html_content;
        respans_headr = "HTTP/1.1 200 OK\r\n";
        respans_headr += "Content-Length:%d\r\n" % len(respans_body);
        respans_headr += "\r\n";

        respans = respans_headr.encode("gbk") + respans_body;

        new_socket.send(respans)



    new_socket.close();

    print(">>>>>>>>>>>>")

def main():

    # 创建套接字
    tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 防止服务器先调用close出错
    tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 设置非解堵塞
    tcp_socket.setblocking(False)

    # 创建一个epoll对象,用于表示那一块共享的内存
    epl = select.epoll();
    # 将套接字对应的fd注册符加入epl对象中并设置为接受事件
    epl.register(tcp_socket.fileno(),select.EPOLLIN)
    # 创建一个字典,用于将fd和套接字对应起来
    fd_socket_dict = dict();

    # 绑定ip/port
    server_addrs = ("127.0.0.1",8088);
    tcp_socket.bind(server_addrs);
    # 设置监听模式
    tcp_socket.listen(128);

    socket_list = list();
    # 循环接受数据并给出相应
    while True:
        # 将epl对象中数据取出来,取出来的是一个列表,列表中的元素是一个元组(fd,事件)
        fd_even_list = epl.poll();
        # 遍历该列表
        for fd,even in fd_even_list:
            # 判断是否为监听套接字
            if fd == tcp_socket.fileno():
                new_socket, client_add = tcp_socket.accept();
                # 将该套接字加入epl中
                epl.register(new_socket,select.EPOLLIN);
                # 将该套接字加入字典
                fd_socket_dict[fd] = new_socket;
            elif even == select.EPOLLIN:
                try:
                    recv_data = fd_socket_dict[fd].recv(1024);
                except Exception as ret:
                    # print(ret);
                    pass;
                else:
                    # 判断客户是否请求完毕
                    # 如果有数据,但数据为空,表示客户调用close
                    if recv_data:
                        server_repans(fd_socket_dict[fd], recv_data.decode("gbk"));
                    else:
                        fd_socket_dict.close();
                        # 从epl中删除
                        epl.unregister(fd);
                        # 从字典中删除
                        del fd_socket_dict[fd];

    # 关闭套接字
    tcp_socket.close();


if __name__ == "__main__":
        main();
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值