【python 第13日】网络通讯socket socketserver

须要弄清的几个问题:python

一、何时用tcp,何时用udp?linux

二、tcp快仍是udp快?nginx

三、视频,直播用udp仍是tcp,效果差别有多少?安全性能有多少?有待完善web

四、若是解决粘包问题算法

五、一次传输多少,会形成缓冲区丢包apache

六、在linux和windows里,对于空包,回撤,和直接断开链接的处理不一样编程

七、端口被占用了怎么办?windows

八、能不能直接操做数据缓冲区,以防丢包缓存

九、测试一下用tcp和udp传输一个G的文件,多久能传完,传输命令安全

本文结构

socket定义------->TCP------------>UDP--------------->多任务-------------------->socketserver

socket

socket 套接字

socket(简称 套接字) 是进程间通讯一个工具,它能实现把数据从一方传输到另一方,完成不一样电脑上进程之间的通讯, 它比如数据的搬运工

基本流程:

简单的服务端和客户端

##############################服务器端
import socket
ip_port = ("127.0.0.1", 9999)

sk = socket.socket()             #创建一个套接字,用来接收请求
sk.bind(ip_port)                 #绑定一个端口
sk.listen(2)                     #接听,最大能候补接听5个

conn, addr = sk.accept()         #conn是链接的套接字, addr是对方的ip_port
print(conn, addr)                #<socket.socket fd=616, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 56779)> ('127.0.0.1', 56779)

conn.sendall(bytes("hello world", encoding="utf8"))     #必定给conn发送数据,发送的是字节,
print("发送完毕")

############################客户端
import  socket
ip_port = ("127.0.0.1", 9999)

sk = socket.socket()
sk.connect(ip_port)                       #创建一个套接字,链接服务器端口

print(sk.recv(1024).decode("utf8"))       #等待接收

 socket经常使用函数以及属性

基本参数:

Socket函数使用的格式为:socket(family,type[,protocol])

  参数一:family 指定应用程序使用的通讯协议的协议族,对于TCP/IP协议族,该参数为AF_INET,如下为该参数的一些经常使用选项

  参数二:type 是要建立套接字的类型,如下为该参数的一些经常使用选项
 

   参数三:protocol 指明所要接收的协议类型,一般为0或者不填。

如下为该参数的一些经常使用选项

使用例子:

   建立TCP Socket:s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

   建立UDP Socket:s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

经常使用函数:

 

sk.bind(address)
  s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。
sk.listen(backlog)
  开始监听传入链接。backlog指定在拒绝链接以前,能够挂起的最大链接数量。
      backlog等于5,表示内核已经接到了链接请求,但服务器尚未调用accept进行处理的链接个数最大为5
      这个值不能无限大,由于要在内核中维护链接队列
sk.setblocking(bool)
  是否阻塞(默认True),若是设置False,那么accept和recv时一旦无数据,则报错。
sk.accept()
  接受链接并返回(conn,address),其中conn是新的套接字对象,能够用来接收和发送数据。address是链接客户端的地址。
  接收TCP 客户的链接(阻塞式)等待链接的到来
sk.connect(address)
  链接到address处的套接字。通常,address的格式为元组(hostname,port),若是链接出错,返回socket.error错误。
sk.connect_ex(address)
  同上,只不过会有返回值,链接成功时返回 0 ,链接失败时候返回编码,例如:10061
sk.close()
  关闭套接字
sk.recv(bufsize[,flag])
  接受套接字的数据。数据以字符串形式返回,bufsize指定最多能够接收的数量。flag提供有关消息的其余信息,一般能够忽略。
sk.recvfrom(bufsize[.flag])
  与recv()相似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
sk.send(string[,flag])
  将string中的数据发送到链接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容所有发送。
sk.sendall(string[,flag])
  将string中的数据发送到链接的套接字,但在返回以前会尝试发送全部数据。成功返回None,失败则抛出异常。
      内部经过递归调用send,将全部内容发送出去。
sk.sendto(string[,flag],address)
  将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
sk.settimeout(timeout)
  设置套接字操做的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。通常,超时期应该在刚建立套接字时设置,由于它们可能用于链接的操做(如 client 链接最多等待5s )
sk.getpeername()
  返回链接套接字的远程地址。返回值一般是元组(ipaddr,port)。
sk.getsockname()
  返回套接字本身的地址。一般是一个元组(ipaddr,port)
sk.fileno()
  套接字的文件描述符

 setsockopt()

python定义了setsockopt()和getsockopt(),一个是设置选项,一个是获得设置。这里主要使用setsockopt(),具体结构以下:

setsockopt(level,optname,value)

level定义了哪一个选项将被使用。一般状况下是SOL_SOCKET,意思是正在使用的socket选项。它还能够经过设置一个特殊协议号码来设置协议选项,然而对于一个给定的操做系统,大多数协议选项都是明确的,因此为了简便,它们不多用于为移动设备设计的应用程序。

optname参数提供使用的特殊选项。关于可用选项的设置,会由于操做系统的不一样而有少量不一样。若是level选定了SOL_SOCKET,那么一些经常使用的选项见下表:

选项

意义

指望值

SO_BINDTODEVICE

可使socket只在某个特殊的网络接口(网卡)有效。也许不能是移动便携设备

一个字符串给出设备的名称或者一个空字符串返回默认值

SO_BROADCAST

容许广播地址发送和接收信息包。只对UDP有效。如何发送和接收广播信息包

布尔型整数

SO_DONTROUTE

禁止经过路由器和网关往外发送信息包。这主要是为了安全而用在以太网上UDP通讯的一种方法。无论目的地址使用什么IP地址,均可以防止数据离开本地网络

布尔型整数

SO_KEEPALIVE

可使TCP通讯的信息包保持连续性。这些信息包能够在没有信息传输的时候,使通讯的双方肯定链接是保持的

布尔型整数

SO_OOBINLINE

能够把收到的不正常数据当作是正常的数据,也就是说会经过一个标准的对recv()的调用来接收这些数据

布尔型整数

SO_REUSEADDR

当socket关闭后,本地端用于该socket的端口号马上就能够被重用。一般来讲,只有通过系统定义一段时间后,才能被重用。

布尔型整数

 

本节在学习时,用到了SO_REUSEADDR选项,具体写法是:

S.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 这里value设置为1,表示将SO_REUSEADDR标记为TRUE,操做系统会在服务器socket被关闭或服务器进程终止后立刻释放该服务器的端口,不然操做系统会保留几分钟该端口。

setsockopt(level,optname,value)

level指定控制套接字的层次.能够取三种值:
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项. 
optname指定控制的方式(选项的名称),咱们下面详细解释 

optval得到或者是设置套接字选项.根据选项名称的数据类型进行转换 


选项名称        说明                  数据类型
========================================================================
            SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST      容许发送广播数据            int
SO_DEBUG        容许调试                int
SO_DONTROUTE      不查找路由               int
SO_ERROR        得到套接字错误             int
SO_KEEPALIVE      保持链接                int
SO_LINGER        延迟关闭链接              struct linger
SO_OOBINLINE      带外数据放入正常数据流         int
SO_RCVBUF        接收缓冲区大小             int
SO_SNDBUF        发送缓冲区大小             int
SO_RCVLOWAT       接收缓冲区下限             int
SO_SNDLOWAT       发送缓冲区下限             int
SO_RCVTIMEO       接收超时                struct timeval
SO_SNDTIMEO       发送超时                struct timeval
SO_REUSERADDR      容许重用本地地址和端口         int
SO_TYPE         得到套接字类型             int
SO_BSDCOMPAT      与BSD系统兼容              int
========================================================================
            IPPROTO_IP
------------------------------------------------------------------------
IP_HDRINCL       在数据包中包含IP首部          int
IP_OPTINOS       IP首部选项               int
IP_TOS         服务类型
IP_TTL         生存时间                int
========================================================================
            IPPRO_TCP
------------------------------------------------------------------------
TCP_MAXSEG       TCP最大数据段的大小           int
TCP_NODELAY       不使用Nagle算法             int
========================================================================

shutdown()

socket.makefile

socket.makefile(mode='r', buffering=None, *, encoding=None, errors=None,newline=None), 返回一个文件对象,具体类型与参数相关,除了只能指定‘r’,‘w’,'b'模式外,与open()函数同样.

前提条件是socket必须是阻塞模式,它能够含有一个超时间(指socket),若是发生超时,内部缓冲区会在不一致的状态中关闭。

关闭文件对象不会关闭socket,除非调用了socket.close()方法,或者全部其余文件对象都关闭了。

注意:在win上,makefile建立的一个类文件对象在须要文件描述符的文件对象的状况下是没法使用的(意思大概是makefile建立的文件对象没有文件描述符)


socket.dup(), 复制一个socket

socket.detach()

,将socket 对象设置为关闭状态,但底层的文件描述符并没关闭,仍能够进行操做,返回值为文件描述符。

socket.get_inheritable()

测试socket是否能够继承,True或者False

socket.recvmsg(bufsize[, ancbufsize[, flags]])

socket.recvmsg(bufsize[, ancbufsize[, flags]]), 接收常规文件接收常规文件,附件等。ancbufsize设定附件接收缓冲区,相似于bufsize。但返回值是有四个元素的元组()。(data, ancdata, msg_flags, address). ancdata是一个含有四个0的列表或者是(cmsg_level, cmsg_type, cmsg_data)这样的列表。

scoket.share()

share(process_id) -> bytes ,给另一个程序传递本身的二进制,别的程序能够接收,经过socket.fromshare()变成一个套接字进行通讯

import socket
ip_port = ("127.0.0.1", 9999)

sk = socket.socket()             #创建一个套接字,用来接收请求
sk.bind(ip_port)                 #绑定一个端口
sk.listen(2)                     #接听,最大能接听5个

coon, addr = sk.accept()
print(coon.getpeername())                              #('127.0.0.1', 64808)
print(coon.getsockname())                              #('127.0.0.1', 9999)
# print(socket.gethostbyaddr('112.80.248.76'))           #('www.baidu.com', [], ['112.80.248.76'])
print(socket.gethostbyname("www.baidu.com"))           #112.80.248.76
print(socket.gethostbyname_ex("www.baidu.com"))        #('www.baidu.com', [], ['112.80.248.75'])
print(socket.getservbyport(80))                        #http
print(sk.get_inheritable())                            #False   实例才有这个属性
sk2 = sk.dup()
print(sk2)                                             #<socket.socket fd=712, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>

TCP

 socket模式是TCP的,经常使用函数 send, sendall, recv

 当发送为空的时候再也不接收卡住,不知道是3.7改进了仍是3.5的问题

服务端:socket()---------->绑定-------->监听listen------------>等待连接accept ----------->接收数据

客户端:socket()---------------->链接服务端connect------------>发送数据

sk.setblocking(bool)   设置True 阻塞,设置为False, 遇到阻塞accept(), recv()就报错

############服务端################
import socket
ip_port = ("127.0.0.1", 9999)                                 #当再虚拟机上运行的时候,不能够写127.0.0.1, 必须写局域网地址
BUFFERSIZE =8196


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)             #创建一个套接字,用来接收请求
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)           #容许端口复用
sk.bind(ip_port)                 #绑定一个端口

sk.listen(2)                     #接听,最大能接听5个

while True:
    conn, addr = sk.accept()
    print("如下客户端要链接主机:",conn, addr)
    conn.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)                   # 禁用nagle算法

    conn.sendall("你好,我是机器人1".encode("utf8"))
    conn.sendall("你好,我是机器人2".encode("utf8"))
    conn.sendall("你好,我是机器人3".encode("utf8"))              #第一次链接完不会粘包,第二三次会,多是时间缘由,若是对方睡眠一秒,则一次收到3个


    #进行某个信息处理
    while True:
        data_recv = conn.recv(BUFFERSIZE).decode("utf8")
        print("服务器收到数据:",data_recv)
        data_send = input("你要问的问题")
        conn.sendall(data_send.encode("utf8"))

    conn.close()

sk.shutdown()
sk.close()

############客户端###########
import  socket
import time

ip_port = ("127.0.0.1", 9999)
# ip_port = ("192.168.0.105", 9999)
BUFFERSIZE =8196

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect(ip_port)                       #创建一个套接字,链接服务器端口

time.sleep(0.3)
data = sk.recv(BUFFERSIZE)
print("客户端收到》》》》:",data.decode("utf8"))

while 1:
    data = input("请问有什么问题")
    sk.sendall(data.encode("utf8"))
    data_recv = sk.recv(BUFFERSIZE)
    print("客户端收到》》》》:", data_recv.decode("utf8"))

sk.close()
聊天机器人

遇到的问题:

  1. 怎么关闭nagle, 用conn.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1),刚开始觉得没弄成功,结果发现客户端接收的时候是流式接收,因此仍是混在一块儿,然并卵
  2. 怎么让端口复用sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)    刚开始觉得是复制形式,后来不知道记错了没有,仍是这一个好用
#############服务器
import socket
ip_port = ("127.0.0.1", 9999)                                 #当再虚拟机上运行的时候,不能够写127.0.0.1, 必须写局域网地址
BUFFERSIZE =8196


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)             #创建一个套接字,用来接收请求
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)           #容许端口复用
sk.bind(ip_port)                 #绑定一个端口

sk.listen(2)                     #接听,最大能接听5个


conn, addr = sk.accept()
print("如下客户端要链接主机:",conn, addr)
size  = conn.recv(1024).decode("utf8")
print("接受到的数据大小为:",size)

while True:
    conn.sendall("开始传送".encode("utf8"))
    print("让客户端开始发送")
    has_size = 0
    with open("2.jpg", "wb") as f:
        while has_size < int(size) :
            data_recv = conn.recv(1024)
            f.write(data_recv)
            has_size += len(data_recv)
    print("接收完毕", has_size, size)
    if has_size == int(size):
        conn.sendall("接收完毕".encode("utf8"))
        break

conn.close()


###############客户端
import  socket
import os
ip_port = ("127.0.0.1", 9999)

sk = socket.socket()
sk.connect(ip_port)

#先发一个文件大小
size = os.stat("1.jpg").st_size
print("要发送的文件大小为:",size)
sk.sendall(str(size).encode("utf8"))
print("发送文件大小完毕")

while True:
    data_recv = sk.recv(1024).decode("utf8")
    print("接收到服务器发送来的数据为:",data_recv)
    if data_recv == "开始传送":
        with open("1.jpg", "rb") as f :
            for line in f:
                sk.sendall(line)
    print("发送完毕")
    data_recv = sk.recv(1024).decode("utf8")
    print("等待接收是否收完:", data_recv)
    if data_recv =="接收完毕":
        break

sk.close()
发送照片

UDP

服务端:socket()------->绑定------------>等待接收数据,而后处理

客服端:socket()------->直接发

UDP发包,要确保丢包率,搞策略确保重发

#服务端
import socket
ip_port = ("127.0.0.1", 9999)                                 #当再虚拟机上运行的时候,不能够写127.0.0.1, 必须写局域网地址
BUFFERSIZE =8196


udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)             #创建一个套接字,用来接收请求
udp.bind(ip_port)                 #绑定一个端口

print("--------------")
#直接进入通讯
data, addr= udp.recvfrom(1024)
print(data.decode("utf8"), addr)
data, addr= udp.recvfrom(1024)
print(data.decode("utf8"), addr)
udp.close()

#客户端

import  socket
import time

ip_port = ("127.0.0.1", 9999)
# ip_port = ("192.168.0.105", 9999)
BUFFERSIZE =8196
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# udp.sendto(b'hello', ip_port)
udp.sendto("".encode("utf8"), ip_port)
udp.sendto(b"hello", ip_port)
udp.close()
UDP流程 

TCP和UDP包

最大包,最小包

报文封装总体结构

报文总体结构

其中以太网(Ethernet)的数据帧在链路层   
IP包在网络层   
TCP或UDP包在传输层   
TCP或UDP中的数据(Data)在应用层   
它们的关系是 数据帧{IP包{TCP或UDP包{Data}}}   

    不一样的协议层对数据包有不一样的称谓,在传输层叫作段(segment),在网络层叫作数据报(datagram),在链路层叫作帧(frame)。数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理。

 

在应用程序中咱们用到的Data的长度最大是多少,直接取决于底层的限制。   
咱们从下到上分析一下:   
1.在链路层,由以太网的物理特性决定了数据帧的长度为(46+18)-(1500+18),其中的18是数据帧的头和尾,也就是说数据帧的内容最大为1500(不包括帧头和帧尾),即MTU(Maximum Transmission Unit)为1500;  
2.在网络层,由于IP包的首部要占用20字节,因此这的MTU为1500-20=1480; 
3.在传输层,对于UDP包的首部要占用8字节,因此这的MTU为1480-8=1472;   
因此,在应用层,你的Data最大长度为1472。当咱们的UDP包中的数据多于MTU(1472)时,发送方的IP层须要分片fragmentation进行传输,而在接收方IP层则须要进行数据报重组,因为UDP是不可靠的传输协议,若是分片丢失致使重组失败,将致使UDP数据包被丢弃。  
从上面的分析来看,在普通的局域网环境下,UDP的数据最大为1472字节最好(避免分片重组)。   
但在网络编程中,Internet中的路由器可能有设置成不一样的值(小于默认值),Internet上的标准MTU值为576,因此Internet的UDP编程时数据长度最好在576-20-8=548字节之内。
 

二、TCP、UDP数据包最大值的肯定     

        UDP和TCP协议利用端口号实现多项应用同时发送和接收数据。数据经过源端口发送出去,经过目标端口接收。有的网络应用只能使用预留或注册的静态端口;而另一些网络应用则可使用未被注册的动态端口。由于UDP和TCP报头使用两个字节存放端口号,因此端口号的有效范围是从0到65535。动态端口的范围是从1024到65535。  

        MTU最大传输单元,这个最大传输单元实际上和链路层协议有着密切的关系,EthernetII帧的结构DMAC+SMAC+Type+Data+CRC因为以太网传输电气方面的限制,每一个以太网帧都有最小的大小64Bytes最大不能超过1518Bytes,对于小于或者大于这个限制的以太网帧咱们均可以视之为错误的数据帧,通常的以太网转发设备会丢弃这些数据帧。

        因为以太网EthernetII最大的数据帧是1518Bytes这样,刨去以太网帧的帧头(DMAC目的MAC地址48bits=6Bytes+SMAC源MAC地址48bits=6Bytes+Type域2Bytes)14Bytes和帧尾CRC校验部分4Bytes那么剩下承载上层协议的地方也就是Data域最大就只能有1500Bytes这个值咱们就把它称之为MTU。

 

UDP 包的大小就应该是 1500 - IP头(20) - UDP头(8) = 1472(Bytes)
TCP 包的大小就应该是 1500 - IP头(20) - TCP头(20) = 1460 (Bytes)

 

注*PPPoE所谓PPPoE就是在以太网上面跑“PPP”。随着宽带接入(这种宽带接入通常为Cable Modem或者xDSL或者以太网的接入),由于以太网缺少认证计费机制而传统运营商是经过PPP协议来对拨号等接入服务进行认证计费的,因此引入PPPoE。PPPoE致使MTU变小了以太网的MTU是1500,再减去PPP的包头包尾的开销(8Bytes),就变成1492。不过目前大多数的路由设备的MTU都为1500。

 

        若是咱们定义的TCP和UDP包没有超过范围,那么咱们的包在IP层就不用分包了,这样传输过程当中就避免了在IP层组包发生的错误;若是超过范围,既IP数据报大于1500字节,发送方IP层就须要将数据包分红若干片,而接收方IP层就须要进行数据报的重组。更严重的是,若是使用UDP协议,当IP层组包发生错误,那么包就会被丢弃。接收方没法重组数据报,将致使丢弃整个IP数据报。UDP不保证可靠传输;可是TCP发生组包错误时,该包会被重传,保证可靠传输。

        UDP数据报的长度是指包括报头和数据部分在内的总字节数,其中报头长度固定,数据部分可变。数据报的最大长度根据操做环境的不一样而各异。从理论上说,包含报头在内的数据报的最大长度为65535字节(64K)。

       咱们在用Socket编程时,UDP协议要求包小于64K。TCP没有限定,TCP包头中就没有“包长度”字段,而彻底依靠IP层去处理分帧。这就是为何TCP经常被称做一种“流协议”的缘由,开发者在使用TCP服务的时候,没必要去关心数据包的大小,只需讲SOCKET看做一条数据流的入口,往里面放数据就是了,TCP协议自己会进行拥塞/流量控制。 

       不过鉴于Internet(非局域网)上的标准MTU值为576字节,因此建议在进行Internet的UDP编程时,最好将UDP的数据长度控制在548字节 (576-8-20)之内。

 

三、TCP、UDP数据包最小值的肯定

     在用UDP局域网通讯时,常常发生“Hello World”来进行测试,可是“Hello World”并不知足最小有效数据(64-46)的要求,为何小于18个字节,对方仍然可用收到呢?由于在链路层的MAC子层中会进行数据补齐,不足18个字节的用0补齐。但当服务器在公网,客户端在内网,发生小于18个字节的数据,就会出现接收端收不到数据的状况。

       以太网EthernetII规定,以太网帧数据域部分最小为46字节,也就是以太网帧最小是6+6+2+46+4=64。除去4个字节的FCS,所以,抓包时就是60字节。当数据字段的长度小于46字节时,MAC子层就会在数据字段的后面填充以知足数据帧长不小于64字节。因为填充数据是由MAC子层负责,也就是设备驱动程序。不一样的抓包程序和设备驱动程序所处的优先层次可能不一样,抓包程序的优先级可能比设备驱动程序更高,也就是说,咱们的抓包程序可能在设备驱动程序尚未填充不到64字节的帧的时候,抓包程序已经捕获了数据。所以不一样的抓包工具抓到的数据帧的大小可能不一样。下列是本人分别用wireshark和sniffer抓包的结果,对于TCP 的ACK确认帧的大小一个是54字节,一个是60字节,wireshark抓取时没有填充数据段,sniffer抓取时有填充数据段。

       

四、实际应用

        用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) - UDP头(8)=65507字节。用sendto函数发送数据时,若是发送数据长度大于该值,则函数会返回错误。  

        用TCP协议发送时,因为TCP是数据流协议,所以不存在包大小的限制(暂不考虑缓冲区的大小),这是指在用send函数时,数据长度参数不受限制。而实际上,所指定的这段数据并不必定会一次性发送出去,若是这段数据比较长,会被分段发送,若是比较短,可能会等待和下一次数据一块儿发送。

ip报头结构

ip头部结构

版本号(Version):标明了IP 协议的版本号,目前的协议版本号为4。下一代IP 协议的版本号为6。

●报文长度:指 IP 包头部长度,占4 位。

●8 位的服务类型:包括一个3 位的优先权字段(COS,Class of Service),4 位TOS 字段和1 位未用位。4 位TOS 分别表明最小时延、最大吞吐量、最高可靠性和最小费用。

●总长度:是整个IP 数据报长度,包括数据部分。

●标识符:惟一地标识主机发送的每一份数据报。一般每发送一份报文它的值就会加1。

●生存时间:设置了数据包能够通过的路由器数目。一旦通过一个路由器,TTL 值就会减1,当该字段值为0 时,数据包将被丢弃。

●协议:肯定在数据包内传送的上层协议,和端口号相似,IP 协议用协议号区分上层协议。TCP 协议的协议号为6,UDP 协议的协议号为17。

●报头校验和:计算IP 头部的校验和,检查报文头部的完整性。

●源IP 地址和目的IP 地址字段:标识数据包的源端设备和目的端设备。

●IP选项:通常格式为1个字节的代码,一个字节的长度,一个字节的指针,指针的值从1开始计数,指向IP选项的内容,通常其值为4(跳过了前面的代码&长度&指针的三个字节),长度包括前面3个字节在内的整个IP选项,最大值为40。

TCP包

 这里写图片描述 1.源端口和目的端口:各占2个字节。

2.序号:占4字节。序号范围是0~2^32-1。TCP是面向字节流的,TCP链接中传送的字节流中的每一个字节都按顺序编号。整个要传送的字节流的起始序号必需要在链接创建时设置。首部中的序号字段值指的是本报文段所发送的数据的第一个字节的序号。

3.确认号:4个字节,是指望收到对方下一个报文段的第一个数据字节的序号。 
若确认号=N,则代表:到序号N-1为止的全部数据都已正确收到。 
4.数据偏移:4位。指出TCP报文段的数据起始处距离报文段的起始处有多远。这个字段其实是指出TCP报文段的首部长度。因为首部中还有长度不肯定的选项字段,所以数据偏移字段是必要的。单位是32位字,也就是4字节,4位二进制最大表示15,因此数据偏移也就是TCP首部最大60字节

5.保留:6位 
下面有6个控制位说明本报文段的性质

6.紧急URG:1位 
当URG=1时,代表紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(至关于高优先级的数据),而不要按原来的排队顺序来传送。例如,已经发送了很长的一个程序在远地的主机上运行。但后来发现了一些问题,须要取消该程序的运行。所以用户从键盘发出中断命令(Control+c)。若是不使用紧急数据,那么这两个字符将存储在接收TCP的缓存末尾。只有在全部的数据被处理完毕后这两个字符才被交付接收方的应用进程。这样作就浪费了许多时间。

当URG置为1时,发送应用进程就告诉发送方的TCP有紧急数据要传送。因而发送方TCP就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍时普通数据。这时要与首部中紧急指针字段配合使用。

7.确认ACK 
仅当ACK=1时确认号字段才有效。当ACK=0时,确认号无效。TCP规定,在链接创建后全部的传送的报文段都必须把ACK置1。

8.推送PSH 
当两个应用进程进行交互式的通讯时,有时在一端的应用进程但愿在键入一个命令后当即就能收到对方的响应。在这种状况下,TCP就可使用推送操做。这时,发送方TCP把PSH置1,并当即建立一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快地交付接收应用进程,而再也不等到整个缓存都填满了后向上交付。

虽然应用程序能够选择推送操做,但推送还不多使用。

9.复位RST 
tcp链接出现严重差错时释放链接,而后从新创建链接。而能够用来拒绝一个非法的报文段或拒绝打开一个链接。

当RST=1时,代表TCP链接中出现严重差错(如因为主机崩溃或其余缘由),必须释放链接,而后再从新创建运输链接。RST置1还用来拒绝一个非法的报文段或拒绝打开一个链接。

10.同步SYN 
在链接创建时用来同步序号。当SYN=1而ACK=0时,代表这是一个链接请求报文段。对方若赞成创建链接,则应在相应的报文段中使用SYN=1和ACK=1。所以,SYN置为1就表示这是一个链接请求或链接接受保温。

11.终止FIN 
用来释放一个链接。当FIN=1时,代表此报文段的发送方的数据已发送完毕,并要求释放运输链接。

12窗口 占2字节。窗口值是【0,2^16-1]之间的整数。窗口指的是发送本报文段的一方的接收窗口(而不是本身的发送窗口)。窗口值告诉对方: 从本报文段首部中的确认号算起,接收方目前容许对方发送的数据量。之因此要有这个限制,是由于接收方的数据缓存空间是有限的。总之,窗口值做为接收方让发送方设置其发送窗口的依据。而且窗口值是常常在动态变化着。

13.检验和:2字节。检验范围包括首部和数据两部分。和UDP用户数据报同样,在计算校验和 时,要在TCP报文段加上12字节的伪首部。

14.紧急指针:2字节。紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。所以,紧急指针指出了紧急数据的末尾在报文段中的位置。当全部紧急数据都处理完时,TCP就告诉应用程序恢复到正常操做。值得注意的是,即便窗口为零时也可发送紧急数据。

15.选项:长度可变,最长可达40字节。当没有使用“选项”时,TCP的首部长度是20字节。 
1)MSS 最大报文段长度 
MSS最大报文段长度(数据字段的最大长度,默认是536字节)。MSS不宜设的太大也不宜设的过小。若选择过小,极端状况下,TCP报文段只含有1字节数据,在IP层传输的数据报的开销至少有40字节(包括TCP报文段的首部和IP数据报的首部)。这样,网络的利用率就不会超过1/41。若TCP报文段很是长,那么在IP层传输时就有可能要分解成多个短数据报片。在终点要把收到的各个短数据报片装配成原来的TCP报文段。当传输出错时还要进行重传,这些也都会使开销增大。

所以MSS应尽量大,只要在IP层传输时不须要再分片就行。在链接创建过程当中,双方都把本身可以支持的MSS接入这一字段,之后就按照这个数值传送数据。 
2)窗口扩大 
窗口扩大选项是为了扩大窗口。TCP首部中窗口字段长度是16位,所以最大窗口大小就是64k字节。对于包含卫星信道的网络多是不够用的。能够在双方初始创建TCP链接的时候就进行协商。 
3)时间戳(计算RTT,防止序号绕回) 
A. 用来计算往返时间RTT。发送方在发送报文段时把当前时钟的时间值放入时间戳字段,接收方在确认该报文段时把时间戳字段值复制到时间戳回送回答字段。所以,发送方在收到确认报文后,能够准确地计算RTT来。 
4)选择确认选项 

UDP 包

这里写图片描述 
内容:16位源端口 16位目标端口
16位的UDP包长度 UDP头部和UDP数据的总长度字节
16位头部校验和 此字段是可选项
仅提供端口号—-0-65535 1-1023注明端口 1024-65535动态端口(高端口)
客户端访问服务器时,随机在高端口中分配一个进程号;做为数据包中的源端口—用于区分客户端上的程序进程;使用注明端口来做为目标端口,用于告知所要访问的服务;

这里写图片描述 
UDP协议分为首部字段和数据字段,其中首部字段只占用8个字节,分别是个占用两个字节的源端口、目的端口、长度和检验和。

偏函数,struct

偏函数,用于固定经常使用函数的某些经常使用参数

from functools import partial
def sum(arg1, arg2):
    return arg1+arg2
if __name__ == '__main__':
    sum1 = partial(sum, 10)       #固定参数
    sum2 = partial(sum, arg2 = 1)       #固定某个参数
    print(sum1(2))
    print(sum2(2))
偏函数

 

struct 

pack(fmt,v1,v2...)<-------------------->unpack(fmt,bytes)

import struct
import ctypes
if __name__ == '__main__':
    s = struct.Struct('ii')
    s_b = s.pack(1,2)
    print(s.unpack(s_b))       #(1, 2)

    str_buf = ctypes.create_string_buffer(s.size)       #造一个缓存
    s.pack_into(str_buf, 0 , 1,2)             #把1,2按照偏移量0,存入缓存str_buf
    print(s.unpack_from(str_buf, 0))  
struct pack pack_into 使用方法

 

1.格式化对照表

2.字节顺序,大小和校准

 

socketserver

解决的问题:不管是udp仍是tcp,只要不用线程,多我的聊天的时候是不能有阻塞的

socketserver流程

 socketserver聊天

TCP线程

import logging
import socketserver
import threading

DATEFMT = "%H:%M:%S"
FORMAT = "[%(asctime)s]\t [%(threadName)s,%(thread)d] %(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt=DATEFMT)
ip_port = ("127.0.0.1", 9999)

class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # print(self.__dict__)
        # print(self.request)
        # print(self.client_address)
        # print(self.server)
        # self.request.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, 2)
        print("已经进入到handle",self.request)
        tlist =[]
        t1 = threading.Thread(target=self.read)
        t2 = threading.Thread(target=self.write)            #不能加参数,默认是传递了一个self
        tlist.append(t1)
        tlist.append(t2)
        for i in tlist:
            i.start()
        for i in tlist:                                     #必须阻塞,不然这个线程会退出,形成self.request不存在,子线程错误
            i.join()

    def read(self):
        while True:
            # print("正在执行read",self, self.request)
            data_recv = self.request.recv(1024)
            print("客户端端传来数据:", data_recv.decode("utf8"))


    def write(self):
        while True:
            # print("正在执行write",self.request)
            # data_send = input("输入您要说的话》》》》》")
            data_send = input()
            print("%40s" % data_send)
            self.request.sendall(data_send.encode("utf8"))

if __name__ == '__main__':
    socketserver.ThreadingTCPServer.allow_reuse_address = True
    s = socketserver.ThreadingTCPServer(ip_port, MyServer, True)   #第三个bool值,为真时候表示开启绑定和监听, 为假表示只是建立一个套接字,并不绑定和监听
    # print(s.__dict__)
    s.serve_forever()
服务器端 用线程实现
import socket
import threading
import time

ip_port = ("127.0.0.1", 9999)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect(ip_port)
time.sleep(3)


def read(sock):
    while True:
        # print("正在执行read",sock)
        data_recv = sock.recv(1024)
        print("服务器端传来数据:",data_recv.decode("utf8"))


def write(sock):
    while True:
        # print("正在执行write",sock)
        # data_send = input("输入您要说的话》》》》》")
        data_send = input()
        print("%40s" % data_send)
        sock.sendall(data_send.encode("utf8"))


tlist = []
t1 = threading.Thread(target=read, args=(s, ))
t2 = threading.Thread(target=write, args=(s,))
tlist.append(t1)
tlist.append(t2)
for i in tlist:
    i.start()
for i in tlist:
    i.join()

s.close()
客户端 线程实现

注意:

  原本想用select实现,可是select对sys.stdin不支持,若是再写一个套接字,又麻烦,因此用线程实现

  暂时不支持多人聊天,主机是发送消息,一次发给客户端1,下一次是客户端2, 能够修改以下:在主线程开写程序,服务端经过“0    发送消息”来提示给第几个发送消息,须要从新修改T和readingTCPServer,在于怎么获取有几个链接管理

import socketserver
import socket
import threading
import time

ip_port = ("127.0.0.1", 9999)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect(ip_port)
time.sleep(3)


def read(sock):
    while True:
        # print("正在执行read",sock)
        data_recv = sock.recv(1024)
        print("服务器端传来数据:",data_recv.decode("utf8"))


def write(sock):
    while True:
        # print("正在执行write",sock)
        # data_send = input("输入您要说的话》》》》》")
        data_send = input(">>>")
        print("%40s" % data_send)
        sock.sendall(data_send.encode("utf8"))


tlist = []
t1 = threading.Thread(target=read, args=(s, ))
t2 = threading.Thread(target=write, args=(s,))
tlist.append(t1)
tlist.append(t2)
for i in tlist:
    i.start()
for i in tlist:
    i.join()

s.close()
TCPserver修订版 基本实现互撩的功能

 

UDP线程

#!/usr/bin/env python3
# encoding: utf-8

"""
@Version: ??
@Author: Administrator 周广露
@Contact: zhouguanglu2012@163.com
@Site: http://www.baidu.com
@Software: PyCharm
@File: stcp测试
@Created_Time: 2019/1/23 21:14
"""
import logging
import socketserver
import threading

DATEFMT = "%H:%M:%S"
FORMAT = "[%(asctime)s]\t [%(threadName)s,%(thread)d] %(message)s"
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt=DATEFMT)
ip_port = ("127.0.0.1", 9999)

class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # print(self.client_address)
        # print(self.server)
        # print(self.__dict__)
        # self.request.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, 2)
        print("已经进入到handle",self.request)

        #添加读取和写模块
        tlist =[]
        t1 = threading.Thread(target=self.read)

        t2 = threading.Thread(target=self.write)            #不能加参数,默认是传递了一个self
        t2.setDaemon(True)                                 #让写变成守护线程和主程序死,读程序退出便可

        tlist.append(t1)
        tlist.append(t2)
        for i in tlist:
            i.start()

        for i in tlist:                                     #必须阻塞,不然这个线程会退出,形成self.request不存在,子线程错误
            i.join()

    def read(self):
        num = 1
        try :
            while True:
                if num == 1:
                    print("客户端端传来第%d数据:"% num,self.request[0].decode("utf8"))
                else:
                    data_recv, addr = self.request[1].recvfrom(1024)
                    print(data_recv, addr)
                    print("客户端端传来第%d数据:" % num, data_recv.decode("utf8"))
                num = num + 1
        except  ConnectionResetError as e:
            print(e)

    def write(self):
        while True:
            # print("正在执行write",self.request)
            # data_send = input("输入您要说的话》》》》》")
            data_send = input(">>>")
            print("%40s" % data_send)
            self.request[1].sendto(data_send.encode("utf8"), self.client_address)

if __name__ == '__main__':
    s = socketserver.ThreadingUDPServer(ip_port, MyServer)   #第三个bool值,为真时候表示开启绑定和监听, 为假表示只是建立一个套接字,并不绑定和监听
    # print(s.__dict__)
    s.serve_forever()
UDP server
#!/usr/bin/env python3
# encoding: utf-8

"""
@Version: ??
@Author: Administrator 周广露
@Contact: zhouguanglu2012@163.com
@Site: http://www.baidu.com
@Software: PyCharm
@File: stcp测试
@Created_Time: 2019/1/23 21:14
"""
import socketserver
import socket
import threading
import time

ip_port = ("127.0.0.1", 9999)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

s.connect(ip_port)
time.sleep(3)


s.sendto(b'123', ip_port)

def read(sock):
    while True:
        # print("正在执行read",sock)
        data_recv = sock.recv(1024)
        print("服务器端传来数据:",data_recv.decode("utf8"))


def write(sock):
    while True:
        # print("正在执行write",sock)
        # data_send = input("输入您要说的话》》》》》")
        data_send = input(">>>")
        print("%40s" % data_send)
        sock.sendto(data_send.encode("utf8"), ip_port)


tlist = []
t1 = threading.Thread(target=read, args=(s, ))
t2 = threading.Thread(target=write, args=(s,))
tlist.append(t1)
tlist.append(t2)
for i in tlist:
    i.start()
for i in tlist:
    i.join()

s.close()
UDP client

注意:

   不支持多人聊天,多人链接时候,都链接到一个handle里面去了,说明UDP链接时候就开了一个链接

TCP进程和UDP进程

ForkingTCPServer在windows不存在,只有linux才有,os.fork()

问题解决

怎么解决粘包问题

在发送每一个包以前加上一个struct.pack('i',文件大小),接收端每次接受一些,直到接受完,而后再decode

发送的时候14M的字符串发送须要1秒,接受第一次很快不到0.015秒,第二次1秒多

import  socket
import struct
import time

ip_port = ("127.0.0.1", 9999)                                 #当再虚拟机上运行的时候,不能够写127.0.0.1, 必须写局域网地址
BUFFERSIZE = 1024


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)             #创建一个套接字,用来接收请求
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)           #容许端口复用
sk.bind(ip_port)                 #绑定一个端口
sk.listen(2)                     #接听,最大能接听5个

conn, addr = sk.accept()
start = time.process_time()
count = 1
while True:
    data = conn.recv(8)
    size = struct.unpack('q', data)[0]
    print(size)
    has_length = 0
    data = b''
    if size:
        while size > has_length:
            data += conn.recv(size - has_length)
            has_length = len(data)
            print("收到数据长度",has_length)
        end = time.process_time()
        # print("第%d次发送带来的是:  %s"%(count, data.decode("utf8")))
        print("用时间为:", end - start)
        count += 1
TCP服务端 屡次发送 小包粘包 大包一块儿发均可以解决
import socket
import struct
import time


def send_one(sock, data:str):
    start = time.process_time()
    data = data.encode("utf8")
    data_length = len(data)
    data_length_bin = struct.pack('q', data_length)
    sock.sendall(data_length_bin)
    sock.sendall(data)
    end = time.process_time()
    print("发送完毕",data_length)
    print("用时间为:",end - start)


def send_more_times(data:str):
    pass


ip_port = ("127.0.0.1", 9999)
BUFFERSIZE = 1024

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect(ip_port)  # 创建一个套接字,链接服务器端口

while True:
    send_one(sk, "吃饭了么")
    send_one(sk, "吃饭了么2")

    time.sleep(100000)
TCP客户端 屡次发送 小包粘包 大包一块儿发均可以解决

tcp快仍是udp快?

#########服务器端
import socket
import time

ip_port = ("127.0.0.1", 9999)                                 #当再虚拟机上运行的时候,不能够写127.0.0.1, 必须写局域网地址
BUFFERSIZE =8196


sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)             #创建一个套接字,用来接收请求
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)           #容许端口复用
sk.bind(ip_port)                 #绑定一个端口

sk.listen(2)                     #接听,最大能接听5个


conn, addr = sk.accept()
tc = time.process_time()
print("如下客户端要链接主机:",conn, addr)
size  = conn.recv(1024).decode("utf8")
print("接受到的数据大小为:",size)

while True:
    conn.sendall("开始传送".encode("utf8"))
    print("让客户端开始发送")
    has_size = 0
    with open("2", "wb") as f:
        while has_size < int(size) :
            data_recv = conn.recv(8192)
            f.write(data_recv)
            has_size += len(data_recv)
    print("接收完毕", has_size, size)
    if has_size == int(size):
        conn.sendall("接收完毕".encode("utf8"))
        break
tc2 = time.process_time()
print("接受完毕时间为:",tc2 - tc, has_size)
conn.close()

#########客户端
import  socket
import os
import  time
ip_port = ("127.0.0.1", 9999)

sk = socket.socket()
sk.connect(ip_port)

tc = time.process_time()
#先发一个文件大小
size = os.stat("1").st_size
print("要发送的文件大小为:",size,"发送时间为:",tc)
sk.sendall(str(size).encode("utf8"))
print("发送文件大小完毕")

i = 1
while True:
    data_recv = sk.recv(1024).decode("utf8")
    print("接收到服务器发送来的数据为:",data_recv)
    if data_recv == "开始传送":
        with open("1", "rb") as f :
            while True:
                s = f.read(8000)
                if not s:
                    break
                sk.sendall(s)
            # for line in f:
            #     if len(line) > 10000:
            #         print("接受到的长度:", len(line))
            #     sk.sendall(line)              #tcp长度超过1500的能够断开发,可是udp不行
    print("发送完毕")
    data_recv = sk.recv(1024).decode("utf8")
    print("等待接收是否收完:", data_recv)
    if data_recv =="接收完毕":
        print("第%d次传送完毕", i)
        break
    i += 1
tc2 = time.process_time()
print("发送完毕时间:",tc2- tc )
sk.close()
tcp发送文件,100M大约1秒,可是不用确保是否传错,若是用for line in f 大约18秒,因此用读取read更快
#######udp服务器
import socket
import time

ip_port = ("127.0.0.1", 9999)                                 #当再虚拟机上运行的时候,不能够写127.0.0.1, 必须写局域网地址
BUFFERSIZE =8192

udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp.bind(ip_port)  # 绑定一个端口


tc = time.process_time()
size, addr = udp.recvfrom(1024)
print("接受到的数据大小为:", size.decode("utf8"))

while True:
    udp.sendto("开始传送".encode("utf8"), addr)
    print("让客户端开始发送")
    tc = time.process_time()
    has_size = 0
    with open("2", "wb") as f:
        num = 0
        while has_size < int(size) * 0.93:
            data_recv, addr = udp.recvfrom(BUFFERSIZE)
            num += 1
            has_size += len(data_recv)
            # print("接受到部分数据:", data_recv[0:3], len(data_recv), has_size, num )
            f.write(data_recv)

    print("接收完毕", has_size, size)
    if has_size >= int(size) * 0.93 :
        udp.sendto("接收完毕".encode("utf8"), addr)
        break
tc2 = time.process_time()
print("接受完毕时间为:", tc2 - tc, has_size)

udp.close()


########udp 客户端
#!/usr/bin/env python3
# encoding: utf-8

"""
@Version: ??
@Author: Administrator 周广露
@Contact: zhouguanglu2012@163.com
@Site: http://www.baidu.com
@Software: PyCharm
@File: server
@Created_Time: 2019/1/21 22:10
"""
import os
import  socket
import time

ip_port = ("127.0.0.1", 9999)
# ip_port = ("192.168.0.105", 9999)
BUFFERSIZE =8000

udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp.connect(ip_port)

tc = time.process_time()
#先发一个文件大小
size = os.stat("1").st_size
print("要发送的文件大小为:",size,"发送时间为:",tc)
udp.sendto(str(size).encode("utf8"), ip_port)
print("发送文件大小完毕")

i = 1
while True:

    data_recv,addr  = udp.recvfrom(1024)
    tc = time.process_time()
    print("接收到服务器发送来的数据为:",data_recv.decode("utf8"),addr)
    if data_recv.decode("utf8") == "开始传送":
        num = 0
        max = 0
        with open("1", "rb") as f :
            while True:
                s = f.read(BUFFERSIZE)
                if s:
                    udp.send(s)
                    num = num + 1
                    # print("发送了:", num, num*BUFFERSIZE)
                else:
                    break
    #         has_length = 0
    #         for line in f:
    #             length = len(line)
    #             left_len = length
    #             has_len = 0
    #             if length > max:
    #                 max = length
    #             while left_len > 0 :
    #                 if left_len > BUFFERSIZE:
    #                     udp.sendall(line[has_len: has_len + BUFFERSIZE + 1])
    #                     has_length = has_length + BUFFERSIZE
    #                     # print("has_length1:",has_length)
    #                     num = num + 1
    #                     has_len = has_len + BUFFERSIZE
    #                 else:
    #                     udp.sendall(line[has_len:])
    #                     has_length = has_length + len(line[has_len:])
    #                     # print("has_length2:",has_length)
    #                     has_len = has_len + len(line[has_len:])
    #                     num = num + 1
    #                 left_len = left_len - BUFFERSIZE
    #                 has_len = has_len + BUFFERSIZE
    #                 print("发送长度:", len(line), has_length, num)
    print("发送完毕",max)
    data_recv, addr  = udp.recvfrom(BUFFERSIZE)
    print("等待接收是否收完:", data_recv.decode("utf8"))
    if data_recv.decode("utf8") =="接收完毕":
        print("第%d次传送完毕", i)
        break
    i += 1
tc2 = time.process_time()
print("发送完毕时间:",tc2- tc )
udp.close()
udp 传送文件100M大约0.6秒,可是须要确认丢包,只能保证接受95%而已

端口复用

socketserver中端口复用是设置

socketserver.ThreadingTCPServer.allow_reuse_address = True

 

设置缓冲区大小网络

TCP有自动控制网络拥塞,UDP没有控制拥塞机制

 默认缓冲区,接受和发送的默认是65536

print(sk.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF))      #65536
print(sk.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF))       #65536
sk.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 100000)
sk.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 100000)
默认缓冲区大小 设置缓冲区大小

 

扩展怎么本身填包,包头

利用setsockopt来设置 

何时用tcp,何时用udp?

那么如何选择tcp仍是udp?

先看下人家怎么选

1 HTTP,  http协议如今已经深刻影响到咱们的方方面面,重要性就不说了, 它采用的是tcp 协议,为何使用tcp, 由于它传输的内容是不能够出现丢失,乱序等各类错误的,其次它须要跨平台实现,而tcp知足了这个要求,发展到今天,http享受了tcp带来的简洁高效和跨平台,可是也承受了tcp的各类缺点,例如缺乏tcp keep alive机制[这个实际上是后来添加的支持,并不是广泛实现], tcp协议栈的实现问题引起的难以支持海量用户并发链接[只能经过dns等级别的集群或者cdn来实现],协议太复杂致使很难模块化处理[其实这个问题已经在nginx解决了,nginx经过模块化和对协议的分段处理机制,并引入消息机制,避免了多进程[线程]的频繁切换,相比apache等老牌web服务器软件,在应对大量用户上拥有极大的优点。 即便站在今天的角度看,http也确实应该选择tcp.

2 FTP, 这个协议比http更加古老,它采用的也是tcp协议, 由于它的每个指令,或者文件传输的数据流,都须要保证可靠性,同时要求在各类平台上普遍支持,那么就只能选择tcp, 和http不一样,它采用了noop指令机制来处理tcp缺乏keep alive机制带来的问题,也就是客户端必须每过一段时间,若是没有发送其余指令,就必须发送一个noop指令给服务器,避免被服务器认为是死链接。 Ftp的缺陷在哪里呢?,其次它的文件传输是采用新的数据链接来执行,等于1个用户须要2个链接,其次当一个文件正在传输的时候,你没法进行其余操做,例如列表,也许你能够把它看成是一一对应的典范,由于这样咱们能够直接用命令行进行控制,可是不少用户实际上是须要在下载的时候同时进行列表等操做的,为了解决这个问题,不少客户端只要开启多个指令链接[和数据链接],这样一来,无形中额外带给了Ftp服务器不少压力,而采用udp可靠传输就不存在这个问题,可是udp可靠传输是没有跨平台支持的,这样是鱼和熊掌不可兼得,对于这样一个简单的开放协议的实现,tcp是个好选择。

3 POP3/SMTP, 常见的邮件协议,没什么好说的,反应--应答模式,跨平台要求,所以tcp是个选择,这个协议的悲剧在于,当初没有考虑到邮件附件会愈来愈大的问题,所以它的实现中将附件文件采用了base64编码格式,用文本模式进行发送,致使产生了大量的额外流量。

4 TFTP ,这是一个很是古老的用于内部传输小文件的协议,没有FTP那么多功能,采用的是udp协议,经过在包中加入包头信息,用尽量简单的代码来实现小文件传输,注意是小文件,是一个值得参考的udp改造应用范例.

5 一般的voip,实时视频流等,一般会采用udp协议,这是之内这些应用能够容许丢包,不少人可能认为是udp的高效率因此才在这方面有普遍应用,这我不敢苟同,我我的认为,之因此采用udp,是由于这些传输容许丢包,这是一个最大的前提

那么如今来概括一下

[1] 若是数据要求完整,不容许任何错误发生

     [A] 应用层协议开放模式 [例如http ftp]

           建议选择tcp,几乎是惟一选择.

    [B] 应用曾协议封闭模式 [例如游戏]

         (1) 大量链接

               [a] 长链接  

                    [一] 少许数据传输

                           优先考虑可靠udp传输 , tcp建议在20000链接如下使用.

                   [二] 大流量数据传输

                          只有在10000链接如下能够考虑tcp , 其余状况优先使用udp可靠传输    

               [b] 短链接

                  [一] 少许数据传输

                         建议使用udp标准模式, 加入序列号, 若是链接上限不超2万,能够考虑tcp

                 [二] 大流量数据传输

                       10000链接如下考虑tcp ,其余状况使用udp可靠传输

               在遇到海量链接的状况下,建议优先考虑udp可靠传输,使用tcp,因为tcp/ip栈的链表实现的影响,链接越多,性能降低越快,而udp能够实现队列,是一条平滑的直线,几乎没有性能影响.

         (2) 有限链接 [一般小于2000 , 通常每服务器为几百到1000左右]

                  [a] 长链接  

                        除非有数据的实时性要求,优先考虑tcp,不然使用udp可靠传输.        

                 

               [b] 短链接

                      优先考虑tcp.

              在有限链接的状况下,使用tcp能够减小代码的复杂性,增长普遍的移植性,而且不须要考虑性能问题.  

[2] 容许丢包,甚至能够乱序

           [a] 对实时性要求比较高,例如voip , 那么udp是最优选择.

           [b] 部分数据容许丢包,部分数据要求完整,部分有实时性要求,一般这样的需求是出如今游戏里, 这时候, 基于udp协议改造的udp多路可靠传输[同时支持不可靠模式],基本是惟一能知足的,固然也可使用tcp来传输要求完整的数据,可是一般不建议,由于同时使用tcp和udp传输数据,会致使代码臃肿复杂度增长,udp可靠传输彻底能够替代tcp.

           [c]部分数据优先传输,部分可丢弃数据在规定时效内传输, 这一般是实时视频流, 在有限链接模式下,能够考虑tcp+udp , 可是一般, 可靠udp传输是最好的选择.     

总结:

  要求不丢包的、兼容性强的、连接在10000如下的就用TCP,时效性高的、连接数高的用udp 

tcp快仍是udp快?

传送100M的文件tcp发送大约用时间1秒,接受也差很少1秒,是按照每次8000接的,若是改用最大发,应该更快

视频,直播用udp仍是tcp,效果差别有多少?安全性能有多少?有待完善

  总结找到的内容,应该说:

  1. 网页上的视频是基于HTTP/HTTPS,传输层是TCP

  2. QQ视频聊天等是基于UDP

  3. 甚至有的应用使用p2p协议,传输层应该也是TCP

  4. 经过http进行流化视频有不少种方法

  5. 传输视频还有不少其余的应用层协议

  一方面,在网页上看视频能够忍受缓冲5s看到更清楚的视频,因此用TCP问题不大,在网络状况较好的状况下更是如此。视频聊天时毫不能容忍等待5s才听到对方的回话,因此用UDP更合适。使用TCP仍是UDP是在网络条件有限的状况下对“实时性”和“传输质量”之间的权衡,不是必须用TCP或者UDP。

一次传输多少,会形成缓冲区丢包

UDP才有丢包,大约95%左右,一次缓冲区为65536,能够修改

在linux和windows里,对于空包,回撤,和直接断开链接的处理不一样

对于直接回撤的空包,tcp和udp在python3.7都能直接接收

相关文章
相关标签/搜索