• python网络编程:通过socket实现TCP客户端和服务端


    目录

    写在开头

    socket服务端(基础)

    socket客户端(基础)

    服务端实现(可连接多个客户端) 

    客户端实现

    数据收发效果

    写在开头

      近期可能会用python实现一些网络安全工具,涉及到许多关于网络的知识,逃不过的就是最基本的socket。本文将介绍如何通过python自带的socket库实现TCP客户端和服务端,实现多个客户端与一个服务端的消息收发,这是网络编程的基础。尽管python有许多第三方库可以创建网络客户端和服务端,但其核心都是socket模块,socket模块提供了许多有关网络连接的接口,可以干很多事情,后续我们还可能基于本文的案例慢慢继续开发,实现一些主机扫描、数据包注入等工具。

    socket(套接字)是进程之间进行网络通信的媒介,通信时分为服务端和客户端,可见下图(图来自黑马程序员的python课件)。

    socket服务端:被动等待其他进程(客户端)的连接,可以同时连接多个客户端,与之通信实现消息的收发。

    socket客户端:主动连接服务端,与服务端通信。

    下面我们先实现一个基础的服务端和客户端,实现一个客户端与一个服务端之间的通信。

    socket服务端(基础)

     首先引入socket模块,并创建socket服务端对象socket_server,此时该对象还无法确定是客户端/服务端,作为服务端,应当绑定一个服务端的主机和端口,这里就以本地"localhost",端口8888为例,通过.bind方法传入元组。

    1. import socket
    2. # 创建socket对象
    3. socket_server = socket.socket()
    4. # 绑定ip和端口,表明是服务端
    5. socket_server.bind(("localhost", 8888))

     接下来通过listen方法让服务器开始监听,传入一个int整数,表示最大连接数。此处我们实现最基础的情况传入1,仅允许一个客户端连接该服务端。接下来通过accept方法等待客户端的连接,在此之前我们可以通过print打印提示“正在等待客户端连接”。accept方法是一个阻塞是的方法,如果没有客户端连接,程序将一直在这里等待连接。.accept()方法返回一个二元元组(conn, client_addr),conn表示客户端和服务端的连接对象,client_addr = (client_host, client_port),我们用两个变量conn和address分别接收:

    1. # listen方法内接受一个整数传参数,表示接受的连接数量,可不填
    2. socket_server.listen(1)
    3. # 等待客户端连接,accept方法返回二元元组(连接对象, 客户端地址信息)
    4. # accept()方法是阻塞式的方法,如果没有客户端连接,会一直等待,不往下执行
    5. print(f"服务端已开始监听,正在等待客户端连接...")
    6. conn, address = socket_server.accept()
    7. print(f"接收到了客户端的连接,客户端的信息:{address}}")

       下面就可以实现数据的收发了,数据的收发基于本次服务端与客户端的连接对象conn,而非socket对象socket_server。这里假设客户端先发来消息,服务端先接收客户端发来的消息,再给客户端发送消息。接收消息使用recv方法,该方法也是阻塞式,只要没有接收到数据就会一直等待。其中的参数是缓冲区大小,通常填1024即可,recv接收到的消息(返回值)是字节流bytes对象,因此还需要通过decode方法解码为字符串:

    data: str = conn.recv(1024).decode("UTF-8")

         接收完成后,服务端通过send方法发送消息到客户端,同样,也需要将字符串编码为字节流的形式:

    1. msg = input("请输入你要回复客户端的消息:")
    2. conn.send(msg.encode("UTF-8")) # encode将字符串编码为字节数组对象

        完善一下代码,服务端实现持续的消息接收和发送,直到服务端输入"exit"退出,关闭服务端:

    1. while True:
    2. # 接收消息
    3. data: str = conn.recv(1024).decode("UTF-8")
    4. print(f"客户端发来的消息是:{data}")
    5. # 回复消息
    6. msg = input("请输入你要回复客户端的消息:")
    7. if msg == 'exit':
    8. break
    9. conn.send(msg.encode("UTF-8")) # encode将字符串编码为字节数组对象

    最后关闭此次连接即可,并关闭服务端。

    1. conn.close()
    2. socket_server.close()

    完整的服务端socket_server.py代码如下:

    1. import socket
    2. socket_server = socket.socket()
    3. socket_server.bind(("localhost", 8888))
    4. # 监听端口
    5. socket_server.listen(1)
    6. # 等待客户端连接,accept方法返回二元元组(连接对象, 客户端地址信息)
    7. print(f"服务端已开始监听,正在等待客户端连接...")
    8. conn, address = socket_server.accept()
    9. print(f"接收到了客户端的连接,客户端的信息:{address}}")
    10. # 接受客户端信息,使用客户端和服务端的本次连接对象,而非socket_server
    11. while True:
    12. # 接收消息
    13. data: str = conn.recv(1024).decode("UTF-8")
    14. print(f"客户端发来的消息是:{data}")
    15. # 回复消息
    16. msg = input("请输入你要回复客户端的消息:")
    17. if msg == 'exit':
    18. break
    19. conn.send(msg.encode("UTF-8")) # encode将字符串编码为字节数组对象
    20. # 关闭连接
    21. conn.close()
    22. socket_server.close()

     写完了不要着急运行,我们搞定客户端再一起试试。

    socket客户端(基础)

       客户端的代码和服务端差不多,只不过客户端是主动连接服务器,因此需要.connect方法连接服务器的host和端口,传入元组(服务端主机, 服务端端口号)即可,此处我们传入("localhost", 8888):

    1. import socket
    2. # 创建socket对象
    3. socket_client = socket.socket()
    4. # 连接到服务器
    5. socket_client.connect(("localhost", 8888))

      接下来实现数据的发送和接收,此处客户端先发送消息给服务端,再接收服务端回复的消息。同样的方法,send方法发送,recv方法接收,注意Bytes字节流和字符串的转换即可:

    1. while True:
    2. send_msg = input("请输入要发送给服务端的消息:")
    3. if send_msg == "exit":
    4. break
    5. # 发送消息到服务端
    6. socket_client.send(send_msg.encode("UTF-8"))
    7. # 接收服务端的消息
    8. recv_data = socket_client.recv(1024).decode("UTF-8") # 1024是缓冲区大小,一般就填1024, recv是阻塞式
    9. print(f"服务端回复的消息是:{recv_data}")

       类似,最后关闭连接:

    1. # 关闭连接
    2. socket_client.close()

       完整的客户端代码socket_client.py如下:

    1. import socket
    2. # 创建socket对象
    3. socket_client = socket.socket()
    4. # 连接到服务器
    5. socket_client.connect(("localhost", 8888))
    6. while True:
    7. send_msg = input("请输入要发送给服务端的消息:")
    8. if send_msg == "exit":
    9. break
    10. # 发送消息
    11. socket_client.send(send_msg.encode("UTF-8"))
    12. # 接受消息
    13. recv_data = socket_client.recv(1024).decode("UTF-8") # 1024是缓冲区大小,一般就填1024, recv是阻塞式
    14. print(f"服务端回复的消息是:{recv_data}")
    15. # 关闭连接
    16. socket_client.close()

       代码完成之后,我们先运行服务端代码socket_server.py,即可实现绑定服务器端口并等待客户端连接:

         然后开启客户端,运行socket_client.py即可建立连接 :

       此时回到刚才的服务端界面,可以发现出现了提示:建立了连接,此次连接的客户端主机是本机127.0.0.1,端口是2133:

      在客户端输入消息,即可实现客户端到服务端的消息传送,同理服务端也可以发送数据到客户端:

     任意一方输入exit即退出,另一方再发消息,由于连接已经关闭,会出现异常:

       这样我们就实现了客户端与服务端的通信,但此时只能有一个客户端连接到服务端,如果想实现多个客户端,就需要修改服务端listen()的参数,允许更多的客户端连接,下面进行讲解。

    服务端实现(可连接多个客户端) 

       这里先直接给出完整的代码server.py:

    1. import socket
    2. import threading
    3. def create_server_socket(host, port):
    4. socket_server = socket.socket()
    5. socket_server.bind((host, port))
    6. socket_server.listen(5)
    7. print(f"服务端已启动,地址{host},端口{port}")
    8. print(f"正在等待客户端连接...")
    9. # 开启多线程,收发来自多个客户端的数据
    10. num = 0 # 标记客户端的编号
    11. while True:
    12. num += 1
    13. conn, address = socket_server.accept()
    14. print(f"服务端已接受到客户端 {num}号 的连接请求,客户端信息:{address}")
    15. client_handler = threading.Thread(target=handle_client, args=(conn, address, num))
    16. client_handler.start()
    17. # 处理收发数据
    18. def handle_client(conn, address, num):
    19. while True:
    20. # 接收客户端发来的数据
    21. data_from_client: str = conn.recv(1024).decode("UTF-8")
    22. print(f"客户端 {num}号:{address}发来的消息是:{data_from_client}")
    23. # 发送消息到客户端
    24. msg = input(f"请输入你要回复客户端 {num}号:{address}的消息:")
    25. if msg == 'exit':
    26. break
    27. conn.send(msg.encode("UTF-8")) # encode将字符串编码为字节数组对象
    28. conn.close()
    29. if __name__ == '__main__':
    30. server_host = input("请输入服务端Host:")
    31. server_port = int(input("请输入服务端port:"))
    32. create_server_socket(server_host, server_port)

       其实也没有特别多的变化,通过函数进行了封装,然后修改listen方法,传一个大于1的整数,表示允许更多的客户端连接,此处设置的最大连接数为5。将数据收发的过程封装在函数handle_client中,额外设置了参数num表示这是第几个连接到该服务端的客户端。

        当一个客户端成功建立连接的时候(即运行完成conn, address = socket_server.accept()之时),将接收到的客户端socket对象保存在conn变量,远程连接信息(客户端host, 客户端port)保存在address变量,并启动线程指向handle_client函数,传入连接对象conn,连接信息address和客户端号num。创建好之后,启动该线程。于此同时,服务端create_server_socket中的while True循环也已经准备好处理下一个潜在的客户端连接

    客户端实现

          客户端和刚才的其实可以没啥区别,这里重新用函数封装为client1.py好了:

    1. import socket
    2. def create_client(host, port):
    3. socket_client = socket.socket()
    4. socket_client.connect((host, port))
    5. # 发送、接受数据
    6. while True:
    7. msg = input(f"请输入客户端1发送给服务端{host}:{port}的数据:")
    8. if msg == "exit":
    9. break
    10. # 发送数据到服务端
    11. socket_client.send(msg.encode("UTF-8"))
    12. # 接收服务端的数据
    13. data_from_server = socket_client.recv(1024).decode("UTF-8")
    14. print(f"客户端接收到服务端的消息:{data_from_server}")
    15. socket_client.close()
    16. if __name__ == '__main__':
    17. server_host = input("请输入想要连接的服务端Host:")
    18. server_port = int(input("请输入想要连接的服务端port:"))
    19. create_client(server_host, server_port)

      由于要实现多个客户端的连接,我们可以创建另外一个文件client2.py,代码和client1一样。这里我就用刚才的socket_client.py做示范好了。

    数据收发效果

       首先运行服务端代码server.py。输入服务端的host(本地localhost)和port(8888),此时服务端就进入了等待客户端连接的状态:

     再运行客户端代码client1.py和socket_client.py 输入服务端的host和端口,即可建立连接:

    服务端显示连接建立成功,两个连接均已建立:

     然后两个连接即可各自实现服务端与客户端的数据收发,注意都是客户端先发送消息,服务端先接收消息。客户端1号client.py如下:

       客户端2号socket_server.py运行交互过程如下:

      服务端server.py的运行交互过程如下:

       任何一端输入exit即可退出,对应的客户端和服务端建立的连接会断开(其他的连接依旧正常通信),比如这里服务端断开与client1的连接,与客户端2 socket_client的通信不受影响:

        client1客户端1由于服务端已经端口,异常退出了:

       客户端2不受影响:

       到这里就实现了多个客户端与服务端的消息传送。当然本文没有考虑如何处理阻塞、如何进行异常处理等细枝末节。后续可能会继续改进,并基于socket基本的通信不断扩展其功能,实现一些简易的网络工具。近期感觉网安就业困难,以后可能还会更新一些大数据相关的内容,敬请期待。如果读者有什么问题也欢迎评论区指出,知无不言。总结不易,还请读者多多点赞关注支持。

  • 相关阅读:
    五.指针与结构体
    基于SSM的课程管理系统
    HUAWEI设备配置VLANIF(SVI,网关)
    【前端Vue3】——JQuery知识点总结(超详细)
    Qt 中 connect 函数实现信号与槽函数的连接
    黑*头条_第1章_项目介绍和工程搭建
    多个jdk版本怎么使用指定的jdk去跑java程序?
    Spring refresh 方法之invokeBeanFactoryPostProcessors 方法解析
    基于信贷业务的量化风险评估简述
    Google Earth Engine(GEE)——用两种方法计算NDWI水域面积提取(Landsat 8)
  • 原文地址:https://blog.csdn.net/Bossfrank/article/details/136489111