Python网络编程

tcp和udp的区别:https://www.jianshu.com/p/c63b082ac565

1.基于tcp 

 # 服务端
import socket ​ # 创建服务端socket对象 server = socket.socket() ​ # 绑定IP和端口 server.bind(('192.168.3.6',8000)) ​ # 后边可以等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()
#客户端
 ​
 import socket
 ​
 client = socket.socket()
 ​
 # 客户端向服务端发起连接请求
 # 阻塞,去连接,直到连接成功后才会继续向下走。
 client.connect(('192.168.3.6',8000))
 # # 链接上服务端后,向服务端发送消息
 client.send(b'hhhhhhhhhhhh')
 # 等待服务端给他发送消息
 data = client.recv(1024)
 print(data)
 ​
 # 关闭自己
 client.close()

1.1 while版 

#服务端
 ​
 import socket
 ​
 server = socket.socket()
 ​
 server.bind(('192.168.3.6',8001))
 ​
 server.listen(5)
 ​
 while True:
     conn,addr = server.accept()
     # 字节类型
     while True:
         data = conn.recv(1024) # 阻塞
         if data == b'exit':
             break
         data1=' 梦想成真'
         data1=data1.encode("utf8")
         response = data +data1
         conn.send(response)
 ​
     conn.close()
#客户端
 ​
 import socket
 ​
 sk = socket.socket()
 ​
 sk.connect(('192.168.3.6',8001))
 ​
 while True:
     name = input("请输入姓名:")
     sk.send(name.encode('utf-8')) # 字节
     if name == 'exit':
         break
 ​
     response = sk.recv(1024) # 字节
     print(response.decode('utf-8'))
 ​
 sk.close()

2.基于udp

 #服务端
   
 import socket
 sk = socket.socket(type=socket.SOCK_DGRAM)  # 建立一个socket对象,
 # 指定以UDP协议的形式来连接
 sk.bind(('127.0.0.1',8080))
 # 指定服务的地址
 ​
 msg,addr = sk.recvfrom(1024) # 接收消息,发送端的地址
 print(msg,addr)
 sk.sendto(b'HELLO',addr)   # 给发送端回复消息
 ​
 sk.close()  # 关闭socket连接
#客户端
 ​
 import socket
 ​
 sk = socket.socket(type=socket.SOCK_DGRAM)
 ​
 sk.sendto(b'hello',('127.0.0.1',8080))   # 直接给服务器发送一段消息
 msg,addr = sk.recvfrom(1024)   # 接收对面的回信
 print(msg)
 sk.close()

2.1 udp聊天小工具

#udp聊天小工具
 #服务端
 import socket
 sk = socket.socket(type=socket.SOCK_DGRAM)
 sk.bind(('127.0.0.1',9090))
 while True:
     msg,addr = sk.recvfrom(1024)
     print('来自[%s:%s]的消息--%s'%(addr[0],addr[1],msg.decode('utf-8')))
 ​
     inp = input('>>>')
     sk.sendto(inp.encode('utf-8'),addr)
 ​
 sk.close()
 #客户端
 ​
 import socket
 sk = socket.socket(type=socket.SOCK_DGRAM)
 addr = ('127.0.0.1',9090)
 while True:
     msg = input('>>>')
     sk.sendto(msg.encode('utf-8'),addr)
     msg_recv,addr = sk.recvfrom(1024)
     print(msg_recv.decode('utf-8'))
 sk.close()

2.2 时间服务器  

#时间服务器
 ​
 #服务端
 ​
 # 需求
 # 写一个时间同步的服务器
 # 服务端接收请求
 # 按照client端发送的时间格式,将服务器时间转换成对应格式
 # 发送给客户端
 import time
 import socket
 ​
 sk = socket.socket(type=socket.SOCK_DGRAM)
 sk.bind(('127.0.0.1',9000))
 while True:
     msg,addr = sk.recvfrom(1024)
     # msg 客户端发送给server端的时间格式 "%Y-%m-%d %H:%M:%S"
     time_format = msg.decode('utf-8')
     time_str = time.strftime(time_format)
     sk.sendto(time_str.encode('utf-8'),addr)
 sk.close()
#客户端
 ​
 # client端每隔一段时间发送请求到服务端
 # 发送时间的格式
 import time
 import socket
 sk = socket.socket(type = socket.SOCK_DGRAM)
 sk.sendto('%Y-%m-%d %H:%M:%S'.encode('utf-8'),('127.0.0.1',9000))
 msg,addr = sk.recvfrom(1024)
 print(msg.decode('utf-8'))
 sk.close()  

3.上传和下载

3.1 知识补充

 # JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式
 # 方法            描述
 # json.dumps()  将 Python 对象编码成 JSON 字符串
 # json.loads()  将已编码的 JSON 字符串解码为 Python 对象
 # json.dump()   将Python内置类型序列化为json对象后写入文件
 # json.load()   读取文件中json形式的字符串元素转化为Python类型
 #服务端
 ​
 import json
 import socket
 ​
 sk = socket.socket()
 sk.bind(('127.0.0.1',8080))
 sk.listen()
 ​
 conn,addr = sk.accept()
 content = conn.recv(1024).decode('utf-8')
 content_dic = json.loads(content)   # json.dumps()函数的使用,将字典转化为字符串 dumps(丢弃)
                                     # json.loads函数的使用,将字符串转化为字典  loads(容器)                                        
 if content_dic['operate'] == 'upload':
     conn.send(b'received!')
     with open(content_dic['filename'],'wb') as f:
         while content_dic['filesize']:
             file = conn.recv(1024)
             f.write(file)
             content_dic['filesize'] -= len(file)
 conn.close()
 sk.close()
# 客户端
​
import os
import json
import socket
​
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))
​

def get_filename(file_path):
    filename = os.path.basename(file_path)
    return filename

​
# 选择 操作
operate = ['upload', 'download']
for num, opt in enumerate(operate, 1):  # 看下面enumerate函数的用法
    print(num, opt)
num = int(input('请输入您要做的操作序号 : '))
if num == 1:
    '''上传操作'''
    # file_path = 'E:python10day33作业.py'
    file_path = input('请输入要上传的文件路径 : ')
    # 告诉对方要上传的文件的名字
    file_name = get_filename(file_path)
    # 读要上传的文件 存成字符串
    with open(file_path, encoding='utf-8') as  f:
        content = f.read()  # content(内容)
    dic = {'operate': 'upload', 'filename': file_name, 'content': content}
    # 将字符串send给server端
    str_dic = json.dumps(dic)
    sk.send(str_dic.encode('utf-8'))
    # server端接收 bytes转码程字符串
    # server端打开文件 写文件
elif num == 2:
    '''下载操作'''
sk.close()
​
# 客户端结果
​
# 1 upload
# 2 download
# 请输入您要做的操作序号 :

# ===========================================================

# enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)
# 组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
​
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
​
list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
​
list(enumerate(seasons, start=1))  # 下标从 1 开始
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

4.远程执行系统命令

4.1 基于tcp

 #基于tcp
 #服务端(服务端向客户端远程执行命令)
   import socket
         sk = socket.socket()
         sk.bind(('127.0.0.1',8090))
         sk.listen()
     conn,addr = sk.accept()
     while True:
         cmd = input('cmd : ')
         if cmd == 'q':
             conn.send(cmd.encode('utf-8'))
             break
         conn.send(cmd.encode('utf-8'))
         print('stdout : ',conn.recv(1024).decode('gbk'))
     conn.close()
     sk.close()   
 #客户端
 ​
 import socket
 import subprocess
 sk = socket.socket()
 sk.connect(('127.0.0.1',8090))
 while True:
     cmd = sk.recv(1024).decode('utf-8')
     if cmd == 'q': break
     res = subprocess.Popen(cmd,shell=True,   #管道的数据只能取一次
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE)
     sk.send(res.stdout.read())
     sk.send(res.stderr.read())
 sk.close()

4.1 基于udp

#基于udp的远程执行系统命令
 #服务端
 ​
 import socket
 sk = socket.socket(type=socket.SOCK_DGRAM)
 sk.bind(('127.0.0.1',8090))
 msg,addr = sk.recvfrom(1024)
 while True:
     cmd = input('cmd : ')
     if cmd == 'q':
         sk.sendto(cmd.encode('utf-8'),addr)
         break
     sk.sendto(cmd.encode('utf-8'),addr)
     print('stdout : ',sk.recvfrom(2048)[0].decode('gbk'))  #接收UDP数据,与recv()类似,
                                                            #但返回值是(data,address)。
                                                            #其中data是包含接收数据的字符串,
                                                            #address是发送数据的套接字地址。
     print('stderr : ',sk.recvfrom(2048)[0].decode('gbk'))
 sk.close()
 ​
#客户端
 ​
 import socket
 import subprocess
 sk = socket.socket(type=socket.SOCK_DGRAM)
 sk.sendto(b'111',('127.0.0.1',8090))
 while True:
     cmd = sk.recvfrom(1024)[0].decode('utf-8')
     if cmd == 'q': break
     res = subprocess.Popen(cmd,shell=True,
                      stdout=subprocess.PIPE,
                      stderr=subprocess.PIPE)
     sk.sendto(res.stdout.read()*100,('127.0.0.1',8090))
     sk.sendto(res.stderr.read(),('127.0.0.1',8090))
 sk.close()

5.黏包

5.1 黏包的产生

 #连续两个send()会出现黏包
 #连续两个recv(),第一个recv()接收很少的数据
 #没接收完的数据会在内存缓存着
 #不能一次性把很多数据放到内存
 #一般一次性传4096字节
 #大文件传输
     #大文件的传输,一定是按照字节读,每一次读固定字节
     #传输过程中,一边读一边传,接收端一边收一边写

5.2 接收发的缓存引起的黏包 ​

 #服务端
 ​
 from socket import *  #导入包,导入模块只需import+模块即可
 ip_port=('127.0.0.1',8080)
 ​
 tcp_socket_server=socket()
 tcp_socket_server.bind(ip_port)
 tcp_socket_server.listen(5)
 conn,addr=tcp_socket_server.accept()
 data1=conn.recv(2)
 data2=conn.recv(10)
 print('----->',data1.decode('utf-8'))
 print('----->',data2.decode('utf-8'))
 import time
 # time.sleep(1)
 data2=conn.recv(10)
 print('----->',data2.decode('utf-8'))
 conn.close()
 tcp_socket_server.close()
 ​
 #服务端结果
 #-----> he
 #-----> lloegg
 #-----> 
#客户端
 ​
 import time
 import socket
 BUFSIZE=1024
 ip_port=('127.0.0.1',8080)
 ​
 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 res=s.connect_ex(ip_port)
 ​
 s.send('hello'.encode('utf-8'))
 # time.sleep(1)
 s.send('egg'.encode('utf-8'))
 ​

5.3 发送发的缓存引起的黏包  

#服务端
 ​
 from socket import *
 ip_port=('127.0.0.1',8080)
 ​
 tcp_socket_server=socket()
 tcp_socket_server.bind(ip_port)
 tcp_socket_server.listen(5)
 conn,addr=tcp_socket_server.accept()
 data1=conn.recv(10)
 data2=conn.recv(10)
 print('----->',data1.decode('utf-8'))
 print('----->',data2.decode('utf-8'))
 conn.close()
 ​
 #服务端结果
 #-----> helloegg
 #-----> 
#客户端
 ​
 import socket
 BUFSIZE=1024
 ip_port=('127.0.0.1',8080)
 ​
 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 res=s.connect_ex(ip_port)
 ​
 s.send('hello'.encode('utf-8'))
 s.send('egg'.encode('utf-8'))

6.黏包问题解决方案

#为什么会出现黏包现象???
     #只有在tcp协议中才会出现黏包现象,是因为tcp是面向流的
     #tcp协议在发送数据的过程中还有缓存机制来避免数据丢失
     #在连续发送小数据的时候,以及接收大小不符合的时候容易出现黏包现象
     #本质还是因为在接收数据的时候不知道发送数据的大小
     
 #解决黏包的问题
     #不用struct
         #不要连续发或者连续收,发一次收一次
     #用struct
         #先发送数据长度,转换成四个字节,再发数据,接收方先接收4个字节知道长度再接数据
    
 #struct模块
     #struct.pack("i",4096)  i是int,就是把一个数字转换成固定长度的bytes类型
 ​
     #函数                return   explain
     #pack(fmt,v1,v2…)      string   按照给定的格式(fmt),把数据转换成字符串(字节流),并将该字符串返回.
     #unpack(fmt,v1,v2…)    tuple    按照给定的格式(fmt)解析字节流,并返回解析结果
#服务端
 #用的上传和下载的代码
 #现在不只是发数据长度,还发文件路径,文件名,文件大小
 import json
 import struct
 import socket
 ​
 sk = socket.socket()
 sk.bind(('127.0.0.1',8080))
 sk.listen()
 ​
 conn,addr = sk.accept()
 dic_len = conn.recv(4)  # 4个字节 数字的大小
 dic_len = struct.unpack('i',dic_len)[0]       # [0]拿到之前打包的数字
 content = conn.recv(dic_len).decode('utf-8')  # 根据解包的数字拿到报头,报头和字节数字一起发的,
                                               # 也可以和数据一起发,报头中有数据size                                   
 content_dic = json.loads(content)             # 拿到字典          
 if content_dic['operate'] == 'upload':
     with open(content_dic['filename'],'wb') as f:
         while content_dic['filesize']:
             file = conn.recv(1024)
             f.write(file)
             content_dic['filesize'] -= len(file)
 conn.close()
 sk.close()
 ​
 #客户端
 ​
 import os
 import json
 import struct
 import socket
 ​
 sk = socket.socket()
 sk.connect(('127.0.0.1',8080))
 ​
 def get_filename(file_path):
     filename = os.path.basename(file_path)
     return filename
 ​
 #选择 操作
 operate = ['upload','download']
 for num,opt in enumerate(operate,1):
     print(num,opt)
 num = int(input('请输入您要做的操作序号 : '))
 if num == 1:
     '''上传操作'''
     file_path = input('请输入要上传的文件路径 : ')
     file_size = os.path.getsize(file_path)  # 获取文件大小
     file_name = get_filename(file_path)     # 根据定义的函数拿到文件名
     dic = {'operate': 'upload', 'filename': file_name,'filesize':file_size}
     str_dic = json.dumps(dic).encode('utf-8')
     ret = struct.pack('i', len(str_dic))  # 将字典的大小转换成一个定长(4)的bytes
     sk.send(ret + str_dic)                # 发送字节类型的长度和字典(报头)
     with open(file_path,'rb') as  f:
         while file_size:
             content = f.read(1024)
             sk.send(content)
             file_size -= len(content)
 elif num == 2:
     '''下载操作'''
 sk.close()

7.验证客户端连接的合法性(hashlib)

# 服务端

import os
import socket
import hashlib


def check_client(conn):
    secret_key = b'egg'  # 密钥
    send_str = os.urandom(32)  # 随机生成32位的字节 
    # random() 方法返回随机生成的一个实数,它在[0,1)范围内
    conn.send(send_str)
    md5_obj = hashlib.md5(secret_key)
    md5_obj.update(send_str)  # 把send_str更新到md5_obj里面
    secret_ret = md5_obj.hexdigest()  # hash.digest() 返回摘要,作为二进制数据字符串值                                            
    # hash.hexdigest() 返回摘要,作为十六进制数据字符串值                                          
    if conn.recv(1024).decode('utf-8') == secret_ret:
        print('合法的客户端')
        return True
    else:
        print('非法的客户端')
        return False

​
sk = socket.socket()
sk.bind(('127.0.0.1', 8090))
sk.listen()

conn, addr = sk.accept()
ret = check_client(conn)
while ret:
    inp = input('>>>')
    conn.send(inp.encode('utf-8'))
    msg = conn.recv(1024)
    print(msg.decode('utf-8'))
conn.close()
sk.close()
#客户端
 ​
 import socket
 import hashlib
 sk = socket.socket()
 sk.connect(('127.0.0.1',8090))
 ​
 recv = sk.recv(1024)  #接收到的是服务端随机生成的32位字节
 # 用和server端相同的手法对这个字符串进行摘要
 secret_key = b'egon'  # 密钥和服务端相同才是合法的客户端
 md5_obj = hashlib.md5(secret_key)
 md5_obj.update(recv)
 ret = md5_obj.hexdigest()
 sk.send(ret.encode('utf-8'))
 msg = sk.recv(1024)
 if msg:
     print(msg.decode('utf-8'))
     while True:
         inp = input('>>>')
         sk.send(inp.encode('utf-8'))
         msg = sk.recv(1024)
         print(msg.decode('utf-8'))
 sk.close()

8.验证客户端连接的合法性(hmac)

 #服务端
 ​
 import os
 import socket
 import hmac
 def check_client(conn):
     secret_key = b'egg'  # 密钥
     send_str = os.urandom(32)
     conn.send(send_str)
     hmac_obj = hmac.new(secret_key,send_str)
     secret_ret = hmac_obj.digest()  #bytes类型
     if conn.recv(1024) == secret_ret:
         print('合法的客户端')
         return   True
     else:
         print('非法的客户端')
         return   False
 sk = socket.socket()
 sk.bind(('127.0.0.1',8090))
 sk.listen()
 ​
 conn,addr = sk.accept()
 ret = check_client(conn)
 while ret:
     inp = input('>>>')
     conn.send(inp.encode('utf-8'))
     msg = conn.recv(1024)
     print(msg.decode('utf-8'))
 conn.close()
 sk.close()
 #客户端
 ​
 import socket
 import hmac
 sk = socket.socket()
 sk.connect(('127.0.0.1',8090))
 ​
 recv = sk.recv(1024)
 # 用和server端相同的手法对这个字符串进行摘要
 secret_key = b'egg'  # 密钥
 hmac_obj = hmac.new(secret_key,recv)
 ret = hmac_obj.digest()
 sk.send(ret)
 msg = sk.recv(1024)
 if msg:
     print(msg.decode('utf-8'))
     while True:
         inp = input('>>>')
         sk.send(inp.encode('utf-8'))
         msg = sk.recv(1024)
         print(msg.decode('utf-8'))
 sk.close()

9.socketserver

 # socketserver的介绍

 # socketserver是标准库中的一个高级模块
 # socketserver可以简化创建客户端跟创建服务端的代码
导入模块 import socketserver

 # 初始化控制器类Handler-->Handler是一个继承BaseRequestHandler的类Handler中的
     # handle方法决定了每一个连接过来的操作  
 # 控制器类的类名可以是其他的,不一定是Handler,只要继承了BaseRequestHandler就行
 ​
 init():   # 初始化控制设置,初始化连接套接字,地址,处理实例等信息
 handle():# 定义了如何处理每一个连接
 setup(): # 在handle()之前执行,一般用作设置默认之外的连接配置
 finish():# 在handle()之后执行
 ​
 #变量:
 self.request  # 属性是套接字对象,所以使用self.request.xxxx调用套接字的函数
 self.server  # 包含调用处理程序的实例
 self.client_address  # 是客户端地址信息
 #服务端
 ​
 import socketserver
 ​
 class Myserver(socketserver.BaseRequestHandler):
     def handle(self):
         # 字节类型
         while 1:
             # 针对window系统
             try:
                 print("等待信息")
                 data = self.request.recv(1024)  # 阻塞
                 # 针对linux
                 if len(data) == 0:
                     break
                 if data == b'exit':
                     break
                 response = data + b'SB'
                 self.request.send(response)
             except Exception as e:
                 break
 ​
         self.request.close()
# 1 创建socket对象 2 self.socket.bind()  3 self.socket.listen(5)
server=socketserver.ForkingUDPServer(("127.0.0.1",8899),Myserver)

server.serve_forever()

#============================================================

#客户端

import socket

sk = socket.socket()

sk.connect(('127.0.0.1',8899))

while 1:
    name = input(">>>>:")
    sk.send(name.encode('utf-8')) # 字节

    response = sk.recv(1024) # 字节
    print(response.decode('utf-8'))
原文地址:https://www.cnblogs.com/yzg-14/p/12204038.html