Python开发【第八篇】: 网络编程

内容概要

  • 楔子 
  • 软件开发架构
  • 网络基础
  • 套接字(socket)
  • 粘包
  • socketserver模块

一. 楔子

   现在有两个python文件a.py和b.py,分别运行,这两个程序之间需要传递一个数据,要怎么做呢?

  可以创建一个文件,把a.py想要传递的内容写到文件中,然后b.py从这个文件中读取。

 

  但是a.py和b.py分别在不同电脑上的时候,怎么办?

  类似的机制有计算机网盘,qq等等。我们可以在我们的电脑上和别人聊天,可以在自己的电脑上向网盘中上传、下载内容。这些都是两个程序在通信。(客户端和服务端的交互)

二. 软件开发架构

  两个程序之间通讯的应用大致可以分为两种:

  第一种是应用类:qq、微信、网盘、优酷这一类是属于需要安装的桌面应用

  第二种是web类:比如百度、知乎、博客园等使用浏览器访问就可以直接使用的应用

  这些应用的本质其实都是两个程序之间的通讯。而这两个分类又对应了两个软件开发的架构~

1. C/S架构

  C/S:Client与Server,客户端和服务器端架构,这种架构也是从用户层面(物理层面)来划分的。

  这里的客户端一般泛指客户端应用程序EXE,程序需要先安装后,才能运行在用户的电脑上,对用户的电脑操作系统环境依赖较大。

 2. B/S架构

  B/S即:Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。

  Browser浏览器,其实也是一种Client客户端,只是这个客户端不需要大家去安装什么应用程序,只需在浏览器上通过HTTP请求服务器端相关的资源(网页资源),客户端Browser浏览器就能进行增删改查。

三. 网络基础

 网络基础

1.OSI七层模型

七层模型_百度百科

2.socket概念

 

  Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式。 门面模式_百度百科

  它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

其实站在你的角度上看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。
也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。
站在你的角度看socket

3. 套接字(socket)发展史

  套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。

  套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 

      socket_百度百科

基于文件类型的套接字家族

  套接字家族的名字:AF_UNIX

  unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

基于网络类型的套接字家族

  套接字家族的名字:AF_INET

  (还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个。

  python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

4. tcp协议和udp协议

  TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

  UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

四. 套接字(socket)初使用

  基于TCP协议的socket

#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
import socket
sk = socket.socket()
sk.bind(("127.0.0.1",9000))   # 把IP绑定到套接字
sk.listen()                  # 监听链接

conn,addr = sk.accept()     # 接收客户端链接

client_data = conn.recv(1024)   # 接收客户端信息
print(client_data)              #打印客户端信息息

conn.send(b"haha")              # 向客户端发送信息

conn.close()            # 关闭这次的连接
sk.close()          # 关闭服务器socket,相当于停止了服务
server
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
import socket

sk = socket.socket()            # 创建客户端套接字
sk.connect(("127.0.0.1",9000))  # 连接服务器
sk.send(b"Hi server, I am a client")    # 向服务端发送信息
server_data = sk.recv(1024)             # 接收信息
print(server_data)                      # 打印服务端信息

sk.close()          # 关闭客户端套接字
client

执行效果:

  服务端,正在等待客户端的连接。

 

#  查看tcp状态, 已经有一个在监听了。

客户端,连接服务端

[root@mongodb ~]# python client.py 
server_data

 

五. 粘包

1. 粘包现象

import socket
sk = socket.socket()
sk.bind(("127.0.0.1",9000))
sk.listen()

conn,_ = sk.accept()
conn.send(b"hello")
conn.send(b"lishichao")
server
import socket

sk = socket.socket()
sk.connect(("127.0.0.1",9000))

msg = sk.recv(1024)
print(msg)

msg2 = sk.recv(1024)
print(msg2)

sk.close()
client

执行结果:

b'hello'
b'lishichao'

以上是我们想要的两条数据,但是如果有延迟,结果就不是我们想要的了。

# 修改client端:

import time
import socket

sk = socket.socket()
sk.connect(("127.0.0.1",9000))
time.sleep(0.1)     # 延迟了0.1
msg = sk.recv(1024)
print(msg)
msg2 = sk.recv(1024)
print(msg2)

sk.close()
client

执行结果:两条数据粘到一块了。

b'hellolishichao'
b''

2. 粘包的原因

    

3.解决粘包

  问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

    

struct模块

  该模块可以把一个类型,如数字,转成固定长度的bytes

import struct
ret = struct.pack("i",999999)
print(ret,len(ret))
# 执行结果: 将数字转成bytes,长度为4
# b'?Bx0fx00' 4

# 将bytes结果转回数字
res = struct.unpack("i",ret)[0]    # 取元素0
print(res)
#执行结果: 结果是元祖
# (999999,)

   在接收端接收真正的信息之前,先接收数据的长度,然后按照长度来接收余下的数据
 利用struct模块完成了自定义协议的严谨模式

import socket
import struct
sk = socket.socket()
sk.bind(("127.0.0.1",9000))
sk.listen()

conn,_ = sk.accept()
msg = b"hello,world,this is a very long message"
len_msg = struct.pack("i",len(msg))
conn.send(len_msg)    # 先发送数据长度
conn.send(msg)
conn.send(b"eva")

conn.close()
sk.close()
server
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
import socket
import struct
import time

sk = socket.socket()
sk.connect(("127.0.0.1",9000))

time.sleep(0.1)
len_msg = sk.recv(4)  # 接受数据的长度
len_msg = len_msg = struct.unpack("i",len_msg)[0]  # 转回实际的长度
msg = sk.recv(len_msg)   # 根据实际长度,接收数据
print(msg)

msg2 = sk.recv(4)
print(msg2)
sk.close()
client
服务端套接字函数
s.bind()    绑定(主机,端口号)到套接字
s.listen()  开始TCP监听
s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数
s.connect()     主动初始化TCP服务器连接
s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数
s.recv()            接收TCP数据
s.send()            发送TCP数据
s.sendall()         发送TCP数据
s.recvfrom()        接收UDP数据
s.sendto()          发送UDP数据
s.getpeername()     连接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()      返回指定套接字的参数
s.setsockopt()      设置指定套接字的参数
s.close()           关闭套接字

面向锁的套接字方法
s.setblocking()     设置套接字的阻塞与非阻塞模式
s.settimeout()      设置阻塞套接字操作的超时时间
s.gettimeout()      得到阻塞套接字操作的超时时间

面向文件的套接字的函数
s.fileno()          套接字的文件描述符
s.makefile()        创建一个与该套接字相关的文件
socket更多方法介绍

4. 基于TCP实现的简单文件传输

#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
import socket
import json
import os
import struct

sk = socket.socket()
sk.bind(("0.0.0.0",9000))
sk.listen()

conn,_ = sk.accept()

# 下载
file_path = r"E:python-25期周末班学习资料day08视频3.网络基础.mp4"
file_name = os.path.basename(file_path)
print(file_name)
file_size = os.path.getsize(file_path)
print(file_size)

file_info = {"file_name":file_name,"file_size":file_size}
info_json = json.dumps(file_info).encode("utf-8")   # str.encode('utf-8') bytes
send_len = struct.pack("i",len(info_json))

conn.send(send_len)         # 发送数据长度
conn.send(info_json)        # 发送文件信息

# 发送文件
with open(file_path,"rb") as f:
    while file_size > 0:
        content = f.read(2048)   # 一次读取2048个字节
        conn.send(content)       # 发送数据
        file_size -= len(content)

conn.close()
sk.close()
server
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
import socket
import struct
import json

sk = socket.socket()
sk.connect(("10.0.2.174",9000))

# 接收文件信息
json_len = sk.recv(4)
json_len = struct.unpack("i",json_len)[0]
info_json = sk.recv(json_len).decode("utf-8")
info_dic = json.loads(info_json)
print(info_dic)

# 接收文件
with open(info_dic["file_name"],"wb") as f:
    while info_dic["file_size"] > 0:
        content = sk.recv(2048)                 # 接收数据
        info_dic["file_size"] -= len(content)
        f.write(content)                        # 写入文件

sk.close()
client

六. socketserver模块

  基于原生的socket tcp协议的server不能同时接收多个client端的请求,使用socketserver模块可以同时接受多个client请求

import socketserver
Host = "0.0.0.0"
Port = 9000

class FTPServer(socketserver.BaseRequestHandler):
    def handle(self):
        conn = self.request
        addr = self.client_address
        print(addr)
        while True:
            msg = conn.recv(1024).decode("utf-8")
            conn.send(b"hhhhh")

# 创建一个server对象,将服务地址绑定到 0.0.0.0:9000
server = socketserver.ThreadingTCPServer((Host,Port),FTPServer)

# 让server永远运行下去,除非强制停止程序
server.serve_forever()
server
import time
import socket
sk = socket.socket()
sk.connect(("127.0.0.1",9000))

while 1:
    sk.send(b"hello")
    msg = sk.recv(1024)
    print(msg)
    time.sleep(2)

sk.close()
client

七. FTP作业

 1. 软件的开发规范

  

bin ---     程序的入口
conf ---    配置信息
core ---    核心代码
db ---       存放程序数据
log ---      程序日志
目录结构
原文地址:https://www.cnblogs.com/root0/p/10477389.html