文成小盆友python-num9 socket编程
socket编程
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。
socket是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)
既然是文件那么socket模块 和 file 模块之间有哪些区别呢?
- file模块是针对某个指定文件进行【打开】【读写】【关闭】
- socket模块是针对 服务器端 和 客户端Socket 进行【打开】【读写】【关闭】
如下为socket流程图
1.OSI 7层模型回顾
七层模型,实际上是一个体系,亦称OSI(Open System Interconnection)参考模型,该参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系
那么具体的每层如下:
- 应用层 (Application):网络服务于最终用户的一个接口 常用协议有:HTTP FTP TFTP SMTP SNMP DNS
- 表示层(Presentation Layer):数据的表示、安全、压缩。(在五层模型里面已经合并到了应用层) 格式有:JPEG、ASCll、DECOIC、加密格式等
- 会话层(Session Layer):建立、管理、终止会话。(在五层模型里面已经合并到了应用层) 对应主机进程,指本地主机与远程主机正在进行的会话
- 传输层 (Transport):定义传输数据的协议端口号,以及流控和差错校验。 协议有:TCP UDP,数据包一旦离开网卡即进入网络传输层
- 网络层 (Network):进行逻辑地址寻址,实现不同网络之间的路径选择。 协议有:ICMP IGMP IP(IPV4 IPV6) ARP RARP
- 数据链路层 (Link):建立逻辑连接、进行硬件地址寻址、差错校验等功能。(由底层网络定义协议) 将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。
- 物理层(Physical Layer):建立、维护、断开物理连接。(由底层网络定义协议)
实际上两个(多个)主机在进行通话时正是遵循如上的模型,如下过程达到两个主机之间的数据交流。
更加详细的过程介绍如下:
那么我门今天所要学习的socket在数据交换过程中充当的是什么角色呢?请看下一张图:
2.python中socket模块
熟悉了以上socket在整个通信过程中的地位,下面就利用socket模块写个简单的server
一开始已经展示了socket的流程图,如果将这连个主机之间通过socket通信的过程比作为两个人打电话的过程,那么那幅图可以这样理解:
就针对如上的过程我们来创建一个简单的server:
server端代码:
# -*- coding:utf-8 -*-
#Author:wencheng.zhao
#
import socket ip_port = ('127.0.0.1',9999) #买手机
s = socket.socket() #买手机卡
s.bind(ip_port) #开机
s.listen(5) #等待电话
conn,addr = s.accept() #接听电话
res_data = conn.recv(1024) #接受客户端一开始发送的问候语。
print('----->>>>>>>>',res_data.decode()) #发消息
sen_data = res_data.upper() #将客户端传入的数据,全部处理成答谢然后在发回客户端去-(必须为字节的方式)
print(sen_data)
conn.send(sen_data) #将要传的数据传给客户端 #挂掉电话
conn.close()
client端代码:
# -*- coding:utf-8 -*-
#Author:wencheng.zhao
#
import socket ip_port = ('127.0.0.1',9999) #买手机
s = socket.socket() #拨号
s.connect(ip_port) #发消息
s.send(bytes("hello my name is zhaowencheng",encoding='utf-8')) #一开始就向server端发送一段问候语 - (必须一字节的方式) #接受消息
res_data = s.recv(1024) #再次接收,服务端处理完后的数据。
print(res_data.decode()) #挂掉电话:
s.close()
下面我们看下这个执行过程:
1.首先执行server端,使server端处于socket监听模式。(这时候server会阻塞,等待客户端传数据过来才能继续)
2.再执行client端代码后出现如下:
#client 显示如下:
HELLO MY NAME IS ZHAOWENCHENG Process finished with exit code 0 #server端显示如下:
----->>>>>>>> hello my name is zhaowencheng
b'HELLO MY NAME IS ZHAOWENCHENG' Process finished with exit code 0
根据上面的内容可以实现一个client端与server端的交互,但是还有改进之处,如本程序当客户端结束之后服务端也会随之结束,这点不符合常理,下面针对此再完善。并且加入判断exit的功能。
###############server端代码##############
# -*- coding:utf-8 -*-
#Author:wencheng.zhao
########简单server
###############################
import socket
ip_port=('127.0.0.1',9999)
s = socket.socket()
s.bind(ip_port)
s.listen(5) #等待电话
while True: #将整个创建链接的过程写到循环里,当客户端中断的时候,这边在重新创建。
conn,addr = s.accept()
while True:
try:
recv_data = conn.recv(1024)
if len(recv_data) == 0:break
print(recv_data,type(recv_data))
send_data = recv_data.upper()
conn.send(send_data) except Exception:
break conn.close() ########client端代码###############
# -*- coding:utf-8 -*-
#Author:wencheng.zhao
#普通客户端
import socket ip_port = ('127.0.0.1',9999)
s = socket.socket()
s.connect(ip_port) while True:
send_data = input(">>:").strip()
if len(send_data) == 0 :continue
s.send(bytes(send_data,encoding='utf8'))
if send_data == 'exit':break recv_data = s.recv(1024)
print(str(recv_data,encoding='utf8')) s.close() ###########
#############################
#####执行代码后如下:
#client
>>:hh
HH
>>:exit Process finished with exit code 0
#server端
/Library/Frameworks/Python.framework/Versions/3.5/bin/python3.5 /Users/wenchengzhao/PycharmProjects/s13/day9/server.py
b'hh' <class 'bytes'>
b'exit' <class 'bytes'> #当client退出的时候 server并没有推出。
实例,利用上面的知识实现一个类似ssh功能(能够执行某些简单的命令)
########server端代码############ while True:
conn,addr = s.accept()
while True:
try:
recv_data = conn.recv(1024)
if len(recv_data) == 0:break
#print(recv_data,type(recv_data))
p = subprocess.Popen(str(recv_data,encoding='utf8'),shell=True,stdout=subprocess.PIPE) #执行系统命令
res = p.stdout.read()
print(res)
if len(res) == 0:
send_data = 'cmd -- err'
else:
send_data = str(res,encoding='gbk')
#send_data = 'ok'
conn.send(bytes(send_data,encoding='utf8')) except Exception as e:
print(e)
break conn.close() ######client端代码如下
# -*- coding:utf-8 -*-
#Author:wencheng.zhao
#普通客户端
import socket ip_port = ('127.0.0.1',9999)
s = socket.socket()
s.connect(ip_port) while True:
send_data = input(">>:").strip()
if len(send_data) == 0 :continue
s.send(bytes(send_data,encoding='utf8'))
if send_data == 'exit':break recv_data = s.recv(1024)
print(str(recv_data,encoding='utf8')) s.close() ######################################
#执行结果如下:
#client
>>:ls
client.py
client2.py
server.py
temp9.py
tttclient.py
ttttblog.py >>:ls -a
.
..
client.py
client2.py
server.py
temp9.py
tttclient.py
ttttblog.py >>: ###########server端显示如下:
b'client.py\nclient2.py\nserver.py\ntemp9.py\ntttclient.py\nttttblog.py\n'
b'.\n..\nclient.py\nclient2.py\nserver.py\ntemp9.py\ntttclient.py\nttttblog.py\n'
如上实现了一个简单的远程执行命令的小程序。
3.socket粘包问题
粘包问题的由来: 由上面我们在接收数据时是用的是:
conn.recv(1024) --这个1024指的是一次接受的大小。
那么这样就存在一个问题,当发送端将大于1024时,会出现什么问题呢?当大于1024时这里最大也只能接受到1024(这个值可以调整,但是永远是一个定值),当再次接受时会首先接受上次未能接受完的内容,这样造成的结果肯定不是我们想要的。下面提供一种解决办法就是,当发送内容之前,先发送一段内容的长度大小,我在接收的时候会判断接受的内容是否已经全部接受完,如果没有接受完就应该一直循环来接收,这样就能确保会包所有的内容都能接收完了。
如下代码实例:
server端代码:
# -*- coding:utf-8 -*-
#Author:wencheng.zhao import socket
import subprocess #导入执行命令模块
ip_port=('127.0.0.1',9999) #定义元祖
#买手机
s=socket.socket() #绑定协议,生成套接字
s.bind(ip_port) #绑定ip+协议+端口:用来唯一标识一个进程,ip_port必须是元组格式
s.listen(5) #定义最大可以挂起胡链接数
#等待电话
while True: #用来重复接收新的链接
conn,addr=s.accept() #接收客户端胡链接请求,返回conn(相当于一个特定胡链接),addr是客户端ip+port
#收消息
while True: #用来基于一个链接重复收发消息
try: #捕捉客户端异常关闭(ctrl+c)
recv_data=conn.recv(1024) #收消息,阻塞
if len(recv_data) == 0:break #客户端如果退出,服务端将收到空消息,退出 #发消息
p=subprocess.Popen(str(recv_data,encoding='utf8'),shell=True,stdout=subprocess.PIPE) #执行系统命令,windows平
# 台命令的标准输出是gbk编码,需要转换
res=p.stdout.read() #获取标准输出
if len(res) == 0: #执行错误命令,标准输出为空,
send_data='cmd err'
else:
send_data=str(res,encoding='gbk') #命令执行ok,字节gbk---->str---->字节utf-8 send_data=bytes(send_data,encoding='utf8') #解决粘包问题
ready_tag='Ready|%s' %len(send_data)
conn.send(bytes(ready_tag,encoding='utf8')) #发送数据长度
feedback=conn.recv(1024) #接收确认信息
feedback=str(feedback,encoding='utf8') if feedback.startswith('Start'):
conn.send(send_data) #发送命令的执行结果
except Exception:
break
#挂电话
conn.close()
client端代码:
# -*- coding:utf-8 -*-
#Author:wencheng.zhao
import socket
ip_port=('127.0.0.1',9999)
#买手机
s=socket.socket()
#拨号
s.connect(ip_port) #链接服务端,如果服务已经存在一个好的连接,那么挂起 while True: #基于connect建立的连接来循环发送消息
send_data=input(">>: ").strip()
if send_data == 'exit':break
if len(send_data) == 0:continue
s.send(bytes(send_data,encoding='utf8')) #解决粘包问题
ready_tag=s.recv(1024) #收取带数据长度的字节:Ready|9998
ready_tag=str(ready_tag,encoding='utf8')
if ready_tag.startswith('Ready'): #Ready|9998
msg_size=int(ready_tag.split('|')[-1]) #获取待接收数据长度
start_tag='Start'
s.send(bytes(start_tag,encoding='utf8')) #发送确认信息 #基于已经收到的待接收数据长度,循环接收数据
recv_size=0
recv_msg=b''
while recv_size < msg_size: #如果接收到的总数比应该接收到的小就继续接收。(等于时结束,不会有大的情况)
recv_data=s.recv(1024)
recv_msg+=recv_data #将每一次接收到内容都追加到接收总内容中,最后统一展示
recv_size+=len(recv_data) #每次汇总一次接收大小
print('MSG SIZE %s RECE SIZE %s' %(msg_size,recv_size)) print(str(recv_msg,encoding='utf8'))
#挂电话
s.close()
执行结果展示:
#依次执行 server端,client端。
#在client输入命令(此命令的返回结果应该大于1024,才有测试粘包问题的效果,例如下面的命令) ####client端输入命令
>>: ls /usr/lib
MSG SIZE 4448 RECE SIZE 1024 #这里可以显示客户端在接收的时候的过程。。。 直到接收完才能停止。
MSG SIZE 4448 RECE SIZE 2048
MSG SIZE 4448 RECE SIZE 3072
MSG SIZE 4448 RECE SIZE 4096
MSG SIZE 4448 RECE SIZE 4448
charset.alias
cron
dtrace
dyld
groff
libATCommandStudioDynamic.dylib
libAVFAudio.dylib
libAccountPolicyTranslation.dylib
libBSDPClient.A.dylib
libBSDPClient.dylib
libCRFSuite.dylib
libCRFSuite0.12.dylib
libChineseTokenizer.dylib
...等等...
..
..
..
4.使用功能详解以及功能补充。
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0)
参数一:地址簇 socket.AF_INET IPv4(默认)
socket.AF_INET6 IPv6 socket.AF_UNIX 只能够用于单一的Unix系统进程间通信 参数二:类型 socket.SOCK_STREAM 流式socket , for TCP (默认)
socket.SOCK_DGRAM 数据报式socket , 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 可靠的连续数据包服务 参数三:协议 0 (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议
s.bind(address)
s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。
s.listen(backlog)
开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。
backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5
这个值不能无限大,因为要在内核中维护连接队列
s.setblocking(bool)
是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。
s.accept()
接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。
接收TCP 客户的连接(阻塞式)等待连接的到来
s.connect(address)
连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex(address)
同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061
s.close()
关闭套接字
s.recv(bufsize[,flag])
接受套接字的数据。bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。
s.recvfrom(bufsize[.flag])
与recv()类似,但返回值是(data,address)。其中data是包含接收数,address是发送数据的套接字地址。
s.send(string[,flag])
将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。
s.sendall(string[,flag])
将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
内部通过递归调用send,将所有内容发送出去。
sk.sendto(string[,flag],address)
将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。
sk.settimeout(timeout)
设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )
sk.getpeername()
返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
sk.getsockname()
返回套接字自己的地址。通常是一个元组(ipaddr,port)
sk.fileno()
套接字的文件描述符
5.socketserver 模块
socketserver内部使用 IO多路复用 以及 “多线程” 和 “多进程” ,从而实现并发处理多个客户端请求的Socket服务端。即:每个客户端请求连接到服务器时,Socket服务端都会在服务器是创建一个“线程”或者“进程” 专门负责处理当前客户端的所有请求。 python3中的socketserver模块与python2中的不同,在python2中对应为 SocketServer 模块。
如下:对于每一个请求都有一个对应的线程或者进程去处理。
ThreadingTCPServer
1、ThreadingTCPServer基础
使用ThreadingTCPServer:
- 创建一个继承自 SocketServer.BaseRequestHandler 的类
- 类中必须定义一个名称为 handle 的方法
- 启动ThreadingTCPServer
下面通过一段代码来实现一个简单的例子
server端代码:
####socketserver
import socketserver
class MyServer(socketserver.BaseRequestHandler): #创建一个类 继承socketserver.BaseRequestHandler
def handle(self): #必须定义一个handel的方法。
self.request.sendall(bytes('欢迎 ---',encoding='utf8')) #
while True:
data = self.request.recv(1024)
if len(data) == 0:break
print("%s sysa:%s" % (self.client_address,data.decode()))
self.request.sendall(data.upper()) if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('127.0.0.1',8009),MyServer)
server.serve_forever()
client:没有太大区别
############# 多线程 client
#普通客户端
import socket ip_port = ('127.0.0.1',8009)
s = socket.socket()
s.connect(ip_port) recv_data = s.recv(1024)
print(recv_data.decode()) while True:
send_data = input(">>:").strip()
if len(send_data) == 0 :continue
s.send(bytes(send_data,encoding='utf8'))
if send_data == 'exit':break recv_data = s.recv(1024)
print(str(recv_data,encoding='utf8')) s.close()
验证-执行结果:
#先运行server端代码
#再运行client端代码(多打开几个) #执行如下:
#client
欢迎 ---
>>:ls
LS
>>:llll
LLLL
>>: #server ##分别对应多个客户端。
('127.0.0.1', 62021) sysa:ls
('127.0.0.1', 62021) sysa:llll
('127.0.0.1', 62044) sysa:ls
('127.0.0.1', 62021) sysa:ls
('127.0.0.1', 62037) sysa:ls
('127.0.0.1', 62037) sysa:llll
客户端打开情况如下:
实例:多线程实现类ssh功能(远程执行简单命令):
server代码如下:(客户端不变)
import socketserver
import subprocess
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
# print self.request,self.client_address,self.server
self.request.sendall(bytes('欢迎-----',encoding="utf-8"))
while True:
data = self.request.recv(1024)
if len(data) == 0:break
print("[%s] says:%s" % (self.client_address,data.decode() ))
#self.request.sendall(data.upper())
cmd = subprocess.Popen(data.decode(),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
cmd_res = cmd.stdout.read()
if not cmd_res:
cmd_res = cmd.stderr.read()
if len(cmd_res) == 0: #cmd has not output
cmd_res = bytes("cmd has output",encoding="utf-8")
self.request.send(cmd_res ) if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('0.0.0.0', 8009), MyServer)
server.serve_forever()
文成小盆友python-num9 socket编程的更多相关文章
- 文成小盆友python-num7 -常用模块补充 ,python 牛逼的面相对象
本篇内容: 常用模块的补充 python面相对象 一.常用模块补充 1.configparser模块 configparser 用于处理特定格式的文件,起内部是调用open()来实现的,他的使用场景是 ...
- 文成小盆友python-num11-(2) python操作Memcache Redis
本部分主要内容: python操作memcache python操作redis 一.python 操作 memcache memcache是一套分布式的高速缓存系统,由LiveJournal的Brad ...
- 文成小盆友python-num12 Redis发布与订阅补充,python操作rabbitMQ
本篇主要内容: redis发布与订阅补充 python操作rabbitMQ 一,redis 发布与订阅补充 如下一个简单的监控模型,通过这个模式所有的收听者都能收听到一份数据. 用代码来实现一个red ...
- 文成小盆友python-num10 socketserver 原理相关。
本节主要内容: 1.IO多路复用 2.多线程多进程 3.小知识点补充(python中作用域相关) 4.socketserver源码分析补充 一.IO多路复用 I/O多路复用指:通过一种机制,可以监视多 ...
- 文成小盆友python-num5 -装饰器回顾,模块,字符串格式化
一.装饰器回顾与补充 单层装饰器: 如上篇文章所讲单层装饰器指一个函数用一个装饰器来装饰,即在函数执行前或者执行后用于添加相应的操作(如判断某个条件是否满足). 具体请见如下: 单层装饰器 双层装饰器 ...
- 文成小盆友python-num4 装饰器,内置函数
一 .python 内置函数补充 chr() -- 返回所给参数对应的 ASCII 对应的字符,与ord()相反 # -*- coding:utf-8 -*- # Author:wencheng.z ...
- 文成小盆友python-num3 集合,函数,-- 部分内置函数
本接主要内容: set -- 集合数据类型 函数 自定义函数 部分内置函数 一.set 集合数据类型 set集合,是一个无序且不重复的元素集合 集合基本特性 无序 不重复 创建集合 #!/bin/en ...
- 文成小盆友python-num2 数据类型、列表、字典
一.先聊下python的运行过程 计算机是不能够识别高级语言的,所以当我们运行一个高级语言程序的时候,就需要一个“翻译机”来从事把高级语言转变成计算机能读懂的机器语言的过程.这个过程分成两类,第一种是 ...
- 文成小盆友python-num17 - django基础
一.首先了解web应用的本质 对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. 下面利用socket实现一个简单的web框架: #!/usr/b ...
随机推荐
- NGUI类之间的关系和架构
NGUI Drawcall 1.使用同一个altals的元素尽量放在同一个UIPanel下面,在NGUI中,它消耗的drawcall是以每个Panel为独立计算单位进行计算的. 2.如果一个UIPan ...
- HDOJ 1202 The calculation of GPA
Problem Description 每学期的期末,大家都会忙于计算自己的平均成绩,这个成绩对于评奖学金是直接有关的.国外大学都是计算GPA(grade point average) 又称GPR(g ...
- DLL模块例2:使用__declspec(dllexport)导出函数,extern "C"规范修饰名称,隐式连接调用dll中函数
以下内容,我看了多篇文章,整合在一起,写的一个例子,关于dll工程的创建,请参考博客里另一篇文章:http://www.cnblogs.com/pingge/articles/3153571.html ...
- [转载]STL map中的一些基本函数
来源:(http://blog.sina.com.cn/s/blog_61533c9b0100fa7w.html) - C++ map的基本操作和使用_Live_新浪博客 Map是c++的一个标准容器 ...
- GF(2^8)乘法
最近在学AES,实现了一下伽罗瓦域(2^8)乘法. 至于什么是伽罗瓦域解释起来比较复杂,我也不一定能解释清楚,自行google.这里只是给出一个简单直观的实现. #include<iostrea ...
- PE基金的运作模式有哪些?
一.信托制(1)信托型基金是由基金管理机构与信托公司合作设立,通过发起设立信托受益份额募集资金,然后进行投资运作的集合投资工具(2)信托公司和基金管理机构组成决策委员会实施,共同进行决策(3)在内部分 ...
- MYSQL触发器学习笔记
课程学至金色晨曦科技公司技术总监沙利穆 触发器 1. 什么是触发器 触发器是一种特殊类型的存储过程,不由用户直接调用.创建触发器时会对其进行定义,以便在对特定表或列作特定类型的数据修改时执 ...
- tcp/ip连接
1.TCP先连接(三次握手) client -> server : SYN a server -> client :SYN b, ack a+1 client -> server a ...
- asp.net 获取系统的根目录
测试有效的 : 系统的根目录 HttpContext.Current.Server.MapPath(HttpContext.Current.Request.ApplicationPath).ToLo ...
- JavaScripts学习日记——DOM SAX JAXP DEMO4J XPath
今日关键词: XML解析器 DOM SAX JAXP DEMO4J XPath XML解析器 1.解析器概述 什么是解析器 XML是保存数据的文件,XML中保存的数据也需要被程序读取然后使用.那么程序 ...