基于TCP的socket编程实现client和server通信

标签: Python学习

基于TCP的socket编程实现client和server通信

.

实验内容:

.

client为单线程、server为多线程(群聊功能)

.

原理:

.

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。

.
环境:

.

Python 3.8.2、Windows10、pycharm

.
实验过程:

.

一、先编写服务器实现群聊、

.

在这里插入图片描述

  • 成功建立连接、每个线程都有自己文件占位符fd和端口号port:

在这里插入图片描述

  • 三个client共同发送消息、验证群聊

在这里插入图片描述

  • 群聊实现,每个client都收到来自时间、端口各异的消息

在这里插入图片描述

在这里插入图片描述

  • TcpServer代码

#服务端
import socket
import threading    #基于线程的并行
import logging      #Python 的日志记录工具
import datetime     #基本的日期和时间类型

FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO)

#创建类
class TcpChatServer:
    #初始化、ip、port
    def __int__(self, ip='127.0.0.1', port=9999):
        self.addr = (ip, port)
        self.sock = socket.socket()
        self.clients = {}           #空字典储存已有进程

    #开启
    def start(self):
        self.sock.bind(self.addr)
        self.sock.listen()      #服务启动、开始监听
        threading.Thread(target=self.accept, name='accept').start()     #开始线程活动

    def accept(self):
        while True:
            s, raddr = self.sock.accept()      #返回新的套接字对象和客户端IP地址
            logging.info(s)
            logging.info(raddr)
            #key = raddr, values = s
            self.clients[raddr] = s
            threading.Thread(target=self.recv, name='recv', args=(s,)).start()

    def recv(self, sock:socket.socket):
        while True:
            try:                                #异常处理
                data = sock.recv(1024)          #阻塞点
                logging.info(data)  # 打印拿到的数据
            except Exception as e:
                logging.error(e)
                data = b'quit'
            if data == b'quit':
                self.clients.pop(sock.getpeername())
                break
            msg = "ack{}. {} {}".format(        #数据整理=地址+时间+内容
                sock.getpeername(),
                datetime.datetime.now().strftime("%Y/%m/%d-%H:%M:%S"),
                data.decode()).encode()
            #一对多、发送所有接受的数据
            for s in self.clients.values():
                    s.send(msg)

    #发送
    def send(self, cmd):
        sMsg = "send{}. {} {}".format(
            self.sock.getsockname(),       #Return the socket's own address
            datetime.datetime.now().strftime("%Y/%m/%d-%H:%M:%S"),
            cmd).encode()
#        self.sock.send(sMsg)
        # 一对多、发送所有接受的数据
        for s in self.clients.values():
            s.send(sMsg)

    #停止
    def stop(self):
        for s in self.clients.values():
            s.close()
        self.sock.close()

#custom
cs = TcpChatServer()
cs.__int__()
cs.start()

#测试、输入显示当前进程
while True:
    cmd = input("输入show查看当前进程,输入quit关闭服务器\n")
    if cmd.strip() == 'quit':               #移除字符串头尾指定的字符(默认为空格或换行符)或字符序列
        cs.stop()
        threading.Event.wait(3)             #阻塞线程直到内部变量为true
        break
    elif cmd.strip() == 'show':
        logging.info(threading.enumerate())  # 以列表形式返回当前所有存活的 Thread 对象
    else:
        cs.send(cmd)
        

.

二、再编写客户端、

.

  • 连接服务器

在这里插入图片描述

  • 实现双向数据传输

在这里插入图片描述

  • TcpClient代码

    
    #客户端
    import socket
    
    import datetime
    
    import threading
    
    class TcpCilent:
    
        def __int__(self):
            host = '127.0.0.1'
            port = 9999
            raddr = (host, port)
            self.bufsize = 1024                                                  # 指定接收数据大小
            self.tcpCilent = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # 创建TCP Socket
            self.tcpCilent.connect(raddr)                                        # 连接
            threading.Thread(target=self.send, name='send').start()  # 开始线程活动
            threading.Thread(target=self.recv, name='recv').start()
    
        def send(self):
            #发送数据
            while True:
                cmd = input("输入要发送的数据、输入quit断开连接:\n")
                if cmd.strip(':') == 'quit':
                    self.stop()
                    break
                cMsg = "send{}. {} {}".format(
                    self.tcpCilent.getsockname(),
                    datetime.datetime.now().strftime("%Y/%m/%d-%H:%M:%S"),
                    cmd
                )
                self.tcpCilent.send(cMsg.encode())
    
        def recv(self):
            while True:
                #接收数据
                try:
                    data = self.tcpCilent.recv(self.bufsize)
                except Exception as e:          #常规错误的基类、线程释放问题?
                    print(e)
                    data = ""
                if not len(data):
                    break
                sMsg = "ack{}. {} {}".format(  # 数据整理=地址+时间+内容
                    self.tcpCilent.getpeername(),
                    datetime.datetime.now().strftime("%Y/%m/%d-%H:%M:%S"),
                    data.decode())
                print(sMsg)
    
        def stop(self):
            self.tcpCilent.close()
    
    cs = TcpCilent()
    cs.__int__()
    
    

    .

三、最后实现两者通信

.

  • cmd运行server、pycharm运行client、建立socket连接

在这里插入图片描述

在这里插入图片描述

  • 验证互相通信

在这里插入图片描述

在这里插入图片描述

.

.

.

Windows 下的 socket 程序和 Linux 思路相同,但细节有所差别:

Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.dll,必须提前加载。DLL 有两种加载方式,请查看:动态链接库DLL的加载

Linux 使用“文件描述符”的概念,而 Windows 使用“文件句柄”的概念;Linux 不区分 socket 文件和普通文件,而 Windows 区分;Linux 下 socket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。

Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。

关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数,python中socket模块也是用的socket.close()。

dows 区分;Linux 下 socket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。

Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。

关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数,python中socket模块也是用的socket.close()。

版权声明:本文为qq_42404383原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_42404383/article/details/104804332