socket协议
如果进程是一幢幢房子,socket就是房子的门,而端口可以确定是哪一扇门。
通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
socket 的典型应用就是 Web 服务器和浏览器:浏览器获取用户输入的URL,向服务器发起请求,服务器分析接收到的URL,将对应的网页内容返回给浏览器,浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。
我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
socket的阻塞与非阻塞:
阻塞式的socket的recv服从这样的规则:当缓冲区内有数据时,立即返回所有的数据;当缓冲区内无数据时,阻塞直到缓冲区中有数据。
非阻塞式的socket的recv服从的规则是:当缓冲区内有数据时,立即返回所有的数据;当缓冲区内无数据时,产生EAGAIN的错误并返回(在Python中会抛出一个异常)。
两种情况都不会返回空字符串,返回空数据的结果是对方关闭了连接之后才会出现的。
socket的默认情况下是阻塞模式:socket.accept()方法在没有接受到连接之前不能处理已经建立连接的其他操作,以及在recv()方法或者其他接受数据的方法时候都是阻塞的,如果没有接受到数据就会一直处于阻塞状态,来等待接受数据,这种情况只有通过开启新的进程或者线程来解决来自不同客户端的连接请求或者接受数据;socket可以支持非阻塞的模式;可以使用以下两种方法来设置socket的非阻塞模式:
1 # socket.setblocking(flag) 2 # 设置套接字为阻塞或非阻塞模式:如果 flag 为 false,则将套接字设置为非阻塞,否则设置为阻塞。 3 # socket.settimeout(value) 4 # 如果value赋为 0,则套接字将处于非阻塞模式。如果指定为 None,则套接字将处于阻塞模式。 5 # 阻塞 6 sock.setblocking(True) 7 sock.settimeout(None) 8 # 非阻塞 9 sock.setblocking(False) 10 sock.settimeout(0)
在非阻塞模式下可以实现在单线程模式下实现与多个客户端连接的交互
非阻塞模式的服务端:
1 # demo_socket_server_2.py文件 2 import logging 3 import socket 4 5 logging.basicConfig(level=logging.DEBUG, 6 format="%(asctime)s>%(message)s", datefmt="%Y-%m-%d %H:%M:%S") 7 logger = logging.getLogger(__name__) 8 9 10 class ServerClass(object): 11 """docstring for ServerClass""" 12 13 def __init__(self): 14 self.__HOST = "127.0.0.1" 15 self.__PORT = 9999 16 self.ADDR = (self.__HOST, self.__PORT) 17 self.__TCP_SOCKET = socket.socket( 18 family=socket.AF_INET, type=socket.SOCK_STREAM) 19 # 设置非阻塞 20 # self.__TCP_SOCKET.setblocking(False) 21 self.__TCP_SOCKET.settimeout(0.0) 22 # 用来存放套接字对象的列表 23 self.connlist = list() 24 25 def start_server(self): 26 with self.__TCP_SOCKET as sock: 27 sock.bind(self.ADDR) 28 sock.listen() 29 logger.info("Server is Running") 30 while True: 31 try: 32 conn, addr = sock.accept() 33 # logger.info(conn) 34 # 将连接的套接字对象设置为非阻塞 35 conn.setblocking(False) 36 msg = f"Hi,{addr}" 37 self.send_data(conn, msg) 38 # 添加到列表 39 self.connlist.append(conn) 40 # 如果没有连接进来需要捕获BlockingIOError异常 41 except BlockingIOError as e: 42 pass 43 # logger.debug("没有新的客户端连接") 44 # 循环套接字对象列表 进行收发数据 45 for conn in self.connlist: 46 msg = self.recv_data(conn) 47 self.send_data(conn, msg) 48 49 def recv_data(self, conn): 50 """接收数据""" 51 try: 52 msg = conn.recv(1024).decode("utf-8") 53 if not msg or msg in ["quit"]: 54 logger.debug("断开连接") 55 # 将套接字对象从列表移除 56 self.connlist.remove(conn) 57 else: 58 logger.info(msg) 59 return msg 60 except IOError as e: 61 pass 62 # logger.debug("没有接收到数据") 63 64 def send_data(self, conn, msg): 65 """发送数据""" 66 if msg: 67 msg = f"From Server {msg}" 68 try: 69 conn.sendall(msg.encode("utf-8")) 70 except ConnectionResetError as e: 71 pass 72 logger.debug("连接已断开,无法再发送信息") 73 74 75 if __name__ == '__main__': 76 ServerClass().start_server()
服务端非阻塞模式的情况下 主要通过循环控制不停的去捕获BlockingIOError 异常来判断是否有新的连接进来,或者是是否有数据可以接受到;该情况下CPU的使用率会很高。
客户端代码:
1 import logging 2 import time 3 import socket 4 import threading 5 6 # demo_logging_1.load_loggingconfig() 7 logging.basicConfig(level=logging.DEBUG, 8 format="%(asctime)s>%(message)s", datefmt="%Y-%m-%d %H:%M:%S") 9 logger = logging.getLogger(__name__) 10 11 12 class ClientClass(object): 13 """docstring for ClientClass""" 14 15 def __init__(self): 16 self.__HOST = "127.0.0.1" 17 self.__PORT = 9999 18 self.__ADDR = (self.__HOST, self.__PORT) 19 self.__TCP_SOCKET = socket.socket( 20 family=socket.AF_INET, type=socket.SOCK_STREAM) 21 22 def start_client(self): 23 """启动客户端""" 24 with self.__TCP_SOCKET as sock: 25 # 链接服务端地址 26 sock.connect(self.__ADDR) 27 logger.info("%s" % sock.recv(1024).decode("utf-8")) 28 recv_t = threading.Thread( 29 target=self.recv_data, args=(sock,)) 30 # 向服务端发送数据 31 send_t = threading.Thread( 32 target=self.send_data, args=(sock,)) 33 # 接收数据线程设置为守护线程 34 recv_t.setDaemon(True) 35 recv_t.start() 36 send_t.start() 37 send_t.join() 38 39 def send_data(self, sock): 40 while True: 41 send_data = input() 42 sock.sendall(send_data.encode("utf-8")) 43 # 如果输入 quit 或者 exit 断开连接 44 if send_data in ("quit"): 45 logger.info("正在退出...") 46 break 47 48 def recv_data(self, sock): 49 while True: 50 try: 51 recv_data = sock.recv(1024).decode("utf-8") 52 logger.info(recv_data) 53 except Exception as e: 54 pass 55 # logger.error(e, exc_info=True) 56 time.sleep(0.5) 57 break 58 59 60 if __name__ == '__main__': 61 ClientClass().start_client()
效果(服务端不需要多开启进程或者线程就可以实现与多个客户端之间通信):
其他案例:
python实现socket协议通信
保持独立思考,就是不要轻易的受到外在的影响,因为每天带给你们痛苦的更多的言论是外界的声音,所以你可以保持自己独立的选择和独立的判断,学会做一个独立思考的人。