python 网络编程

一、基本原理:                      

1.软件 

  客户端:CS架构,client --> server

  浏览器:BS架构,borowser --> server

2.如何实现相互通信 

  1.相互通讯的本质是发送把01代码通过网线的高低电平的方式传输

  2.交换机的作用

  3.通过ipconfig查看自己的IP

  4.公网IP (需要购买)

二、编写网络相关的程序 (socket模块 )   

示例:基于socket模块实现网络通信

  服务端.py

import socket
#创建服务端socket对象
server = socket.socket()

#绑定IP和端口
server.bind(('192.168.13.156',6060))

# 后边可以等5个人
server.listen(5)

print('服务器端准备接收客户端的链接')
#等待客户端来连接,如果没人来就继续等
#conn是客户端和服务端连接的对象,服务端以后要通过该对象收发数据
#addr是客户端的地址信息
#=====阻塞只有客户端进行链接,则获取客户端链接然后开始进行通信
conn,addr = server.accept()
print('已经有人连上了,客户端信息:',conn,addr)

# 通过对象去获取
# 1024表示,服务端通过获取数据时,一次性最多拿1024个字节
data = conn.recv(1024)
print('已经有人发来了消息:',data)

# 服务端通过连接对象给客户端回复一个消息
conn.send(b'stop')

#与服务端断开链接
conn.close()

#关闭服务端的服务
server.close()

  客户端.py

import socket

client = socket.socket()

#客户端向服务端发起连接请求
#阻塞,去连接,直到连接成功才继续向下走
client.connect(('192.168.13.156',6060))

#连接上服务端后,向服务端发送消息
client.send(b'hello')

#等待服务器发来的消息
data = client.recv(1024)
print(data)

#关闭自己
client.close()

  为什么要网络通信发送的是字节?而不是字符串?      

    py3, send/recv 都是字节      

    py2, send/recv 都是字符串

  服务端:     

    accept,阻塞:等待客户端来连接。     

    recv, 阻塞:等待客户端发来数据。   

  客户端:     

    connect,阻塞:一直在连接,直到连接成功才往下运行其他代码。     

    recv, 阻塞:等待服务端发来数据。

三、黏包                         

  1.什么是黏包  

  同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。

  2.tcp协议的拆包机制   

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。 
MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。

   3.基于tcp协议特点的黏包现象成因   

      

  4.解决黏包的方法        

         struck模块   

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

import struct


res=struct.pack("i","")

print(res)
print(len(res))


obj=struct.unpack("i",res)
print(obj[0])

  

  例:简单的文件上传代码中解决黏包

  服务端.py

import socket
import struct

server = socket.socket()
server.bind(('192.168.13.156', 8005))
server.listen(5)
while True:
    print("服务器等待连接")
    conn,addr = server.accept()
    while True:
        try:
            # 接收文件名长度:
            header_pack = conn.recv(4)                      # 接收压缩的4个字节(文件名的长度)
            data_length = struct.unpack('i',header_pack)[0] # 解压接收到的4个字节得到文件名的长度
            # print("文件名长度",data_length)

            #取出文件名:
            ret = conn.recv(data_length)        # 根据文件名长度取文件名
            name = ret.decode('utf-8')

            #接收文件大小:
            size = conn.recv(4)               # 接收压缩的4个字节(文件的大小)
            file_size = struct.unpack('i',size)[0]      # 解压接收到的4个字节得到文件的大小
            print("接收文件:%s,大小为%s字节" % (name,file_size))


            recv_data_length = 0    #计数,初始为0
            while recv_data_length < file_size:
                data = conn.recv(1024)
                # with open(name,mode='a',encoding='utf-8') as f1:
                #     f1.write(data.decode('utf-8'))
                with open(name, mode='ab') as f1:
                    f1.write(data)
                recv_data_length += len(data)   #计数每次加1024

            print("接收完成")
            conn.send('上传成功!'.encode('utf-8'))
        except ConnectionResetError as e:
            break
    conn.close()

  客户端.py

import socket
import os
import struct
client = socket.socket()
client.connect(('192.168.13.156',8005))
while True:
    file_name = input(">>>>>")
    if file_name == 'exit':
        break
    if os.path.exists(file_name):
        filename = os.path.basename(file_name)
        #传文件名长度,和文件名
        name = struct.pack('i',len(filename))
        client.send(name)
        # print(len(name))
        client.send(filename.encode('utf-8'))
        #传文件大小
        size = os.path.getsize(filename)
        filesize = struct.pack('i',size)
        client.send(filesize)
        #传文件内容
        # with open(filename,mode='r',encoding='utf-8') as f:
        #     for line in f:
        #         client.send(line.encode('utf-8'))
        with open(filename,mode='rb') as f:
            for line in f:
                client.send(line)
        ret = client.recv(1024)
        print(ret.decode('utf-8'))
    else:
        print('文件不存在,请重新选择!')


client.close()

 四、socketserver           

 创建一个socketserver的步骤: 

  首先,您必须通过对BaseReStHeuldLeCar类进行子类化并重写它的Hoad()方法来创建请求处理程序类;该方法将处理传入的请求。

  其次,必须实例化服务器类中的一个,将其传递给服务器的地址和请求处理程序类。

  然后调用Server对象的HealLeReestEnter(OrServEyPro)()方法来处理一个或多个请求。

  最后,调用ServEnLoce()关闭套接字。

         流程图                         

例:文件上传下载

import json
import os
import socketserver
import struct


class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                print("服务器等待连接.....")
                self.read_dir()
                # 接收json的打包长度
                file_info_length_pack = self.request.recv(4)
                file_info_length = struct.unpack('i', file_info_length_pack)[0]
                # 接收json字符串
                file_info_json = self.request.recv(file_info_length).decode("utf8")
                file_info = json.loads(file_info_json)
                action = file_info['action']
                filename = file_info['filename']
                filesize = file_info['filesize']
                if action == 'upload':
                    self.receive_upload(filename, filesize)
                elif action == 'download':
                    self.receive_downloads(filename)
            except ConnectionResetError as e:
                break

    def receive_upload(self,filename, filesize):
        recv_data_length = 0
        while recv_data_length < filesize:
            data = self.request.recv(1024)
            with open('requestup/'+filename, mode='ab') as f1:
                f1.write(data)
            recv_data_length += len(data)
        print("用户上传已完成!")
        self.request.send('上传成功!'.encode('utf-8'))

    def receive_downloads(self,filename):
        with open('requestdown/'+filename, mode='rb') as f:
            for line in f:
                self.request.send(line)
        # 接收返回的结果
        Result = self.request.recv(1024)
        print(Result.decode('utf-8'))

    def read_dir(self):
        """
        读取可供下载的文件夹中的所有文件,并发送给客户端
        """
        returnfilename ={
            'filename': [],
            'filesize': []
        }
        rootdir = r'E:pythonworkday29File_up_downServer_side
equestdown'
        list = os.listdir(rootdir)  # 列出文件夹下所有的目录与文件
        for i in range(0, len(list)):
            path = os.path.join(rootdir, list[i])
            if os.path.isfile(path):
                returnfilename['filename'].append(os.path.basename(path))
                returnfilename['filesize'].append(os.path.getsize(path))
        allfilename = json.dumps(returnfilename).encode('utf-8')
        self.request.send(allfilename)  # 返回文件夹所有文件名



Server = socketserver.ThreadingTCPServer(('192.168.13.156',8005), MyServer)
Server.serve_forever()
服务端
import socket
import os
import struct
import json


class MYTCPClient:

    def __init__(self):
        self.client = socket.socket()

    def compression_transmission(self,updown,filename,filesize=None):
        """
        把文件信息序列化后打压发送
        """
        file_info = {
            "action": updown,
            "filename": filename,
            "filesize": filesize,
        }
        file_info_json = json.dumps(file_info).encode('utf-8')
        # 序列化
        ret = struct.pack('i', len(file_info_json))
        # 发送file_info_json打包的长度
        self.client.send(ret)
        # 发送file_info_json字节串
        self.client.send(file_info_json)

    def fileupload(self):
        """
        文件上传功能
        """
        file_path = input('请输入文件路径:').strip(" ")
        if os.path.exists(file_path):
            filename = os.path.basename(file_path)
            filesize = os.path.getsize(filename)
            self.compression_transmission('upload',filename,filesize)
            # 发送文件
            with open(file_path, mode='rb') as f:
                for line in f:
                    self.client.send(line)
            # 接收返回的结果
            Result = self.client.recv(1024)
            print(Result.decode('utf-8'))
        else:
            print('文件不存在,请重新选择!')

    def filedownload(self,allfilename):
        """
        文件下载功能
        """
        print('可下载文件目录:')
        for i in range(len(allfilename['filename'])):
            print("     序号:%s,文件名:%s,大小:%s字节" % (i + 1, allfilename['filename'][i], allfilename['filesize'][i]))
        file_path = int(input('请选择要下载的文件:'))
        filename = allfilename['filename'][file_path-1]
        filesize = allfilename['filesize'][file_path-1]
        self.compression_transmission('download', filename)
        if os.path.exists('user_download') == False:
            # 如果不存在则创建目录
            os.makedirs('user_download')
        ##########接收文件############
        recv_data_length = 0
        while recv_data_length < filesize:
            data = self.client.recv(1024)
            with open('user_download/'+filename, mode='ab') as f1:
                f1.write(data)
            recv_data_length += len(data)
        print("下载完成")
        self.client.send('用户下载已完成!'.encode('utf-8'))

    def run(self):
        """
        主程序
        """
        self.client.connect(('192.168.13.156', 8005))
        file_info_json = self.client.recv(1024).decode("utf8")
        allfilename = json.loads(file_info_json)
        while True:
            print("请选择功能:1.上传,2.下载")
            Functional_selection = input(">>>>>").strip()
            if Functional_selection == '1':
                self.fileupload()
            elif Functional_selection == '2':
                self.filedownload(allfilename)

user = MYTCPClient()
user.run()
客户端
原文地址:https://www.cnblogs.com/JinMuBaoBao/p/9580958.html