IPC:进程间通信

本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类:

  • 消息传递(管道、FIFO、消息队列)
  • 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
  • 共享内存(匿名的和具名的)
  • 远程过程调用(Solaris门和Sun RPC)

现在是网络时代,我们更关心的是网络中进程之间如何通信呢?首先要通信必须进程彼此之间互相认识对方,在本地可以通过进程PID来唯一标识一个进程,在网络上其实也可以用三元组(ip,协议,端口)来标识一个进程,这样就为网络进程间通信提供了可能,具体如何实现呢?

这就要引出我们今天的重点啦!!!!! socket

socket 即“套接字”,是TCP/IP协议的一个封装,一个软件抽象层,它对用户进程来说就是一个接口(可以看作是TCP/IP协议的接口),我们要使用TCP/IP协议来进行数据传输时就不需要直接处理TCP/IP协议,而是使用socket就可以啦,用户进程通过它来和TCP/IP进行数据的发送接收

socket 起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“open-->read/write-->close”模式来操作。socket作为一个特殊的文件,socket里的很多函数都是这种模式的。

下面的流程图详细的描述了socket服务端进程和客户端进程之间的工作关系,我们可以形象的把它类比成一次打电话的过程!

 #/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan import socket,subprocess
ip_port=("127.0.0.1",9007) #买电话
s=socket.socket() #绑定电话卡
s.bind(ip_port) #开机
s.listen(5) #待机
conn,addr=s.accept() while True:
#接听消息
recv_data=str(conn.recv(1024),encoding="utf8")
if recv_data == "exit":break
print("客户端说:>>",recv_data)
#发送消息
send_data=bytes("我收到你的消息啦!",encoding="utf8")
conn.send(send_data) #挂断
conn.close()

socket server

 #/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan import socket
ip_port=("127.0.0.1",9007) #找到公用电话
s=socket.socket() #给服务端打电话
s.connect(ip_port) while True:
#发消息
send_data=input(">>").strip()
s.send(bytes(send_data,encoding="utf8"))
if send_data == "exit":break
#收消息
resv_data=str(s.recv(1024),encoding="utf8")
print("服务端说:>>",resv_data) #挂断
s.close()

socket client

client:
>>hello
服务端说:>> 我收到你的消息啦!
>>hi
服务端说:>> 我收到你的消息啦!
>>你好
服务端说:>> 我收到你的消息啦!
>>库尼奇瓦
服务端说:>> 我收到你的消息啦!
>>哦哈呦
服务端说:>> 我收到你的消息啦!
>>你还会说别的吗
服务端说:>> 我收到你的消息啦!
>>exit server:
客户端说:>> hello
客户端说:>> hi
客户端说:>> 你好
客户端说:>> 库尼奇瓦
客户端说:>> 哦哈呦
客户端说:>> 你还会说别的吗

执行结果

上面是两个最简单的socket server和client的例子,从中我们可以看出,socket的代码很符合我们的那个例子------打电话,现在你应该可以凭自己的理解和记忆写个socket小程序了吧,恭喜你完成了很重要的一小步!

下面我们看下socket的功能:

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

socket功能

 server:
#/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan import socket sk=socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(("127.0.0.1",9999)) while True:
data,ip_port=sk.recvfrom(1024)
sk.sendto(data,ip_port) client:
#/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan import socket sk=socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
ip_port=("127.0.0.1",9999) while True:
inp=input(">>:").strip()
sk.sendto(bytes(inp,encoding="utf8"),ip_port)
data,ip_port=sk.recvfrom(1024)
print(str(data,encoding="utf8"))

UDP

使用socket的注意事项:

1.基于python3.5版本的socket只能收发字节(pyhton2.7可以收发str)

2.在链接正常的情况下accept()和recv()是阻塞的

3.listen(backlog) backlog代表能挂起的链接数,即:如果backlog=1,则代表可链接一个,挂起一个,剩余的请求将被拒绝。

下面我们有个小的需求,从这个小需求里会慢慢的暴露我们程序的不足并完善它:

需求:

我们实现一个服务端和一个客户端,客户端模拟ssh远程执行命令操作服务端。

 #/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan import socket,subprocess
ip_port=("127.0.0.1",9006) #买电话
s=socket.socket() #电话卡
s.bind(ip_port) #开机
s.listen(5) while True:
try:
#待机
conn,addr=s.accept()
while True:
#接听消息
recv_data=str(conn.recv(1024),encoding="utf8")
if recv_data == "exit" or len(recv_data) == 0: break
cmd=recv_data
p=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
out=str(p.stdout.read(),encoding="utf8")
err=str(p.stderr.read(),encoding="utf8")
#发送消息
if len(err) == 0:
if len(out) == 0:
send_data=bytes("执行无输出",encoding="utf8")
conn.send(send_data)
continue
else:
num=len(bytes(out,encoding="utf8"))
send_data="out size is %d bytes" % num
send_data=bytes(send_data,encoding="utf8")
else:
send_data=bytes(err,encoding="utf8")
conn.send(send_data)
continue
conn.send(send_data)
recv_data = str(conn.recv(1024), encoding="utf8")
if recv_data == "y":
send_data = bytes(out, encoding="utf8")
conn.send(send_data)
else:
send_data=bytes("客户端选择不接收数据",encoding="utf8")
conn.send(send_data)
except KeyboardInterrupt:
break
#挂断
conn.close()

socket server

 #/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan import socket
ip_port=("127.0.0.1",9006) #找到公用电话
s=socket.socket() #打电话
s.connect(ip_port) while True:
try:
#发消息
send_data=input(">>").strip()
if len(send_data) == 0:continue
s.send(bytes(send_data,encoding="utf8"))
if send_data == "exit":break
#收消息
resv_data=str(s.recv(1024),encoding="utf8")
if resv_data.startswith("out size is"):
print(resv_data)
num=int(resv_data.split(" ")[3])
st=input("is starting? y/n")
s.send(bytes(st,encoding="utf8"))
n1,n2=divmod(num,1024)
if n2 == 0:
n=n1
else:
n=n1+1
for i in range(n):
resv_data=str(s.recv(1024),encoding="utf8")
if resv_data.startswith("客户端"):
print(resv_data)
break
print(resv_data)
else:
print(resv_data)
except KeyboardInterrupt or BrokenPipeError:
break
#挂断
s.close()

socket client

client:
>>
>>ls
out size is 170 bytes
is starting? y/ny
__init__.py
socket_client.py
socket_client2.py
socket_client_simple.py
socket_server.py
socket_server_simple.py
socket_server_thread.py
thread_client.py
thread_server.py >>cat __init__.py
out size is 60 bytes
is starting? y/nn
客户端选择不接收数据
>>cat __init__.py
out size is 60 bytes
is starting? y/ny
#/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan
>>exit client断开时server继续等待下一次的链接

执行结果

在上面的代码中我们遇到的最大的一个问题就是“黏包”的问题,什么是黏包呢?

黏包: socket在传输过程中,客户端第一次请求的数据包没有接收完全,第二次请求时第一次请求的数据包和第二次请求的数据包合到一起发送到了客户端,这个就叫黏包。

怎么解决呢?其实很简单,我们只要确保每次我们发送的数据都能被对方全部接收,就不会和之后传送的包合并到一起,这样就避免了黏包的问题。具体如何操作呢?上面的代码里已经解决了这个问题啦。

解决思路是:

客户端请求数据后,服务端先给客户端发送一个数据长度的数据,客户端拿到这个数据长度的数据之后,循环的取数据,直到取完所有的数据,如何循环取呢?我采用这种方式:假定我们一次接收1024M的数据,我们用总数据长度除以1024得到商和余数,如果余数为0,取数据的次数就是商,如果非0,取数据的次数就是商+1,这样我们就能保证每次传输数据的完整性啦,从而就解决了黏包的问题。


上面我们使用socket实现了一对一的通信,现在大家可能会想,现在是互联网时代,我们必须要考虑高并发的问题,及实现一对多(一个服务端对多个客户端),也可以说是多对多(多个线程或进程对多个请求),python已经为我们提供了一个很好的模块(socketserver)来满足这个需求啦!

python把网络服务抽象成两个主要的类,一个是Sever类,用于处理链接相关的网络操作,另外一个则是RequestHandle类,用于处理数据相关的操作。并且提供两个MixIn类,用于扩展server,实现多进程和多线程。在构建网络服务时,server和requesthandle并不是分开的,requesthander的实例对象在server内配合server工作。

该模块的几个server继承关系如下:

socketserver的源码我们下次分析,这次我们只先关注它的使用:

创建服务器的步骤:首先,你必须创建一个请求处理类(eg:Myserver),它是BaseRequestHandler的子类并重载其hangle()方法。其次,你必须实例化一个服务器类,传人服务器的地址和请求处理程序类。最后,调用handle_request()(一般是调用其他事件循环或者使用select()或serve_forever())

下面是一个socketserver的简单例子:

 #/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan import socketserver class MyServer(socketserver.BaseRequestHandler):
def handle(self):
self.request.sendall(bytes("欢迎致电10086",encoding="utf8"))
while True:
data=self.request.recv(1024)
print("[%s] says:%s" % (self.client_address,str(data,encoding="utf8")))
self.request.sendall(data.upper())
if __name__ == "__main__":
ip_port=("127.0.0.1",3397)
server=socketserver.ThreadingTCPServer(ip_port,MyServer)
server.serve_forever()

socketserver_server

 #/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan import socket
ip_port=("127.0.0.1",3397) #找到公用电话
s=socket.socket() #打电话
s.connect(ip_port) welcome_msg=s.recv(1024)
print("from server:",str(welcome_msg,encoding="utf8")) while True:
try:
#发消息
send_data=input(">>").strip()
if len(send_data) == 0:continue
s.send(bytes(send_data,encoding="utf8"))
#收消息
resv_data=str(s.recv(1024),encoding="utf8")
print(resv_data)
except KeyboardInterrupt or BrokenPipeError:
break
#挂断
s.close()

client

 server:
[('127.0.0.1', 52136)] says:hi 我是client1
[('127.0.0.1', 52137)] says:hi 我是client2 client1:
from server: 欢迎致电10086
>>hi 我是client1
HI 我是CLIENT1
>> client2:
from server: 欢迎致电10086
>>hi 我是client2
HI 我是CLIENT2
>>

执行结果

下面我们将之前的那个需求(ssh那个)用socketserver实现下:

 #/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan import socketserver,subprocess
ip_port=("127.0.0.1",9007) class MyServer(socketserver.BaseRequestHandler):
def handle(self):
conn=self.request
data="[%s]欢迎登录本系统!" % str(self.client_address)
conn.sendall(bytes(data,encoding="utf8"))
while True:
# 接听消息
recv_data = str(conn.recv(1024), encoding="utf8")
if recv_data == "exit" or len(recv_data) == 0: break
cmd = recv_data
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out = str(p.stdout.read(), encoding="utf8")
err = str(p.stderr.read(), encoding="utf8")
# 发送消息
if len(err) == 0:
if len(out) == 0:
send_data = bytes("执行无输出", encoding="utf8")
conn.send(send_data)
continue
else:
num = len(bytes(out, encoding="utf8"))
send_data = "out size is %d bytes" % num
send_data = bytes(send_data, encoding="utf8")
else:
send_data = bytes(err, encoding="utf8")
conn.send(send_data)
continue
conn.send(send_data)
recv_data = str(conn.recv(1024), encoding="utf8")
if recv_data == "y":
send_data = bytes(out, encoding="utf8")
conn.send(send_data)
else:
send_data = bytes("客户端选择不接收数据", encoding="utf8")
conn.send(send_data)
if __name__ == "__main__":
server=socketserver.ThreadingTCPServer(ip_port,MyServer)
server.serve_forever()

server

 #/usr/bin/env python
#-*- coding:utf-8 -*-
#Authot:Zhang Yan import socket
ip_port=("127.0.0.1",9007) s=socket.socket() s.connect(ip_port) wel_msg=s.recv(1024)
print(str(wel_msg,encoding="utf8")) while True:
try:
send_data=input(">>").strip()
if len(send_data) == 0:continue
s.send(bytes(send_data,encoding="utf8"))
if send_data == "exit":break
resv_data=str(s.recv(1024),encoding="utf8")
if resv_data.startswith("out size is"):
print(resv_data)
num=int(resv_data.split(" ")[3])
st=input("is starting? y/n")
s.send(bytes(st,encoding="utf8"))
n1,n2=divmod(num,1024)
if n2 == 0:
n=n1
else:
n=n1+1
for i in range(n):
resv_data=str(s.recv(1024),encoding="utf8")
if resv_data.startswith("客户端"):
print(resv_data)
break
print(resv_data)
else:
print(resv_data)
except KeyboardInterrupt or BrokenPipeError:
break
s.close()

client

 client1:
[('127.0.0.1', 52161)]欢迎登录本系统!
>>pwd
out size is 29 bytes
is starting? y/nn
客户端选择不接收数据
>>pwd
out size is 29 bytes
is starting? y/ny
/Users/admin/Desktop/zy/day9 >> client2:
[('127.0.0.1', 52162)]欢迎登录本系统!
>>pwd
out size is 29 bytes
is starting? y/ny
/Users/admin/Desktop/zy/day9 >>pwd
out size is 29 bytes
is starting? y/nn
客户端选择不接收数据
>>

执行结果

												

python成长之路9——socket和socketserver的更多相关文章

  1. 【Python成长之路】Python爬虫 --requests库爬取网站乱码(\xe4\xb8\xb0\xe5\xa)的解决方法【华为云分享】

    [写在前面] 在用requests库对自己的CSDN个人博客(https://blog.csdn.net/yuzipeng)进行爬取时,发现乱码报错(\xe4\xb8\xb0\xe5\xaf\x8c\ ...

  2. Python进阶----UDP协议使用socket通信,socketserver模块实现并发

    Python进阶----UDP协议使用socket通信,socketserver模块实现并发 一丶基于UDP协议的socket 实现UDP协议传输数据 代码如下:

  3. (转)Python成长之路【第九篇】:Python基础之面向对象

    一.三大编程范式 正本清源一:有人说,函数式编程就是用函数编程-->错误1 编程范式即编程的方法论,标识一种编程风格 大家学习了基本的Python语法后,大家就可以写Python代码了,然后每个 ...

  4. 【Python成长之路】装逼的一行代码:快速共享文件

    [Python成长之路]装逼的一行代码:快速共享文件 2019-10-26 15:30:05 华为云 阅读数 335 文章标签: Python编程编程语言程序员Python开发 更多 分类专栏: 技术 ...

  5. python成长之路10——socketserver源码分析

    s = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) 参数一:地址簇 socket.AF_INET ipv4(默认) socket.AF_INE ...

  6. python成长之路第三篇(1)_初识函数

    目录: 函数 为什么要使用函数 什么是函数 函数的返回值 文档化函数 函数传参数 文件操作(二) 1.文件操作的步骤 2.文件的内置方法 函数: 一.为什么要使用函数 在日常写代码中,我们会发现有很多 ...

  7. 我的Python成长之路---第八天---Python基础(23)---2016年3月5日(晴)

    socketserver 之前讲道德socket模块是单进程的,只能接受一个客户端的连接和请求,只有当该客户端断开的之后才能再接受来自其他客户端的连接和请求.当然我们也可以通过python的多线程等模 ...

  8. 我的Python成长之路---第一天---Python基础(1)---2015年12月26日(雾霾)

    2015年12月26日是个特别的日子,我的Python成之路迈出第一步.见到了心目中的Python大神(Alex),也认识到了新的志向相投的伙伴,非常开心. 尽管之前看过一些Python的视频.书,算 ...

  9. python成长之路10

    断点续传   python2.7 多继承  py35多继承   socketserver源码     支持并发处理socket   i/o多路复用   上节回顾     socket          ...

随机推荐

  1. 当cpu飙升时,找出php中可能有问题的代码行

    参考大牛: http://www.searchtb.com/2014/04/%E5%BD%93cpu%E9%A3%99%E5%8D%87%E6%97%B6%EF%BC%8C%E6%89%BE%E5%8 ...

  2. BZOJ 1196 [HNOI2006]公路修建问题(二分答案+并查集)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1196 [题目大意] 对于每条可能维修的公路可选择修一级公路或者二级公路,价值不同 要求 ...

  3. Java知识点复习

    总结下java的知识点 final 关键字-方法:不能被子类重写(override)-变量:不能被修改-类:不可以被继承,派生子类 finally 关键字与try/catch语句配合使用,即使有异常抛 ...

  4. iOS搜索指定字符在字符串中的位置

    NSString *tmpStr = @"asd341234aaaaccd"; NSRange range; range = [tmpStr rangeOfString:@&quo ...

  5. zoj 3197 Google Book

    这道题告诉我想法正确是多么重要,先是我自己想的时候没考虑到最后的页码作为循环的终止,我一直以区间个数来终止循环,这是多么愚蠢啊!然后,我看了别人的代码,但是很不幸超时了! 我自己wa的代码,我感觉很正 ...

  6. 在mac系统安装Apache Tomcat的详细步骤(转载自himi的博客,修改了错误添加了图片)

    链接地址:http://blog.csdn.net/liuyuyefz/article/details/8072485 1. 2. 3. 4. 5. 对于Apache Tomcat 估计很多童鞋都会, ...

  7. windows下fitness python版本安装测试

    FitNesse介绍¶ FitNesse是一套软件开发协作工具. 伟大的软件需要协作和交流,FitNesse可以帮助大家加强软件开发过程中的协作.能够让客户.测试人员和开发人员了解软件要做成什么样,自 ...

  8. 0520 python

    配置python环境变量我的电脑->右键->属性->高级系统设置->环境变量->(1)用户变量->新建 Path=C:\Python27(2)系统变量->编辑 ...

  9. 【LeetCode题意分析&解答】33. Search in Rotated Sorted Array

    Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 migh ...

  10. [LeetCode]题解(python):001-Two-Sum

    题目来源: https://leetcode.com/problems/two-sum/ 题意分析: 这道题目是输入一个数组和target,要在一个数组中找到两个数字,其和为target,从小到大输出 ...