技术交流QQ群:1027579432,欢迎你的加入!

1.Socket编程介绍

  • socket是基于C/S架构的,也就是说进行socket网络编程时,通常需要两个文件,一个是服务端,一个客户端
  • 首先导入socket模块:import socket
  • python中socket通信逻辑如下图所示:


    socket网络编程.png
  • python中,使用socket.socket()方法来创建套接字,sk = socket.socket([family[, type[, proto]]])
    • family: 套接字家族,可以使AF_UNIX或者AF_INET。
    • type: 套接字类型,根据是面向连接的还是非连接分为SOCK_STREAM或SOCK_DGRAM,也就是TCP和UDP的区别
    • protocol: 一般不填默认为0


      socket具体参数.png

      服务端和客户端方法.png

      公共方法.png
  • 注意:
    • a.Python3以后,socket传递的都是bytes类型的数据,字符串需要先转换一下,string.encode()即可;另一端接收到的bytes数据想转换成字符串,只要bytes.decode()一下就可以。
    • b.在正常通信时,accept()和recv()方法都是阻塞的。所谓的阻塞,指的是程序会暂停在那,一直等到有数据过来。

2.Socket编程思路

  • 服务端:
    • a.创建套接字,绑定套接字到本地IP与端口:socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.bind()
    • b.开始监听连接:s.listen()
    • c.进入循环,不断接受客户端的连接请求:s.accept()
    • d.接收传来的数据,或者发送数据给对方:s.recv() , s.sendall()
    • e.传输完毕后,关闭套接字:s.close()
  • 客户端:
    • a.创建套接字,连接服务器地址:socket.socket(socket.AF_INET,socket.SOCK_STREAM) , s.connect()
    • b.连接后发送数据和接收数据:s.sendall(), s.recv()
    • c.传输完毕后,关闭套接字:s.close()
  • 注意:Python的socket编程,通常可分为TCP和UDP编程两种,前者是带连接的可靠传输服务,每次通信都要握手,结束传输也要挥手,数据会被检验,是使用最广的通用模式;后者是不带连接的传输服务,简单粗暴,不加控制和检查的一股脑将数据发送出去的方式,但是传输速度快,通常用于安全和可靠等级不高的业务场景,比如文件下载

3.TCP编程

  • 服务端程序,服务端先运行,程序如下:
import socket

# TCP编程
ip_port = ('127.0.0.1', 9999)
sk = socket.socket()  # 创建socket对象
sk.bind(ip_port)  # 为对象绑定ip地址和端口
sk.listen(5)  # 监听设置的端口,等待客户端的请求
print("启动socket服务,等待客户端连接...")
conn, address = sk.accept()  # 等待连接,此处自动阻塞
while True:
    client_data = conn.recv(1024).decode()  # 接收的数据是bytes数据,转成string类型
    if client_data == 'exit':  # 判断是否退出连接
        exit("通信结束")
    print("来自%s的客户端向发来信息:%s" % (address, client_data))
    conn.sendall("服务端已经收到你的信息".encode())  # 对客户端给出确认
conn.close()  # 关闭连接
  • 客户端程序,客户端程序后运行,程序如下:
import socket 

# TCP编程


ip_port = ('127.0.0.1', 9999)

s = socket.socket()  # 创建socket对象

s.connect(ip_port)  # 连接服务端

while True:   # 通过一个死循环不断接收用户输入,并发送给服务器
    inp = input("请输入你想要发送的一个信息: ").strip()
    if not inp:   # 防止输入空的信息,导致异常退出
        continue
    s.sendall(inp.encode())  # socket传递的都是bytes类型的数据,字符串先encode转成bytes类型
    
    if inp == "exit":
        print("通信结束!")
        break
    server_reply = s.recv(1024).decode()  # decode()是将bytes类型转成string类型
    print(server_reply)
s.close()   # 关闭连接

4.上面TCP编程例子的不足

  • 当服务端和客户端是一对一进行通信的情况下,工作是良好的。但是,如果有多个客户端同时连接同一个服务器时,结果可能不行。因为服务端无法同时对多个客户端提供服务。因为Python的socket模块,默认情况下创建的是单进程单线程,同时只能处理一个连接请求,如果实现多用户服务,那么需要使用多线程机制
  • 下面使用Python内置的threading模块,配合socket模块创建多线程服务端。客户端的代码不需要更改,可以继续使用。服务端的代码如下:
import threading
import socket


def link_handler(link, client):
    """
    此函数是线程需要执行的函数,负责具体的服务器和客户端之间的通信工作
    link: 当前线程处理的连接
    client: 客户端ip和端口信息
    """
    print("服务端开始接收来自[%s:%s]的请求..." % (client[0], client[1]))
    
    while True:
        client_data = link.recv(1024).decode()
        if client_data == "exit":
            print("结束与[%s:%s]的通信..." % (client[0], client[1]))
            break
        print("来自[%s:%s]的客户端向你发来信息:%s" % (client[0], client[1], client_data))
        link.sendall('服务器已经收到你的信息'.encode())
    link.close()
    

ip_port = ("127.0.0.1", 9999)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)

print("启动socket服务,等待客户端连接...")
while True:              # 一个死循环,不断的接收的连接请求
    conn, address = sk.accept()  # 等待连接,此处自动阻塞
    # 每当有新的连接过来,自动创建一个新的线程,
    # 并将连接对象和访问者的ip信息作为参数传递给线程的执行函数
    t = threading.Thread(target=link_handler, args=(conn, address))
    t.start()

5.UDP编程

  • 相对于TCP编程,UDP编程就简单很多。但是可靠性与安全性也差很多。由于UDP没有握手和挥手的过程,因此accept()和connect()都不需要,如下面的例子:
#######服务端#########
import socket

ip_port = ('127.0.0.1', 9999)

sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
sk.bind(ip_port)

while True:
    data = sk.recv(1024).strip().decode()
    print(data)
    
    if data == "exit":
        print("客户端主动断开连接!")
        break
sk.close()
#######客户端#########
import socket

ip_port = ("127.0.0.1", 9999)

sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
while True:
    inp = input("发送的消息: ").strip()
    sk.sendto(inp.encode(), ip_port)
    
    if inp == "exit":
        break
sk.close()

6.博客原文

原文链接