1 原理

1.1 模型

  应用层协议需要必须传输数据,需要把数据封装为TCP/UDP包来传输,这个对TCP/UDP的封装就是socket通信。在socket里,包括send和receive。

  一个服务器上最多开通的port为65535个,一个ServerAPP监听在它的ip:port上,然后client 给这个ip:port发送socket send通信,信息里封装自己的ip:port(client的port是随机分配的),ServerAPP收到后, 返回信息给client的ip:port。clinet就收到了消息。

  

1.2 socket families

  socket.AF_UNIX  本机之间通信

  socket.AF_INET  ipv4网络通信

1.3 socket types

  socket.SOCK_STREAM  tcp通信,最常用。

  socket.SOCK_DGRAM   udp通信

  socket.SOCK_RAW    原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。就是说前两个是在传输层,这个是在网络层,甚至可以伪造ip地址。

1.4 基本实现

客户端:

先声明一个socket实例

然后连接到一个server

然后发数据。

然后接受server发过来的数据(是bytes类型,需要解码)

import socket

client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('localhost',3939))
client.send(b'in client,to server')
data = client.recv(1024)
print(data.decode())
client.close()

服务端:

先声明socket实例(地址族和类型)

accept后就处于阻塞状态,等待client。conn就是为连接过来的client开启的实例。

(这时客服端断开,server会收到一个null,会进入死循环。)

然后使用recv方法,将接受的数据赋值给data。recv(n)中n的值,官方建议最大8192(bytes)。

服务端后面把相关数据send给client的conn实例。

>>>

这样的服务端只能并发接受一个client的连接,并且这个连接断开后服务端自己也停止运行了。

import socket

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('localhost',3939))
server.listen()
conn,addr = server.accept()
print(conn,addr)
data = conn.recv(1024)
print('receive: %s' %(data))
send_data = '在服务器,给客户端'.encode()
conn.send(send_data)

  

1.5 服务端响应多个client(还是单并发)

二层循环中,if not data是为了应对client断开时发送过来的null导致的死循环

外层循环是为了当一个client断开后,服务端重新accept阻塞,然后把新client请求复制给conn。

在bind时,0.0.0.0代表本机的ip和127.0.0.1。如果bind ip,则客户端listen 127.0.0.1无法访问。如果bind 127.0.0.1,则网络中其他ip无法访问。0.0.0.0则都能。

服务端接受到的数据,用subprocess.Popen调用shell。

服务端:

import socket
import subprocess server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('0.0.0.0',3937))
server.listen()
while True:
conn,addr = server.accept()
while True:
data = conn.recv(1024)
if not data:
print('conn lost')
break
print('执行指令: %s' % (data))
p = subprocess.Popen(data.decode(), stdout=subprocess.PIPE, shell=True)
stdout = p.stdout.read()
if len(stdout) == 0:
send_data = 'cmd has no output...'
else:
send_data = stdout
print('data size: %d' %(len(send_data)))
conn.send(str(len(send_data)).encode())
conn.send(send_data)

  

1.6 根据数据包长度接受多包完整数据

客户端:

#!/usr/bin/env python
import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('192.168.10.12',3937)) while True:
msg = input('>>').strip()
if len(msg) == 0:continue #防止空输入产生卡死
client.send(msg.encode()) #bytes类型发送
rece_total_size = int(client.recv(1024).decode('gbk')) #返回数据的长度
#print(rece_total_size)
rece_current_size = 0 #计算已经收到的长度
rece_current_data = b'' #收集已经接受的数据段 while rece_current_size != rece_total_size: #每个数据包接受后都要进行判断,长度一致说明接受已完成。
data = client.recv(1024)
rece_current_data += data
rece_current_size += len(data)
#print('rece_current_size: %d' %(rece_current_size)) print(rece_current_data.decode('gbk')) #打印最终接受的数据

  

1.7 粘包

  1.5中这两条send语句,在win没有问题,但在linux上会被合并,然后从缓冲区里一次性发给client。这种紧挨着的send语句被一次性发送的现象叫粘包。

  一种解决办法是加入防粘包信号

服务端:

import socket
import subprocess server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('localhost',3937))
server.listen()
while True:
conn,addr = server.accept()
while True:
data = conn.recv(1024)
if not data:
print('conn lost')
break
print('执行指令: %s' % (data))
p = subprocess.Popen(data.decode(), stdout=subprocess.PIPE, shell=True)
stdout = p.stdout.read()
if len(stdout) == 0:
send_data = 'cmd has no output...'
else:
send_data = stdout
print('data size: %d' %(len(send_data)))
conn.send(str(len(send_data)).encode())
conn.recv(1024) #接受防粘包信息
conn.send(send_data)

客户端:

#!/usr/bin/env python
import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('192.168.10.12',3935)) while True:
msg = input('>>').strip()
if len(msg) == 0:continue #防止空输入产生卡死
client.send(msg.encode()) #bytes类型发送
rece_total_size = int(client.recv(1024).decode()) #返回数据的长度
client.send(b'1') #发送防粘包信号
#print(rece_total_size)
rece_current_size = 0 #计算已经收到的长度
rece_current_data = b'' #收集已经接受的数据段 while rece_current_size != rece_total_size: #每个数据包接受后都要进行判断,长度一致说明接受已完成。
data = client.recv(1024)
rece_current_data += data
rece_current_size += len(data)
#print('rece_current_size: %d' %(rece_current_size)) print(rece_current_data.decode()) #打印最终接受的数据

  

1.8 传文件

  socket文件传输效率低,容易丢包,实际上传文件用RabbitMQ消息队列。

2 socketserver模块

  实际上就是对socket的封装,这里模块里包含一些简化TCP、UDP操作的类。

  class socketserver.TCPServer()   #使用tcp协议,在服务端和客户端之间提供连续的数据流。单线程。

  class socketserver.ForkingTCPServer()  #多进程

  class socketserver.ThreadingTCPServer()   #多线程

  

2.1 TCPServer

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):    #首先要定义一个处理基类
def handle(self): #对这个基类进行重写
while True:
try:
self.data = self.request.recv(1024).strip() #self是一个实例,下面有requset这个子类,它里面有recv和send方法。
print(self.client_address[0]) #实例的连接客户端地址,client_address[0]是ip,[1]是port print(self.data) #recv的内容赋值给data
self.request.send(self.data.upper())
except: #当客户端断开时会抛出一个异常,所以要进行异常处理
print('%s disconnected..' %(self.client_address[0]))
break if __name__ == '__main__':
HOST,PORT = '0.0.0.0',3934
conn = socketserver.TCPServer((HOST,PORT),MyTCPHandler) #每个请求过来,TCPServer就会去实例化MyTCPHandler这个基类,然后用它的handler去和客户端交互。
conn.serve_forever() #serve_forver表示不停息的响应。

  

2.2 ForkingTCPServer

if __name__ == '__main__':
HOST,PORT = '0.0.0.0',3934
conn = socketserver.ForkingTCPServer((HOST,PORT),MyTCPHandler)
conn.serve_forever()

没有启动客户端时:

# ps aux | grep python3 | grep -v grep
root 40981 0.0 0.7 132580 7340 pts/1 S+ 14:38 0:00 python3 ts.py

启动每个客户端,就会多一条进程

[root@yhzk01 scripts]# ps aux | grep python3 | grep -v grep
root 40981 0.0 0.7 132580 7340 pts/1 S+ 14:38 0:00 python3 ts.py
root 41069 0.0 0.4 132580 4820 pts/1 S+ 14:46 0:00 python3 ts.py
root 41070 0.0 0.4 132580 4820 pts/1 S+ 14:46 0:00 python3 ts.py
root 41071 0.0 0.4 132580 4820 pts/1 S+ 14:46 0:00 python3 ts.py

  

2.3 ThreadingTCPServer

if __name__ == '__main__':
HOST,PORT = '0.0.0.0',3934
conn = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler)
conn.serve_forever()

 

最新文章

  1. bzoj2555: SubString
  2. Daily Scrum Meeting ——SeventhDay
  3. knockout+bootstrap--一些复杂的应用合集
  4. Training - Problem and Change Management
  5. VC6.0 C++ 如何调用微软windows系统SDK 语音API
  6. java web 学习 --第三天(Java三级考试)
  7. [转]建立swap分区
  8. sql 截取字符串第一次出现字符之前的数据
  9. 【HDOJ】1667 The Rotation Game
  10. 委托、匿名函数、Lambda表达式和事件的学习
  11. Lucene/Solr开发经验
  12. LigerUI 分页 MVC
  13. Node.js学习笔记1(简介)
  14. Oracle 使用命令导入dmp文件
  15. 启动springjar
  16. Android 8.0对隐式广播的进一步限制
  17. 027 storm面试小题
  18. Xaramin IOS 开发常见问题
  19. TPYBoard开发板搭建,实现隐秘通信
  20. python爬虫之PyQuery的基本使用

热门文章

  1. Vue生命周期方法。
  2. Domino函件收集器的配置及使用方法
  3. 设计模式学习笔记——Observer观察者模式
  4. Pattern: API Gateway / Backend for Front-End
  5. swt_table 回车可编辑Esc取消
  6. 关于JAVA中的前期绑定 后期绑定(动态绑定)
  7. 目录操作(PHP)
  8. laya的skeleton骨骼动画事件响应问题
  9. 书写优雅的shell脚本(二)- `dirname $0`
  10. 命令行 sql 将结果导出到文件