python套接字
from socket import socket, SOCK_STREAM, AF_INET from datetime import datetime def main(): # 1.创建套接字对象并指定使用哪种传输服务 # family=AF_INET - IPv4地址 # family=AF_INET6 - IPv6地址 # type=SOCK_STREAM - TCP套接字 # type=SOCK_DGRAM - UDP套接字 # type=SOCK_RAW - 原始套接字 server = socket(family=AF_INET, type=SOCK_STREAM) # 2.绑定IP地址和端口(端口用于区分不同的服务) # 同一时间在同一个端口上只能绑定一个服务否则报错 server.bind(('192.168.1.2', 6789)) # 3.开启监听 - 监听客户端连接到服务器 # 参数512可以理解为连接队列的大小 server.listen(512) print('服务器启动开始监听...') while True: # 4.通过循环接收客户端的连接并作出相应的处理(提供服务) # accept方法是一个阻塞方法如果没有客户端连接到服务器代码不会向下执行 # accept方法返回一个元组其中的第一个元素是客户端对象 # 第二个元素是连接到服务器的客户端的地址(由IP和端口两部分构成) client, addr = server.accept() print(str(addr) + '连接到了服务器.') # 5.发送数据 client.send(str(datetime.now()).encode('utf-8')) # 6.断开连接 client.close() if __name__ == '__main__': main()
from socket import socket def main(): # 1.创建套接字对象默认使用IPv4和TCP协议 client = socket() # 2.连接到服务器(需要指定IP地址和端口) client.connect(('192.168.1.2', 6789)) # 3.从服务器接收数据 print(client.recv(1024).decode('utf-8')) client.close() if __name__ == '__main__': main()
需要注意的是,上面的服务器并没有使用多线程或者异步I/O的处理方式,这也就意味着当服务器与一个客户端处于通信状态时,
其他的客户端只能排队等待。很显然,这样的服务器并不能满足我们的需求,我们需要的服务器是能够同时接纳和处理多个用户请求的。
下面我们来设计一个使用多线程技术处理多个用户请求的服务器,该服务器会向连接到服务器的客户端发送一张图片。
服务端代码
from socket import socket, SOCK_STREAM, AF_INET from base64 import b64encode from json import dumps from threading import Thread def main(): # 自定义线程类 class FileTransferHandler(Thread): def __init__(self, cclient): super().__init__() self.cclient = cclient def run(self): my_dict = {} my_dict['filename'] = 'guido.jpg' # JSON是纯文本不能携带二进制数据 # 所以图片的二进制数据要处理成base64编码 my_dict['filedata'] = data # 通过dumps函数将字典处理成JSON字符串 json_str = dumps(my_dict) # 发送JSON字符串 self.cclient.send(json_str.encode('utf-8')) self.cclient.close() # 1.创建套接字对象并指定使用哪种传输服务 server = socket() # 2.绑定IP地址和端口(区分不同的服务) server.bind(('192.168.1.2', 5566)) # 3.开启监听 - 监听客户端连接到服务器 server.listen(512) print('服务器启动开始监听...') with open('guido.jpg', 'rb') as f: # 将二进制数据处理成base64再解码成字符串 data = b64encode(f.read()).decode('utf-8') while True: client, addr = server.accept() # 启动一个线程来处理客户端的请求 FileTransferHandler(client).start() if __name__ == '__main__': main()
客户端代码:
from socket import socket from json import loads from base64 import b64decode def main(): client = socket() client.connect(('192.168.1.2', 5566)) # 定义一个保存二进制数据的对象 in_data = bytes() # 由于不知道服务器发送的数据有多大每次接收1024字节 data = client.recv(1024) while data: # 将收到的数据拼接起来 in_data += data data = client.recv(1024) # 将收到的二进制数据解码成JSON字符串并转换成字典 # loads函数的作用就是将JSON字符串转成字典对象 my_dict = loads(in_data.decode('utf-8')) filename = my_dict['filename'] filedata = my_dict['filedata'].encode('utf-8') with open('/Users/Hao/' + filename, 'wb') as f: # 将base64格式的数据解码成二进制数据并写入文件 f.write(b64decode(filedata)) print('图片已保存.') if __name__ == '__main__': main()
在这个案例中,我们使用了JSON作为数据传输的格式(通过JSON格式对传输的数据进行了序列化和反序列化的操作),
但是JSON并不能携带二进制数据,因此对图片的二进制数据进行了Base64编码的处理。Base64是一种用64个字符表
示所有二进制数据的编码方式,通过将二进制数据每6位一组的方式重新组织,刚好可以使用0~9的数字、大小写字母
以及“+”和“/”总共64个字符表示从`000000`到`111111`的64种状态。上有关于Base64编码的详细讲解,不熟悉Base64的读者可以自行阅读。
udp套接字
传输层除了有可靠的传输协议TCP之外,还有一种非常轻便的传输协议叫做用户数据报协议,简称UDP。TCP和UDP
都是提供端到端传输服务的协议,二者的差别就如同打电话和发短信的区别,后者不对传输的可靠性和可达性做出
任何承诺从而避免了TCP中握手和重传的开销,所以在强调性能和而不是数据完整性的场景中(例如传输网络音视
频数据),UDP可能是更好的选择。可能大家会注意到一个现象,就是在观看网络视频时,有时会出现卡顿,有时
会出现花屏,这无非就是部分数据传丢或传错造成的。在Python中也可以使用UDP套接字来创建网络应用。