利用python实现socket通信,需要实现客户端和服务端。通常,服务端不仅仅是为一个客户端服务,可能同时为多个客户端提供连接服务,在python中常见的提供多连接的方式有进程、线程、协程以及select和poll实现的异步I/O。这篇文章,主要介绍使用进程、线程、协程实现的socket多连接通信。
首先,这里先介绍一下进程、线程、协程的区别?(https://my.oschina.net/u/4543837/blog/4465255)
进程:操作系统提供的抽象概念,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。程序本身是没有生命周期的,它只是存在磁盘上的一些指令,程序一旦运行就是进程。
线程:也是操作系统提供的抽象概念,是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,同一进程中的多个线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。
协程(Coroutine,又称微线程)是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制。协程与线程以及进程的关系见下图所示。
-
协程可以比作子程序,但执行过程中,子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。协程之间的切换不需要涉及任何系统调用或任何阻塞调用
-
协程只在一个线程中执行,是子程序之间的切换,发生在用户态上。而且,线程的阻塞状态是由操作系统内核来完成,发生在内核态上,因此协程相比线程节省线程创建和切换的开销
-
协程中不存在同时写变量冲突,因此,也就不需要用来守卫关键区块的同步性原语,比如互斥锁、信号量等,并且不需要来自操作系统的支持。
协程适用于IO阻塞且需要大量并发的场景,当发生IO阻塞,由协程的调度器进行调度,通过将数据流yield掉,并且记录当前栈上的数据,阻塞完后立刻再通过线程恢复栈,并把阻塞的结果放到这个线程上去运行。
进程实现tcp socket通信
server
import socket
from multiprocessing import Process
class ServerProcess:
def __init__(self, ipaddr, port, num):
self.ipaddr = ipaddr
self.port = port
self.num = num
# 服务端的数据接收,在调用时使用多进程
def server_link(self, conn, addr):
conn.send("Welcome connect!".encode())
while True:
try:
data = conn.recv(1024)
if data:
print("from {0}:".format(addr), data.decode('utf-8'))
else:
break
except Exception:
break
conn.close()
# 服务端的启动程序
def server_start(self):
# IPv4
s_pro = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 操作系统会在服务器socket被关闭或服务器进程终止后马上释放该服务器的端口
s_pro.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s_pro.bind((self.ipaddr, self.port))
s_pro.listen(self.num)
print('Waiting link...')
while True:
conn, addr = s_pro.accept()
print("Success connect from ", addr)
# 启动多进程实现多连接
p = Process(target=self.server_link, args=(conn, addr))
p.start()
if __name__ == '__main__':
server = ServerProcess('127.0.0.1', 12345, 5)
server.server_start()
线程实现tcp socket通信
server
import socket
import threading
class ServerThread:
def __init__(self, ipaddr, port, num):
self.ipaddr = ipaddr
self.port = port
self.num = num
def server_link(self, conn, addr):
conn.send("Welcome connect!".encode())
while True:
try:
data = conn.recv(1024)
if data:
print("from {0}:".format(addr), data.decode('utf-8'))
else:
break
except Exception:
break
conn.close()
def server_start(self):
s_pro = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s_pro.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s_pro.bind((self.ipaddr, self.port))
s_pro.listen(self.num)
print('Waiting link...')
while True:
conn, addr = s_pro.accept()
print("Success connect from ", addr)
p = threading.Thread(target=self.server_link, args=(conn, addr))
p.start()
if __name__ == '__main__':
server = ServerThread('127.0.0.1', 12345, 5)
server.server_start()
协程实现tcp socket通信
server
import socket
import asyncio
class ServerProcess:
def __init__(self, ipaddr, port, num):
self.ipaddr = ipaddr
self.port = port
self.num = num
async def server_link(self, conn, addr):
conn.send("Welcome connect!".encode())
while True:
try:
data = conn.recv(1024)
if data:
print("from {0}:".format(addr), data.decode('utf-8'))
else:
break
except Exception:
break
conn.close()
def server_start(self):
s_pro = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s_pro.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s_pro.bind((self.ipaddr, self.port))
s_pro.listen(self.num)
print('Waiting link...')
while True:
conn, addr = s_pro.accept()
print("Success connect from ", addr)
loop = asyncio.get_event_loop()
loop.run_until_complete(self.server_link(conn, addr))
if __name__ == '__main__':
server = ServerProcess('127.0.0.1', 12345, 5)
server.server_start()
对于以上三种方式实现的服务端,客户端这里我都采用同样的代码,可以写多个客户端测试socket通信的并发性。
client
import socket
class Client1:
def __init__(self, ipaddr, port):
self.ipaddr = ipaddr
self.port = port
def client_link(self):
c1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c1.connect((self.ipaddr, self.port))
print('receive info:', c1.recv(1024).decode('utf-8'))
while True:
info = input('enter info:')
c1.send(info.encode())
if __name__ == '__main__':
c = Client1('127.0.0.1', 8888)
c.client_link()