• 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)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103

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

    • 客户端的代码实现

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

      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)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66

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

    • 执行结果

      在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
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15

        服务器端返回的结果记录在<< >>中间。当输入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 ->>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11

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

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

  • 相关阅读:
    电压源的电路分析知识分享
    首次安装我们的Vue脚手架
    sed 命令
    初阶数据结构 堆的问题详解 (三)
    pytorch复习笔记--nn.Embedding()的用法
    NCCoE发布“向后量子密码学迁移”项目进展情况说明书
    嵌入式分享合集61
    基于C语言的词法分析程序的设计与实现
    深入解析Redis的LRU与LFU算法实现
    SQL SERVER中存储过程的使用场景
  • 原文地址:https://blog.csdn.net/ikkyphoenix/article/details/125903553