zoukankan      html  css  js  c++  java
  • python 网络编程 socket编程

     一、服务端和客户端

    BS架构 (腾讯通软件:server+client)

    CS架构 (web网站)

    C/S架构与socket的关系:

    我们学习socket就是为了完成C/S架构的开发

    二、OSI七层模型

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

    每层运行常见物理设备

    详细参考:

    http://www.cnblogs.com/linhaifeng/articles/5937962.html#_label4

    学习socket一定要先学习互联网协议:

    1.首先:本节课程的目标就是教会你如何基于socket编程,来开发一款自己的C/S架构软件

    2.其次:C/S架构的软件(软件属于应用层)是基于网络进行通信的

    3.然后:网络的核心即一堆协议,协议即标准,你想开发一款基于网络通信的软件,就必须遵循这些标准。

    4.最后:就让我们从这些标准开始研究,开启我们的socket编程之旅

    TCP/IP协议族包括运输层、网络层、链路层。

    三、socket层,不懂看图就明白了。

    Socket是介于应用层和传输层之间。

    四、socket是什么

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

    所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

     扫盲篇:

    1 将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序
    2 
    3 而程序的pid是同一台机器上不同进程或者线程的标识(Google Chrome会有多个PID)

    五、套接字发展史及分类

    套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。 

    1、基于文件类型的套接字家族

    套接字家族的名字:AF_UNIX

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

    2、基于网络类型的套接字家族

    套接字家族的名字:AF_INET

    (还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

    六、套接字工作流程

          生活中的场景,你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。    

    生活中的场景就解释了这工作原理,也许TCP/IP协议族就是诞生于生活中,这也不一定。

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

    1、socket模块发送和接收消息

    示例:模拟发送消息和接收消息的过程

    tcp服务端(server)

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 import socket
     6                              
     7 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  #买手机
     8 phone.bind(('127.0.0.1',8000))  #绑定手机卡   #改成服务端网卡IP地址和端口
     9 phone.listen(5)  #开机  5的作用是最大挂起连接数   #backlog连接池(也叫半链接)
    10 print('------------->')
    11 conn,addr=phone.accept()  #等电话
    12 
    13 msg=conn.recv(1024)  #收消息
    14 print('客户端发来的消息是:',msg)
    15 conn.send(msg.upper())  #发消息
    16 
    17 conn.close()
    18 phone.close()

    执行结果:

    1 ------------->

    tcp客户端(client)

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 import socket
     6 
     7 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
     8 
     9 phone.connect(('127.0.0.1',8000)) #拔通电话   #改成服务端网卡IP地址和端口
    10 
    11 phone.send('hello'.encode('utf-8'))  #发消息
    12 data=phone.recv(1024)
    13 print('收到服务端的发来的消息: ',data)
    14 
    15 phone.close()

    执行结果:

    1 收到服务端的发来的消息:  b'HELLO'


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

    主动断开连接 :FIN_WAIT_1
    被动断开连接: FIN_WAIT_2
    马上断开连接: TIME_WAIT

    socket中TCP的三次握手建立连接详解

    流程如下:

    • 客户端向服务器发送一个SYN J
    • 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
    • 客户端再向服务器发一个确认ACK K+1

    只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:

    image

                        图1、socket中发送的TCP三次握手

    从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

    总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。

    socket中TCP的四次握手释放连接详解

    上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:

    image

                     图2、socket中发送的TCP四次握手

    图示过程如下:

    • 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
    • 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
    • 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
    • 接收到这个FIN的源发送端TCP对它进行确认。

    这样每个方向上都有一个FIN和ACK。

     

    总结:

    四次挥手断开连接原则:

    记住一条原则:谁先发起客户端请求,谁先断开连接
    但是在大并发情况下,大部分都是服务端先断开连接,不会保留连接。因为每一分钟都有很多人在访问网站。

    3、socket()模块函数用法

     1 import socket
     2 socket.socket(socket_family,socket_type,protocal=0)
     3 socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。
     4 
     5 获取tcp/ip套接字
     6 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     7 
     8 获取udp/ip套接字
     9 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    10 
    11 由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。
    12 例如tcpSock = socket(AF_INET, SOCK_STREAM)

    服务端套接字函数

    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()               关闭套接字

    面向锁的套接字方法
    s.setblocking()   设置套接字的阻塞与非阻塞模式
    s.settimeout()    设置阻塞套接字操作的超时时间
    s.gettimeout()    得到阻塞套接字操作的超时时间

    面向文件的套接字的函数
    s.fileno()        套接字的文件描述符
    s.makefile()   创建一个与该套接字相关的文件

     

    七、基于TCP的套接字

    tcp语法格式:

    tcp服务端

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

    tcp客户端

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

    1、基于tcp实现:客户端发送空格,服务端也会接收

    示例:

    tcp_server端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4 
     5 from socket import *
     6 
     7 ip_port = ('127.0.0.1', 8080)
     8 back_log = 5
     9 buffer_size = 1024
    10 
    11 tcp_server = socket(AF_INET, SOCK_STREAM)
    12 tcp_server.bind(ip_port)
    13 tcp_server.listen(back_log)
    14 
    15 print('服务端开始运行了')
    16 conn, addr = tcp_server.accept()  #服务器阻塞
    17 print('双向链接是', conn)
    18 print('客户端地址', addr)
    19 
    20 while True:
    21     data = conn.recv(buffer_size)    #收缓存为空,则阻塞
    22     print('客户端发来的消息是', data.decode('utf-8'))
    23     conn.send(data.upper())
    24 conn.close()
    25 
    26 tcp_server.close()

    tcp_client端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4 
     5 from socket import *
     6 
     7 ip_port = ('127.0.0.1', 8080)
     8 back_log = 5
     9 buffer_size = 1024
    10 
    11 tcp_client = socket(AF_INET, SOCK_STREAM)
    12 tcp_client.connect(ip_port)
    13 
    14 while True:
    15     msg = input('>>:')          #发送空格到自己的发送缓存中
    16     # msg=input('>>:').strip()  #去掉空格
    17     tcp_client.send(msg.encode('utf-8'))
    18     print('客户端已经发送消息')
    19     data = tcp_client.recv(buffer_size)  #收缓存为空则阻塞
    20     print('收到服务端发来的消息是', data.decode('utf-8'))
    21 
    22 tcp_client.close()

    执行结果:

    复制代码
     1 server:
     2 服务端开始运行了
     3 双向链接是 <socket.socket fd=304, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 53365)>
     4 客户端地址 ('127.0.0.1', 53365)
     5 客户端发来的消息是  
     6 
     7 client:
     8 >>: 
     9 客户端已经发送消息
    10 收到服务端发来的消息是  
    复制代码

    实验过程中遇到的问题:

    在重启服务端时可能会遇到如下报错:

      这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)。

    解决方法:

    法一:在程序中处理

    1 #加入一条socket配置,重用ip和端口
    2 
    3 phone=socket(AF_INET,SOCK_STREAM)
    4 phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
    5 phone.bind(('127.0.0.1',8080))

    法二:在linux系统中,通过调整系统内核参数的方式来解决

     1 发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,(系统优化的一个优化点)
     2 
     3 vi /etc/sysctl.conf
     4 
     5 编辑文件,加入以下内容:
     6 net.ipv4.tcp_syncookies = 1
     7 net.ipv4.tcp_tw_reuse = 1
     8 net.ipv4.tcp_tw_recycle = 1
     9 net.ipv4.tcp_fin_timeout = 30
    10  
    11 然后执行 /sbin/sysctl -p 让参数生效。
    12  
    13 net.ipv4.tcp_syncookies = 1   表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
    14 
    15 net.ipv4.tcp_tw_reuse = 1     表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
    16 
    17 net.ipv4.tcp_tw_recycle = 1   表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
    18 
    19 net.ipv4.tcp_fin_timeout = 30 修改系統默认的 TIMEOUT 时间

    八、基于UDP的套接字

    udp语法格式:

    udp服务端

    1 ss = socket()   #创建一个服务器的套接字
    2 ss.bind()       #绑定服务器套接字
    3 inf_loop:       #服务器无限循环
    4     cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
    5 ss.close()

    udp客户端

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

    1、基于upd实现方法

    示例:

    udp_server端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 ip_port=('127.0.0.1',8080)
     7 buffer_size = 1024
     8 
     9 udp_server = socket(AF_INET,SOCK_DGRAM)  #数据报套接字
    10 udp_server.bind(ip_port)
    11 
    12 while True:
    13     data,addr=udp_server.recvfrom(buffer_size)
    14     print(data)
    15 
    16     udp_server.sendto(data.upper(),addr)  #upper() 小写变大写

    udp_client端:

     1 from socket import *
     2 ip_port=('127.0.0.1',8080)  #服务端IP+端口
     3 buffer_size = 1024
     4 
     5 udp_client=socket(AF_INET,SOCK_DGRAM) #udp数据报套接字
     6 
     7 while True:
     8     msg=input('>>:').strip()
     9     udp_client.sendto(msg.encode('utf-8'),ip_port)
    10     #数据,ip地址+端口
    11     data,addr=udp_client.recvfrom(buffer_size)
    12     print(data.decode('utf-8'))

    执行结果:

    先运行udp_server,再运行udp_client。

    服务端返回结果:

    1 b'sfdsfds'  #bytes类型
    2 b'fdsfds'
    3 b'fsdfds'
    4 b'sdfdsf'

    在客户端输入:

    1 >>:sfdsfds  #在客户端输入
    2 SFDSFDS     #服务端返回的结果,把客户端输入的字符变大写
    3 
    4 >>:fdsfds
    5 FDSFDS
    6 
    7 >>:fsdfds
    8 FSDFDS

    2、实现ntp时间服务器

    示例:

    tup_server端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4 
     5 #实现ntp时间服务器
     6 import time
     7 from socket import *
     8 ip_port=('127.0.0.1',8080)
     9 buffer_size = 1024
    10 
    11 udp_server = socket(AF_INET,SOCK_DGRAM)  #数据报套接字
    12 udp_server.bind(ip_port)
    13 
    14 while True:
    15     data,addr=udp_server.recvfrom(buffer_size)
    16     print(data)
    17 
    18     if not data:
    19         fmt='%Y-%m-%d %X'   #如果用户没有输入时间,就返回默认格式
    20     else:
    21         fmt=data.decode('utf-8')
    22     back_time=time.strftime(fmt)
    23 
    24     udp_server.sendto(back_time.encode('utf-8'),addr)

    udp_client端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 ip_port=('127.0.0.1',8080)  #服务端IP+端口
     7 buffer_size = 1024
     8 
     9 udp_client=socket(AF_INET,SOCK_DGRAM) #数据报套接字
    10 
    11 while True:
    12     msg=input('>>:').strip()
    13     udp_client.sendto(msg.encode('utf-8'),ip_port)
    14 
    15     data,addr=udp_client.recvfrom(buffer_size)
    16     print('ntp服务器的标准时间是',data.decode('utf-8'))

    执行结果:

    运行udp_server,再运行udp_client,然后在udp_client里输入:

    1 >>:%Y   #在客户端输入%Y
    2 ntp服务器的标准时间是 2017  #就会返回服务端的时间
    3 >>:%m-%d-%Y
    4 ntp服务器的标准时间是 01-03-2017
    5 >>:

    3、基于tcp实现远程执行命令

    备注:因系统差异,请尽量把程序放在linux服务器上面运行,windows上面可能会报错。

    socket_server_tcp服务端 (在linux上面运行)

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 import subprocess
     7 
     8 ip_port = ('192.168.1.135', 8000)
     9 back_log = 5
    10 buffer_size = 1024
    11 
    12 tcp_server = socket(AF_INET, SOCK_STREAM)
    13 tcp_server.bind(ip_port)
    14 tcp_server.listen(back_log)
    15 
    16 while True:
    17     conn,addr=tcp_server.accept()
    18     print('新的客户端链接',addr)
    19     while True:
    20         #收
    21         try:
    22             cmd=conn.recv(buffer_size)
    23             #if not cmd:break  MAC笔记本处理方法
    24             print('收到客户端的命令',cmd)
    25 
    26             #执行命令,得到命令的运行结果cmd_res
    27             res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
    28                                  stderr=subprocess.PIPE,
    29                                  stdout=subprocess.PIPE,
    30                                  stdin=subprocess.PIPE)
    31             err=res.stderr.read()
    32             if err:
    33                 cmd_res=err
    34             else:
    35                 cmd_res=res.stdout.read()
    36             #发
    37             conn.send(cmd_res)
    38         except Exception as e:
    39             print(e)
    40             break
    41 conn.close()

    socket_client_tcp客户端(windows系统上面运行)

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 
     7 # ip_port = ('127.0.0.1', 8082)
     8 ip_port = ('192.168.1.135', 8000)
     9 back_log = 5
    10 buffer_size = 1024
    11 
    12 tcp_client = socket(AF_INET, SOCK_STREAM)
    13 tcp_client.connect(ip_port)
    14 
    15 while True:
    16     cmd=input('>>:').strip()
    17     if not cmd:continue
    18     if cmd == 'quit':break
    19 
    20     tcp_client.send(cmd.encode('utf-8'))
    21     cmd_res=tcp_client.recv(buffer_size)
    22     # print('命令的执行结果是 ',cmd_res.decode('gbk'))
    23     print('命令的执行结果是 ',cmd_res.decode('utf-8'))
    24 tcp_client.close()

    执行结果:

    在客户端执行命令:

     1 >>:df -h
     2 命令的执行结果是  Filesystem      Size  Used Avail Use% Mounted on
     3 /dev/sda3       9.6G  1.8G  7.3G  20% /
     4 tmpfs           931M     0  931M   0% /dev/shm
     5 /dev/sda1       190M   32M  149M  18% /boot
     6 /dev/sr0        4.4G  4.4G     0 100% /opt
     7 
     8 >>:dir
     9 命令的执行结果是  s3.py       server_ssh.py     socket_server.py
    10 server.py  socket_clinet_udp.py  socket_server_udp.py
    11 
    12 服务端返回结果:
    13 [root@python3 scripts]# python socket_server.py
    14 新的客户端链接 ('192.168.1.115', 53569)
    15 收到客户端的命令 b'df -h'
    16 收到客户端的命令 b'dir'

    4、基于udp实现远程执行命令

    socket_server_udp服务端:

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 import subprocess
     7 
     8 ip_port = ('192.168.1.135', 8000)
     9 back_log = 5
    10 buffer_size = 1024
    11 
    12 udp_server = socket(AF_INET, SOCK_DGRAM)
    13 udp_server.bind(ip_port)
    14 
    15 while True:
    16     cmd,addr=udp_server.recvfrom(buffer_size)
    17     print(cmd)
    18 
    19     #执行命令,得到命令的运行结果cmd_res
    20     res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
    21                            stderr=subprocess.PIPE,
    22                            stdout=subprocess.PIPE,
    23                            stdin=subprocess.PIPE)
    24     err = res.stderr.read()
    25     if err:
    26         cmd_res = err
    27     else:
    28         cmd_res = res.stdout.read()
    29 
    30     if not cmd_res:  # 判断为空的情况
    31         cmd_res = '执行成功'.encode('gbk')  #linux改成utf-8
    32     print(cmd_res)
    33     #发
    34     udp_server.sendto(cmd_res,addr)

    socket_clinet_udp客户端:

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 
     7 ip_port = ('192.168.1.135', 8000)
     8 # ip_port = ('192.168.12.63', 8000)
     9 back_log = 5
    10 buffer_size = 10240
    11 
    12 udp_client = socket(AF_INET, SOCK_DGRAM)
    13 
    14 while True:
    15     cmd=input('>>:').strip()
    16     if not cmd:continue
    17     if cmd == 'quit':break
    18 
    19     udp_client.sendto(cmd.encode('utf-8'),ip_port)
    20     cmd_res,addr=udp_client.recvfrom(buffer_size)
    21     print('命令的执行结果是 ',cmd_res.decode('gbk'))  #如果在linux上面运行,把gbk改成utf-8
    22 udp_client.close()

    执行结果:

    复制代码
     1 #在客户端执行命令:(在windows服务器上面运行)
     2 
     3 >>:cat /etc/passwd
     4 命令的执行结果是  root:x:0:0:root:/root:/bin/bash
     5 bin:x:1:1:bin:/bin:/sbin/nologin
     6 daemon:x:2:2:daemon:/sbin:/sbin/nologin
     7 adm:x:3:4:adm:/var/adm:/sbin/nologin
     8 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
     9 sync:x:5:0:sync:/sbin:/bin/sync
    10 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
    11 halt:x:7:0:halt:/sbin:/sbin/halt
    12 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
    13 uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
    14 operator:x:11:0:operator:/root:/sbin/nologin
    15 games:x:12:100:games:/usr/games:/sbin/nologin
    16 gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
    17 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
    18 nobody:x:99:99:Nobody:/:/sbin/nologin
    19 dbus:x:81:81:System message bus:/:/sbin/nologin
    20 vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin
    21 abrt:x:173:173::/etc/abrt:/sbin/nologin
    22 haldaemon:x:68:68:HAL daemon:/:/sbin/nologin
    23 ntp:x:38:38::/etc/ntp:/sbin/nologin
    24 saslauth:x:499:76:Saslauthd user:/var/empty/saslauth:/sbin/nologin
    25 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
    26 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
    27 tcpdump:x:72:72::/:/sbin/nologin
    28 
    29 
    30 #服务端会返回相同结果:(在linux服务器上面运行)
    31 
    32 [root@python3 scripts]# python socket_server_udp.py 
    33 b'cat /etc/passwd'
    34 b'root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    adm:x:3:4:adm:/var/adm:/sbin/nologin
    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
    sync:x:5:0:sync:/sbin:/bin/sync
    shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
    halt:x:7:0:halt:/sbin:/sbin/halt
    mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
    uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
    operator:x:11:0:operator:/root:/sbin/nologin
    games:x:12:100:games:/usr/games:/sbin/nologin
    gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
    ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
    nobody:x:99:99:Nobody:/:/sbin/nologin
    dbus:x:81:81:System message bus:/:/sbin/nologin
    vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin
    abrt:x:173:173::/etc/abrt:/sbin/nologin
    haldaemon:x:68:68:HAL daemon:/:/sbin/nologin
    ntp:x:38:38::/etc/ntp:/sbin/nologin
    saslauth:x:499:76:Saslauthd user:/var/empty/saslauth:/sbin/nologin
    postfix:x:89:89::/var/spool/postfix:/sbin/nologin
    sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
    tcpdump:x:72:72::/:/sbin/nologin
    '
    复制代码

    九、recv与recvfrom的区别

    1、收发原理详解:

    发消息:都是将数据发送到己端的发送缓冲中

    收消息:都是从己端的缓冲区中收

    2、发消息二者类似,收消息确实有区别的?

    tcp协议:send发消息,recv收消息

    (1)如果收消息缓冲区里的数据为空,那么recv就会阻塞

    (2)tcp基于链接通信,如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空

    udp协议:sendto发消息,recvfrom收消息

    (1)如果如果收消息缓冲区里的数据为“空”,recvfrom不会阻塞

    (2)recvfrom收的数据小于sendinto发送的数据时,数据丢失

    (3)只有sendinto发送数据没有recvfrom收数据,数据丢失

    注意:

    1.你单独运行上面的udp的客户端,你发现并不会报错,相反tcp却会报错,因为udp协议只负责把包发出去,对方收不收,我根本不管,而tcp是基于链接的,必须有一个服务端先运行着,客户端去跟服务端建立链接然后依托于链接才能传递消息,任何一方试图把链接摧毁都会导致对方程序的崩溃。

    2.上面的udp程序,你注释任何一条客户端的sendinto,服务端都会卡住,为什么?因为服务端有几个recvfrom就要对应几个sendinto,哪怕是sendinto(b'')那也要有。

    3.总结:

    1.udp的sendinto不用管是否有一个正在运行的服务端,可以己端一个劲的发消息

    2.udp的recvfrom是阻塞的,一个recvfrom(x)必须对一个一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

    3.tcp的协议数据不会丢,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

    十、粘包

    须知:只有TCP有粘包现象,UDP永远不会粘包。(原因详见第3点)

    1、socket收发消息的原理

                                                           socket发送原理图

     

    2、为什么会出现所谓的粘包

    原因:接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

      此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

    1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
    2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
    3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。

    3、tcp会发生粘包的两种情况如下:

    1、发送端多次send间隔较短,并且数据量较小,tcp会通过Nagls算法,封装成一个包,发送到接收端,接收端不知道这个包由几部分组成,所以就会产生粘包。

    2、数据量发送的大,接收端接收的小,再接一次,还会出现上次没有接收完成的数据。就会出现粘包。

    示例1: 发送端多次send间隔较短,并且数据量较小,tcp会通过Nagls算法,封装成一个包,发送到接收端,接收端不知道这个包由几部分组成,所以就会产生粘包。

    server服务端:

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 ip_port=('127.0.0.1',8082)
     7 back_log=5
     8 buffer_size=1024
     9 
    10 tcp_server=socket(AF_INET,SOCK_STREAM)
    11 tcp_server.bind(ip_port)
    12 tcp_server.listen(back_log)
    13 
    14 conn,addr=tcp_server.accept()
    15 
    16 data1=conn.recv(buffer_size)  #指定buffer_size ,得到的结果就是通过Nagle算法,随机接收次数。
    17 print('第1次数据',data1)
    18 
    19 data2=conn.recv(buffer_size)
    20 print('第2次数据',data2)
    21 
    22 data3=conn.recv(buffer_size)
    23 print('第3次数据',data3)

    client客户端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 import time
     7 
     8 ip_port=('127.0.0.1',8082)
     9 back_log=5
    10 buffer_size=1024
    11 
    12 tcp_client=socket(AF_INET,SOCK_STREAM)
    13 tcp_client.connect(ip_port)
    14 
    15 tcp_client.send('hello'.encode('utf-8'))
    16 tcp_client.send('world'.encode('utf-8'))
    17 tcp_client.send('egon'.encode('utf-8'))
    18 
    19 
    20 time.sleep(1000)

    执行结果:

    1 第1次数据 b'helloworldegon'  #不确定接收次数。

    示例2:指定接收字节数,相当于服务端知道接收长度,就不会出现粘包现象

    粘包服务端

     1 from socket import *
     2 ip_port=('127.0.0.1',8080)
     3 back_log=5
     4 buffer_size=1024
     5 
     6 tcp_server=socket(AF_INET,SOCK_STREAM)
     7 tcp_server.bind(ip_port)
     8 tcp_server.listen(back_log)
     9 
    10 conn,addr=tcp_server.accept()
    11 
    12 data1=conn.recv(5)  #指定每次接收字节数,就不会出现粘包现象
    13 print('第一次数据',data1)
    14 
    15 data2=conn.recv(5)
    16 print('第2次数据',data2)
    17 
    18 data3=conn.recv(5)
    19 print('第3次数据',data3)

    粘包客户端

     1 from socket import *
     2 import time
     3 ip_port=('127.0.0.1',8080)
     4 back_log=5
     5 buffer_size=1024
     6 
     7 tcp_client=socket(AF_INET,SOCK_STREAM)
     8 tcp_client.connect(ip_port)
     9 
    10 tcp_client.send('hello'.encode('utf-8'))
    11 tcp_client.send('world'.encode('utf-8'))
    12 tcp_client.send('egon'.encode('utf-8'))
    13 
    14 
    15 time.sleep(1000)

    执行结果:

    1 第1次数据 b'hello'   #不会出现粘包现象,发送三次,就接收三次
    2 第2次数据 b'world'
    3 第3次数据 b'egon'

    示例3:数据量发送的大,接收端接收的小,再接一次,还会出现上次没有接收完成的数据。就会出现粘包。

    粘包服务端

     1 from socket import *
     2 ip_port=('127.0.0.1',8080)
     3 back_log=5
     4 buffer_size=1024
     5 
     6 tcp_server=socket(AF_INET,SOCK_STREAM)
     7 tcp_server.bind(ip_port)
     8 tcp_server.listen(back_log)
     9 
    10 conn,addr=tcp_server.accept()
    11 
    12 data1=conn.recv(1)
    13 print('第1次数据',data1)
    14 
    15 # data2=conn.recv(5)
    16 # print('第2次数据',data2)
    17 #
    18 # data3=conn.recv(1)
    19 # print('第3次数据',data3)

    粘包客户端

     1 from socket import *
     2 import time
     3 ip_port=('127.0.0.1',8080)
     4 back_log=5
     5 buffer_size=1024  #接收的数据只有1024
     6 
     7 tcp_client=socket(AF_INET,SOCK_STREAM)
     8 tcp_client.connect(ip_port)
     9 
    10 tcp_client.send('helloworldegon'.encode('utf-8'))
    11 
    12 time.sleep(1000)

    执行结果: 

    1 第1次数据 b'h'
    2 第2次数据 b'ellow'  #发送的数据过大,接收的数据设置的较小,就会出现导致粘包 
    3 第3次数据 b'o'

    4、udp永远不会粘包

    示例:

    udp不粘包服务端

     1 from socket import *
     2 ip_port=('127.0.0.1',8080)
     3 buffer_size=1024
     4 
     5 udp_server=socket(AF_INET,SOCK_DGRAM) #数据报
     6 udp_server.bind(ip_port)
     7 
     8 data1=udp_server.recvfrom(10)
     9 print('第1次',data1)
    10 
    11 data2=udp_server.recvfrom(10)
    12 print('第2次',data2)
    13 
    14 
    15 data3=udp_server.recvfrom(10)
    16 print('第3次',data3)
    17 
    18 data4=udp_server.recvfrom(2)
    19 print('第4次',data4)

    udp不粘包客户端

    1 from socket import *
    2 ip_port=('127.0.0.1',8080)
    3 buffer_size=1024
    4 
    5 udp_client=socket(AF_INET,SOCK_DGRAM) #udp叫数据报
    6 
    7 udp_client.sendto(b'hello',ip_port)
    8 udp_client.sendto(b'world',ip_port)
    9 udp_client.sendto(b'egon',ip_port)

     执行结果:

    1 第1次 (b'hello', ('127.0.0.1', 57813))  #udp没有Nagle优化算法
    2 第2次 (b'world', ('127.0.0.1', 57813))  #每次都是一次独立的包,所以不会出现粘包现象
    3 第3次 (b'egon', ('127.0.0.1', 57813))

    5、qq聊天(由于udp无连接,所以可以同时多个客户端去跟服务端通信)

    udp_socket_server服务端代码:

     1 #实现类似于QQ聊天功能
     2 
     3 #!/usr/bin/env python
     4 # -*- coding:utf-8 -*-      
     5 #Author: nulige
     6 
     7 import socket
     8 ip_port=('127.0.0.1',8081)
     9 udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    10 udp_server_sock.bind(ip_port)
    11 
    12 while True:
    13     qq_msg,addr=udp_server_sock.recvfrom(1024)
    14     print('来自[%s:%s]的一条消息:33[1;44m%s33[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
    15     back_msg=input('回复消息: ').strip()
    16 
    17     udp_server_sock.sendto(back_msg.encode('utf-8'),addr)

    udp_socket_client客户端:

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4 
     5 import socket
     6 BUFSIZE=1024
     7 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
     8 
     9 qq_name_dic={
    10     '努力哥':('127.0.0.1',8081),
    11     '刘哥':('127.0.0.1',8081),
    12     '李哥':('127.0.0.1',8081),
    13     '王哥':('127.0.0.1',8081),
    14 }
    15 
    16 while True:
    17     qq_name=input('请选择聊天对象: ').strip()   #选择字典中的聊天对象,再发送消息
    18     while True:
    19         msg=input('请输入消息,回车发送: ').strip()
    20         if msg == 'quit':break
    21         if not msg or not qq_name or qq_name not in qq_name_dic:continue
    22         udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
    23 
    24         back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
    25         print('来自[%s:%s]的一条消息:33[1;44m%s33[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
    26 
    27 udp_client_socket.close()

    执行结果:

    先启动服务端,再启动客户端向服务端发送消息:

     1 #客户端发送消息
     2 
     3 请选择聊天对象: 努力哥
     4 请输入消息,回车发送: 吃饭没有
     5 来自[127.0.0.1:8081]的一条消息:还没吃呢
     6 请输入消息,回车发送: 
     7 
     8 #服务端接收消息
     9 
    10 来自[127.0.0.1:62642]的一条消息:吃饭没有
    11 回复消息: 还没吃呢

    补充知识:

    1、tcp是可靠传输

      tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的。

    2、udp是不可靠传输

       udp发送数据,对端是不会返回确认信息的,因此不可靠。

    十一、解决粘包的办法

    法一:比较(LOW)版本

     示例:

    low_socket_server服务端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 #low版解决粘包版本服务端
     6 from socket import *
     7 import subprocess
     8 ip_port=('127.0.0.1',8080)
     9 back_log=5
    10 buffer_size=1024
    11 
    12 tcp_server=socket(AF_INET,SOCK_STREAM)
    13 tcp_server.bind(ip_port)
    14 tcp_server.listen(back_log)
    15 
    16 while True:
    17     conn,addr=tcp_server.accept()
    18     print('新的客户端链接',addr)
    19     while True:
    20         #收消息
    21         try:
    22             cmd=conn.recv(buffer_size)
    23             if not cmd:break
    24             print('收到客户端的命令',cmd)
    25 
    26             #执行命令,得到命令的运行结果cmd_res
    27             res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
    28                                  stderr=subprocess.PIPE,
    29                                  stdout=subprocess.PIPE,
    30                                  stdin=subprocess.PIPE)
    31             err=res.stderr.read()
    32             if err:
    33                 cmd_res=err
    34             else:
    35                 cmd_res=res.stdout.read()
    36 
    37             #发送消息
    38             if not cmd_res:
    39                 cmd_res='执行成功'.encode('gbk')
    40 
    41             length=len(cmd_res)  #计算长度
    42             conn.send(str(length).encode('utf-8')) #把长度发给客户端
    43             client_ready=conn.recv(buffer_size)    #卡着一个recv
    44             if client_ready == b'ready':  #如果收到客户端的ready消息,就说明准备好了。
    45                 conn.send(cmd_res)        #就可以send给客户端发送消息啦!
    46         except Exception as e:
    47             print(e)
    48             break

    low_socket_client客户端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 #low版解决粘包版客户端
     6 from socket import *
     7 ip_port=('127.0.0.1',8080)
     8 back_log=5
     9 buffer_size=1024
    10 
    11 tcp_client=socket(AF_INET,SOCK_STREAM)
    12 tcp_client.connect(ip_port)
    13 
    14 while True:
    15     cmd=input('>>: ').strip()
    16     if not cmd:continue
    17     if cmd == 'quit':break
    18 
    19     tcp_client.send(cmd.encode('utf-8'))
    20 
    21 
    22     #解决粘包
    23     length=tcp_client.recv(buffer_size)  #接收发送过来的长度(1024*8=8192,2**8192=可以接收的长度)
    24     tcp_client.send(b'ready')   #客户端再send给服务端,告诉服务端我准备好啦!
    25 
    26     length=int(length.decode('utf-8'))  #先解码,转成字符串的长度
    27     #解决思路:就是提前发一个头过去,告诉客户端需要接收的长度(分两步:1、发送发度 2、再次发送数据)
    28     recv_size=0   #接收的尺寸
    29     recv_msg=b''  #最后要拼接起来
    30     while recv_size < length:  #要收多大?,要先判断接收的尺寸<length
    31         recv_msg += tcp_client.recv(buffer_size)  #接收到的数据,拼接buffer_size,
    32         recv_size=len(recv_msg) #1024  #衡量自己接收了多少数据,有没有收完(统计recv_msg的长度)
    33 
    34 
    35     print('命令的执行结果是 ',recv_msg.decode('gbk'))
    36 tcp_client.close()

    执行结果:

    复制代码
     1 #客户端执行命令:
     2 >>: cat /etc/passwd
     3 命令的执行结果是  'cat' is not recognized as an internal or external command,
     4 operable program or batch file.
     5 
     6 >>: dir
     7 命令的执行结果是   Volume in drive D is SSD
     8  Volume Serial Number is 687D-EF64
     9 
    10  Directory of D:pythonday30
    11 
    12 2017/01/04  00:36    <DIR>          .
    13 2017/01/04  00:36    <DIR>          ..
    14 2017/01/03  11:39               613 client.py
    15 2017/01/03  11:40               597 client_01.py
    16 2017/01/03  11:40               597 client_02.py
    17 2017/01/04  00:35               770 low_socket_client.py
    18 2017/01/04  00:36             1,408 low_socket_server.py
    19 2017/01/03  15:54               438 ntp_clinet.py
    20 2017/01/03  16:01               591 ntp_server.py
    21 2017/01/03  19:36               206 s1_server.py
    22 2017/01/03  11:26               588 server.py
    23 2017/01/03  11:55               717 server01.py
    24 2017/01/03  23:57               603 socket_clinet_tcp.py
    25 2017/01/04  00:12               531 socket_clinet_udp.py
    26 2017/01/03  17:48             1,301 socket_server_tcp.py
    27 2017/01/03  18:27               897 socket_server_udp.py
    28 2017/01/03  15:38               440 udp_clinet.py
    29 2017/01/03  15:23               355 udp_server.py
    30               16 File(s)         10,652 bytes
    31                2 Dir(s)  638,994,411,520 bytes free
    32 
    33 >>: cd ..
    34 命令的执行结果是  执行成功
    35 
    36 
    37 #服务端返回结果:
    38 新的客户端链接 ('127.0.0.1', 54395)
    39 收到客户端的命令 b'cat /etc/passwd'
    40 收到客户端的命令 b'dir'
    41 收到客户端的命令 b'cd ..'
    复制代码

    总结:

    (为何low):  程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗。

    法二:节省网络传输版本(牛逼版本)

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

    示例:(没实现多客户端并发)

     tcp_socket_server服务端:

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 import subprocess
     7 import struct
     8 ip_port=('127.0.0.1',8080)
     9 back_log=5
    10 buffer_size=1024
    11 
    12 tcp_server=socket(AF_INET,SOCK_STREAM)
    13 tcp_server.bind(ip_port)
    14 tcp_server.listen(back_log)
    15 
    16 while True:
    17     conn,addr=tcp_server.accept()
    18     print('新的客户端链接',addr)
    19     while True:
    20         #收
    21         try:
    22             cmd=conn.recv(buffer_size)
    23             if not cmd:break
    24             print('收到客户端的命令',cmd)
    25 
    26             #执行命令,得到命令的运行结果cmd_res
    27             res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
    28                                  stderr=subprocess.PIPE,
    29                                  stdout=subprocess.PIPE,
    30                                  stdin=subprocess.PIPE)
    31             err=res.stderr.read()
    32             if err:
    33                 cmd_res=err
    34             else:
    35                 cmd_res=res.stdout.read()
    36 
    37             #发
    38             if not cmd_res:
    39                 cmd_res='执行成功'.encode('gbk')
    40 
    41             length=len(cmd_res)
    42 
    43             data_length=struct.pack('i',length)
    44             conn.send(data_length)
    45             conn.send(cmd_res)
    46         except Exception as e:
    47             print(e)
    48             break

     tcp_socket_client客户端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 import struct
     7 from functools import partial
     8 ip_port=('127.0.0.1',8080)
     9 back_log=5
    10 buffer_size=1024
    11 
    12 tcp_client=socket(AF_INET,SOCK_STREAM)
    13 tcp_client.connect(ip_port)
    14 
    15 while True:
    16     cmd=input('>>: ').strip()
    17     if not cmd:continue
    18     if cmd == 'quit':break
    19 
    20     tcp_client.send(cmd.encode('utf-8'))
    21 
    22 
    23     #解决粘包
    24     length_data=tcp_client.recv(4)
    25     length=struct.unpack('i',length_data)[0]
    26 
    27     recv_size=0
    28     recv_data=b''
    29     while recv_size < length:
    30         recv_data+=tcp_client.recv(buffer_size)
    31         recv_size=len(recv_data)
    32     print('命令的执行结果是 ',recv_data.decode('gbk'))
    33 tcp_client.close()

    执行结果:

    复制代码
     1 #客户端向服务器发送消息
     2 >>: dir
     3 命令的执行结果是   Volume in drive D is SSD
     4  Volume Serial Number is 687D-EF64
     5 
     6  Directory of D:pythonday31
     7 
     8 2017/01/05  18:31    <DIR>          .
     9 2017/01/05  18:31    <DIR>          ..
    10 2017/01/05  14:33               244 s1.py
    11 2017/01/05  18:29               752 s1_tcp_socket_client.py
    12 2017/01/05  18:31               679 s1_tcp_socket_client01.py
    13 2017/01/05  18:28             1,325 s1_tcp_socket_server.py
    14 2017/01/05  15:01               498 tcp_socket_client.py
    15 2017/01/05  15:00               670 tcp_socket_server.py
    16 2017/01/05  17:16               391 udp_socket_clinet.py
    17 2017/01/05  17:20               512 udp_socket_server.py
    18                8 File(s)          5,071 bytes
    19                2 Dir(s)  609,921,380,352 bytes free
    20 
    21 #服务端返回结果:
    22 新的客户端链接 ('127.0.0.1', 53585)
    23 收到客户端的命令 b'dir'
    复制代码

    十二、用到的相关模块知识讲解

     1、subprocess模块

    subprocess 作用:启动一个新的进程并与之通信

     语法:

    subprocess.Popen(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)

     参数:

    Popen类:    用Popen来创建进程,并与进程进行复杂的交互
    shell=True:  指定的命令行会通过shell来执行
    stdin :   标准输入
    stdout : 标准输出
    stderr :  标准错误的文件句柄
    PIPE :    管道 ,默认值 为: None, 表示不做重定向,管道可以用来接收数据。

     示例1:执行dir命令,就会交给shell解释器执行

     1 import subprocess  #导入模块
     2 
     3 命令:
     4 >>> subprocess.Popen("dir", shell=True)    #执行dir命令,交给shell解释器执行
     5 
     6 执行结果:
     7 <subprocess.Popen object at 0x00A7B950>
     8  Directory of C:Python3.5
     9 2016/11/21  14:14    <DIR>          .
    10 2016/11/21  14:14    <DIR>          ..
    11 2016/11/21  14:14    <DIR>          DLLs
    12 2016/11/21  14:14    <DIR>          Doc
    13 2016/11/21  14:14    <DIR>          include
    14 2016/11/21  14:14    <DIR>          Lib
    15 2016/11/21  14:14    <DIR>          libs
    16 2016/06/25  22:08            30,345 LICENSE.txt
    17 2016/06/25  21:48           340,667 NEWS.txt
    18 2016/06/25  22:02            39,576 python.exe
    19 2016/06/25  22:02            51,864 python3.dll
    20 2016/06/25  22:02         3,127,960 python35.dll
    21 2016/06/25  22:02            39,576 pythonw.exe
    22 2016/06/25  21:48             8,282 README.txt
    23 2016/11/21  14:14    <DIR>          Scripts
    24 2016/11/21  14:14    <DIR>          tcl
    25 2016/11/21  14:14    <DIR>          Tools
    26 2016/03/17  22:48            85,840 vcruntime140.dll
    27                8 File(s)      3,724,110 bytes
    28               10 Dir(s)  211,565,547,520 bytes free

    示例2:subprocess 把标准输出放入管道中,屏幕上就不会输出内容

     1 示例2:把标准输出放入管道中,屏幕上就不会输出内容。
     2 res=subprocess.Popen("dir", shell=True,stdout=subprocess.PIPE,stdin=subprocess.PIPE,stderr=subprocess.PIPE)   #执行dir命令,交给shell解释器执行,通过标准类型和subprocess.PIPE放入管道中。
     3 
     4 >>> res.stdout.read()  #读取管道里面的数据,在程序中,读取也不会输出到屏幕上。
     5 
     6 执行结果:
     7 b' Volume in drive C has no label.
     Volume Serial Number is 4C49-9FA8
    
     Directory of C:\Python3.5
    
    2016/11/21  14:14    <DIR>          .
    2016/11/21  14:14    <DIR>          ..
    2016/11/21  14:14    <DIR>          DLLs
    2016/11/21  14:14    <DIR>          Doc
    2016/11/21  14:14    <DIR>          include
    2016/11/21  14:14    <DIR>          Lib
    2016/11/21  14:14    <DIR>          libs
    2016/06/25  22:08            30,345 LICENSE.txt
    2016/06/25  21:48           340,667 NEWS.txt
    2016/06/25  22:02            39,576 python.exe
    2016/06/25  22:02            51,864 python3.dll
    2016/06/25  22:02         3,127,960 python35.dll
    2016/06/25  22:02            39,576 pythonw.exe
    2016/06/25  21:48             8,282 README.txt
    2016/11/21  14:14    <DIR>          Scripts
    2016/11/21  14:14    <DIR>          tcl
    2016/11/21  14:14    <DIR>          Tools
    2016/03/17  22:48            85,840 vcruntime140.dll
                   8 File(s)      3,724,110 bytes
                  10 Dir(s)  211,560,914,944 bytes free
    '
     8 
     9 >>> res.stdout.read()   #再read一次,内容就为空,说明读取完成啦!
    10 b''  #显示为:bytes类型

     示例3:subprocess 执行一个系统没有的命令,就会产生正常的输出

    1 #执行一个系统没有的命令,就会产生正常的输出
    2 
    3 >>> res=subprocess.Popen("sfsfdsfdsfs", shell=True,stdout=subprocess.PIPE,stdin=subprocess.PIPE,stderr=subprocess.PIPE)
    4 
    5 >>> res.stdout.read()  #读取没有内容
    6 b''
    7 
    8 >>> res.stderr.read()  #有正常的输出
    9 b"'sfsfdsfdsfs' is not recognized as an internal or external command,
    operable program or batch file.
    "

    2、struct模块

    struct模块作用:解决bytes和其他二进制数据类型的转换

    示例用法:
    struct.pack('i',12)

    参数说明:

    pack函数作用:把任意数据类型变成bytes

    i 表示4字节无符号整数。

    示例1:

    1 >>> import struct
    2 >>> struct.pack('i',12) #把后面的整形数据,封装成一个bytes类型
    3 b'x0cx00x00x00' #长度就是4
    4 
    5 >>> l=struct.pack('i',12313123)
    6 >>> len(l)
    7 4 #长度就是4

     示例2:

     1 >>> struct.pack('i',1)
     2 b'x01x00x00x00'
     3 
     4 #反解
     5 >>> struct.unpack('i',l)
     6 (12313123,)
     7 
     8 #查看类型
     9 >>> l=struct.pack('i',1)
    10 >>> type(l)
    11 <class 'bytes'>  #bytes类型

    Format Characters(格式化字符):

    FormatC TypePython typeStandard sizeNotes
    x pad byte no value    
    c char bytes of length 1 1  
    b signed char integer 1 (1),(3)
    B unsigned char integer 1 (3)
    ? _Bool bool 1 (1)
    h short integer 2 (3)
    H unsigned short integer 2 (3)
    i int integer 4 (3)
    I unsigned int integer 4 (3)
    l long integer 4 (3)
    L unsigned long integer 4 (3)
    q long long integer 8 (2), (3)
    Q unsigned long long integer 8 (2), (3)
    n ssize_t integer   (4)
    N size_t integer   (4)
    e (7) float 2 (5)
    f float float 4 (5)
    d double float 8 (5)
    s char[] bytes    
    p char[] bytes    
    P void * integer   (6)

    详细用法参考:

    http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431955007656a66f831e208e4c189b8a9e9f3f25ba53000

    官方文档参考:(英文文档)
    https://docs.python.org/3/library/struct.html#format-characters

    3、urandom模块

    作用:产生随机数

    1 >>> import os
    2 >>> os.urandom(32)  #产生32位字节随机数
    3 b'=xbcCxa3xe0xd5x12xe4CZ?xd9Q{x97x89g7lvDxd4xedxd8xeauxc1x9cxb6xd8fR'

    示例:使用md5 + os.urandom(n) 产生随机字符串

    1 import os
    2 from hashlib import md5
    3 
    4 for i in range(5):  #循环几次就产生几次随机数
    5     print(md5(os.urandom(32)).hexdigest())

    执行结果:

    1 1fc70d335903283e1ac8165a28fbdddb
    2 7a1305507f485e4d3c03f4e0c200ab6d
    3 824db1b1076302f46166bbd93c41f0dd
    4 a350c246781d5a6139d18df267e50485
    5 f38fb315a24e33d1703df81fe6b7a4e2

    十三、socket 实现并发

    SocketServer是基于socket写成的一个更强大的模块。

    SocketServer简化了网络服务器的编写。它有4个类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer。这4个类是同步进行处理的,另外通过ForkingMixIn和ThreadingMixIn类来支持异步。

    在python3中该模块是socketserver

    在python2中该模块是Socketserver

    1 分情况导入导入模块
    2 try:
    3     import socketserver      #Python 3
    4 except ImportError:
    5     import SocketServer      #Python 2

    服务器

      服务器要使用处理程序,必须将其出入到服务器对象,定义了5个基本的服务器类型(就是“类”)。BaseServer,TCPServer,UnixStreamServer,UDPServer,UnixDatagramServer。注意:BaseServer不直接对外服务。

    关系如下:

     服务器:

      要使用处理程序,必须将其传入到服务器的对象,定义了四个基本的服务器类。

    (1)TCPServer(address,handler)   支持使用IPv4的TCP协议的服务器,address是一个(host,port)元组。Handler是BaseRequestHandler或StreamRequestHandler类的子类的实例。

    (2)UDPServer(address,handler)   支持使用IPv4的UDP协议的服务器,address和handler与TCPServer中类似。

    (3)UnixStreamServer(address,handler)   使用UNIX域套接字实现面向数据流协议的服务器,继承自TCPServer。

    (4)UnixDatagramServer(address,handler)  使用UNIX域套接字实现数据报协议的服务器,继承自UDPServer。

    这四个类的实例都有以下方法。

    1、s.socket   用于传入请求的套接字对象。

    2、s.sever_address  监听服务器的地址。如元组("127.0.0.1",80)

    3、s.RequestHandlerClass   传递给服务器构造函数并由用户提供的请求处理程序类。

    4、s.serve_forever()  处理无限的请求  #无限处理client连接请求

    5、s.shutdown()   停止serve_forever()循环

    SocketServer模块中主要的有以下几个类:

    1、BaseServer    包含服务器的核心功能与混合类(mix-in)的钩子功能。这个类主要用于派生,不要直接生成这个类的类对象,可以考虑使用TCPServer和UDPServer类。

    2、TCPServer     基本的网络同步TCP服务器

    3、UDPServer     基本的网络同步UDP服务器

    4、ForkingTCPServer      是ForkingMixIn与TCPServer的组合

    5、ForkingUDPServer    是ForkingMixIn与UDPServer的组合

    6、ThreadingUDPServer  是ThreadingMixIn和UDPserver的组合

    7、ThreadingTCPServer   是ThreadingMixIn和TCPserver的组合

    8、BaseRequestHandler   必须创建一个请求处理类,它是BaseRequestHandler的子类并重载其handle()方法。

    9、StreamRequestHandler        实现TCP请求处理类的

    10、DatagramRequestHandler  实现UDP请求处理类的

    11、ThreadingMixIn  实现了核心的线程化功能,用于与服务器类进行混合(mix-in),以提供一些异步特性。不要直接生成这个类的对象。

    12、ForkingMixIn     实现了核心的进程化功能,用于与服务器类进行混合(mix-in),以提供一些异步特性。不要直接生成这个类的对象。

    关系图如下:

    创建服务器的步骤:

    1:首先必须创建一个请求处理类

    2:它是BaseRequestHandler的子类

    3:该请求处理类是BaseRequestHandler的子类并重新写其handle()方法

    实例化  请求处理类传入服务器地址和请求处理程序类

    最后实例化调用serve_forever()  #无限处理client请求

    记住一个原则:对tcp来说:self.request=conn

    示例:

    1、tcp_socket_server服务端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4 
     5 #服务端已经实现并发,处理客户端请求
     6 
     7 import socketserver
     8 
     9 class MyServer(socketserver.BaseRequestHandler):   #基本的通信循环
    10     def handle(self):
    11         print('conn is: ',self.request)  #与client的链接请求信息
    12         print('addr is: ',self.client_address)  #获取client的地址和端口号
    13         #通信循环
    14         while True:
    15             #收消息
    16             data=self.request.recv(1024)
    17             print('收到客户端的消息是',data)
    18 
    19             #发消息
    20             self.request.sendall(data.upper())
    21 
    22 if __name__ == '__main__':
    23     s=socketserver.ThreadingTCPServer(('127.0.0.1',8000),MyServer) #开启多线程,绑定地址,和处理通信的类
    24     s.serve_forever() #连接循环

    tcp_socket_client客户端

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 ip_port=('127.0.0.1',8000)
     7 back_log=5
     8 buffer_size=1024
     9 
    10 tcp_client=socket(AF_INET,SOCK_STREAM)
    11 tcp_client.connect(ip_port)
    12 
    13 while True:
    14     msg=input('>>: ').strip()
    15     if not msg:continue
    16     if msg == 'quit':break
    17 
    18     tcp_client.send(msg.encode('utf-8'))
    19 
    20     data=tcp_client.recv(buffer_size)
    21     print('收到服务端发来的消息: ',data.decode('utf-8'))
    22 
    23 tcp_client.close()

    执行结果:

    开启一个服务端程序,再开多个客户端,向服务器发送命令:

     1 #客户端1
     2 >>: hello   #输入要发送的消息
     3 收到服务端发来的消息:  HELLO
     4 
     5 #客户端2
     6 >>: word
     7 收到服务端发来的消息:  WORD
     8 
     9 #服务端
    10 conn is:  <socket.socket fd=412, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8000), raddr=('127.0.0.1', 62813)>
    11 addr is:  ('127.0.0.1', 62813)
    12 收到客户端的消息是 b'hello'  #客户端收到的消息
    13 
    14 conn is:  <socket.socket fd=256, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8000), raddr=('127.0.0.1', 62816)>
    15 addr is:  ('127.0.0.1', 62816)
    16 收到客户端的消息是 b'word'

    2、udp实现并发

    记住一个原则:对udp来说:self.request=(client_data_bytes,udp的套接字对象)

    实例:

     udp_socket_server服务端:

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 import socketserver
     6 
     7 class MyServer(socketserver.BaseRequestHandler):
     8     def handle(self):
     9         print(self.request)
    10         print('收到客户端的消息是',self.request[0])
    11         self.request[1].sendto(self.request[0].upper(),self.client_address) #发送的是第1个消息,第2个地址
    12 
    13 
    14 if __name__ == '__main__':
    15     s=socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyServer) #多线程
    16     s.serve_forever()

    udp_socket_client客户端:

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4  
     5 from socket import *
     6 ip_port=('127.0.0.1',8080)
     7 buffer_size=1024
     8 
     9 udp_client=socket(AF_INET,SOCK_DGRAM) #数据报
    10 
    11 while True:
    12     msg=input('>>: ').strip()
    13     udp_client.sendto(msg.encode('utf-8'),ip_port)
    14 
    15     data,addr=udp_client.recvfrom(buffer_size)
    16     # print(data.decode('utf-8'))
    17     print(data)

    执行结果:

    先启动服务端,再开多个客户端,向服务端发送消息。

     1 #客户端
     2 >>: welcome  #输入要发送的消息
     3 b'WELCOME'
     4 
     5 >>: hello
     6 b'HELLO'
     7 >>: 
     8 
     9 #服务端
    10 (b'welcome', <socket.socket fd=388, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
    11 收到客户端的消息是 b'welcome'   #服务端接收到的消息
    12 
    13 (b'hello', <socket.socket fd=388, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
    14 收到客户端的消息是 b'hello'

    十四、认证客户端的链接合法性

    如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不像SSL那么复杂,那么利用hmac+加盐的方式来实现

    tcp_socket_server服务端:

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4 
     5 
     6 from socket import *
     7 import hmac,os
     8 
     9 secret_key=b'linhaifeng bang bang bang'  #加段代码(加盐)
    10 def conn_auth(conn):
    11     '''
    12     认证客户端链接
    13     :param conn:
    14     :return:
    15     '''
    16     print('开始验证新链接的合法性')
    17     msg=os.urandom(32)
    18     conn.sendall(msg)
    19     h=hmac.new(secret_key,msg)
    20     digest=h.digest()
    21     respone=conn.recv(len(digest))
    22     return hmac.compare_digest(respone,digest)
    23 
    24 def data_handler(conn,bufsize=1024):
    25     if not conn_auth(conn):
    26         print('该链接不合法,关闭')
    27         conn.close()
    28         return
    29     print('链接合法,开始通信')
    30     while True:
    31         data=conn.recv(bufsize)
    32         if not data:break
    33         conn.sendall(data.upper())
    34 
    35 def server_handler(ip_port,bufsize,backlog=5):
    36     '''
    37     只处理链接
    38     :param ip_port:
    39     :return:
    40     '''
    41     tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    42     tcp_socket_server.bind(ip_port)
    43     tcp_socket_server.listen(backlog)
    44     while True:
    45         conn,addr=tcp_socket_server.accept()
    46         print('新连接[%s:%s]' %(addr[0],addr[1]))
    47         data_handler(conn,bufsize)
    48 
    49 if __name__ == '__main__':
    50     ip_port=('127.0.0.1',9999)
    51     bufsize=1024
    52     server_handler(ip_port,bufsize)
    

    tcp_socket_client客户端:

     1 #!/usr/bin/env python
     2 # -*- coding:utf-8 -*-      
     3 #Author: nulige
     4 
     5 from socket import *
     6 import hmac,os
     7 
     8 secret_key=b'linhaifeng bang bang bang'  #加盐
     9 def conn_auth(conn):
    10     '''
    11     验证客户端到服务器的链接
    12     :param conn:
    13     :return:
    14     '''
    15     msg=conn.recv(32)
    16     h=hmac.new(secret_key,msg)
    17     digest=h.digest()
    18     conn.sendall(digest)
    19 
    20 def client_handler(ip_port,bufsize=1024):
    21     tcp_socket_client=socket(AF_INET,SOCK_STREAM)
    22     tcp_socket_client.connect(ip_port)
    23 
    24     conn_auth(tcp_socket_client)
    25 
    26     while True:
    27         data=input('>>: ').strip()
    28         if not data:continue
    29         if data == 'quit':break
    30 
    31         tcp_socket_client.sendall(data.encode('utf-8'))
    32         respone=tcp_socket_client.recv(bufsize)
    33         print(respone.decode('utf-8'))
    34     tcp_socket_client.close()
    35 
    36 if __name__ == '__main__':
    37     ip_port=('127.0.0.1',9999)
    38     bufsize=1024
    39     client_handler(ip_port,bufsize)
  • 相关阅读:
    linux软件安装方式
    docker 安装 jenkins touch: cannot touch ‘/var/jenkins_home/copy_reference_file.log’: Permission denied Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
    [ERR] Node goodsleep.vip:6379 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
    Linux 常用命令 服务器间scp 用户 export 创建文件、软连接
    redis 安装 集群 主从 哨兵 docker
    WPF密码框中禁止复制、粘贴
    Application 统计在线人数
    【转义字符】HTML 字符实体&lt; &gt: &amp;等
    SQL语句统计每天的数据
    正则表达式计算代码数
  • 原文地址:https://www.cnblogs.com/huangxiaohan/p/7921078.html
Copyright © 2011-2022 走看看