python之Socket网络编程

一、软件开发架构

C/S架构:客户端/服务端

B/S架构:浏览器/服务端

手机端:好像是c/s架构比较火,但是b/s架构正在逐步火起来
目的:统一接口,聚集流量用户

服务端:24小时不间断提供服务
客户端:什么时候想体验服务,就去找服务端体验服务

学习网络编程
你就能够开发一个c/s架构的软件
学习并发编程 数据库  前端
你就能够开发一个b/s架构的软件

二、OSI七层模型

OSI七层协议:
    应用层
    表示层
    会话层
    传输层
    网络层
    数据链路层
    物理连接层

也有人将上面的七层概括为五层
    1.应用层
    2.传输层
    3.网络层
    4.数据链路层
    5.物理连接层
1.物理连接层
    数据都是基于电信号传输(010101这样的二进制)
    电信号特点:只有高低电平

2.数据链路层
    010:我
    0101:你
    01010101010101
    1.规定了电信号的分组方式
    2.规定了任何一台接入互联网的计算机都必须有一块网卡
        网卡:每台计算机在出厂的时候网卡上面有刻有一段唯一标识码(不能重复)
        16进制12位数字(前6位:厂商编号 后6位是流水线号)
        这个独一无二的标识码我们称之为"mac地址"
    ————》》》上述两个规定统称为"以太网协议"
以太网不能够跨局域网传输

交换机:所有的电脑都连交换机就可以实现多台电脑之间互联,你的电脑就不会变成马蜂窝了

基于以太网协议通信:通信基本靠吼
弊端:广播风暴


3.网络层(IP协议)
    规定了任何一台互联网的计算机都必须有一个ip地址:点分十进制(ipv4版本)
    ip地址目前有两个版本
        ipv4
            最小:0.0.0.0
            最大:255.255.255.255
        ipv6(冒号16进制)
            最小:0.0.0.0.0.0
            最大:。。。。
    ip地址:能够找到所有联入互联网任何一台计算机

4.传输层(端口协议)
    TCP,UDP
    端口(port): 用来标识一台计算机上的某个应用程序
    端口范围(0~65535)
    动态分配
    0~1024操作系统占用
    端口号推荐使用8000之后的
    Django默认端口号8000
    flask默认端口号5000
    MySQL默认端口号3306
    Redis默认端口号6379

    端口:用来标识某一台计算机上的某一个应用程序
    注意:同一台计算机上同一时间同一端口号不能重复


小总结:计算机于计算机之间通信其实计算机上的应用程序之间的通信
ip地址
port号
ip+port:能够找到任何接入互联网一台计算机上面的某个应用程序

5.应用层(HTTP协议,FTP协议...)

TCP协议(流式协议,可靠协议)
    三次握手四次挥手
    
TCP流式协议
    它会将数据量比较小的并且时间间隔比较短的数据
    一次性发送给对方
View Code

三、套接字函数

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

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

# 公共用途的套接字函数
s.recv()            接收TCP数据
s.send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
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初识

# 服务端
# 举例
import socket

server = socket.socket()  # 买手机   不传参数默认就是TCP协议
server.bind(('127.0.0.1', 8080))  # 插手机卡,传输的是一个元组(IP,端口)
server.listen(5)  # 半连接池:同时允许5个会话链接,超过链接不上,客户端会报错,默认为None

conn, addr = server.accept()  # 待机  阻塞

data = conn.recv(1024)  # 听别人说话   接收1024个bytes
print(data)
conn.send(b'hello big baby~')  # 回话

conn.close()  # 挂电话
server.close()  # 关机

# 客户端
import socket

client = socket.socket()  # 买手机
client.connect(('127.0.0.1', 8080))  # 拨号

client.send(b'hello big baby~')
data = client.recv(1024)
print(data)

client.close()
# 上面的代码可以实现一次简单的信息传递
"""
服务端
1.24小时不间断提供服务端
2.有固定的ip和port
3.支持高并发

并发:只要看起来像同时进行就可以称之为并发
并行:真正意义上的同时执行
"""

socket 通讯循环

# 服务端
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

conn, addr = server.accept()
while True:
    try:
        data = conn.recv(1024)  # b'' 如果发送这个字符代表,通话结束
        if len(data) == 0:break  # 针对mac linux系统而言
        print(data)
        conn.send(data.upper())
    except ConnectionResetError:
        break

conn.close()
server.close()
# 端口占用:OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。

# 客户端 # 可以循环接收发送消息 import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: msg = input('please input your msg>>>:').encode('utf-8') if len(msg) == 0:continue client.send(msg) data = client.recv(1024) print(data)

socket通讯循环2

# 服务端
import socket

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)  # 如果超过5个人连接过来就会报错:ConnectionResetError,由于目标计算机积极拒绝,无法连接


while True:
    conn, addr = server.accept()
    while True:
        try:
            data = conn.recv(1024)  # b''
            if len(data) == 0:break  # 针对mac linux系统而言
            print(data)
            conn.send(data.upper())
        except ConnectionResetError:
            break
    conn.close()

server.close()

# 客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    msg = input('please input your msg>>>:').encode('utf-8')
    if len(msg) == 0:continue
    client.send(msg)
    data = client.recv(1024)
    print(data)

 subprocess模块回忆

import subprocess

cmd = subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout = cmd.stdout.read().decode('gbk')
stderr = cmd.stderr.read().decode('gbk')
print(stdout+stderr)

subprocess模块模拟远程命令

# 实现服务端接收客户端发出的命令并执行返回结果
# 服务端
import socket
import subprocess

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn,addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024).decode('utf-8')
            if len(cmd) == 0:break
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            print(len(stdout+stderr))  # 查看返回的长度
            conn.send(stdout+stderr) # 将数据发回客户端
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()

server.close()

# 客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    cmd = input('please input your cmd>>:').encode('utf-8')
    if len(cmd) == 0:continue
    client.send(cmd)
    data = client.recv(1024)
    print(data.decode('gbk'))


# 上面测试执行dir可以返回服务端目录下的目录结构
# 但是执行tasklist返回的数据没有返回完整!!!!!
# ----原因是接收发送数据recv接收的长度是1024,但是如果我不知道接收的长度那怎么办呢?

粘包现象:

  ------ 从上面tasklist中引出如果接收的字节超过规定的字节怎么处理?

TCP流式协议
    它会将数据量比较小的并且时间间隔比较短的数据一次性发送给对方
  ----服务端接收的时候会将数据当成一行数据接收
-- 那如何解决这个问题呢?
必须有一个数据先传到服务端告诉服务端接下来传输的数据大小

struct模块

# 打包一个数据,并打包成一个固定长度
import struct

res = 'ssssssssssssssssssssssssssssssssss'

print(len(res))  # 字符串长度34
head = struct.pack('i',len(res))  # pack打包
print(len(head))  # 多次改变res长度,head长度还是为4

real_len = struct.unpack('i',head)[0]
print('real_len解压后长度',real_len) # 长度34
# 打包字典发送
import json
d = {
    'file_name':'xxx',
    'file_size':123213123123234543545324546536546563465435435423543256546546635463465563565653,
    'md5':'dkfkdsafksdklafj;asfdsaf'
}
data_bytes = json.dumps(d).encode('utf-8')
print(len(data_bytes))
head = struct.pack('i',len(data_bytes))

head_bytes = struct.unpack('i',head)[0]
print(head_bytes)
View Code

socket解决粘包问题初级版本

# 服务端
import socket
import subprocess
import struct

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn, addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024).decode('utf-8')
            if len(cmd) == 0:break
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            print(len(stdout+stderr))
            # 先制作报头
            header = struct.pack('i',len(stdout+stderr))
            # 发送报头
            conn.send(header)
            # 再发真实数据
            conn.send(stdout+stderr)
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()
server.close()

# 客户端
import socket
import struct

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    cmd = input('please input your cmd>>>:').encode('utf-8')
    if len(cmd) == 0:continue
    client.send(cmd)
    # 先接收报头
    header = client.recv(4)
    # 解析真实数据长度
    total_size = struct.unpack('i',header)[0]
    # 循环接收真实数据,判断数据长度
    data = b''
    recv_size = 0
    while recv_size < total_size:
        info = client.recv(1024)
        data += info
        recv_size += len(info)
    print(data.decode('gbk'))
View Code

socket解决粘包问题终极版本

# 解决上面返回字符长度或不知字符长度问题
#
服务端 import socket import subprocess import struct import json server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn, addr = server.accept() while True: try: cmd = conn.recv(1024).decode('utf-8') if len(cmd) == 0:break obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout = obj.stdout.read() stderr = obj.stderr.read() print(len(stdout+stderr)) send_dic = {"name":'simon','password':'123','total_size':len(stdout+stderr)} send_bytes = json.dumps(send_dic).encode('utf-8') # 先制作字典的报头 header = struct.pack('i',len(send_bytes)) # 发送报头 conn.send(header) # 再发字典 conn.send(send_bytes) # 最后再发真实数据 conn.send(stdout+stderr) except ConnectionResetError as e: print(e) break conn.close() server.close() # 客户端 import socket import struct import json client = socket.socket() client.connect(('127.0.0.1',8080)) while True: cmd = input('please input your cmd>>>:').encode('utf-8') if len(cmd) == 0:continue client.send(cmd) # 先接收报头 header = client.recv(4) # 解析获取字典的长度 dic_len = struct.unpack('i',header)[0] dic_bytes = client.recv(dic_len) real_dic = json.loads(dic_bytes.decode('utf-8')) print(real_dic) # 循环接收真实数据 data = b'' recv_size = 0 while recv_size < real_dic['total_size']: info = client.recv(1024) data += info recv_size += len(info) print(data.decode('gbk'))

socket实现大文件上传:

# 服务端
import socket
import struct
import json
# 解决报错信息
from socket import SOL_SOCKET,SO_REUSEADDR

server = socket.socket()
# 解决报错信息
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8090))
server.listen(5)

conn,addr = server.accept()
# 先收4个长度报头
header = conn.recv(4)
# 获取字典长度
dic_len = struct.unpack('i',header)[0]
# 接收字典
dic_bytes = conn.recv(dic_len)
real_dic = json.loads(dic_bytes.decode('utf-8'))
# 获取文件相关信息
file_name = real_dic.get('file_name')
total_size = real_dic.get("file_size")
with open(file_name,'wb') as f:
    # 循环接收文件
    recv_size = 0
    while recv_size < total_size:
        data = conn.recv(1024)
        f.write(data)  # 收取就存入文件
        recv_size += len(data)
    print('文件上传成功')

# 客户端
import socket
import struct
import json
import os

client = socket.socket()
client.connect(('127.0.0.1',8090))

file_path = r'D:周末dayday1 今日内容.mp4'
file_size = os.path.getsize(file_path)  # 获取文件大小
send_dic = {"file_name":'FBI warning.mp4','file_info':"注意身体",'file_size':file_size}
send_bytes = json.dumps(send_dic).encode('utf-8')
# 制作报头
head = struct.pack('i',len(send_bytes))
# 发送报头
client.send(head)
# 发字典
client.send(send_bytes)
# 发文件
with open(file_path,'rb') as f:
    for line in f:
        client.send(line)

五、UDP协议:数据报协议

UDP特点:
1.udp协议客户端允许发空
2.udp协议不会粘包
3.udp协议服务端不存在的情况下,客户端照样不会报错
# 服务端
import socket

server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))

msg, addr = server.recvfrom(1024)
print(msg.decode('utf-8'))
server.sendto(b'hello', addr)

server.close()

#客户端
import socket

client = socket.socket(type=socket.SOCK_DGRAM)
# 配置服务端地址端口
server_addr = ('127.0.0.1', 8080)

# 发的时候必须带上IP地址,服务端才能知道你是谁;客户端允许发空,服务端也接收为空
client.sendto(b'hello server baby!', server_addr)
msg, addr = client.recvfrom(1024)
print(msg, addr)
"""
# udp特点    >>>    无链接,类似于发短信,发了就行对方爱回不回,没有任何关系
# 将服务端关了,客户端起起来照样能够发数据。因为不需要考虑服务端能不能收到
"""
# 验证udp协议有无粘包问题
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))
print(server.recvfrom(1024))
print(server.recvfrom(1024))
print(server.recvfrom(1024))

import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1',8080)
client.sendto(b'hello',server_addr)
client.sendto(b'hello',server_addr)
client.sendto(b'hello',server_addr)

练习:基于UDP实现简易版本的对话

# 服务端
import socket

server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))

while True:
    data, addr = server.recvfrom(1024)
    print(data.decode('utf-8'))
    server.sendto(data.upper(),addr)

# 多个客户端
import socket

client = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1', 8080)

while True:
    info = input('>>>:')
    info = ('来自客户端1的消息:%s'%info).encode('utf-8')  # 改中文备注即可
    client.sendto(info, server_addr)
    msg, addr = client.recvfrom(1024)
    print(msg.decode('utf-8'), addr)

client.close()

"""
服务端接收:
客户端1:sdafasewrtwe
客户端2:erwtwetwe
客户端3:retwtrwetw
客户端4:ewrbxcbvxb
"""

补充:windows电脑和mac电脑的时间同步功能,其实就是基于udp向windows,mac服务器发送请求获取标准时间

总结:TCP协议就类似于打电话;UDP协议就类似于发短信

SocketServer模块介绍(让tcp也能支持并发)

# TCP socketserver使用
import socketserver
class MyTcpServer(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                data = self.request.recv(1024)  # 对于tcp,self.request相当于conn对象
                if len(data) == 0:break
                print(data)
                self.request.send(data.upper())
            except ConnectionResetError:
                break
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8081),MyTcpServer)  # 请求来了交给MyTcpServer处理
    server.serve_forever()
    
# UDP socketserver使用
import socketserver

class MyUdpServer(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            data, sock = self.request
            print(data)
            sock.sendto(data.upper(), self.client_address)


if __name__ == '__main__':
    server = socketserver.ThreadingUDPServer(('127.0.0.1', 8080), MyUdpServer)
    server.serve_forever()

 作业:

作业:开发一个支持多用户在线的FTP程序
要求:
1.用户加密认证
2.允许同时多用户登录
3.每个用户有自己的家目录,且只能访问自己的家目录
4.对用户进行磁盘配额,每个用户的可用空间不同
5.允许用户在ftp server 上随意切换目录
6.允许用户查看当前目录下的文件
7.允许上传和下载文件,保证文件一致性
8.文件传输过程中显示进度条
9.附加功能:支持断点续传
原文地址:https://www.cnblogs.com/yangmeichong/p/10967078.html