RPC 笔记(05)— socket 通信(单线程服务器)
1. Python 标准库
1.1 socket
提供 RPC
服务的网络通信功能,方便用户编写 tcp/udp
相关的代码。两个不同机器的进程需要通信时,可以通过 socket
来传输数据。
客户端 API
,参数略。
s = socket.socket() # 创建一个套接字
s.connect() # 连接远程服务器
s.recv() # 读
s.send() # 尽可能地写
s.sendall() # 完全写
s.close() # 关闭
send
和 sendall
方法的区别,在网络状况良好的情况下,这两个方法几乎没有区别。
send
方法有可能只会发送了部分内容,它通过返回值来指示实际发出去了多少内容。sendall
方法是对send
方法的封装,它考虑了这个情况,如果第一次send
方法发送不完全,就会尝试第二次第三次循环发送直到全部内容都发送出去了或者中间出了错误才会返回。
服务端 API
,参数略。
s = socket.socket() # 创建一个服务器套接字
s.bind() # 绑定端口
s.listen() # 监听连接
s.accept() # 接受新连接
s.close() # 关闭服务器套接字
1.2 struct
Python
内置的二进制解码编码库,用于将各种不同的类型的字段编码成二进制字节串。通过 struct
包将消息的长度整数编码成 byte
数组。
# 将整数 100 编码成 4 个字节的字符串,大写的 I 表示整形
byte_values = struct.pack("I", 100)
# 将一个 4 字节的字符串解码成一个整数
value, = struct.unpack("I", byte_values)
1.3 json
它可以将内存的对象序列化成 json
字符串,也可以将字符串反序列化成 Python
对象。它的序列化性能不高,但是使用方便直观。
2. 消息协议
使用长度前缀法来确定消息边界,将请求和响应使用 json
序列化成字符串作为消息体,然后通过 Python
内置的 struct
包将消息体的长度整数转成 4 个字节的长度前缀字符串。
3. 客户端代码
使用约定的长度前缀法对请求消息进行编码,对响应消息进行解码。如果要演示多个并发客户端进行 RPC
请求,那就启动多个客户端进程。
import json
import time
import struct
import socketdef send_request(sock_obj, method, parameter):request = json.dumps({"client": method, "params": parameter}) # 请求消息体length_prefix = struct.pack("I", len(request)) # 请求长度前缀print("request is {}".format(str.encode(request)))sock_obj.sendall(length_prefix)# sock.sendall(request) python2sock_obj.sendall(str.encode(request)) # python3def receive_response(sock_obj):length_prefix = sock_obj.recv(4) # 响应长度前缀length, = struct.unpack("I", length_prefix)# print("length is {}".format(length))body = sock_obj.recv(length) # 响应消息体res = json.loads(body)return res # 返回响应if __name__ == '__main__':s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect(("localhost", 8080))for i in range(10):send_request(s, "func1", "I am {} message".format(i))response = receive_response(s)print("response is {}".format(response))time.sleep(1)s.close()
socket.recv
方法来读取期望的消息,通过传入一个长度值来获取想要的字节数组。实际上这样使用是不严谨的,甚至根本就是错误的。
socket.recv(int)
默认是阻塞调用,不过这个阻塞也是有条件的。如果内核的套接字接收缓存是空的,它才会阻塞。只要里面有哪怕只有一个字节,这个方法就不会阻塞,它会尽可能将接受缓存中的内容带走指定的字节数,然后就立即返回,而不是非要等待期望的字节数全满足了才返回。这意味着我们需要尝试循环读取才能正确地读取到期望的字节数。
def receive(sock, n):rs = [] # 读取的结果while n > 0:r = sock.recv(n)if not r: # EOFreturn rsrs.append(r)n -= len(r)return ''.join(rs)
但是为了简单起见,我们直接使用 socket.recv
,在开发环境中网络延迟的情况较少发生,一般来说很少会遇到 recv
方法一次读不全的情况发生。
4. 服务器代码
单线程同步模型的服务器是最简单的服务器模型,每次只能处理一个客户端连接,其它连接必须等到前面的连接关闭了才能得到服务器的处理。否则发送过来的请求会悬挂住,没有任何响应,直到前面的连接处理完了才能继续。
import json
import struct
import socketdef handle_conn(conn, ip, handlers):print("{} connect ...".format(ip))# 循环读写while True:length_prefix = conn.recv(4) # 请求长度前缀if not length_prefix: # 连接关闭了conn.close()print("{} close ...".format(ip))break # 退出循环,处理下一个连接length, = struct.unpack("I", length_prefix)body = conn.recv(length) # 请求消息体request = json.loads(body)client_method = request['client']client_parameter = request['params']print("client request method is {}, params is {}".format(client_method, client_parameter))handler = handlers[client_method] # 查找请求处理器handler(conn, client_parameter) # 处理请求def process_request(sock, handlers):while True:conn, host_ip = sock.accept() # 接收连接handle_conn(conn, host_ip, handlers) # 处理连接def func1(conn, params):res = json.dumps({"response": "OK", "result": params}) # 响应消息体length_prefix = struct.pack("I", len(res)) # 响应长度前缀conn.sendall(length_prefix)# conn.sendall(response) # python2conn.sendall(str.encode(res)) # python3def main():s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建一个 TCP 套接字s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 打开 reuse addr 选项s.bind(("localhost", 8080)) # 绑定端口s.listen(1) # 监听客户端连接handlers = {"func1": func1 # 注册请求处理器}process_request(s, handlers) # 进入服务循环if __name__ == '__main__':main()
如果在上一个客户端运行期间,再开一个新的客户端,会看到新的客户端没有任何输出。直到前一个客户端运行结束,输出才开始显示出来。因为服务器是串行地处理客户端连接。
参考:
https://www.cnblogs.com/rainbow-ran/p/12201913.html
https://juejin.cn/book/6844733722936377351/section/6844733723133345805
RPC 笔记(05)— socket 通信(单线程服务器)相关推荐
- 嵌入式课程设计:socket通信模拟服务器客户端实现文件传送(基于c++语言)
本次课程设计时间大概是两天半,磕磕碰碰,一路上遇到了很多的问题,也不断的查资料,想办法解决各种拦路的BUG,最后做出来的效果自己还是挺满意的,能够实现多台电脑之间模拟服务端,客户端,进 ...
- socket服务器文件名和客户端一致,缘分锝天空的博客:嵌入式课程设计:socket通信模拟服务器客户端...
#include //#include #include #include #include #pragma comment(lib,"ws2_32.lib") using nam ...
- Socket通信---网络通信学习笔记(一)
两台计算机进行通信的基本前提: (1)IP地址: 每台计算机都有自己独一无二的IP地址,根据IP地址判断与哪台计算机进行通信. (2)端口号: 每个应用程序都有自己专属的端口,根据端口号判断与计算机中 ...
- Android开发笔记(一百一十一)聊天室中的Socket通信
Socket通信 基本概念 对于程序开发来说,网络通信的基础就是Socket,但因为是基础,所以用起来不容易,今天我们就来谈谈Socket通信.计算机网络有个大名鼎鼎的TCP/IP协议,普通用户在电脑 ...
- Android客户端与PC服务器实现Socket通信(wifi)
转载自:http://www.cnblogs.com/dwayne/archive/2012/05/22/Android_pc_wifi_socket.html 本文介绍Android终端持续扫描AP ...
- 咸鱼笔记—Socket 通信
咸鱼笔记-Socket 通信 socket是在应用层和传输层之间的一个抽象层,socket本质是编程接口(API),它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信. ...
- 笔记2:VC++ socket通信实例
VC++ socket通信实例 网络中进程之间如何通信 首要解决的问题是如何唯一标识一个进程,在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的.其实TCP/IP协议族已经帮我们 ...
- Vue学习笔记05 组件的自定义事件-组件通信-$nextTick-脚手架解决ajax跨域-插槽-过渡动画
文章目录 Vue学习笔记05 父组件给子组件传值 注意点 子组件给父组件传值 父组件接受子组件的传值 通过函数 组件的自定义事件 事件绑定的第一种写法 @或v-on 事件绑定的第二种写法:使用ref ...
- Linux C++服务器项目——网络编程1 (socket通信,服务端,客户端)
牛客 C++高并发服务器开发 参考笔记 1.MAC地址 2 IP地址 2.1 简介 2.2 IP地址编址方式 2.3 子网掩码 3 端口 3.1 简介 3.2 端口类型 4 网络模型 4.1 OSI七 ...
最新文章
- ubuntu终端切换快捷键
- 纹理对象纹理单元纹理目标_网页设计理论:纹理
- PAT乙级(1013 数素数)
- 配置的代理服务器未响应 电脑连不上网_手机、电脑为什么连不上网(断网)?...
- laravel框架解决sql注入问题
- 怎样通过迅捷PDF编辑器来修改PDF文件
- 安全基础-防火墙四种登录方式 SSH Telnet SSH
- 超大图片(4000×3000像素)的畸变矫正,python+OpenCV实现
- 屏的接口类型种类以及接口定义分析
- php doctrine 使用,php – Doctrine 2 – 多数据库配置和使用
- 26.看前端视频的感悟,来自碎碎念记录
- 解析android系统下Dex2oat的实现
- JDK19都出来了~是时候梳理清楚JDK的各个版本的特性了【JDK9特性讲解】
- FT232H如何使用jtag接口
- jsp与servlet的联系与区别
- 知乎上48个神回复,真心值得一看!
- 使用华盛顿特区地铁数据确定可获利的广告位置
- 逻辑卷管理器(LVM) 之 创扩缩秘籍
- 2021阳城一中高考成绩查询,山西高考分数线,晋城一中、阳城一中高考捷报这里都有...
- Launcher3之应用卸载过程分析