第八章:Python网络编程和粘包问题解决(2)

Python面向对象知识

大纲: 

  1、网络基础

  2、socket

  3、套接字工作流

  4、socket编程(tcp)

  5、粘包现象

  6、粘包解决方法

  7、基于udp套接字编程

一、网络基础

1、osi七层协议

互联网协议按照功能不同分为osi七层或tcp/ip五层或tcp/ip四层

应用层 ……………….计算机:应用程序,如FTP,SMTP,HTTP 
表示层 ……………….计算机:编码方式,图像编解码、URL字段传输编码 
会话层 ……………….计算机:建立会话,SESSION认证、断点续传 
传输层 ……………….计算机:进程和端口 
网络层…………………网络:路由器,防火墙、多层交换机 
数据链路层 ………..网络:网卡,网桥,交换机 
物理层…………………网络:中继器,集线器、网线、HU

 

2、tcp三次握手和四次挥手

tcp报文

tcp三次握手和四次挥手

 

 

二、socket

  我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。

  能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

三、套接字工作流程

  先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束

四、socket编程基础(tcp)

  Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页、QQ 聊天、收发 email 等等。要解决网络上两台主机之间的进程通信问题,首先要唯一标识该进程,在 TCP/IP 网络协议中,就是通过 (IP地址,协议,端口号) 三元组来标识进程的,解决了进程标识问题,就有了通信的基础了。

  本文主要介绍使用 Python 进行 TCP Socket 网络编程,假设你已经具有初步的网络知识及 Python 基本语法知识。

TCP 是一种面向连接的传输层协议,TCP Socket 是基于一种 Client-Server 的编程模型,服务端监听客户端的连接请求,一旦建立连接即可以进行传输数据。那么对 TCP Socket 编程的介绍也分为客户端和服务端

1、简单的套接字

服务端: 

# -*- coding: utf-8 -*-
__author__ = 'ShengLeQi'
import  socket
Socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建服务器套接字
Socket.bind(('127.0.0.1',8080)) #把地址绑定到套接字
Socket.listen(5) #监听链接
conn,client_addr=Socket.accept()#接受客户端链接
clent_data=conn.recv(1024) #对话(接收客户端消息)
conn.send(clent_data.upper())#对话(发送端消息)
conn.close()#关闭客户端套接字
Socket.close()#关闭服务器套接字 

客户端: 

# -*- coding: utf-8 -*-
__author__ = 'ShengLeQi'
import  socket
Socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #创建服务器套接字
Socket.connect(('127.0.0.1',8080)) #客户端发起连接
Socket.send('hello'.encode('utf-8')) #客户端发送数据
server_data=Socket.recv(1024)
print(server_data)

 

2、循环连接和循环通信

服务端:

# -*- coding: utf-8 -*-
__author__ = 'ShengLeQi'
import  socket
#创建服务器套接字
Socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#把地址绑定到套接字
Socket.bind(('127.0.0.1',8080))
#监听链接
Socket.listen(5)
while True:  #链接循环
    conn,client_addr=Socket.accept()#接受客户端链接
    while True:#收发消息
        try:
            client_data=conn.recv(1024)
            if not client_data:break  #如果收到空的时候重新接受
            conn.send(client_data.upper())
        except Exception:
            break
    conn.close()#关闭客户端套接字
Socket.close()#关闭服务器套接字
server.py

客户端:

# -*- coding: utf-8 -*-
__author__ = 'ShengLeQi'
import  socket
#创建服务器套接字
Socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #客户端发起连接
Socket.connect(('127.0.0.1',8080))
while True: #客户端发送数据
    msg=input('>>').strip()
    if not  msg: continue
    Socket.send(msg.encode('utf-8'))
    server_data=Socket.recv(1024)
    print(server_data.decode('utf-8'))
client.py

3模拟ssh远程命令

服务端:

# -*- coding: utf-8 -*-
__author__ = 'ShengLeQi'
import  socket
import  subprocess
#创建服务器套接字
Socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
Socket.bind(('127.0.0.1',8080)) #把地址绑定到套接字
Socket.listen(5)#监听链接
while True:  #链接循环
    conn,client_addr=Socket.accept()#接受客户端链接
    while True:#收发消息
        try:
            cmd=conn.recv(1024)
            if not cmd:break  #如果收到空的时候重新接受
            res=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE
                             )
            stdout=res.stdout.read()
            stderr=res.stderr.read()
            conn.send(stdout+stderr)
        except Exception:
            break
    conn.close()#关闭客户端套接字
Socket.close()#关闭服务器套接字
View Code

客户端:

import  socket
#创建服务器套接字
Socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#客户端发起连接
Socket.connect(('127.0.0.1',8080))
#客户端发送数据
while True:
    msg=input('>>').strip()
    if not  msg: continue
    Socket.send(msg.encode('utf-8'))
    server_data=Socket.recv(1024)
    print(server_data.decode('gbk'))
client.py

五、粘包现象

让我们基于tcp先制作一个远程执行命令的程序(1:执行错误命令 2:执行ls 3:执行ifconfig)

注意注意注意:

res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)

的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码

   粘包原因:因为服务端和客户端都设置了1024字节,当执行的命令返回的结果,如果结果的字符串大小大于1024字节,那么只会一次发送1024字节,当第二次执行命令的时候,会把第一次没有发送的结果继续发送过去,

六、粘包的解决方法

为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据

struct模块 

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

服务端:

# -*- coding: utf-8 -*-
__author__ = 'ShengLeQi'
import  socket
import  subprocess
import  struct,json

#创建服务器套接字
Socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#把地址绑定到套接字
Socket.bind(('127.0.0.1',8080))

Socket.listen(5)#监听链接

while True:  #链接循环
    conn,client_addr=Socket.accept()#接受客户端链接
    while True:#收发消息
        try:
            cmd=conn.recv(1024)
            if not cmd:break  #如果收到空的时候重新接受
            res=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE
                             )
            stdout=res.stdout.read()
            stderr=res.stderr.read()

            #制作报头
            header_dic={'total_size':len(stdout)+len(stderr),'md5':None}
            header_json=json.dumps(header_dic)
            header_bytes=header_json.encode('utf-8')

            #1、先发报头长度
            conn.send(struct.pack('i',len(header_bytes)))

            #2、在发报头
            conn.send(header_bytes)

            #3、再发数据
            conn.send(stdout)
            conn.send(stderr)

        except Exception:
            break

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

Socket.close()#关闭服务器套接字
server.py

客户端:

# -*- coding: utf-8 -*-
__author__ = 'ShengLeQi'

import  socket
import  struct
import  json

#创建服务器套接字
Socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#客户端发起连接
Socket.connect(('127.0.0.1',8080))

#客户端发送数据
while True:
    msg=input('>>').strip()
    if not  msg: continue
    Socket.send(msg.encode('utf-8'))

    #1、先接受报头的长度
    struct_size=Socket.recv(4)
    header_size = struct.unpack('i', struct_size)[0]

    #2、在接受报头
    header_bytes=Socket.recv(header_size)
    head_json=header_bytes.decode('utf-8')
    head_dic=json.loads(head_json)

    total_size = head_dic['total_size']

    #3、在接受数据
    recv_size=0
    data=b''
    while recv_size <total_size:
        recv_data=Socket.recv(1024)
        recv_size+=len(recv_data)
        data+=recv_data
    print(data.decode('gbk'))
client.py

 

七、基于udp协议的套接字

udp是无链接的,先启动哪一端都不会报错

正常接收:

服务端:

from  socket import  *
import socketserver 
udp_server=socket(AF_INET,SOCK_DGRAM) #创建一个服务器的套接字
udp_server.bind(('127.0.0.1',8080))  #绑定服务器套接字
while True:  #服务器无限循环
    data1,client_addr=udp_server.recvfrom(1024)  #接受
    print(data1.decode('utf-8'))
    print(client_addr)

客户端:

from  socket import  *
udp_client=socket(AF_INET,SOCK_DGRAM)  #创建客户套接字
while True:
    msg=input('>>').strip()
    udp_client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))  #发送数据

并发情况下udp 

服务端:

from  socket import  *
import  socketserver
class Myudpserver(socketserver.BaseRequestHandler):
    def handle(self):
        print(self.request)
        self.request[1].sendto(self.request[0].upper(),self.client_address)
if __name__=='__main__':
    s=socketserver.ThreadingUDPServer(('127.0.0.1',8080),Myudpserver)
    s.serve_forever()
#服务端

客户端:

from  socket import  *
udp_client=socket(AF_INET,SOCK_DGRAM)
while True:
    msg=input('>>').strip()
    udp_client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
    data,udp_server=udp_client.recvfrom(1024)
    print(data.decode('utf-8'))
#客户端

 

posted @ 2017-08-25 10:55  ShengLeQi  阅读(349)  评论(0)    收藏  举报