Python网络通信与服务器

本文介绍了网络通信的基础知识,包括IP地址、端口的作用,详细讲解了UDP和TCP协议的使用,包括如何创建UDP和TCP服务端与客户端套接字。同时,探讨了线程、进程和协程的概念,以及多任务处理的不同方式。此外,还提到了HTTP协议的工作流程和TCP的3次握手、4次挥手机制。

网络概述

ip地址

用来标记网络上的一台电脑
linux:ifconfig sudo ifconfig ens40(网卡) down关闭/up开启 ctrl+a回到命令行行首
ctrl+e回到命令行行尾
windows:ipconfig
ipv4:256256256*256
ipv6:很多
网络号+主机号

端口

确认哪个程序(“进程”是运行起来的“程序”):0-65535 2的16次方
80端口是http服务
21端口是ftp服务

私有ip

在局域网使用,不在公网使用
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255

socket

from socket import *

udp

发送数据的流程:                     接收数据的流程:
1.创建套接字                        1.创建套接字
2.发送数据                          2.绑定本地自己的信息(ip,port)  
3.关闭                              3.接收数据
                                    4.关闭

创建udp服务端套接字

udp_serversocket = socket(AF_INET, SOCK_DGRAM)
# 2.绑定ip和端口号(必须是自己电脑的)
IP = "127.0.0.1"
PORT = 3333
udp_serversocket.bind((IP, PORT))
while True:
    # 3.接受数据
    BUFLEN = 1024  # 每次最多接收多少字节
    recv_data = udp_serversocket.recvfrom(BUFLEN)  # recvfrom接收的数据是元组,(内容,(源ip+port))
    # 4.打印接收的数据
    recv_msg=recv_data[0] #存储接收的数据
    send_addr=str(recv_data[1]) #存储发送方的地址信息
    print(f'{send_addr}:{recv_msg.decode()}')
    print(recv_data)
# 5.关闭套接字
udp_serversocket.close()

创建udp客户端套接字(客户端可以先不绑定端口,系统会随机分)

udp_socket=socket(AF_INET,SOCK_DGRAM)
#可以使用套接字持续收发数据
while True:
    content=input('>>>')#内容
    if content=='exit':
        break
    #!!!内容需要转换为二进制才能发送.encode(“默认utf-8”)//b"xxxx"
    IP='127.0.0.1'#服务器ip
    PORT=3333#端口
    #发送内容和连接服务器的ip和端口
    udp_socket.sendto(content.encode("utf-8"),(IP,PORT))
#关闭套接字
udp_socket.close()

tcp

(可靠的,基于连接,稳定的)

创建tcp客户端

#1.创建tcp套接字
# 实例化一个socket对象,指明协议,ip/tcp
tcpc_Socket = socket(AF_INET, SOCK_STREAM)

#2.链接服务器
#服务端ip和端口
IP = '127.0.0.1'
SERVER_PORT = 13333
BUFLEN = 1024
# 连接服务端socket
tcpc_Socket.connect((IP, SERVER_PORT))

#3.发送数据
while True:
    # 从终端读入用户输入的字符串
    toSend = input('>>> ')
    if  toSend =='exit':
        break
    # 发送消息,也要编码为 bytes
    tcpc_Socket.send(toSend.encode())
    # 等待接收服务端的消息
    recved = tcpc_Socket.recv(BUFLEN)
    # 如果返回空bytes,表示对方关闭了连接
    if not recved:
        break
    # # 打印读取的信息
    print(recved.decode())

#4.关闭套接字
tcpc_Socket.close()

创建tcp服务端

# 1.创建套接字
# 实例化一个socket对象
# 参数 AF_INET 表示该socket网络层使用IP协议(ipv4)
# 参数 SOCK_STREAM 表示该socket传输层使用TCP协议
listenSocket= socket(AF_INET, SOCK_STREAM)

# 2.绑定本地信息blid
# 主机地址为空字符串,表示绑定本机所有网络接口ip地址
# 等待客户端来连接
IP = '127.0.0.1'
# 端口号 9999
PORT = 13333
# 定义一次从socket缓冲区最多读入512个字节数据
BUFLEN = 512
# socket绑定地址和端口
listenSocket.bind((IP, PORT))

# 3.让默认的套接字由主动变为被动listen
# 使socket处于监听状态,等待客户端的连接请求
# 参数 128 表示 最多接受多少个等待连接的客户端,处于堵塞状态
listenSocket.listen(128)
print(f'服务端启动成功,在{PORT}端口等待客户端连接...')

# 4.等待客户端的连接
# 接受客户端的数据,处于堵塞状态
# (监听套接字负责等待新的客户端进行连接)
# (accept)产生的新的套接字用来为客户端服务
dataSocket, client_addr = listenSocket.accept()
print('接受一个客户端连接:', client_addr)

#5.接收客户端发送过来的请求
# netstat -an|find /i "13333"cmd查看13333端口号服务
# 连接成功的话,有三个,一个是监听,两个是建立连接的客户端和服务端
global info  # 声明全局变量,将数据传入到flask中
while True:
    # 尝试读取对方发送的消息
    # BUFLEN 指定从接收缓冲里最多读取多少字节
    recved = dataSocket.recv(BUFLEN)
    # 如果返回空bytes,表示对方关闭了连接
    # 退出循环,结束消息收发
    if not recved:
        break
    # 客户端发送的信息是什么类型的数据,就怎么解码,不一定都是utf8的字符串
    # 读取的字节数据是bytes类型,需要解码为字符串
    info = recved.decode()
    print(f'收到对方信息: {info}')
    #回收客户端数据
    # 服务器接收到之后,告诉客户端接到了,发送的数据类型必须是bytes,所以要编码
    dataSocket.send(f'服务端接收到了信息 {info}'.encode())

#6.关闭套接字
# 服务端也调用close()关闭socket
dataSocket.close()
listenSocket.close()

多用户服务端

from socket import *
# 1.创建套接字
listenSocket= socket(AF_INET, SOCK_STREAM)

# 2.绑定本地信息bind
IP = '127.0.0.1'
PORT = 13333
BUFLEN = 1024
listenSocket.bind((IP, PORT))

# 3.让默认的套接字由主动变为被动listen
listenSocket.listen(128)
print(f'服务端启动成功,在{PORT}端口等待客户端连接...')


#5.接收客户端发送过来的请求
global info  # 声明全局变量,将数据传入到flask中
while True:

    # 4.等待客户端的连接
    dataSocket, client_addr = listenSocket.accept()
    print('接受一个客户端连接:', client_addr)
    while True:
        recved = dataSocket.recv(BUFLEN)
        # 如果返回空bytes,表示对方关闭了连接(客户端调用close那么recv()就会阻塞)
        if not recved:
            break
        info = recved.decode()
        print(f'收到对方信息: {info}')
        dataSocket.send(f'服务端接收到了信息 {info}'.encode())
    dataSocket.close() #表示关闭为一个客户端的服务,accept()会继续服务

#6.关闭套接字
listenSocket.close()#表示关闭一个服务器的服务,accept()停止服务

线程的基本知识

并行:真的多任务
并发:假的多任务

一个程序运行起来之后,一定有一个执行代码的东西,这个东西就称之为线程
线程的执行没有先后顺序(不知道谁先执行,可通过延时的方式控制)
如果创建thread时执行的函数,运行结束那么意味着子线程结束了。
主线程默认最后结束(主线程结束,子线程肯定死了)

1. #创建实例对象和线程
import threading
t2 = threading.Thread(target=函数名,args=(X))#只是创建了实例对象,没有创建线程
t2.start()#创建了线程,并启动线程,开始执行
    length=len(threading.enumerate())
    print('当前运行的线程数为:%d'%length)
2.创建一个类继承threading.Thread的run(self)方法。t=类名()  t.start()

多线程共享全局变量

global
在一个函数中,对全局变量进行修改的时候,到底是否需要使用global进行说明,要看是否对全局变量的指向进行了修改。
如果修改了执行,即让全局变量指向了一个新的地方,那么必须使用global。如果,仅仅是修改了指向的空间中的数据,此时不用必须使用global。
可能遇到的问题:
    资源竞争,因为cpu处理多线程是没有先后顺序的,第一个线程未进行完成,可能就结束一次执行。然后直接开始第二个线程的执行。数据越大,出现问题的可能性越大。
同步:按预定的先后次序进行运行。

互斥锁

    解决多线程共享全局变量可能遇到的问题
    某个线程要更改共享数据时,先将其锁定,此时资源的状态为”锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作。从而保证了多线程情况下数据的正确性。
创建锁:mutex=threading.lock()
锁定:mutex.acquire()如果之前没有被上锁,那么上锁成功
                    如果之前被上锁,堵塞在此,直到这个锁被解开
释放:mutex.release()

死锁

a锁等b锁释放才能继续执行,b锁等a锁释放才能继续执行。a锁进程中有b锁,b锁进程中有a锁
避免死锁
银行家算法
添加超时时间

线程多任务版udp聊天器

import threading
from socket import *

def recv_msg(udp_socket):
“”“接收数据并显示”“”
while True:
recv_data = udp_socket.recvfrom(1024)
print(recv_data)

def send_msg(udp_socket,dest_ip,dest_port):
“”“发送数据”“”
while True:
send_data = input(“>>>”)
udp_socket.sendto(send_data.encode(“utf-8”), (dest_ip, dest_port))

def main():
“”“完成udp聊天器的整体控制”“”
# 1.创建套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)
# 2.绑定本地信息
ip = “”
port = 8888 # 本机调试时,收发数据端口保持不一致
udp_socket.bind((ip, port))
# 3.获取对方的ip
dest_ip = input(“>>>ip”)
dest_port = int(input(“>>>port”))
# 4.创建两个线程,去执行相应的功能
t_recv = threading.Thread(target=recv_msg, args=(udp_socket,))
t_send = threading.Thread(target=send_msg, args=(udp_socket,dest_ip,dest_port))
t_recv.start()
t_send.start()
if name == “main”:
main()

进程、程序的概念

未运行的为程序,运行的为进程
一个程序可以有多个进程

进程:操作系统分配资源的基本单元(使用进程实现多任务)
进程的状态:新建、就绪、等待(堵塞)、运行、死亡
import time
import multiprocessing

def sing():
“”“唱歌5秒钟”“”
for i in range(5):
print(“—正在唱—”)
time.sleep(1)

def dance():
“”“跳舞5秒钟”“”
for i in range(5):
print(“—dance—”)
time.sleep(1)

def main():
#创建实例对象
p1=multiprocessing.Process(target=sing)
p2=multiprocessing.Process(target=dance,args=())
p1.start()
p2.start()

if name == “main”:
main()

进程和线程的区别:

一个进程里至少有一个主线程
线程依赖于进程
进程是资源分配单位
线程是操作系统调度单位

多进程通过queue实现数据共享

import multiprocessing

def download_from_web(q):
“”“下载数据”“”
#模拟从网上下载的数据
data=[11,22,33,44]
#向队列中写入数据
for temp in data:
q.put(temp)
print(“已完成下载,并存入队列”)
def analysis_data(q):
“”“数据处理”“”
waitting_analysis=list()
#从队列获取数据
while True:
data=q.get()
waitting_analysis.append(data)
if q.empty():
break
#模拟数据处理
print(waitting_analysis)
def main():
#1.创建一个队列
q=multiprocessing.Queue()
#2.创建多个进程,将队列的引用当做实参进行传递到里面
p1=multiprocessing.Process(target=download_from_web,args=(q,))
p2=multiprocessing.Process(target=analysis_data,args=(q,))
p1.start()
p2.start()
if name ==“main”:
main()

进程池

任务数不确认且数据多时,需要用进程池pool
初始化pool时,可以指定一个最大进程数,当有新的请求提交到pool时,如果pool还没有满,那么就会新建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到pool中有进程结束,才会用之前的进程(进程号一致)来执行新的任务。
po=multiprocessting.pool(5)创建进程池
po.apply_async(***)向进程池添加任务
po.close()
po.join()作用是等待进程池中的任务做完
在进程池中需要创建队列的话:    
            q=multiprocessing.manager().queue()

多任务-协程

迭代器

from collections import iterable
isinstance(xxx,iterable) 返回true的话是可迭代对象,false不可迭代

自己定义的类生成的实例对象,可以使用for循环:
class Classmate(object):
    def __init__(self):
        self.names=list()
        self.current_num=0
    def add(self):
        pass
        """有这两个方法,就是迭代器"""
    def __iter__(self):
        """如果想要一个对象成为一个可迭代对象,那么必须使用__iter__方法""""
        return self            
    def __next__(self):#返回什么那么迭代的就是什么
        if self.current_num < len(self.names) :
            ret=self.obj.names[self.curremt_num]
            self.current_num+=1
            return ret
        else:
            raise StopIteration

classmate=Classmate()#生成实例对象
#是否为可迭代对象
isinstance(classmate,iterable)
#是否为迭代器
isinstance(classmate,iterator)

优点:占用极小的内存空间,存储的是生成的对象

生成器(特殊的迭代器)可以控制是否执行

nums=x*2 for x in range(10) 约等于 nums=(x*2 for x in range(10));
如果一个函数中有yield语句,那么这个就不再是函数,而是一个生成器的模板。(yield a 返回a的值);
两个生成器对象的值互不影响;
send一般不会放到第一次启动生成器,如果非要这样做,那么传递None;

使用yield完成多任务

使用gevent完成多任务

pip install gevent
from gevent import monkey
monkey.patch_all()
gevent.spawn()

进程、线程、协程对比

1.进程是资源分配的单位
2.线程是操作系统调度的单位
3.进程切换需要的资源最大,效率最低
4.线程切换需要的资源一般,效率一般(不考虑GIL)
5.协程切换任务资源最小,效率最高
6.多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中,一定是并发的。

正则表达式

import re#导包
result=re.match(正则表达式,要匹配的字符串)#匹配//match默认只能判断以谁开头,不能判断结尾
result.group()#提取

匹配单个字符

\d:0-9
\D:非数字
[12345678]:12345678
[1-36-8]:123678
[1-8abcd]:1-8、abcd
[1-8a-z]:1-8、a-z
[1-8a-zA-Z]:1-8、a-z、A-Z
\w:a-z、A-Z、0-9、_、…
\W:非字符
\s:空格、tab
\S:非空白
.:所有的一个字符(除了换行)(加,re.S可以匹配到)

匹配多个字符

\d{1,3}:一个到三个连续的数字字符
\d{11}:11个连续的字符
?:?前面的可以有或者没有(仅一次)
*:任意个
+:任意个(不能没有)
$:增加判断结尾,判断所有字符串
^:增加判断开头,
|:匹配任意左边和右边(group(1)取第1个括号的东西)

案例

email=input(">>>")##邮箱地址
#如果在正则表达式中需要用到某些特殊的字符,如:. ? 等,需要在他们前面添加一个反斜杠进行转义
ret=re.match(r"^[a-zA-Z_0-9]{4,20}@163\.com$",email)
if ret:
    print("%s符合要求"%email)
else:
    print("%s不符合要求" % email)
#将后面的匹配保存与前面的一致(在分组前面可以加?P<取名>)

re.match(r"<(\w*)>.<\1>“,html).group()
re.match(r”<(?P\w
)>.*</?P>",html).group()

re模块的高级用法

re.search:不从头开始
re.findall:直接返回,不用group()
re.sub():替换
<!-- re.split()切割 -->

http协议

浏览器–>服务器(request)
get / http/1.1
host:请求的目标
connection:长/短连接
accept:浏览器接收什么形式内容
user-agent:客户端浏览器版本
accept-encoding:浏览器接收的压缩格式
accept——language:

服务器–>浏览器(response)
http/1.1 200 ok
head body(之间空一行)

tcp3次握手

c---->s(syn11)
s---->c(ack12、syn44)
c----->s(ack45)

tcp4次挥手

单进程、线程,非堵塞

tcp_server.setblocking(false) 设置套接字为非堵塞的方式
from socket import *

tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind((“”,7899))
tcp_server.listen(128)
tcp_server.setblocking(False)

client_socket_list=list()

while True:
try:
new_socket,new_addr=tcp_server.accept()
except Exception as e:
print(“没有新的客户端到到来”)
else:
print(“到来一个新的客户端”)
new_socket.setblocking(False)
client_socket_list.append(new_socket)

for client_socket in client_socket_list:
    try:
        recv_data=client_socket.recv(1024)
    except Exception as e:
        print(e)
        print("没有收到数据")
    else:
        print(recv_data)
        if recv_data:
            print("客户端发来了数据")
        else :
            client_socket.close()
            client_socket_list.remove(client_socket)

epoll的原理过程讲解

tcp-ip简介

应用层
传输层
网络层
链路层

osi简介

应用层
表示层
会话层
传输层
网络层
数据链路层
物理层

浏览器访问服务器过程

1.解析域名(dns服务器)
2.向服务器发送tcp的3次握手
3.发送http的请求数据以及等待服务器的应答
4.发送tcp的4次握手

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值