python socket编程

    Socket 是什么?

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

 

    套接字的工作流程

 

     socket()模块函数用法

服务端套接字函数
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() 关闭套接字

    基于TCP的套接字使用

tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

   服务端

 

ss = socket() #创建服务器套接字
ss.bind()      #把地址绑定到套接字
ss.listen()      #监听链接
inf_loop:      #服务器无限循环
    cs = ss.accept() #接受客户端链接
    comm_loop:         #通讯循环
        cs.recv()/cs.send() #对话(接收与发送)
    cs.close()    #关闭客户端套接字
ss.close()        #关闭服务器套接字(可选)

  客户端

cs = socket()    # 创建客户套接字
cs.connect()    # 尝试连接服务器
comm_loop:        # 通讯循环
     cs.send()/cs.recv()    # 对话(发送/接收)
cs.close()            # 关闭客户套接字

   粘包的处理。

tcp协议又称流协议,与udp不同,udp协议是数据报协议,会在每个数据包加报头信息的,所以不会出现粘包现象。

 tcp出现粘包现象有两种情况:

  发送端等缓冲满了才发送信息,发送数据时间较短,数据包较小的数据包,会和在一起发送出去。

 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 

处理粘包的方法

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

举例 

# -*- coding:utf-8 -*-
import socket
import subprocess
import struct

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #默认是tcp连接
phone.bind(('127.0.0.1',8080))  #绑定ip和端口
phone.listen(5)    #指定连接次数
while True:
    a,b=phone.accept()   #监听 a 套接字链接,b 客户端的ip和port
    while True:
        try:
            data=a.recv(1024)  #接收的数据大小
            if not data:break
            #拿到命令执行结果
            obj = subprocess.Popen(data.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout_res=obj.stdout.read()
            stderr_res=obj.stderr.read()
            #先发送报头
            baotou=len(stderr_res)+len(stdout_res)  #统计结果大小
            daxiao=struct.pack('i',baotou)    #变成4个字节的固定长度
            a.send(daxiao)    #发送长度信息

            #发送数据信息
            a.send(stdout_res+stderr_res)  #发送消息
        except AttributeError:
            break


a.close()

phone.close()
服务端
# -*- coding:utf-8 -*-
import socket
import struct
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) #使用的ip端口

while True:
    a=input('>>').strip()
    if not a.strip():continue
    phone.send(a.encode('gbk')) #发送信息
    #先接收报头信息
    data=phone.recv(4)  #接收报头信息
    shuju=struct.unpack('i',data)[0]   #解压报头信息
    #接收真是数据
    d=b''   #收集接收的数据
    recv=0  #收集长度信息
    while recv < shuju:
        cmd=phone.recv(1024)
        d+=cmd
        recv+=len(cmd)

    print(d.decode('gbk'))   #转换成gdk (windows字符集)




phone.close()
客户端

改进版

自定义报头信息

#_*_coding:utf-8_*_
import socket
import subprocess
import struct
import json

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议
phone.bind(('127.0.0.1',8080))
phone.listen(5)

while True:
    conn,client_addr=phone.accept() #(套接字链接,客户端的ip和port)
    print(client_addr)

    while True: #通信循环
        #收消息
            cmd=conn.recv(1024) # 1024最大的限制
            if not cmd:break #针对linux系统

            #执行,拿到执行结果
            obj = subprocess.Popen(cmd.decode('gbk'), shell=True,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE)

            stdout_res=obj.stdout.read()
            stderr_res=obj.stderr.read()

            # 制作报头
            header_dic = {
                'filename': 'a.txt',
                'total_size': len(stdout_res)+len(stderr_res),
                'md5': 'xxxxxxxxx'
            }
            head_json = json.dumps(header_dic)
            head_bytes = head_json.encode('utf-8')




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

            #先发报头
            conn.send(head_bytes)

            #再发真是的数据
            # conn.send(stdout_res+stderr_res)
            conn.send(stdout_res)
            conn.send(stderr_res)
            break

    #挂电话
    conn.close()

#关机
phone.close()
服务端
#_*_coding:utf-8_*_

import socket
import struct
import json

#买手机
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议

#发起电话链接
phone.connect(('127.0.0.1',8080))


while True:
    #发消息
    cmd= input('>>: ').strip()
    if not cmd:continue
    phone.send(cmd.encode('gbk'))

    #先收报头长度
    struct_res=phone.recv(4)
    header_size=struct.unpack('i',struct_res)[0]

    #再收报头
    head_bytes=phone.recv(header_size)
    head_json=head_bytes.decode('utf-8')
    head_dic=json.loads(head_json)
    #print(head_dic)

    #最收消息
    cmd_res=b''
    recv_size=0
    total_size=head_dic['total_size']
    while recv_size < total_size:
        recv_data=phone.recv(1024)
        cmd_res+=recv_data
        recv_size+=len(recv_data)

    print(cmd_res.decode('gbk'))

#关机
phone.close()
客户端

ps:脚本使用到了struct模块 该模块可以把一个类型,如数字,转成固定长度的bytes

 

posted @ 2017-11-14 15:16  刘阔  阅读(154)  评论(0编辑  收藏  举报