Python中基于Socket实现服务器端和客户端之间的通信

1. 实现如下效果

1.1. 要求

  1. server端监听指定的tcp端口

  2. server端预先实现简单加减法的代码(可以自行扩展其他更复杂功能)

  3. client端可以通过socket连接与server端通信传输需要参数

  4. server端根据传的参数计算结果并返回

  5. client端打印返回结果

1.2. 分析

上述要求中,明确了需要使用tcp协议,所以必然用到socket套接字编程。另外需要实现服务器端:实现加减法代码,并将计算结果返回给客户端;客户端:给服务器端的加减法代码传递参数,并打印服务器端的计算结果。

  • 服务器端分析

    服务器端的代码实现,需要借助socket模块,由于这个模块中的一些方法式阻塞式的,在特定方法组赛的时候,为了使其他任务可以继续执行,此时还需要借助threading模块实现多线程的操作。

    服务器端的socket编程,在主线程中创建了套接字对象之后,需要使用该套接字对象绑定到特定的IP地址以及端口(通过socket模块创建的socket套接字对象提供的bind方法完成绑定),并对该IP地址和端口进行监听(通过socket模块创建的socket套接字对象提供的listen方法对绑定的地址和端口进行监听)。随后就需要等待客户端发起连接请求,并且接受客户端的连接请求(通过socket对象的accept方法完成),随后就是通过socket对象的send以及recv方法实现服务器端与客户端之间的信息交互了。

    在下面的示例中,服务器端通过类实现,在类中除了实例对象初始化方法__init__之外,还提供了如下几个方法:

    • def start(self):在主线程之外,启动一个新的线程绑定到特定的IP地址以及端口上,并启动监听 。同时在这个新的线程中,再创建一个子线程,用于接受客户端的连接请求(因为socket对象的accept方法为阻塞式方法,如果不在新的子线程中处理连接请求,会导致程序阻塞在accept方法调用的地方)。
    • def accept(self):用于定义服务器端发现客户端连接请求的时候所执行的操作,如果客户端的连接请求正常建立,则保存该客户端的信息,同时创建一个新的线程,用于接收客户端发送的消息;否则抛出异常。
    • def recv(self, c_sock: socket.socket, c_addr: tuple):用于定义服务器端与客户端建立连接之后的操作,这个函数用于接收客户端发送给服务器端的信息,并将服务器端的处理结果发送给客户端。
    • def stop(self):用于定义服务器端结束的时候,所执行的操作。此时会将已经被记录的与客户端通信的套接字对象全部关闭,并且关闭主套接字对象。

    在主线程中,定义了一个无限循环,在这个循环中,可以通过指令结束主线程的循环,否则主线程会持续保持运行状态。

  • 客户端分析

    客户端程序只需要使用socket套接字对象的connect方法,连接到服务器端的IP地址和端口号即可。随后就可以与服务器端进行消息交互了。

    在客户端的代码实现中,也是用类实现了客户端的主要功能,在其中除了定义了用于实例对象初始化的__init__方法之外,还定义了如下几个主要功能方法,具体如下所示:

    • def start(self):用于连接到IP地址和端口号指定的服务器端的套接字对象,并且在这个方法中,创建一个新的线程,用于接收来自服务器端的消息(由于socket对象的recv方法是阻塞式方法,所以为了避免主线程被recv方法阻塞,将recv方法调用放在一个新的线程中完成)。
    • def recv(self):用于接收来自服务器端的消息。
    • def send(self, msg: str):用于向服务器端发送消息。
    • def stop(self):用于停止客户端程序,关闭客户端的socket套接字对象。

    在主线程中,创建了上述类的实例对象,并调用其start方法。同时在主线程中有一个无限循环,用于向服务器端发送消息,同时通过特定的输入内容,退出该无限循环。

1.3. 代码实现

  • 服务器端的代码实现

    服务器端的代码实现如下所示:

    import logging
    import string
    import threading
    import socket
    
    
    """
    服务器端:
    实现tcp网络通信,服务器端实现加减法,并将计算结果返回给客户端;
    客户端给服务器端传递参数,并打印服务器端的计算结果。
    """
    
    
    FORMAT = "%(asctime)s %(threadName)s %(thread)d <<- %(message)s ->>"
    logging.basicConfig(format=FORMAT, level=logging.INFO)
    
    
    class SocketMathServer:
    	def __init__(self, ip_addr, port_num):
        	self.addr = ip_addr, port_num
    	    self._sock = socket.socket()
        	self._event = threading.Event()
    	    self._lock = threading.Lock()
        	self.clients = {}
    	    # self._data = None
        	self._result = None
    
    	def start(self):   # 在主线程之外,启动一个线程接收客户端的连接请求
        	self._sock.bind(self.addr)
    	    self._sock.listen()
        	thread_obj = threading.Thread(target=self.accept, name='accept thread')   # accept方法为阻塞式方法
    	    thread_obj.start()
    
    	def accept(self):   # recv方法也为阻塞式方法,在accept线程之外,启动一个线程接收客户端发送的消息
        	while not self._event.is_set():
            	try:
                	client_sock, client_addr = self._sock.accept()
    	        except Exception as e:
        	        logging.info('quit server with {}'.format(e))
            	    break
    	        else:
        	        with self._lock:
            	        self.clients[client_addr] = client_sock
    
                	thread_obj = threading.Thread(target=self.recv, args=(client_sock, client_addr), name='recv thread')
                	thread_obj.start()
    
    	def recv(self, c_sock: socket.socket, c_addr: tuple):   # 用于接收客户端发送的消息
        	while not self._event.is_set():
            	try:
                	encode_data = c_sock.recv(1024)
    	        except Exception as e:
        	        logging.info('quit the server with {}'.format(e))
            	    break
    	        else:
        	        data = encode_data.decode().strip()
            	    logging.info(data)
                	target_str = string.digits + '+-'
    	            for s in ''.join(data.split(' ')):
        	            if s in target_str:
            	            continue
                	    else:
                    	    err_msg = '<< {} >> is invalid math expression'.format(data)
                        	logging.info(err_msg)
    	                    try:
        	                    c_sock.send(err_msg.encode())
            	            except Exception as e:
                	            logging.info(e)
                    	    break
    	            else:
        	            self._result = eval(data)
            	        res_msg = 'the result of << {} is {} >>.'.format(data, self._result)
                	    c_sock.send(res_msg.encode())
    
    	            if data == 'quit' or data == 'exit' or data == '':
        	            with self._lock:
            	            self.clients.pop(c_addr)
                	        c_sock.close()
                    	logging.info('{} quit'.format(c_addr))
    	                break
    
    	def stop(self):
        	self._event.set()
    	    with self._lock:
        	    for sock in self.clients.values():
            	    sock.close()
    	    self._sock.close()
    
    
    if __name__ == '__main__':
    	addr = '127.0.0.1', 9988
    	s1 = SocketMathServer(*addr)
    	s1.start()
    
    	while True:
        	cmd = input('if you want to exit server, please enter "quit" or "exit" >>> '.strip())
    	    if cmd == 'quit' or cmd == 'exit':
        	    s1.stop()
            	threading.Event().wait(3)
    	        break
    
        	logging.info(threading.enumerate())
    	    logging.info(s1.clients)
    

    上述就是服务器端的代码实现。

  • 客户端的代码实现

    客户端的代码实现如下所示:

    import logging
    import threading
    import socket
    
    
    """
    客户端:
    实现tcp网络通信,服务器端实现加减法,并将计算结果返回给客户端;
    客户端给服务器端传递参数,并打印服务器端的计算结果。
    """
    
    FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s)'
    logging.basicConfig(format=FORMAT, level=logging.INFO)
    
    
    class SocketMathClient:
    	def __init__(self, ip_addr, port_num):
        	self.addr = ip_addr, port_num
    	    self._sock = socket.socket()
        	self._event = threading.Event()
    
    	def start(self):
        	self._sock.connect(self.addr)
    	    # my_addr, my_port = self._sock.getsockname()
        	# self._sock.send('{} is ready'.format((my_addr, my_port)).encode())
       		#msg = input()
    	    #encode_msg = msg
        	#self.send(encode_msg)
    	    thread_obj = threading.Thread(target=self.recv, name='recv')
        	thread_obj.start()
    
    	def recv(self):
        	while not self._event.is_set():
            	try:
                	encode_data = self._sock.recv(1024)
    	        except Exception as e:
        	        logging.info('client receive error with << {} >>'.format(e))
            	    break
    	        else:
        	        data = encode_data.decode().strip()
            	    logging.info('{}'.format(data))
    
    	def send(self, msg: str):
        	encode_data = '{}\n'.format(msg.strip()).encode()
    	    self._sock.send(encode_data)
    
    	def stop(self):
        	client_ip, client_port = self._sock.getsockname()
    	    self.send('{} is quit'.format((client_ip, client_port)))
        	self._sock.close()
    	    self._event.wait(3)
        	self._event.set()
    	    logging.info('Client is over')
    
    
    if __name__ == '__main__':
    	server_ip, server_port = '127.0.0.1', 9988
    	sc = SocketMathClient(server_ip, server_port)
    	sc.start()
    
    	while True:
        	cmd = input("If you want to exit client, please enter 'quit' or 'exit' >>> ").strip()
    	    if cmd == 'quit' or cmd == 'exit':
        	    sc.stop()
            	break
    	    sc.send(cmd)
    

    上述就是客户端的代码实现。

  • 执行结果

    在PyCharm中,先运行服务器端程序,然后启动客户端程序,由于客户端程序中用于输入消息,并且接收服务器端的返回结果,所以主要观察客户端的交互输出接口。

    • 客户端程序的输出结果

      交互接口内容如下所示:

      If you want to exit client, please enter 'quit' or 'exit' >>> 123 + 345
      If you want to exit client, please enter 'quit' or 'exit' >>> 2022-01-28 15:35:31,940 recv 74320 the result of << 123 + 345 is 468 >>.)
      456 + 789
      2022-01-28 15:35:38,690 recv 74320 the result of << 456 + 789 is 1245 >>.)
      If you want to exit client, please enter 'quit' or 'exit' >>> 567 - 234
      If you want to exit client, please enter 'quit' or 'exit' >>> 2022-01-28 15:35:48,135 recv 74320 the result of << 567 - 234 is 333 >>.)
      a12 + 3
      2022-01-28 15:35:56,383 recv 74320 << a12 + 3 >> is invalid math expression)
      If you want to exit client, please enter 'quit' or 'exit' >>> 34-b
      If you want to exit client, please enter 'quit' or 'exit' >>> 2022-01-28 15:36:02,980 recv 74320 << 34-b >> is invalid math expression)
      quit
      2022-01-28 15:36:05,549 recv 74320 client receive error with << [WinError 10053] An established connection was aborted by the software in your host machine >>)
      2022-01-28 15:36:08,558 MainThread 74976 Client is over)
      
      Process finished with exit code 0
      

      服务器端返回的结果记录在<< >>中间。当输入quit或者exit的时候,就退出客户端的程序。

    • 服务器端的输出结果

      交互接口的内容如下所示:

      if you want to exit server, please enter "quit" or "exit" >>>2022-01-28 15:35:31,940 recv thread 69676 <<- 123 + 345 ->>
      2022-01-28 15:35:38,690 recv thread 69676 <<- 456 + 789 ->>
      2022-01-28 15:35:48,135 recv thread 69676 <<- 567 - 234 ->>
      2022-01-28 15:35:56,383 recv thread 69676 <<- a12 + 3 ->>
      2022-01-28 15:35:56,383 recv thread 69676 <<- << a12 + 3 >> is invalid math expression ->>
      2022-01-28 15:36:02,980 recv thread 69676 <<- 34-b ->>
      2022-01-28 15:36:02,980 recv thread 69676 <<- << 34-b >> is invalid math expression ->>
      2022-01-28 15:36:05,549 recv thread 69676 <<- ('127.0.0.1', 1739) is quit ->>
      2022-01-28 15:36:05,549 recv thread 69676 <<- << ('127.0.0.1', 1739) is quit >> is invalid math expression ->>
      2022-01-28 15:36:05,549 recv thread 69676 <<- [WinError 10054] An existing connection was forcibly closed by the remote host ->>
      2022-01-28 15:36:05,549 recv thread 69676 <<- quit the server with [WinError 10054] An existing connection was forcibly closed by the remote host ->>
      

      上述代码还有些小瑕疵,比如上述的第9行的内容,可以在代码中进行逻辑判断,即可解决。

    至此,就实现了预期的要求,即在客户端中输入加减法表达式,并将这个表达式传递给服务器端,随后在服务器计算该表达式,并将结果返回给客户端,然后在客户端中进行打印输出。

PythonSocket库(socket模块)常用于实现网络通信,包括服务器端客户端的连接。以下是简单概述: **服务器端示例**: ```python import socket # 创建套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定地址和端口 server_address = ('localhost', 12345) server_socket.bind(server_address) # 开始监听连接 server_socket.listen(1) # 监听队列长度为1的连接请求 while True: # 等待客户端连接 client_connection, client_address = server_socket.accept() print(f"Accepted connection from {client_address}") # 接收并处理客户端数据 data = client_connection.recv(1024).decode('utf-8') print(f"Received message: {data}") # 发送响应给客户端 response = "Hello from Server!" client_connection.sendall(response.encode('utf-8')) # 关闭连接 client_connection.close() ``` **客户端示例**: ```python import socket # 创建套接字 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接到服务器 client_address = ('localhost', 12345) client_socket.connect(client_address) # 发送消息到服务器 message = "Hello from Client!" client_socket.sendall(message.encode('utf-8')) # 接收服务器响应 response = client_socket.recv(1024).decode('utf-8') print(f"Response received: {response}") # 关闭连接 client_socket.close() ``` 在上面的例子服务器等待客户端连接,然后接收、处理信息并发送回应。客户端则主动连接到服务器,发送消息并接收回复。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值