Python开采【第八篇】:socket网络编制程序

Socket套接字方法

 

1 什么是C/S架构

服务端:

Python开发【第八篇】:socket网络编程,python第八篇

服务端:

客户端:

服务端输出:

客户端输出:

连续发送信息:

服务端:

客户端:

在linux上客户端执行命令

服务端:

客户端:

linux发送文件

服务端:

客户端:

一次最多发送的数据量32768(不同系统有区别)

多次发送

服务端:

客户端:

每交互一次文件的变化:

socket 实例类(8-10分钟)

socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

 family***(*socke*t家族)***

  • socket.AF_UNIX:用于本机进程间通讯,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通讯,可以通过创建一个本地的socket来完成
  • socket.AF_INET:(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

 socket type***类型***

  • socket.SOCK_STREAM #for tcp
  • socket.SOCK_DGRAM #for udp
  • socket.SOCK_RAW
    #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
  • socket.SOCK_RDM
    #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
  • socket.SOCK_SEQPACKET #废弃了

(Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.)

 proto=0 请忽略,特殊用途

 fileno=None 请忽略,特殊用途

c指的是client,S指的是Server,C/S架构的软件,实现服务端软件与客户端软件基于网络通信

  1. import socket

  2. server = socket.socket()

  3. #绑定要监听的端口

  4. server.bind((‘localhost’,6969))

  5. #监听

  6. server.listen()

  7. print(“等待连接…”)

  8. #等待连接,conn就是客户端连过来在服务端为其生成的一个连接实例

socket实现简单的ssh

服务端:

客户端:

注意socket粘包问题。

方法一:在多个相连的send之间添加sleep。

方法二:在每次send之后进行一次交互。

服务端:

客户端:

服务端套接字函数(2分钟)

  • s.bind()
    绑定(主机,端口号)到套接字
  • s.listen()
    TCP开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。 backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5, 这个值不能无限大,因为要在内核中维护连接队列
  • s.accept()
    被动接受TCP客户的连接,(阻塞式)等待连接的到来 

  服务程序调用accept函数从处于监听状态的流套接字s的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;如果失败就返回 INVALID_SOCKET。该函数的第一个参数指定处于监听状态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地址结构;操作系统利用第三个参数来返回新创建的套接字的地址结构的长度。

 

2 互联网协议是什么?分别介绍五层协议中每一层的功能

  1. conn,addr = server.accept()

  2. print(“连接进行中…”)

  3. print(conn,addr)

  4.  

  5. data = conn.recv(1024)

  6. print(“recv:”,data)

  7. conn.send(data.upper())

  8. server.close()

socket实现简单的ftp

ftp server:

1、读取文件名

2、检测文件是否存在

3、打开文件

4、检测文件大小

5、发送文件大小给客户端

6、等待客户端确认

7、开始边读边发数据

8、发送md5

服务端:

客户端:

客户端套接字函数(2分钟)

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

互联网协议就是计算机界的通讯标准

客户端:

SocketServer

实现并发处理。

The socketserver module simplifies the task of writing network servers.

socketserver常用类型

class
socketserver.TCPServer(server_address,RequestHandlerClass,bind_and_activate=True)

This uses the Internet TCP protocol,which provides for continuous
streams of data between the client and server.

class
socketserver.UDPServer(server_address,RequestHandlerClass,bind_and_activate=True)

This uses datagrams,which are discrete packets of information that may
arrive out of order or be lost while in transit.The parameters are the
same as for TCPServer.

创建一个socketserver至少分一下几步:

1、You mast create a request handler class by subclassing the
BaseRequestHandler class and overriding its handle() method;this method
will process incoming requests.

创建一个请求处理类继承BaseRequestHandler,并且重写父类handle()。

2、You mast instantiate one of the server classes,passing it the
server’s address and the request handler class.

实例化TCPServer,并传递server ip和上述创建的请求处理类给这个TCPServer。

3、Then call the handle_request() or server_forever() method of the
server object to process one or many requests.

server.handle_request():只处理一个请求

server.server_forever():处理多个请求

4、Call server_close() to close the socket.

服务端:

客户端:

开启一个新线程

 

服务端: 客户端: 服务端输出: 客户端输出: 连续发送信息: 服务端:
客户端: 在…

公共用途的套接字函数(3-5分钟)

  • s.recv()
    接收数据
  • s.send()
    发送数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完,可后面通过实例解释)
  • s.sendall()
    发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
  • s.recvfrom()
    Receive data from the socket. The return value is a pair (bytes,
    address)
  • s.getpeername()
    连接到当前套接字的远端的地址
  • s.close()
    关闭套接字
  • socket.setblocking(flag)
    #True or False,设置socket为非阻塞模式,以后讲io异步时会用
  • socket.getaddrinfo(host,
    port, family=0, type=0, proto=0, flags=0)
    返回远程主机的地址信息,例子
    socket.getaddrinfo(‘luffycity.com’,80)
  • socket.getfqdn()
    拿到本机的主机名
  • socket.gethostbyname()
    通过域名解析ip地址

物理层功能:主要是基于电器特性发送高低电压,高电压对应数字1,低电压对应数字0

  1. import socket

  2.  

  3. #声明socket类型,同时生产socket连接对象

tcp套接字

1.简单套接字
客户端和服务端:两个主要功能,1、建立链接 2、数据通讯
服务端程序会产生两个套接字socket,一个用于三次握手建立链接,另一个用于收发消息数据通讯;
客户端产生一个套接字socket,既可以用于建立链接后,再用于收发消息数据通讯。

client.py

威尼斯网址开户网站 1威尼斯网址开户网站 2

 1 import socket
 2 
 3 #1.买手机
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 print(phone)
 6 
 7 #2.拨号
 8 phone.connect(('127.0.0.1',8081)) 
 9 #端口范围0-65535,0-1024给操作系统用的,若一直无法连接上server,则会一直停留在这一步
10 
11 #3.发收消息
12 phone.send('hello'.encode('utf-8'))
13 data = phone.recv(1024)
14 print(data)
15 
16 #4.关闭
17 phone.close()

View Code

 service.py

威尼斯网址开户网站 3威尼斯网址开户网站 4

 1 import socket
 2 
 3 #1.买手机
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 print(phone)
 6 
 7 #2.绑定手机卡
 8 phone.bind(('127.0.0.1',8081)) #端口范围0-65535,0-1024给操作系统用的
 9 
10 #3.开机
11 phone.listen(5) # 参数表示最大监听数
12 
13 #4.等电话链接
14 print('starting...')
15 conn,client = phone.accept() #返回一个新的套接字conn用于通讯,client为发起通讯链接的客户端的ip和端口号
16 print(conn,client)
17 # print('===>')
18 
19 #5.收,发消息
20 data = conn.recv(1024) # 单位:bytes,  1024代表最大接收1024个bytes
21 print('客户端的数据',data)
22 conn.send(data.upper())
23 
24 #6.挂电话
25 conn.close()
26 
27 #7.关机
28 phone.close()

View Code

数据链路层的功能:定义了电信号的分组方式

  1. client = socket.socket()

  2. client.connect((‘localhost’,6969))

 2.加上循环套接字

client.py 

威尼斯网址开户网站 5威尼斯网址开户网站 6

 1 import socket
 2 
 3 #1.买手机
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 
 6 print(phone)
 7 
 8 #2.拨号
 9 phone.connect(('127.0.0.1',8080)) #端口范围0-65535,0-1024给操作系统用的
10 
11 while True:
12     msg = input('>>:').strip()
13     if not msg:continue
14     phone.send(msg.encode('utf-8'))  #phone.send(b'')
15     print('has send') #判断能否发空
16     data = phone.recv(1024)
17     print(data.decode('utf-8'))
18 
19 #4.关闭
20 phone.close()

View Code

service.py

威尼斯网址开户网站 7威尼斯网址开户网站 8

 1 import socket
 2 
 3 
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
 6 print(phone)
 7 phone.bind(('127.0.0.1',8080)) #端口范围0-65535,0-1024给操作系统用的
 8 phone.listen(5) #最大链接挂起数
 9 
10 print('starting...')
11 conn,client = phone.accept() #监听
12 # print('===>')
13 
14 #监听到到后,进行通讯循环
15 # while True:
16 #     data = conn.recv(1024) # 单位:bytes,  1024代表最大接收1024个bytes
17 #     #conn tcp协议三次握手的成果,双向链接
18 #     if not data:break #适用与linux操作,当client单方面终止链接时,service端会出现死循环
19 #     print('客户端的数据',data)
20 #     conn.send(data.upper())
21 
22 while True:
23     try:
24         data = conn.recv(1024) # 单位:bytes,  1024代表最大接收1024个bytes
25         #conn tcp协议三次握手的成果,双向链接
26 
27         print('客户端的数据',data)
28         conn.send(data.upper())
29     except ConnectionResetError:
30 
31         break
32 
33 conn.close()
34 phone.close()

View Code

if not data:break 是用于linux的判断,因为在linux中当client端单方面终止时,servce端会一直接收到空,会一直循环print('客户端的数据',data),因此需要加上判断;

except ConnectionResetError: 是针对windows的,当client端单方面终止时,server端会报ConnnectionRsetError。

有时重启服务端时会遇到报错:

威尼斯网址开户网站 9

网络层功能:引入一套新的地址用来区分不同的广播域/子网,这套地址即网络地址

  1. client.send(b”hello world!”)

  2. data = client.recv(1024)

  3. print(“recv:”,data)

  4. client.close()

 

由于重启时系统还没来得及回收端口,因此会提示端口已被占用。

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

解决方法:加入一条socket配置,重用ip和端口。

phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
#就是它,在bind ip和端口 前加。

或者:

实验之前要全部关闭掉所用占用端口的程序,用以下指令
linux:pkill -9 python
windows:taskkill python

 

传输层功能:建立端口到端口的通信 补充:端口范围0-65535,0-1023为系统占用端口

服务端输出:

3.加上 链接循环

之前代码运行可知,client端关闭后,service端也会关闭,但此刻我们想client端关闭后,service端应该能在接收新的client端的链接请求,因此,在建

立链接的部分加入循环。

client.py 

威尼斯网址开户网站 10威尼斯网址开户网站 11

 1 import socket
 2 
 3 #1.买手机
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 
 6 print(phone)
 7 
 8 #2.拨号
 9 phone.connect(('127.0.0.1',8080)) #端口范围0-65535,0-1024给操作系统用的
10 
11 while True:
12     msg = input('>>:').strip()
13     if not msg:continue
14     phone.send(msg.encode('utf-8'))  #phone.send(b'')
15     print('has send') #判断能否发空
16     data = phone.recv(1024)
17     print(data.decode('utf-8'))
18 
19 #4.关闭
20 phone.close()

View Code

service.py

威尼斯网址开户网站 12威尼斯网址开户网站 13

 1 import socket
 2 
 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
 5 print(phone)
 6 phone.bind(('127.0.0.1',8080)) #端口范围0-65535,0-1024给操作系统用的
 7 phone.listen(5) #最大链接挂起数
 8 
 9 print('starting...')
10 
11 while True:
12     '''
13     用于监听多次client端的链接,但一次链接发起结束后,
14     可继续监听下一次client端的连接
15     '''
16     conn,client = phone.accept()
17     print(client)
18     while True:
19         try:
20             data = conn.recv(1024) # 单位:bytes,  1024代表最大接收1024个bytes
21             #conn tcp协议三次握手的成果,双向链接
22             if not data: break
23             print('客户端的数据',data)
24             conn.send(data.upper())
25         except ConnectionResetError:
26             break
27     conn.close()

View Code

应用层功能:将应用程序的数据打包传给传输层

  1. 等待连接…

  2. 连接进行中…

  3. <socket.socket fd=328,
    family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM,
    proto=0, laddr=(‘127.0.0.1’, 6969), raddr=(‘127.0.0.1’, 50248)>
    (‘127.0.0.1’, 50248)

  4. recv: b’hello world!’

 4.模拟ssh远程执行命令

client.py 

威尼斯网址开户网站 14威尼斯网址开户网站 15

 1 import socket,subprocess
 2 
 3 
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
 6 print(phone)
 7 phone.bind(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的
 8 phone.listen(5) #最大链接挂起数
 9 
10 print('starting...')
11 while True:
12     conn,client = phone.accept() #监听
13 
14     while True: #通讯循环
15         try:
16             #1、收命令
17             cmd = conn.recv(1024) # 单位:bytes,  1024代表最大接收1024个bytes
18             #conn tcp协议三次握手的成果,双向链接
19             if not cmd: break
20             #2、执行命令、拿到结果,命令的结果存入stdout=subprocess.PIPE管道,而不是直接输出到终端
21             obj = subprocess.Popen(cmd.decode('utf-8'),shell=True,
22                                    # 指令由client端发送过来是以utf-8解码为bytes发送过来的,因此处应该以utf-8来编码,
23                                    # 因此此处的命令编码应该与client端的一致
24                                    stdout=subprocess.PIPE,
25                                    stderr=subprocess.PIPE)
26             print(obj)
27             stdout = obj.stdout.read()
28             stderr =  obj.stderr.read() #s收发都是bytes格式
29 
30             #3、把命令的结果返回给客户端
31             conn.send(stdout+stderr) #申请一块新的内存空间存放stdout+stderr,会占内存,效率会低
32         except ConnectionResetError:
33             break
34     conn.close()
35 
36 phone.close()

View Code

service.py

威尼斯网址开户网站 16威尼斯网址开户网站 17

 1 import socket
 2 
 3 #1.买手机
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 
 6 print(phone)
 7 
 8 #2.拨号
 9 phone.connect(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的
10 
11 while True:
12     msg = input('>>:').strip()
13     if not msg:continue
14     phone.send(msg.encode('utf-8'))
15     # 注意:信息由utf-8解码为bytes格式发送到service端,因此service端也必须把bytes格式以utf-8来编码,
16 
17     data = phone.recv(1024)  #返回值可能超过1024bytes,
18     print(data.decode('gbk'))
19     # windows上,res.stdout.read()读出的就是GBK编码,因此此处也用GBK编码,linux上默认是utf-8
20 
21 #4.关闭
22 phone.close()

View Code

此处注意两个小问题:

  1.service端的命令的编码应该与client端的解码模式对应,client端以utf-8解码指令为bytes,则service端必须以utf-8来编码;

  2.service端的把命令结果发送给client端,client则需要将命令结果进行编码,若serice端在windows上,则以GBK进行编码,若在linux上则以utf-8进行编码。

3 基于tcp协议通信,为何建立链接需要三次握手,而断开链接却需要四次挥手

客户端输出:

5.粘包现象分析

须知:只有TCP有粘包现象,UDP永远不会粘包

威尼斯网址开户网站 18

 

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

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

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

两种情况下会发生粘包:

发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据段很小,会合到一起,产生粘包)。TCP使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。

client.py

威尼斯网址开户网站 19威尼斯网址开户网站 20

1 import socket
2 
3 client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
4 client.connect(('127.0.0.1',9903))
5 
6 #TCP使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,
7 # 合并成一个大的数据块,然后进行封包。从而在发送端造成粘包。
8 client.send('hello'.encode('utf-8'))
9 client.send('world'.encode('utf-8'))

View Code

service.py

威尼斯网址开户网站 21威尼斯网址开户网站 22

 1 import socket
 2 
 3 service = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 service.bind(('127.0.0.1',9903))
 5 service.listen(5)
 6 
 7 conn,addr = service.accept()
 8 
 9 res1 = conn.recv(1024)
10 print('第一次',res1.decode())
11 
12 res2 =  conn.recv(1024)
13 print('第二次',res2.decode())

View Code

 输出结果:

第一次 helloworld
第二次 

发送端由于TCP
优化算法造成粘包

 

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

 

client.py

威尼斯网址开户网站 23威尼斯网址开户网站 24

 1 #_*_coding:utf-8_*_
 2 __author__ = 'Linhaifeng'
 3 import socket
 4 BUFSIZE=1024
 5 ip_port=('127.0.0.1',8080)
 6 
 7 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 8 res=s.connect_ex(ip_port)
 9 
10 
11 s.send('hello feng'.encode('utf-8'))
12 
13 客户端

View Code

service.py

威尼斯网址开户网站 25威尼斯网址开户网站 26

 1 #_*_coding:utf-8_*_
 2 __author__ = 'Linhaifeng'
 3 from socket import *
 4 ip_port=('127.0.0.1',8080)
 5 
 6 tcp_socket_server=socket(AF_INET,SOCK_STREAM)
 7 tcp_socket_server.bind(ip_port)
 8 tcp_socket_server.listen(5)
 9 
10 
11 conn,addr=tcp_socket_server.accept()
12 
13 
14 data1=conn.recv(2) #一次没有收完整
15 data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的
16 
17 print('----->',data1.decode('utf-8'))
18 print('----->',data2.decode('utf-8'))
19 
20 conn.close()
21 
22 服务端

View Code

输出结果

-----> he
-----> llo feng

接收端由于没能一次将发送端一次发送的数据全部接受,导致粘包

 

拆包的发生情况

当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。

补充问题一:为何tcp是可靠传输,udp是不可靠传输

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

威尼斯网址开户网站,而udp发送数据,对端是不会返回确认信息的,因此不可靠。

补充问题二:send(字节流)和recv(1024)及sendall

recv里指定的1024意思是从缓存里一次拿出1024个字节的数据

send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失。

send 和 recv:
1.不管是recv还是send都不是直接接收对方的数据,而是操作自己的操作系统内存-->不是一个send对应一个recv
2.recv阶段,耗时分析:
  wait data 耗时非常长
  copy data 耗时短
  send耗时分析:
  copy data

三次握手:client发送请求建立通道;server收到请求并同意,同时也发送请求建通道;client收到请求并同意,建立完成

  1. recv: b’HELLO WORLD!’

5.粘包解决办法

粘包问题的原因在于接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

第一步:先拿到数据的长度

第二步:接收真实的数据

 先来介绍下struct模块

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

威尼斯网址开户网站 27

 

但注意类型如数字是有范围的,超出范围就会报错

>>>
struct.pack(‘i’,1111111111111)

。。。。。。。。。

struct.error:
‘i’ format requires -2147483648 <= number <= 2147483647
 #这个是范围

威尼斯网址开户网站 28威尼斯网址开户网站 29

1 import struct
2 
3 res = struct.pack('i', 1235)
4 print(res,type(res), len(res))
5 
6 obj = struct.unpack('i', res)
7 print(obj)

struct 用法

输出结果

b'\xd3\x04\x00\x00' <class 'bytes'> 4
(1235,)

在数据发送端将数据长度打包,发送给接收端,解包获取实际数据的长度。

 

 简单版本报头自制

client.py

威尼斯网址开户网站 30威尼斯网址开户网站 31

 1 import socket, struct
 2 
 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 print(phone)
 5 
 6 phone.connect(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的
 7 
 8 while True:
 9     #1、发命令
10     msg = input('>>:').strip()
11     if not msg:continue
12     phone.send(msg.encode('utf-8'))
13 
14     #2、拿到命令的结果,并打印
15 
16     #第一步:先收报头
17     header = phone.recv(4)
18     #第二步:从报头中解析出对真实数据的描述信息
19     total_size = struct.unpack('i',header)[0]
20 
21     #第三部:接收真实数据
22     recv_size = 0
23     recv_data = b''
24     while recv_size < total_size:
25         data = phone.recv(1024)
26         recv_data += data
27         recv_size += len(data)
28 
29     print(data.decode('gbk'))
30     # windows上,res.stdout.read()读出的就是GBK编码,因此此处也用GBK编码,linux上默认是utf-8
31 
32 phone.close()

自制报头

service.py

威尼斯网址开户网站 32威尼斯网址开户网站 33

 1 import socket,subprocess,struct
 2 
 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 phone.bind(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的
 5 phone.listen(5) #最大链接挂起数
 6 
 7 print('starting...')
 8 while True:
 9     conn,client = phone.accept() #监听
10 
11     while True: #通讯循环
12         try:
13             #1、收命令
14             cmd = conn.recv(1024) # 单位:bytes,  1024代表最大接收1024个bytes
15             #conn tcp协议三次握手的成果,双向链接
16             if not cmd: break
17             #2、执行命令、拿到结果,命令的结果存入stdout=subprocess.PIPE管道,而不是直接输出到终端
18             obj = subprocess.Popen(cmd.decode('utf-8'),shell=True,
19                                    stdout=subprocess.PIPE,
20                                    stderr=subprocess.PIPE)
21             print(obj)
22             stdout = obj.stdout.read()
23             stderr = obj.stderr.read() #s收发都是bytes格式
24 
25             #3、把命令的结果返回给客户端
26             #第一步:制作固定长度的报头
27             total_size = len(stdout)+len(stderr)
28             header = struct.pack('i', total_size)
29 
30             #第二步:把报头(固定长度)发送给客户端
31             conn.send(header)
32 
33             #第三步:再发送真实数据
34             conn.send(stdout)
35             conn.send(stderr)
36 
37         except ConnectionResetError:
38             break
39     conn.close()
40 
41 phone.close()

自制报头

 

高阶报头自制

以上讲解了简单报头的自制,但有缺陷:

1、报头存有的信息少。

2、struct模块打包的int数字有范围,普通指令的结果长度虽然不会超过这个范围,但是上传下载文件时就很有可能会超过此范围,因此下面介绍同样使用struct模块来自制跟高端的报头。

以字典的形式制作报头,字典中可以存文件名、文件md5值、文件大小等,再将字典序列化,将序列化的字符串长度通过struct
pack,这样既可让报头存有足够的信息,又不会超出struct模块打包的int的数字范围。

client.py

威尼斯网址开户网站 34威尼斯网址开户网站 35

 1 import socket, struct, json
 2 
 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 print(phone)
 5 
 6 phone.connect(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的
 7 
 8 while True:
 9     #1、发命令
10     msg = input('>>:').strip()
11     if not msg:continue
12     phone.send(msg.encode('utf-8'))
13 
14     #2、拿到命令的结果,并打印
15 
16     #第一步:先收报头长度
17     obj = phone.recv(4)
18     header_size = struct.unpack('i',obj)[0]
19     # 第二步:再收报头
20     header = phone.recv(header_size)
21 
22     #第三步:从报头中解析出对真实数据的描述信息
23     header_dic = json.loads(header)
24     total_size = header_dic['total_size']
25 
26     #第三部:接收真实数据
27     recv_size = 0
28     recv_data = b''
29     while recv_size < total_size:
30         data = phone.recv(1024)
31         recv_data += data
32         recv_size += len(data)
33 
34     print(data.decode('gbk'))
35     # windows上,res.stdout.read()读出的就是GBK编码,因此此处也用GBK编码,linux上默认是utf-8
36 
37 phone.close()

高阶报头自制

service.py

威尼斯网址开户网站 36威尼斯网址开户网站 37

 1 import socket, subprocess, struct, json
 2 
 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 phone.bind(('127.0.0.1',9900)) #端口范围0-65535,0-1024给操作系统用的
 5 phone.listen(5) #最大链接挂起数
 6 
 7 print('starting...')
 8 while True:
 9     conn,client = phone.accept() #监听
10 
11     while True: #通讯循环
12         try:
13             #1、收命令
14             cmd = conn.recv(1024) # 单位:bytes,  1024代表最大接收1024个bytes
15             #conn tcp协议三次握手的成果,双向链接
16             if not cmd: break
17             #2、执行命令、拿到结果,命令的结果存入stdout=subprocess.PIPE管道,而不是直接输出到终端
18             obj = subprocess.Popen(cmd.decode('utf-8'),shell=True,
19                                    stdout=subprocess.PIPE,
20                                    stderr=subprocess.PIPE)
21             print(obj)
22             stdout = obj.stdout.read()
23             stderr = obj.stderr.read() #s收发都是bytes格式
24 
25             #3、把命令的结果返回给客户端
26             #第一步:制作报头
27 
28             header_dic = {
29                 'filename':'a.txt',
30                 'md5':'xxfdxxx',
31                 'total_size': len(stdout)+len(stderr)
32             }
33             header_json = json.dumps(header_dic)
34             header_types = header_json.encode('utf-8')
35 
36             #第二步:把报头(固定长度)发送给客户端
37             conn.send(struct.pack('i',len(header_types)))
38 
39             #第三步:再发送报头、
40             conn.send(header_types)
41 
42             #第四步:再发送真实数据
43             conn.send(stdout)
44             conn.send(stderr)
45 
46         except ConnectionResetError:
47             break
48     conn.close()
49 
50 phone.close()

高阶报头自制

四次挥手:client发送请求断开通道;server收到请求并同意,但是这时sever可能还在发数据,并不关闭接口,所有回复同意和发送sever断开请求不是一同发送的;等到数据发送完毕server也发送请求断开通道;client受到消息结束

连续发送信息:

 udp 协议套接字

4 为何基于tcp协议的通信比基于udp协议的通信更可靠?

服务端:

tcp基于链接通信

  • 基于链接,则需要listen(backlog),指定连接池的大小
  • 基于链接,必须先运行的服务端,然后客户端发起链接请求
  • 对于mac系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)
  • 对于windows/linux系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环)

tcp协议是面向链接的协议,在通信过程中,双方通过三次握手建立连接、四次挥手断开连接,发送方给接收方发送数据,如果没有得到接收方的回应,就会继续给它发消息,直到接收方回应。

  1. import socket

  2. client = socket.socket()

  3. client.connect((‘localhost’,6969))

udp无链接

udp 不需要经过3次握手和4次挥手,不需要提前建立连接,直接发数据就行。
  • 无链接,因而无需listen(backlog),更加没有什么连接池之说了
  • 无链接,udp的sendinto不用管是否有一个正在运行的服务端,可以己端一个劲的发消息,只不过数据丢失
  • recvfrom收的数据小于sendinto发送的数据时,在mac和linux系统上数据直接丢失,在windows系统上发送的比接收的大直接报错
  • 只有sendinto发送数据没有recvfrom收数据,数据丢失
  • 一个sendto对应一个recvfrom,不会产生粘包问题

     
 udp协议虽然不会产生粘包,但
udp协议不安全,tcp协议会在发数据前发个信息,在接收端回复确认可以接收数据后,才会发送数据,发送数据
       
 后还要等待接收端回复已接收后才会继续发送,因此tcp协议是稳定安全的。

client.py 

威尼斯网址开户网站 38威尼斯网址开户网站 39

 1 import socket
 2 # 建立套接字对象
 3 client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 4 #client.connect(('127.0.0.0',8080)) #udp没有链接
 5 
 6 
 7 while True:
 8     msg = input('>>:').strip()
 9     client.sendto(msg.encode('utf-8'),('127.0.0.1',8080)) #udp没有链接,发送信息必须指定ip和端口
10 
11     data,server_addr = client.recvfrom(1024)
12     print(data,server_addr)
13 
14 client.close()

udp套接字 client

>>:hah
b'HAH' ('127.0.0.1', 8080)
>>:yy
b'YY' ('127.0.0.1', 8080)
>>:

service.py

威尼斯网址开户网站 40威尼斯网址开户网站 41

 1 import socket
 2 # 建立套接字对象
 3 server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 4 server.bind(('127.0.0.1',8080))
 5 
 6 #server.listen() #监听最大链接数,udp没有链接
 7 #server.accept() #建立链接,udp无链接
 8 
 9 while True:
10     data,client = server.recvfrom(1024)
11     print(data,client) #返回数据和数据发送端的ip和端口
12     server.sendto(data.upper(),client) #udp没有链接,发送信息必须指定ip和端口
13 
14 server.close()

udp套接字 server

b'hah' ('127.0.0.1', 59001)
b'yy' ('127.0.0.1', 59001)

  

 

 

 

udp是面向数据报的协议,不需要三次握手建立连接,它不会管接收方有没有收到数据

  1. while
    True:

  2.     msg = input(“>>:”).strip()

  3.     client.send(msg.encode(“utf-8”))

  4.     data = client.recv(1024)

  5.     print(“recv:”,data.decode())

  6. client.close()

5‍流式协议指的是什么协议,数据报协议指的是什么协议?

客户端:

流式协议指TCP协议,是通过三次握手建立连接再发送数据的,会存在粘包现象,当发送空消息时,对方并不会收到,不一定是一个send就要对应一个recv,传输效率低,网络开销大,可靠性高。

  1. import socket

  2.  

  3. #声明socket类型,同时生产socket连接对象

数据报协议是指UDP协议,是以消息为单位发送的数据的,一个sendto就对应一个recvfrom,不会存在粘包现象,即使发送空消息也能收到,传输效率高,网络开销小,可靠性低。

  1. client = socket.socket()

  2. client.connect((‘localhost’,6969))

6什么是socket?简述基于tcp协议的套接字通信流程

  1. client.send(b”hello world!”)

  2. # client.send(“我要下载”.encode(“utf-8”))

  3. data = client.recv(1024)

  4. print(“recv:”,data)

  5. client.close()

socket是介于应用层和传输层之间的一组接口。将复杂的TCP/IP协议封装到接口里面,使用者只需要知道怎么用即可,不需要关心底层的实现。

在linux上客户端执行命令

基于TCP的套接字通信流程:

服务端:

1)服务端:创建一个套接字对象;绑定本机地 址信息;开始时监听;接收连接;

  1. [root@test-c2c-console01 oldboy]#
    cat socket_server.py

  2. #-*-coding:utf-8-*-

  3. #_author_=”golden”

  4. #服务端

  5. import socket

  6. import os

  7.  

  8. server = socket.socket()

  9. #绑定要监听的端口

  10. server.bind((‘localhost’,6969))

  11. #监听,最大连接数

  12. server.listen(5)

  13. print(“等待连接…”)

  14. while
    True:

  15.     #conn就是客户端连过来在服务端为其生成的一个连接实例

2)客户端:创建套接字对象;主动连接客户端;等待对方接收

  1.     conn,addr = server.accept()

  2.     print(“连接进行中…”)

  3.     print(conn,addr)

  4.     while True:

  5.         data = conn.recv(1024)

  6.         print(“recv:”,data)

  7.         if not data:

  8.             print(“客户端断开!”)

  9.             break

  10.         res = os.popen(data).read()

通过三次握手后建立连接,开始收发消息。

  1.         conn.send(res)

  2. server.close()

收发消息完了之后,通过四次挥手断开连接。

客户端:

7什么是粘包? socket 中造成粘包的原因是什么? 哪些情况会发生粘包现象?

  1. [root@test-c2c-console01 oldboy]#
    cat socket_client.py

  2. #-*-coding:utf-8-*-

  3. _author_ = “golden”

  4. #客户端

  5. import socket

  6. #声明socket类型,同时生成socket连接对象

粘包是指两次命令执行的结果黏在一起。粘包发生在TCP协议中。

  1. client = socket.socket()

  2. client.connect((‘localhost’,6969))

造成粘包的原因:接收方不知道所要接收消息的大小和界限。

  1. while
    True:

  2.     msg = raw_input(“>>:”).strip()

  3.     if
    len(msg) == 0:continue

  4.     client.send(msg.encode(“utf-8”))

  5.     data = client.recv(102400)

  6.     print(data.decode())

  7.  

  8. client.close()

发生粘包的情况:1、socket缓冲区导致,socket为了提高传输效率,往往会将较短时间间隔内较小的数据包合并发送,这样接收方就会收到一个粘包数据;

linux发送文件

        2、接收方不知道该接收多大数据量,当接收方的最大接收量小于消息大小时,会发生粘包。

服务端:

8基于socket开发一个聊天程序,实现两端互相发送和接收消息

import socketsev = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sev.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)sev.bind(('127.0.0.1', 9999))sev.listen(5)print('starting...')while True:    conn, client_addr = sev.accept()    print    print(client_addr)    while True:        try:            data = conn.recv(1024)            print(data.decode            inp = input('-->').strip()            conn.send(inp.encode        except ConnectionResetError:            breakconn.close()sev.close()

import socketcli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)cli.connect(("127.0.0.1", 9999))while True:    inp = input('---->').strip()    if not inp:        continue    cli.send(inp.encode    data = cli.recv(1024)    print(data.decodecli.close()
  1. [root@test-c2c-console01 oldboy]#
    cat socket_server.py

  2. #-*-coding:utf-8-*-

  3. #_author_=”golden”

  4. #服务端

  5. import socket

  6. import os

  7.  

  8. server = socket.socket()

  9. #绑定要监听的端口

  10. server.bind((‘localhost’,6969))

  11. #监听,最大连接数

  12. server.listen(5)

  13. print(“等待连接…”)

  14. while
    True:

  15.     #conn就是客户端连过来在服务端为其生成的一个连接实例

9基于tcp socket,开发简单的远程命令执行程序,允许用户执行命令,并返回结果

import socketimport subprocessimport jsonimport structsev = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sev.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)sev.bind(('127.0.0.1', 5555))sev.listen(5)print('start'.center(50, '-'))while True:    conn, addr = sev.accept()    while True:        try:            cmd = conn.recv(1024)            obj = subprocess.Popen(cmd.decode(),                             shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)            stdout = obj.stdout.read()            stderr = obj.stderr.read()            total_size = len+len            dict_msg = {'file_name': 'a.txt',                        'md5': 12431564,                        'total_size': total_size}            dict_bytes = json.dumps.encode()            header = struct.pack('i', len(dict_bytes))[0]            conn.send            conn.send(dict_bytes)            conn.send            conn.send        except ConnectionResetError:            break    conn.close()sev.close()

import socketimport structimport jsoncli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)cli.connect(('127.0.0.1', 5555))while True:    cmd = input('-->')    if not cmd:        continue    cli.send(cmd.encode    obj = cli.recv(4)    header_len = struct.unpack('i', obj)[0]    header = cli.recv(header_len)    dict_msg = json.loads(header.decode    total_size = dict_msg['total_size']    recv_size = 0    recv_data = b''    while recv_size < total_size:        data = cli.recv(1024)        recv_size += len        recv_data += data    print(recv_data.decode('gbk'))cli.close()
  1.     conn,addr = server.accept()

  2.     print(“连接进行中…”)

  3.     print(conn,addr)

  4.     while True:

  5.         data = conn.recv(1024)

  6.         print(“recv:”,data)

  7.         if not data:

  8.             print(“客户端断开!”)

  9.             break

  10.         #res = os.popen(data).read()

10基于tcp协议编写简单FTP程序,实现上传、下载文件功能,并解决粘包问题

import jsonimport socketimport osimport structBASE_DIR = os.path.dirname(os.path.abspath(__name__))SHARE_PATH = os.path.join(BASE_DIR, 'share')print(SHARE_PATH)class Sever():    def __init__:        print('start....')        self.ser = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        self.ser.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)        self.ser.bind(('127.0.0.1', 9999))        self.ser.listen(5)        self.run()    def run:        while True:            self.conn, self.client_addr = self.ser.accept()            while True:                try:                    cmd_b = self.conn.recv(1024)                    cmd, file_name = cmd_b.decode                    if hasattr(self, cmd):                        func = getattr(self, cmd)                        func(file_name)                    else:                        print('none...')                except ConnectionResetError:                    break    def get(self, file_name):        '''客户端下载'''        if os.path.exists('%s\%s' % (SHARE_PATH, file_name)):            file_size = os.path.getsize('%s\%s' % (SHARE_PATH, file_name))            dict_msg = {'file_name': file_name,                        'md5': 124521,                        'file_size': file_size,                        'file': True}            header = json.dumps.encode()            header_size = len            self.conn.send(struct.pack('i', header_size))            self.conn.send            with open('%s\%s' % (SHARE_PATH, file_name), 'rb') as f:                for line in f:                    self.conn.send                else:                    print('send has done...')        else:            dict_msg = {'file_name': file_name,                        'md5': 124521,                        'file_size': None,                        'file': False}            header = json.dumps.encode()            header_size = len            self.conn.send(struct.pack('i', header_size))            self.conn.send            print('file not exist')    def put(self, file_name):        '''客户端上传'''        obj = self.conn.recv(4)        header_len = struct.unpack('i', obj)[0]        header = self.conn.recv(header_len)        dict_msg = json.loads(header.decode        file_size = dict_msg['file_size']        with open('%s\%s' % (SHARE_PATH, file_name), 'wb')as f:            recv_size = 0            while recv_size < file_size:                data = self.conn.recv(1024)                f.write                recv_size += len            print("%s 接受完成" % file_name)sever = Sever()

import socketimport jsonimport structimport osBASE_DIR = os.path.dirname(os.path.abspath(__name__))DOWMLOAD_PATH = os.path.join(BASE_DIR, 'download')class Client():    def __init__:        self.cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)        self.cli.connect(("127.0.0.1", 9999))        print('go....')        while True:            inp = input('-->').strip()            if not inp:                continue            try:                cmd, file_name = inp.split()            except ValueError:                print('输入不合法')                continue            self.cli.send(inp.encode            if hasattr(self, cmd):                func = getattr(self, cmd)                func(file_name)    def get(self,file_name):        obj = self.cli.recv(4)        header_size = struct.unpack('i', obj)[0]        header = self.cli.recv(header_size)        dict_msg = json.loads(header.decode        if dict_msg['file']:            file_size = dict_msg['file_size']            recv_size = 0            with open("%s\%s" % (DOWMLOAD_PATH, file_name), 'wb')as f:                while recv_size < file_size:                    data = self.cli.recv(1024)                    f.write                    recv_size += len                print('%s 下载完成 ' % file_name)        else:            print('%s 不存在 ' % file_name)    def put(self,file_name):        if os.path.exists('%s\%s' % (DOWMLOAD_PATH, file_name)):            file_size = os.path.getsize('%s\%s' % (DOWMLOAD_PATH, file_name))            dict_msg = {'file_name': file_name,                        'md5': 124521,                        'file_size': file_size,                        'file': True}            header = json.dumps.encode()            header_size = len            self.cli.send(struct.pack('i', header_size))            self.cli.send            with open('%s\%s' % (DOWMLOAD_PATH, file_name), 'rb') as f:                for line in f:                    self.cli.send                else:                    print('send has done...')        else:            print('file not exist')
  1.         #conn.send(res)

  2.         f = open(“move.mkv”)

  3.         data = f.read()

  4.         conn.send(data)

  5. server.close()

11基于udp协议编写程序,实现功能

import socketsev = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)sev.bind(('127.0.0.1', 9999))print('start...')while True:    msg, client_addr = sev.recvfrom(1024)    print(msg.decode    inp = input('-->').strip().encode()    sev.sendto(inp, client_addr)

import socketcli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)while True:    inp = input('-->').strip()    cli.sendto(inp.encode(), ('127.0.0.1', 9999))    msg, sever_addr =cli.recvfrom(1024)    print(msg.decode

客户端:

  1. [root@test-c2c-console01 oldboy]#
    cat socket_client.py

  2. #-*-coding:utf-8-*-

  3. _author_ = “golden”

  4. #客户端

  5. import socket

  6. #声明socket类型,同时生成socket连接对象

  1. client = socket.socket()

  2. client.connect((‘localhost’,6969))

  1. while
    True:

  2.     msg = raw_input(“>>:”).strip()

  3.     if
    len(msg) == 0:continue

  4.     client.send(msg.encode(“utf-8”))

  5.     data = client.recv(102400)

  6.     #print(data.decode())

  7.     f = open(“video.avi”,’wb’)

  8.     f.write(data)

  9.     f.close()

  10.  

  11. client.close()

一次最多发送的数据量32768(不同系统有区别)

  1. [root@test-c2c-console01 oldboy]# ll
    *.avi *.mkv

  2. -rw-r–r– 1 root root 681790584 Dec 27
    19:26 move.mkv

  3. -rw-r–r– 1 root root 32768 Feb 12
    08:47 video.avi

多次发送

服务端:

  1. [root@test-c2c-console01 oldboy]#
    cat socket_server.py

  2. #-*-coding:utf-8-*-

  3. #_author_=”golden”

  4. #服务端

  5. import socket

  6. import os

  7.  

  8. server = socket.socket()
    #实例化一个socket

  9. #绑定要监听的端口

  10. server.bind((‘localhost’,6969))

  11. #监听,最大连接数

  12. server.listen(5)

  13. print(“等待连接…”)

  14. while
    True:

  15.     #conn就是客户端连过来在服务端为其生成的一个连接实例

  1.     conn,addr = server.accept()

  2.     print(“连接进行中…”)

  3.     print(conn,addr)

  4.     while True:

  5.         data = conn.recv(1024)

  6.         print(“recv:”,data)

  7.         if not data:

  8.             print(“客户端断开!”)

  9.             break

  10.         #res = os.popen(data).read()

  1.         #conn.send(res)

  2.         f = open(“move.mkv”)

  3.         data = f.read()

  4.         print(len(data))

  5.         conn.send(data)

  6. server.close()

客户端:

  1. [root@test-c2c-console01 oldboy]#
    cat socket_client.py

  2. #-*-coding:utf-8-*-

  3. _author_ = “golden”

  4. #客户端

  5. import socket

  6. #声明socket类型,同时生成socket连接对象

  1. client = socket.socket()

  2. client.connect((‘localhost’,6969))

  1. f = open(“video.avi”,’wb’)

  2. while
    True:

  3.     msg = raw_input(“>>:”).strip()

  4.     if
    len(msg) == 0:continue

  5.     client.send(msg.encode(“utf-8”))

  6.     data = client.recv(102400)

  7.     #print(data.decode())

  8.     f.write(data)

  9.     f.flush()

  10.  

  11. client.close()

每交互一次文件的变化:

  1. [root@test-c2c-console01 oldboy]# ll
  1. total 665832

  2. -rw-r–r– 1 root root 681790584 Dec 27
    19:26 move.mkv

  3. -rw-r–r– 1 root root 0 Feb 12 09:08
    video.avi

  4. [root@test-c2c-console01 oldboy]# ll

  1. total 665864

  2. -rw-r–r– 1 root root 681790584 Dec 27
    19:26 move.mkv

  3. -rw-r–r– 1 root root 32768 Feb 12
    09:08 video.avi

  4. [root@test-c2c-console01 oldboy]# ll

  1. total 665964

  2. -rw-r–r– 1 root root 681790584 Dec 27
    19:26 move.mkv

  3. -rw-r–r– 1 root root 135168 Feb 12
    09:08 video.avi

  4. [root@test-c2c-console01 oldboy]# ll

  1. total 666164

  2. -rw-r–r– 1 root root 681790584 Dec 27
    19:26 move.mkv

  3. -rw-r–r– 1 root root 339968 Feb 12
    09:09 video.avi

  4. [root@test-c2c-console01 oldboy]# ll

  1. total 666164

  2. -rw-r–r– 1 root root 681790584 Dec 27
    19:26 move.mkv

  3. -rw-r–r– 1 root root 339968 Feb 12
    09:09 video.avi

socket实现简单的ssh

服务端:

  1. import socket,os

  2. server = socket.socket()

  3. server.bind((‘localhost’,9999))

  4. server.listen()

  5. while
    True:

  6.     conn,addr = server.accept()

  7.     print(“new conn:”,addr)

  8.     while True:

  9.         data = conn.recv(1024)

  10.         if not data:

  11.             print(“客户端已断开!”)

  12.             break

  13.         print(“执行指令:”,data)

  14.         cmd_res =
    os.popen(data.decode()).read() #接收字符串,执行结果也是字符串

  1.         print(“before send “,len(cmd_res))

  2.         if len(cmd_res) ==0:

  3.             cmd_res = “cmd has no output”

  4.         conn.send(str(len(cmd_res.encode())).encode(“utf-8”))#先发大小给客户端

  5.         conn.send(cmd_res.encode(“utf-8”))

  6.  

  7. server.close()

客户端:

  1. import socket

  2. client = socket.socket()

  3. client.connect((‘localhost’,9999))

  1.  

  2. while
    True:

  3.     cmd = input(“>>:”).strip()

  4.     if
    len(cmd) == 0:continue

  5.     client.send(cmd.encode(“utf-8”))

  6.     cmd_res_size =
    client.recv(1024)#接收大小

  7.     print(“结果大小:”,cmd_res_size)

  8.     received_size = 0

  9.     received_data = b”

  10.     while received_size < int(cmd_res_size.decode()):

  11.         data = client.recv(1024)

  1.         received_size += len(data)
    #每次收到的有可能小于1024,所以必须用len判断

  2.         # print(data.decode())

  3.         received_data += data

  4.     else:

  5.         print(“cmd res receive
    done…”,received_size)

  6.         print(received_data.decode())

  1.  

  2. client.close()

注意socket粘包问题。

方法一:在多个相连的send之间添加sleep。

  1. conn.send(str(len(cmd_res.encode())).encode(“utf-8”))#先发大小给客户端

  2. time.sleep(0.5)

  3. conn.send(cmd_res.encode(“utf-8”))

方法二:在每次send之后进行一次交互。

服务端:

  1. conn.send(str(len(cmd_res.encode())).encode(“utf-8”))#先发大小给客户端client_ack
    = conn.recv(1024) #wait client to confirm

  2. print(client_ack.decode())

  3. conn.send(cmd_res.encode(“utf-8”))

客户端:

  1. cmd_res_size =
    client.recv(1024)#接收大小

  2. client.send(“已准备好接收数据!”.encode(“utf-8”))

socket实现简单的ftp

ftp server:

1、读取文件名

2、检测文件是否存在

3、打开文件

4、检测文件大小

5、发送文件大小给客户端

6、等待客户端确认

7、开始边读边发数据

8、发送md5

服务端:

  1. import socket,os,hashlib

  2. server = socket.socket()

  3. server.bind((“localhost”,9999))

  4. server.listen()

  5. while
    True:

  6.     conn,addr = server.accept()

  7.     print(“new conn:”,addr)

  8.     while True:

  9.         print(“等待指令…”)

  10.         data = conn.recv(1024)

  11.         if not data:

  12.             print(“客户端已断开!”)

  13.             break

  14.         cmd,filename =
    data.decode().split()

  15.         print(filename)

  16.         if os.path.isfile(filename):

  17.             f = open(filename,”rb”)

  18.             m = hashlib.md5()

  19.             file_size =
    os.stat(filename).st_size

  20.             conn.send(str(file_size).encode())
    #send file size

  21.             conn.recv(1024) #wait
    for ack

  22.             for line in f:

  23.                 m.update(line)

  24.                 conn.send(line)

  25.             print(“file md5”,m.hexdigest())

  26.             f.close()

  27.             conn.send(m.hexdigest().encode())
    #sed med5

  28.         print(“send done”)

  29.  

  30. server.close()

客户端:

  1. import socket,hashlib

  2. client = socket.socket()

  3. client.connect((‘localhost’,9999))

  1. while
    True:

  2.     cmd = input(“>>:”).strip()

  3.     if
    len(cmd) == 0:continue

  4.     if
    cmd.startswith(“get”):

  5.         client.send(cmd.encode())

  1.         server_response =
    client.recv(1024)

  2.         print(“server_response:”,server_response)

  1.         client.send(b”read to recv file”)

  2.         file_total_size = int(server_response.decode())

  3.         received_size = 0

  4.         filename = cmd.split()[1]

  1.         f = open(filename + “.new”,”wb”)

  2.         m = hashlib.md5()

  3.         while received_size <
    file_total_size:

  4.             if file_total_size –
    received_size > 1024:

  5.                 size = 1024

  6.             else:

  7.                 size =
    file_total_size – received_size

  8.  

  9.             data = client.recv(size)

  1.             received_size += len(data)
  1.             m.update(data)

  2.             f.write(data)

  3.             #
    print(file_total_size,received_size)

  4.         else:

  5.             new_file_md5 =
    m.hexdigest()

  6.             print(“file recv
    done”,received_size,file_total_size)

  7.             f.close()

  8.         server_file_md5 =
    client.recv(1024)

  9.         print(“server file md5:{0},client file
    md5:{1}”.format(server_file_md5,new_file_md5))

  10. client.close()

SocketServer

实现并发处理。

The socketserver module simplifies the task of writing network servers.

socketserver常用类型

class
socketserver.TCPServer(server_address,RequestHandlerClass,bind_and_activate=True)

This uses the Internet TCP protocol,which provides for continuous
streams of data between the client and server.

class
socketserver.UDPServer(server_address,RequestHandlerClass,bind_and_activate=True)

This uses datagrams,which are discrete packets of information that may
arrive out of order or be lost while in transit.The parameters are the
same as for TCPServer.

创建一个socketserver至少分一下几步:

1、You mast create a request handler class by subclassing the
BaseRequestHandler class and overriding its handle() method;this method
will process incoming requests.

创建一个请求处理类继承BaseRequestHandler,并且重写父类handle()。

2、You mast instantiate one of the server classes,passing it the
server’s address and the request handler class.

实例化TCPServer,并传递server ip和上述创建的请求处理类给这个TCPServer。

3、Then call the handle_request() or server_forever() method of the
server object to process one or many requests.

server.handle_request():只处理一个请求

server.server_forever():处理多个请求

4、Call server_close() to close the socket.

服务端:

  1. import socketserver

  2.  

  3. class
    MyTCPHandler(socketserver.BaseRequestHandler):

  4.  

  5.     def handle(self):

  6.         while True:

  7.             try:

  8.                 self.data =
    self.request.recv(1024).strip()

  9.                 print(“{} wrote
    :”.format(self.client_address[0]))

  10.                 print(self.data)

  1.                 self.request.send(self.data.upper())
  1.             except ConnectionResetError
    as CR:

  2.                 print(“err”,CR)

  3.                 break

  4.  

  5. if
    __name__ == “__main__”:

  1.     HOST,PORT = “localhost”,9999

  2.     # server =
    socketserver.TCPServer((HOST,PORT),MyTCPHandler) # 单线程

  3.     server =
    socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler) #
    多线程,ForkingTCPServer多进程

  4.     server.serve_forever()

客户端:

  1. import socket

  2.  

  3. client = socket.socket()

  4. client.connect((“localhost”,9999))

  5. while
    True:

  6.     msg = input(“>>:”).strip()

  7.     if
    len(msg) == 0:continue

  8.     client.send(msg.encode(“utf-8”))

  9.     data = client.recv(1024)

  10.     print(“recv:”,data.decode())

  11. client.close()

开启一个新线程

  1. def process_request(self, request,
    client_address):

  2.     “””Start a new thread to process the request.”””

  1.     t = threading.Thread(target =
    self.process_request_thread,

  2.                          args =
    (request, client_address))

  3.     t.daemon = self.daemon_threads

  1.     t.start()  

相关文章