公海赌船备用网址俺们学习socket就是为了做到C/S架构的开销。我们上学socket就是以成功C/S架构的出。

平、客户端/服务器架设

  网络中四处都应有了C/S架构,我们学习socket就是为成功C/S架构的开发。

同、客户端/服务器架设

  网络中处处都当了C/S架构,我们上socket就是为完成C/S架构的支付。

第二、scoket与网络协议

  一经想要兑现网络通信我们用针对tcpip,http等多网络文化来于深刻的习后才发出这般的力,但是对于咱们先后开发程序员来说是同样桩永的时刻,所以就是时有发生矣打包比较好的socket来援助我们解决这些题目,使得我们的关注点不再是烂的网络协议等题材。socket已经也咱封装好了,我们特需要遵循socket的规定去编程,写来底程序自然就是是准tcp/udp标准的。

  socket是应用层与TCP/IP协议族通信的中等软件抽象层,它是同组接口。在设计模式中,Socket其实就算是一个门面模式,它将纷繁的TCP/IP协议族隐藏在socket接口后面,对用户来说,一组大概的接口就是整个,让Socket去组织数量,以符合指定的协议。

公海赌船备用网址 1

  简单来说我们得拿socket说成ip+端口,所以标识了互联网遭受绝无仅有之一个应用程序。

亚、scoket与网络协议

  倘若想使促成网络通信我们得对tcpip,http等诸多网络知识有比厚的读下才发如此的能力,但是对于我们先后支付程序员来说是同等码永的光阴,所以即便来了打包比较好之socket来帮忙咱缓解这些题目,使得我们的关注点不再是无规律的网络协议等问题。socket已经为我们封装好了,我们就待依照socket的确定去编程,写来底次序自然就是按照tcp/udp标准的。

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

公海赌船备用网址 2

  简单的话我们可以把socket说成ip+端口,所以标识了互联网遭受绝无仅有的一个应用程序。

三、套接字

  模仿接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的
Unix,即人们所说之 BSD Unix。一开始,套接字被规划用当同
一大主机及大都独应用程序之间的简报。套接字有一定量种植(或者叫做有一定量单种族),分别是基于文件型的以及因网络型的。

  基于文件类型的模仿接字家族:AF_UNIX

  unix一切都文件,基于文件之套接字调用的便是脚的文件系统来取数据,两个拟接字进程运行在同一机器,可以通过访问和一个文件系统间接完成通信

  基于网络项目的依样画葫芦接字家族:AF_INET

  还有AF_INET6叫用于ipv6,还有一些其它的地方家族,AF_INET是行使最广的一个,python支持大多种地点家族,但是由于我们无非关注网络编程,所以大部分辰光我么只下AF_INET

  套接字函数:

  1)socket()模块

import socket
获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

  使用 ‘from socket import *’,我们就算将 socket
模块里的富有属性都带来顶我们的命名空间里了,这样能
大幅减短我们的代码。例如tcpSock = socket(AF_INET, SOCK_STREAM)

2)服务端套接字函数

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

3)客户端套接字函数

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

4)公共用途的套接字函数

recv()            接收TCP数据
send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
recvfrom()        接收UDP数据
sendto()          发送UDP数据
getpeername()     连接到当前套接字的远端的地址
getsockname()     当前套接字的地址
getsockopt()      返回指定套接字的参数
setsockopt()      设置指定套接字的参数
close()           关闭套接字

5)面向锁的套接字函数

setblocking()     设置套接字的阻塞与非阻塞模式
settimeout()      设置阻塞套接字操作的超时时间
gettimeout()      得到阻塞套接字操作的超时时间

6)面向文件之套接字函数

fileno()          套接字的文件描述符
makefile()        创建一个与该套接字相关的文件

三、套接字

  宪章接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的
Unix,即人们所说的 BSD Unix。一开始,套接字被设计用当跟
一雅主机上大多单应用程序之间的通讯。套接字有零星种植(或者叫做有有限独种族),分别是依据文件型的同基于网络型的。

  基于文件类型的仿接字家族:AF_UNIX

  unix一切都文件,基于文件的套接字调用的就是脚的文件系统来取数据,两独拟接字进程运行在同一机器,可以由此访和一个文件系统间接完成通信

  基于网络型的模拟接字家族:AF_INET

  还有AF_INET6吃用来ipv6,还有有任何的地方家族,AF_INET是行使最广大的一个,python支持大多种地方家族,但是由于我们只关心网络编程,所以大部分时节我么只利用AF_INET

  套接字函数:

  1)socket()模块

import socket
获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

  使用 ‘from socket import *’,我们即便把 socket
模块里的所有属性都带顶我们的命名空间里了,这样能
大幅减短我们的代码。例如tcpSock = socket(AF_INET, SOCK_STREAM)

2)服务端套接字函数

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

3)客户端套接字函数

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

4)公共用途的套接字函数

recv()            接收TCP数据
send()            发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
sendall()         发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
recvfrom()        接收UDP数据
sendto()          发送UDP数据
getpeername()     连接到当前套接字的远端的地址
getsockname()     当前套接字的地址
getsockopt()      返回指定套接字的参数
setsockopt()      设置指定套接字的参数
close()           关闭套接字

5)面向锁之套接字函数

setblocking()     设置套接字的阻塞与非阻塞模式
settimeout()      设置阻塞套接字操作的超时时间
gettimeout()      得到阻塞套接字操作的超时时间

6)面向文件之套接字函数

fileno()          套接字的文件描述符
makefile()        创建一个与该套接字相关的文件

季、基于tcp的套接字代码实现

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

  server:

公海赌船备用网址 3公海赌船备用网址 4

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

View Code

  client:

公海赌船备用网址 5公海赌船备用网址 6

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

View Code

  socket通信和打电话的法门大相似:

  server:

公海赌船备用网址 7公海赌船备用网址 8

#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8080)#电话卡
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
s.bind(ip_port) #手机插卡
s.listen(5)     #手机待机,监听最多五个

while True:                         #新增接收链接循环,可以不停的接电话
    conn,addr=s.accept()            #手机接电话
    print('接到来自%s的电话' %addr[0])
    while True:                         #新增通信循环,可以不断的通信,收发消息
        msg=conn.recv(BUFSIZE)             #听消息,听话

        # if len(msg) == 0:break        #如果不加,那么正在链接的客户端突然断开,recv便不再阻塞,死循环发生

        print(msg,type(msg))

        conn.send(msg.upper())          #发消息,说话

    conn.close()                    #挂电话

s.close()                       #手机关机

server

  client:

公海赌船备用网址 9公海赌船备用网址 10

#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8081)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect_ex(ip_port)           #拨电话

while True:                             #新增通信循环,客户端可以不断发收消息
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    s.send(msg.encode('utf-8'))         #发消息,说话(只能发送字节类型)

    feedback=s.recv(BUFSIZE)                           #收消息,听话
    print(feedback.decode('utf-8'))

s.close()                                       #挂电话

client

  以操作的长河被还开服务端可能会见冒出OSError,地址早就当采用了,出现这种问题之缘由是以根据tcp四不成挥手并不曾完,所以端口仍于霸占,所以用在一长条socket配置更行使ip和端口。

公海赌船备用网址 11公海赌船备用网址 12

phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))

View Code

  我们还足以效仿ssh实现长途模拟命令:

公海赌船备用网址 13公海赌船备用网址 14

import socket
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8080))
phone.listen(5)

print('starting...')
while True:
    conn,client_addr=phone.accept()

    while True:
        try:
            cmd=conn.recv(1024)
            #if not cmd:break #针对linux
            #执行cmd命令,拿到cmd的结果,结果应该是bytes类型
            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() #挂电话
phone.close() #关机

server

公海赌船备用网址 15公海赌船备用网址 16

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
phone.connect(('127.0.0.1',8080))

while True:
    cmd=input('>>: ').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))
    cmd_res=phone.recv(1024)
    print(cmd_res.decode('gbk'))
phone.close()

client

  这里我们因而到了subprocess模块,允许你错过创造一个初的进程被那实践另外的次,并与她进行通信,获取标准的输入、标准输出、标准错误和返回码等。

  subprocess模块中定义了一个Popen类,通过它好来创造进程,并同该开展复杂的互动。subprocess模块的结果的编码是为手上所于的系统吧依照的,如果是windows,那么res.stdout.read()读出的哪怕是GBK编码的,在接收端需要为此GBK解码。

  他的init函数是这样的:

__init__(self, args, bufsize=0, executable=None, 
stdin=None, stdout=None, stderr=None, preexec_fn=None, 
close_fds=False, shell=False, cwd=None, env=None, 
universal_newlines=False, startupinfo=None, 
creationflags=0)

  args:必须是一个字符串或者序列类型,用于指定进程的可执行文件及其参数。如果是一个序列类型参数,则行的首先独因素通常都须是一个可执行文件的路径。当然为足以使用executeable参数来指定可执行文件的路。

  stdin,stdout,stderr:分别代表程序的专业输入、标准输出、标准错误。有效的价值好是PIPE,存在的公文描述符,存在的文件对象或None,如果也None需从大进程继续过来,stdout可以是PIPE,表示对进程创造一个管道,stderr可以是STDOUT,表示专业错误数据应从应用程序中抓获并当规范输出流stdout的文本句柄。

  shell:如果是参数为安装为True,程序用通过shell来推行。 
  env:它讲述的是子进程的环境变量。如果也None,子进程的环境变量将于父亲进程继续而来。

res = subprocess.Popen(r'dir', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

季、基于tcp的套接字代码实现

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

  server:

公海赌船备用网址 17公海赌船备用网址 18

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

View Code

  client:

公海赌船备用网址 19公海赌船备用网址 20

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

View Code

  socket通信和打电话的措施大一般:

  server:

公海赌船备用网址 21公海赌船备用网址 22

#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8080)#电话卡
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
s.bind(ip_port) #手机插卡
s.listen(5)     #手机待机,监听最多五个

while True:                         #新增接收链接循环,可以不停的接电话
    conn,addr=s.accept()            #手机接电话
    print('接到来自%s的电话' %addr[0])
    while True:                         #新增通信循环,可以不断的通信,收发消息
        msg=conn.recv(BUFSIZE)             #听消息,听话

        # if len(msg) == 0:break        #如果不加,那么正在链接的客户端突然断开,recv便不再阻塞,死循环发生

        print(msg,type(msg))

        conn.send(msg.upper())          #发消息,说话

    conn.close()                    #挂电话

s.close()                       #手机关机

server

  client:

公海赌船备用网址 23公海赌船备用网址 24

#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8081)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect_ex(ip_port)           #拨电话

while True:                             #新增通信循环,客户端可以不断发收消息
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    s.send(msg.encode('utf-8'))         #发消息,说话(只能发送字节类型)

    feedback=s.recv(BUFSIZE)                           #收消息,听话
    print(feedback.decode('utf-8'))

s.close()                                       #挂电话

client

  于操作的长河遭到再次开服务端可能会见起OSError,地址已经以运用了,出现这种题材的故是以根据tcp四糟挥手并不曾收,所以端口仍给占用,所以待在一漫长socket配置更采用ip和端口。

公海赌船备用网址 25公海赌船备用网址 26

phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))

View Code

  我们尚足以学ssh实现远程模拟命令:

公海赌船备用网址 27公海赌船备用网址 28

import socket
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',8080))
phone.listen(5)

print('starting...')
while True:
    conn,client_addr=phone.accept()

    while True:
        try:
            cmd=conn.recv(1024)
            #if not cmd:break #针对linux
            #执行cmd命令,拿到cmd的结果,结果应该是bytes类型
            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() #挂电话
phone.close() #关机

server

公海赌船备用网址 29公海赌船备用网址 30

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 
phone.connect(('127.0.0.1',8080))

while True:
    cmd=input('>>: ').strip()
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))
    cmd_res=phone.recv(1024)
    print(cmd_res.decode('gbk'))
phone.close()

client

  这里我们用到了subprocess模块,允许而去创造一个新的进程被该行另外的主次,并跟她进行通信,获取标准的输入、标准输出、标准错误以及返回码等。

  subprocess模块中定义了一个Popen类,通过她好来创造过程,并与那展开复杂的并行。subprocess模块的结果的编码是为目前所当的系啊遵循的,如果是windows,那么res.stdout.read()读出的虽是GBK编码的,在接收端需要用GBK解码。

  他的init函数是这么的:

__init__(self, args, bufsize=0, executable=None, 
stdin=None, stdout=None, stderr=None, preexec_fn=None, 
close_fds=False, shell=False, cwd=None, env=None, 
universal_newlines=False, startupinfo=None, 
creationflags=0)

  args:必须是一个字符串或者序列类型,用于指定进程的可执行文件及其参数。如果是一个行类型参数,则排的第一个元素通常还必须是一个可执行文件的路。当然也得以使用executeable参数来指定可执行文件的门道。

  stdin,stdout,stderr:分别表示程序的标准输入、标准输出、标准错误。有效的值好是PIPE,存在的文书描述符,存在的文书对象或None,如果为None需从翁进程继续过来,stdout可以是PIPE,表示对进程创造一个管道,stderr可以是STDOUT,表示业内错误数据应打应用程序中捕获并作标准输出流stdout的公文句柄。

  shell:如果这参数为装置也True,程序用经过shell来推行。 
  env:它讲述的是子进程的环境变量。如果也None,子进程的环境变量将起翁进程继续而来。

res = subprocess.Popen(r'dir', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

五、基于udp的套接字

  udp是随便链接的,先启动哪一样端都非会见报错。

ss = socket()   #创建一个服务器的套接字
ss.bind()       #绑定服务器套接字
while True :       #服务器无限循环
    cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
ss.close()                         # 关闭服务器套接字

cs = socket()   # 创建客户套接字
while True :      # 通讯循环
    cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
cs.close()                      # 关闭客户套接字

  基于udp套接字的简短示例

公海赌船备用网址 31公海赌船备用网址 32

import socket
ip_port=('127.0.0.1',9000)
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

udp_server_client.bind(ip_port)

while True:
    msg,addr=udp_server_client.recvfrom(1024)
    print(msg,addr)

    udp_server_client.sendto(msg.upper(),addr)

udpserver

公海赌船备用网址 33公海赌船备用网址 34

from socket import *

udp_cs=socket(AF_INET,SOCK_DGRAM)

while True:
    msg=input('>>: ').strip()
    if not msg:continue
    udp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',9000))
    msg,addr=udp_cs.recvfrom(1024)
    print(msg.decode('utf-8'),addr)

udpclient

  qq聊天就是是基于udp完成的,由于udp无连接,所以可以又多个客户端去跟服务端通信。

公海赌船备用网址 35公海赌船备用网址 36

from socket import *

udp_ss=socket(AF_INET,SOCK_DGRAM)
udp_ss.bind(('127.0.0.1',8081))

while True:
    msg,addr=udp_ss.recvfrom(1024)
    print('来自[%s]的一条消息:%s' %(addr,msg.decode('utf-8')))
    msg_b=input('回复消息: ').strip()
    udp_ss.sendto(msg_b.encode('utf-8'),addr)

qqserver

公海赌船备用网址 37公海赌船备用网址 38

from socket import *

udp_cs = socket(AF_INET,SOCK_DGRAM)

while True :
    msg = input('请输入消息,回车发送: ').strip()
    if msg == 'quit' : break
    if not msg : continue
    udp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',8081))

    back_msg,addr = udp_cs.recvfrom(1024)
    print('来自[%s]的一条消息:%s' %(addr,back_msg.decode('utf-8')))

udp_cs.close()

qqclient

  client可以被多只。

五、基于udp的套接字

  udp是凭链接的,先启动哪一样端都不见面报错。

ss = socket()   #创建一个服务器的套接字
ss.bind()       #绑定服务器套接字
while True :       #服务器无限循环
    cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
ss.close()                         # 关闭服务器套接字

cs = socket()   # 创建客户套接字
while True :      # 通讯循环
    cs.sendto()/cs.recvfrom()   # 对话(发送/接收)
cs.close()                      # 关闭客户套接字

  基于udp套接字的简约示例

公海赌船备用网址 39公海赌船备用网址 40

import socket
ip_port=('127.0.0.1',9000)
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

udp_server_client.bind(ip_port)

while True:
    msg,addr=udp_server_client.recvfrom(1024)
    print(msg,addr)

    udp_server_client.sendto(msg.upper(),addr)

udpserver

公海赌船备用网址 41公海赌船备用网址 42

from socket import *

udp_cs=socket(AF_INET,SOCK_DGRAM)

while True:
    msg=input('>>: ').strip()
    if not msg:continue
    udp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',9000))
    msg,addr=udp_cs.recvfrom(1024)
    print(msg.decode('utf-8'),addr)

udpclient

  qq聊天就是是基于udp完成的,由于udp无连接,所以可以而且多单客户端去同服务端通信。

公海赌船备用网址 43公海赌船备用网址 44

from socket import *

udp_ss=socket(AF_INET,SOCK_DGRAM)
udp_ss.bind(('127.0.0.1',8081))

while True:
    msg,addr=udp_ss.recvfrom(1024)
    print('来自[%s]的一条消息:%s' %(addr,msg.decode('utf-8')))
    msg_b=input('回复消息: ').strip()
    udp_ss.sendto(msg_b.encode('utf-8'),addr)

qqserver

公海赌船备用网址 45公海赌船备用网址 46

from socket import *

udp_cs = socket(AF_INET,SOCK_DGRAM)

while True :
    msg = input('请输入消息,回车发送: ').strip()
    if msg == 'quit' : break
    if not msg : continue
    udp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',8081))

    back_msg,addr = udp_cs.recvfrom(1024)
    print('来自[%s]的一条消息:%s' %(addr,back_msg.decode('utf-8')))

udp_cs.close()

qqclient

  client可以开多独。

六、粘包

  粘包现象就会发生在tcp的链接过程中,udp是未见面产生粘包现象之(UDP是面向消息之合计,每个UDP段都是如出一辙长达信息,应用程序必须以信吧单位领到数额,不可知平等不行提取任意字节的数据)。

公海赌船备用网址 47

  上图是socket收发信息的法则图,TCP协议是面向流的商谈,应用程序得到的数额是一个完全数据流(stream),一长达信息产生稍许字节对于应用程序是不可见的,消息从哪起交哪了,应用程序一无所知,这就算导致出现粘包问题了。

  粘包问题本质就是是为接收方不清楚消息里的界限,不明了一次性领取多少字节的多少所造成的。

   从根数据报文来拘禁:tcp收发两端(客户端与服务器端)都要有各个成对的socket,因此,发送端为了将大半个发朝接收端的管,更管用之发到对方,使用了优化措施(Nagle算法),将反复间距较小且数据量小之数码,合并成一个那个的数据块,然后进行封包。这样,接收端,就难上加难分辨出来了。而udp支持的凡一样针对性几近之模式,所以吸收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个抵达的UDP包,在每个UDP包中尽管发出矣消息头(消息来源地址,端口等信息),这样,对于接收端来说,就易进行分处理了。

  因为这些差距,tcp收发信息还非能够啊空,在客户端与服务端都设填补加空信处理体制,防止程序卡死。udp不是基于流的数据报,即使你输入的内容是空发出去的多少报或由包头证明自己的尺寸是0。

  udp虽然非糊包可呢起异的老毛病,大家都被他不得因传输,udp的recvfrom是死的,一个recvfrom(x)必须对唯一一个sendinto(y),收收了x个字节的数就完事,若是y>x数据就是少,这代表udp根本不会见粘包,但是会扔数据,不可靠
 tcp的协商数不会见丢,没有完完包,下次收取,会延续上次持续接收,己端总是以收受ack时才会破缓冲区内容。数据是可靠的,但是会粘包。

  粘包的星星点点种情形:

  1.当殡葬端发送数据的工夫距离很不够,数据我很小会合到一起产生粘包现象。

  2.客户端发送的数较充分跨越了劳动端一不善好吸纳的限制,所以服务端只收了平粗片,服务端下次再次收之下还是打缓冲区拿上次留的数据,产生粘包。大之数量报在殡葬端的缓冲区长度逾网卡的顶酷传输数据单元,tcp会将数据拆分成几独数据包再发送出。

六、粘包

  粘包现象只见面起在tcp的链接过程中,udp是休会见来粘包现象的(UDP是面向消息之商事,每个UDP段都是同等漫长消息,应用程序必须坐信息啊单位领到数据,不可知一如既往不良提取任意字节的数额)。

公海赌船备用网址 48

  上图是socket收发信息的法则图,TCP协议是面向流的说道,应用程序得到的数是一个整数据流(stream),一长消息发出多少字节对于应用程序是不可见的,消息从哪开始交哪了,应用程序一无所知,这就招致出现粘包问题了。

  粘包问题本质就是是盖接收方不亮消息里的限,不知情一次性领取多少字节的数目所造成的。

   从底层数据报文来拘禁:tcp收发两端(客户端与服务器端)都使发各个成对之socket,因此,发送端为了以多单发作于接收端的管教,更实用的发到对方,使用了优化措施(Nagle算法),将反复区间较小且数据量小的数量,合并成为一个挺之数据块,然后开展封包。这样,接收端,就寸步难行分辨出了。而udp支持之是均等针对性几近的模式,所以接受端的skbuff(套接字缓冲区)采用了链式结构来记录每一个至的UDP包,在每个UDP包中虽时有发生矣消息头(消息来源地址,端口等消息),这样,对于接收端来说,就便于开展分处理了。

  因为这些差距,tcp收发信息都非能够为空,在客户端和服务端都使补偿加空音处理机制,防止程序卡死。udp不是基于流的数据报,即使你输入的情节是空发出去的数目报或者由包头证明自己之长短是0。

  udp虽然非粘包可也发他的症结,大家还为他不行因传输,udp的recvfrom是死的,一个recvfrom(x)必须对唯一一个sendinto(y),收了了x个字节的多寡就是成功,若是y>x数据就不见,这表示udp根本不见面粘包,但是会丢弃数据,不可靠
 tcp的商谈数未会见丢掉,没有完结完包,下次吸收,会持续上次蝉联接收,己端总是以吸纳ack时才见面去掉缓冲区内容。数据是牢靠的,但是会粘包。

  粘包的少种情景:

  1.在殡葬端发送数据的时光间隔很缺乏,数据本身非常小会合到一起出粘包现象。

  2.客户端发送的多寡较坏跨越了劳动端一不好好接过的限定,所以服务端只得了了一致聊一些,服务端下次再结的时节还是于缓冲区将上次留的数额,产生粘包。大的数额报在发送端的缓冲区长度超过网卡的最为充分传输数据单元,tcp会将数据拆分成几个数据包再发送出。

  如何化解粘包问题?

  客户端每次都拿温馨之长短告诉服务端,这样好完成不粘包。

公海赌船备用网址 49公海赌船备用网址 50

#_*_coding:utf-8_*_
import socket,subprocess
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

s.bind(ip_port)
s.listen(5)

while True:
    conn,addr=s.accept()
    print('客户端',addr)
    while True:
        msg=conn.recv(1024)
        if not msg:break
        res=subprocess.Popen(msg.decode('utf-8'),shell=True,\
                            stdin=subprocess.PIPE,\
                         stderr=subprocess.PIPE,\
                         stdout=subprocess.PIPE)
        err=res.stderr.read()
        if err:
            ret=err
        else:
            ret=res.stdout.read()
        data_length=len(ret)
        conn.send(str(data_length).encode('utf-8'))
        data=conn.recv(1024).decode('utf-8')
        if data == 'recv_ready':
            conn.sendall(ret)
    conn.close()

View Code

公海赌船备用网址 51公海赌船备用网址 52

import socket,time
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))
    length=int(s.recv(1024).decode('utf-8'))
    s.send('recv_ready'.encode('utf-8'))
    send_size=0
    recv_size=0
    data=b''
    while recv_size < length:
        data+=s.recv(1024)
        recv_size+=len(data)


    print(data.decode('utf-8'))

View Code

  这种艺术单是一样栽缓解问题的方式,实际上并无会见这样做,程序的运作速度多快给网络传输速度,所以当发送一截字节前,先用send去发送该字节约流长,这种方式会推广网络延迟带来的属性损耗。这里用来帮忙我们再好的明白粘包的题目。

  在这个基础及,我们可以设想将协调的长度等信息,写及报头头部,这样接收端拆开报头就会明白长度,就未会见出粘包的观了。

  我们先要认识一下struct模块。

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

公海赌船备用网址 53

公海赌船备用网址 54公海赌船备用网址 55

from socket import *
import subprocess
import struct
ss=socket(AF_INET,SOCK_STREAM)
ss.bind(('127.0.0.1',8082)) 
ss.listen(5)

print('starting...')
while True:
    conn,addr=ss.accept()
    print('-------->',conn,addr)

    while True:
        try:
            cmd=conn.recv(1024)
            res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout=res.stdout.read()
            stderr=res.stderr.read()

            #先发报头(转成固定长度的bytes类型)
            header = struct.pack('i',len(stdout)+len(stderr))
            conn.send(header)
            #再发送命令的结果
            conn.send(stdout)
            conn.send(stderr)
        except Exception:
            break
    conn.close()
ss.close()

客户端

公海赌船备用网址 56公海赌船备用网址 57

from socket import *
import struct
cs=socket(AF_INET,SOCK_STREAM)
cs.connect(('127.0.0.1',8082))

while True:
    cmd=input('>>: ').strip()
    if not cmd:continue

    cs.send(cmd.encode('utf-8'))
    #先收报头
    header_struct=cs.recv(4)
    unpack_res = struct.unpack('i', header_struct)
    total_size=unpack_res[0]

    #再收数据
    recv_size=0 #10241=10240+1
    total_data=b''
    while recv_size < total_size:
        recv_data=cs.recv(1024)
        recv_size+=len(recv_data)
        total_data+=recv_data
    print(total_data.decode('gbk'))
cs.close()

服务端

  struct的i模式:

struct.pack('i',11111111)
#struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围

  struct.pack用于将Python的价值根据格式符,转换为字符串(因为Python中尚无字节(Byte)类型)。它的函数原型为:struct.unpack(fmt,
string)。

  struct.unpack做的干活刚刚和struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt,
string),该函数返回一个元组。

公海赌船备用网址 58公海赌船备用网址 59

import json,struct
#假设通过客户端上传1T:1073741824000的文件a.txt

#为避免粘包,必须自定制报头
header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值

#为了该报头能传送,需要序列化并且转为bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输

#为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度

#客户端开始发送
conn.send(head_len_bytes) #先发报头的长度,4个bytes
conn.send(head_bytes) #再发报头的字节格式
conn.sendall(文件内容) #然后发真实内容的字节格式

#服务端开始接收
head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式
x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度

head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式
header=json.loads(json.dumps(header)) #提取报头

#最后根据报头的内容提取真实的数据,比如
real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)

View Code

  使用于定制报头的法子来化解粘包问题。

公海赌船备用网址 60公海赌船备用网址 61

import socket,struct,json
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 
phone.bind(('127.0.0.1',8080))

phone.listen(5)

while True:
    conn,addr=phone.accept()
    while True:
        cmd=conn.recv(1024)
        if not cmd:break
        print('cmd: %s' %cmd)

        res=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        err=res.stderr.read()
        print(err)
        if err:
            back_msg=err
        else:
            back_msg=res.stdout.read()


        conn.send(struct.pack('i',len(back_msg))) #先发back_msg的长度
        conn.sendall(back_msg) #在发真实的内容

    conn.close()

劳端起定制报头

公海赌船备用网址 62公海赌船备用网址 63

import socket,time,struct

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))

    l=s.recv(4)
    x=struct.unpack('i',l)[0]
    print(type(x),x)
    # print(struct.unpack('I',l))
    r_s=0
    data=b''
    while r_s < x:
        r_d=s.recv(1024)
        data+=r_d
        r_s+=len(r_d)

    # print(data.decode('utf-8'))
    print(data.decode('gbk')) #windows默认gbk编码

客户端起定制报头

  当然我们的报头可以增长更多信息。

  我们管报头做成字典,字典里含有将要发送的真实数据的详细信息,然后json序列化,然后据此struck将序列化后底数长度从包改成4独字节(4个自己够用了)

  发送时优先发报头长度,再编码报头内容然后发送,最后发真实内容。

  接收时先用报头长度,用struct取出来,根据取出的尺寸收取报头内容,然后解码,反序列化,从反序列化的结果吃取出待取数据的详细信息,然后去得到真实的多少内容。

公海赌船备用网址 64公海赌船备用网址 65

from socket import *
import subprocess
import struct
import json
ss = socket(AF_INET,SOCK_STREAM)
ss.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
ss.bind(('127.0.0.1',8082))
ss.listen(5)

print('starting...')
while True : #链接循环
    conn,addr = ss.accept() #链接,客户的的ip和端口组成的元组
    print('-------->',conn,addr)

    #收,发消息
    while True :#通信循环
        try :
            cmd = conn.recv(1024)
            res = subprocess.Popen(cmd.decode('utf-8'), shell = True,
                                   stdout = subprocess.PIPE,
                                   stderr = subprocess.PIPE)
            stdout = res.stdout.read()
            stderr = res.stderr.read()
            #制作报头
            h_dic = {
                'total_size': len(stdout) + len(stderr),
                'filename': None,
                'md5': None}

            h_json = json.dumps(h_dic)
            h_bytes = h_json.encode('utf-8')
            #发送阶段
            #先发报头长度
            conn.send(struct.pack('i',len(h_bytes)))
            #再发报头
            conn.send(h_bytes)

            #最后发送命令的结果
            conn.send(stdout)
            conn.send(stderr)
        except Exception :
            break
    conn.close()
ss.close()

json序列化报头server

公海赌船备用网址 66公海赌船备用网址 67

from socket import *
import struct
import json
cs = socket(AF_INET,SOCK_STREAM) #买手机
cs.connect(('127.0.0.1',8082)) #绑定手机卡

#发,收消息
while True :
    cmd = input('>>: ').strip()
    if not cmd : continue

    cs.send(cmd.encode('utf-8'))
    #先收报头的长度
    h_len = struct.unpack('i',cs.recv(4))[0]

    #再收报头
    h_bytes = cs.recv(h_len)
    h_json = h_bytes.decode('utf-8')
    h_dic = json.loads(h_json)
    total_size = h_dic['total_size']

    #最后收数据
    recv_size = 0
    total_data = b''
    while recv_size < total_size :
        recv_data = cs.recv(1024)
        recv_size += len(recv_data)
        total_data += recv_data
    print(total_data.decode('gbk'))
cs.close()

client

 

  如何缓解粘包问题?

  客户端每次都把好之长告诉服务端,这样可形成不粘包。

公海赌船备用网址 68公海赌船备用网址 69

#_*_coding:utf-8_*_
import socket,subprocess
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

s.bind(ip_port)
s.listen(5)

while True:
    conn,addr=s.accept()
    print('客户端',addr)
    while True:
        msg=conn.recv(1024)
        if not msg:break
        res=subprocess.Popen(msg.decode('utf-8'),shell=True,\
                            stdin=subprocess.PIPE,\
                         stderr=subprocess.PIPE,\
                         stdout=subprocess.PIPE)
        err=res.stderr.read()
        if err:
            ret=err
        else:
            ret=res.stdout.read()
        data_length=len(ret)
        conn.send(str(data_length).encode('utf-8'))
        data=conn.recv(1024).decode('utf-8')
        if data == 'recv_ready':
            conn.sendall(ret)
    conn.close()

View Code

公海赌船备用网址 70公海赌船备用网址 71

import socket,time
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))
    length=int(s.recv(1024).decode('utf-8'))
    s.send('recv_ready'.encode('utf-8'))
    send_size=0
    recv_size=0
    data=b''
    while recv_size < length:
        data+=s.recv(1024)
        recv_size+=len(data)


    print(data.decode('utf-8'))

View Code

  这种方法仅是平种植缓解问题的法子,实际上并无会见如此做,程序的运转速度远快被网络传输速度,所以于发送一段字节前,先用send去发送该字节约流长,这种办法会放网络延迟带来的性能损耗。这里用来支援我们再度好的亮粘包的题材。

  以这基础及,我们得考虑以好的尺寸等消息,写到报头头部,这样接收端拆开报头就可知亮长度,就不见面生出粘包的情景了。

  我们事先要认识一下struct模块。

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

公海赌船备用网址 72

公海赌船备用网址 73公海赌船备用网址 74

from socket import *
import subprocess
import struct
ss=socket(AF_INET,SOCK_STREAM)
ss.bind(('127.0.0.1',8082)) 
ss.listen(5)

print('starting...')
while True:
    conn,addr=ss.accept()
    print('-------->',conn,addr)

    while True:
        try:
            cmd=conn.recv(1024)
            res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout=res.stdout.read()
            stderr=res.stderr.read()

            #先发报头(转成固定长度的bytes类型)
            header = struct.pack('i',len(stdout)+len(stderr))
            conn.send(header)
            #再发送命令的结果
            conn.send(stdout)
            conn.send(stderr)
        except Exception:
            break
    conn.close()
ss.close()

客户端

公海赌船备用网址 75公海赌船备用网址 76

from socket import *
import struct
cs=socket(AF_INET,SOCK_STREAM)
cs.connect(('127.0.0.1',8082))

while True:
    cmd=input('>>: ').strip()
    if not cmd:continue

    cs.send(cmd.encode('utf-8'))
    #先收报头
    header_struct=cs.recv(4)
    unpack_res = struct.unpack('i', header_struct)
    total_size=unpack_res[0]

    #再收数据
    recv_size=0 #10241=10240+1
    total_data=b''
    while recv_size < total_size:
        recv_data=cs.recv(1024)
        recv_size+=len(recv_data)
        total_data+=recv_data
    print(total_data.decode('gbk'))
cs.close()

服务端

  struct的i模式:

struct.pack('i',11111111)
#struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围

  struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中绝非字节(Byte)类型)。它的函数原型为:struct.unpack(fmt,
string)。

  struct.unpack做的做事正与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt,
string),该函数返回一个元组。

公海赌船备用网址 77公海赌船备用网址 78

import json,struct
#假设通过客户端上传1T:1073741824000的文件a.txt

#为避免粘包,必须自定制报头
header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值

#为了该报头能传送,需要序列化并且转为bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输

#为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度

#客户端开始发送
conn.send(head_len_bytes) #先发报头的长度,4个bytes
conn.send(head_bytes) #再发报头的字节格式
conn.sendall(文件内容) #然后发真实内容的字节格式

#服务端开始接收
head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式
x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度

head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式
header=json.loads(json.dumps(header)) #提取报头

#最后根据报头的内容提取真实的数据,比如
real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)

View Code

  使用自定制报头的办法来解决粘包问题。

公海赌船备用网址 79公海赌船备用网址 80

import socket,struct,json
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 
phone.bind(('127.0.0.1',8080))

phone.listen(5)

while True:
    conn,addr=phone.accept()
    while True:
        cmd=conn.recv(1024)
        if not cmd:break
        print('cmd: %s' %cmd)

        res=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        err=res.stderr.read()
        print(err)
        if err:
            back_msg=err
        else:
            back_msg=res.stdout.read()


        conn.send(struct.pack('i',len(back_msg))) #先发back_msg的长度
        conn.sendall(back_msg) #在发真实的内容

    conn.close()

服务端起定制报头

公海赌船备用网址 81公海赌船备用网址 82

import socket,time,struct

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))

    l=s.recv(4)
    x=struct.unpack('i',l)[0]
    print(type(x),x)
    # print(struct.unpack('I',l))
    r_s=0
    data=b''
    while r_s < x:
        r_d=s.recv(1024)
        data+=r_d
        r_s+=len(r_d)

    # print(data.decode('utf-8'))
    print(data.decode('gbk')) #windows默认gbk编码

客户端起定制报头

  当然我们的报头可以长更多信息。

  我们把报头做成字典,字典里含将要发送的实在数据的详细信息,然后json序列化,然后据此struck将序列化后底数据长度从包改成4单字节(4单温馨够用了)

  发送时先发报头长度,再编码报头内容然后发送,最后发真实内容。

  接收时优先将报头长度,用struct取出来,根据取出的长度收取报头内容,然后解码,反序列化,从反序列化的结果受取出待取数据的详细信息,然后去取得真实的数码内容。

公海赌船备用网址 83公海赌船备用网址 84

from socket import *
import subprocess
import struct
import json
ss = socket(AF_INET,SOCK_STREAM)
ss.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
ss.bind(('127.0.0.1',8082))
ss.listen(5)

print('starting...')
while True : #链接循环
    conn,addr = ss.accept() #链接,客户的的ip和端口组成的元组
    print('-------->',conn,addr)

    #收,发消息
    while True :#通信循环
        try :
            cmd = conn.recv(1024)
            res = subprocess.Popen(cmd.decode('utf-8'), shell = True,
                                   stdout = subprocess.PIPE,
                                   stderr = subprocess.PIPE)
            stdout = res.stdout.read()
            stderr = res.stderr.read()
            #制作报头
            h_dic = {
                'total_size': len(stdout) + len(stderr),
                'filename': None,
                'md5': None}

            h_json = json.dumps(h_dic)
            h_bytes = h_json.encode('utf-8')
            #发送阶段
            #先发报头长度
            conn.send(struct.pack('i',len(h_bytes)))
            #再发报头
            conn.send(h_bytes)

            #最后发送命令的结果
            conn.send(stdout)
            conn.send(stderr)
        except Exception :
            break
    conn.close()
ss.close()

json序列化报头server

公海赌船备用网址 85公海赌船备用网址 86

from socket import *
import struct
import json
cs = socket(AF_INET,SOCK_STREAM) #买手机
cs.connect(('127.0.0.1',8082)) #绑定手机卡

#发,收消息
while True :
    cmd = input('>>: ').strip()
    if not cmd : continue

    cs.send(cmd.encode('utf-8'))
    #先收报头的长度
    h_len = struct.unpack('i',cs.recv(4))[0]

    #再收报头
    h_bytes = cs.recv(h_len)
    h_json = h_bytes.decode('utf-8')
    h_dic = json.loads(h_json)
    total_size = h_dic['total_size']

    #最后收数据
    recv_size = 0
    total_data = b''
    while recv_size < total_size :
        recv_data = cs.recv(1024)
        recv_size += len(recv_data)
        total_data += recv_data
    print(total_data.decode('gbk'))
cs.close()

client