socket总结

socket使用文档

socket(简称 套接字) 是 进程间通信 的一种方式,它与其他进程间通信的一个主要不同是:

它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的

1 socket的使用

在 python 中如何使用 socket?

import socket

socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

参数说明:

函数 socket.socket 创建一个 socket,该函数带有两个参数:

  • family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET
  • type:套接字类型,可以使用SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者使用 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)

创建一个tcp套接字

import socket

# 创建tcp的套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

"""
这里省略功能代码
"""

# 不用的时候,关闭套接字
s.close()

创建一个udp套接字

import socket

# 创建udp的套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

"""
这里省略功能代码
"""

# 不用的时候,关闭套接字
s.close()
  • 套接字使用流程 与 文件的使用流程很类似:
    • 创建套接字
    • 使用套接字收/发数据
    • 关闭套接字

2 udp 套接字

创建一个udp客户端程序,具体步骤如下:

  1. 创建客户端套接字
  2. 发送/接收数据
  3. 关闭套接字
# -*- coding: utf-8 -*-
import socket

# 1. 创建一个 udp 套接字
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 2. 绑定ip和端口  一般ip不用书写, 表示本机上的任何一个ip
address = ('', 7788)
udp.bind(address)

# 3. 发送数据
to_address = ('169.254.252.93', 7788)
udp.sendto('你好'.encode('utf-8'), to_address)

# 4. 等待对方发送请求 这个过程是阻塞的 直到接收到数据  1024表示本次接收的最大字节数
recv_data = udp.recvfrom(1024)

# 4. 显示接收到的数据
print(recv_data[0].decode('utf-8'))

# 5. 关闭套接字
udp.close()

3 tcp 套接字

TCP协议,传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议

TCP通信需要经过创建连接、数据传送、终止连接三个步骤

TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据

  • TCP特点

    • 面向连接

      通信双方必须先建立连接才能进行数据的传输,双方都必须为该连接分配必要的系统内核资源,以管理连接的状态和连接上的传输

    • 可靠传输

      • TCP采用发送应答机制

        TCP发送的每个报文段都必须得到接收方的应答才认为这个TCP报文段传输成功

      • 超时重传

        发送端发出一个报文段之后就启动定时器,如果在定时时间内没有收到应答就重新发送这个报文段。

        TCP保证了数据不丢包

      • 错误校验

        TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验

      • 流量控制和阻塞管理

        流量控制用来避免主机发送得过快而使接收方来不及完全收下。

3.1 tcp客户端

发送数据,要知道tcp服务端的 ip和端口

# -*- coding: utf-8 -*-
import socket

# 1. 创建tcp 套接字
tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 连接到 tcp服务端
server_address = ('169.254.252.93', 7788)
tcp_client.connect(server_address)

# 3. 发送数据
send_data = "获取服务端数据"
tcp_client.send(send_data.encode('utf-8'))

# 4. 等待服务端数据返回  最大接收1024个字节
recv_data = tcp_client.recv(1024)
print(recv_data.decode('utf-8'))

# 5. 关闭套接字
tcp_client.close()

3.2 tcp服务端

具体流程如下:

  1. 创建一个socket套接字
  2. bind绑定ip和port
  3. listen使套接字变为可以被动链接
  4. accept等待客户端的链接
  5. recv/send接收发送数据
# -*- coding: utf-8 -*-
import socket

# 1. 创建tcp套接字
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 设置端口复用  表示: 服务端关闭,端口释放
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 3. 绑定 ip和端口  ip地址不指定,本地上的任何ip都可以使用
address = ('', 7788)
tcp_server.bind(address)

# 4. 设置监听  128表示 指定等待建立连接的客户端个数
tcp_server.listen(128)

while True:
    # 5. 等待客户端连接请求 client: 为客户端服务的套接字对象, client_address: 客户端地址
    client, client_address = tcp_server.accept()
    print(client_address)

    # 6. 等待客户端传来的数据
    recv_data = client.recv(1024)
    print(recv_data.decode('utf-8'))

    # 7. 返回数据给客户端 发送的数据是二进制数据
    send_data = '我是服务器'
    client.send(send_data.encode('utf-8'))

    # 8. 关闭为客户端服务的套接字
    client.close()

上面的代码案例,服务端只能同时处理一个客户端的请求,不能同时处理多个客户端的请求,怎么改造成适应多客户端呢?

  • 采用线程方式实现

    # -*- coding: utf-8 -*-
    import socket
    import threading
    
    
    def start_tcp_server(ip='', port=7788):
        # 1. 创建 tcp套接字
        tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2. 设置端口复用  表示: 服务端关闭,端口释放
        tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 3. 绑定 ip和port
        tcp_server.bind((ip, port))
        # 4. 设置监听  128表示 指定等待建立连接的客户端个数
        tcp_server.listen(128)
        return tcp_server
    
    
    def handle_client_request(client, ip_address):
        """
        处理客户端请求
        :param client: 处理客户端请求的套接字对象
        :param ip_address: 客户端的ip和port
        :return:
        """
        print(f'当前客户端地址:{ip_address}')
        while True:
            # 6. 等待客户端传来的数据  该方法是阻塞过程,直到收到客户端发来的请求
            recv_data = client.recv(1024)
            # 如果客户端断开连接时,此时recv_data就是空数据
            if recv_data:
                print(recv_data.decode('utf-8'))
                # 7. 返回数据给客户端 发送的数据是二进制数据
                send_data = '我是服务器'
                client.send(send_data.encode('utf-8'))
            else:
                print('客户端下线了')
                break
        # 8. 关闭处理客户端的套接字
        client.close()
    
    
    def main():
        # 开启tcp服务
        tcp_server = start_tcp_server()
        # 循环等待客户端连接请求
        while True:
            # 5. 等待客户端连接请求 client: 为客户端服务的套接字对象, client_address: 客户端地址
            client, client_address = tcp_server.accept()
            # 创建子线程
            t = threading.Thread(target=handle_client_request, args=(client, client_address))
            # 开启子线程
            t.start()
    
    
    if __name__ == '__main__':
        main()
    
  • 协程实现

    其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

    由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO

    # -*- coding: utf-8 -*-
    import socket
    import gevent
    from gevent import monkey
    
    monkey.patch_all()
    
    
    def start_tcp_server(ip='', port=7788):
        # 1. 创建 tcp套接字
        tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2. 设置端口复用  表示: 服务端关闭,端口释放
        tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 3. 绑定 ip和port
        tcp_server.bind((ip, port))
        # 4. 设置监听  128表示 指定等待建立连接的客户端个数
        tcp_server.listen(128)
        return tcp_server
    
    
    def handle_client_request(client, ip_address):
        """
        处理客户端请求
        :param client: 处理客户端请求的套接字对象
        :param ip_address: 客户端的ip和port
        :return:
        """
        print(f'当前客户端地址:{ip_address}')
        while True:
            # 6. 等待客户端传来的数据  该方法是阻塞过程,直到收到客户端发来的请求
            recv_data = client.recv(1024)
            # 如果客户端断开连接时,此时recv_data就是空数据
            if recv_data:
                print(recv_data.decode('utf-8'))
                # 7. 返回数据给客户端 发送的数据是二进制数据
                send_data = '我是服务器'
                client.send(send_data.encode('utf-8'))
            else:
                print('客户端下线了')
                break
        # 8. 关闭处理客户端的套接字
        client.close()
    
    
    def main():
        # 开启tcp服务
        tcp_server = start_tcp_server()
        # 循环等待客户端连接请求
        while True:
            # 5. 等待客户端连接请求 client: 为客户端服务的套接字对象, client_address: 客户端地址
            client, client_address = tcp_server.accept()
            # 准备协程 自动切换执行协程
            gevent.spawn(handle_client_request, client, client_address)
    
    
    if __name__ == '__main__':
        main()
    

3.3 tcp要点

  1. tcp服务器需要绑定端口地址,否则客户端找不到这个服务器
  2. tcp客户端一般不需要绑定,因为是主动链接服务器,所以只要确定好服务器的ip、port等信息就好,本地客户端可以随机
  3. tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的
  4. 当客户端需要链接服务器时,就需要使用connect进行链接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信
  5. 当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
  6. listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的
  7. 关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信。
  8. 关闭accept返回的套接字意味着这个客户端已经服务完毕
  9. 当客户端的套接字调用close后,服务器端会recv解堵塞,并且返回的长度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线
posted @ 2022-07-26 10:58  三叶草body  阅读(38)  评论(0)    收藏  举报