TCP之Socket连接基本概念

服务端代码

复制代码
import socket
import logging
import time

def main():
    # 设置服务器地址和端口
    host = '192.168.0.103'
    port = 54321
    # 配置日志格式和级别
    logging.basicConfig(
        format='%(asctime)s - %(levelname)s - %(message)s',
        level=logging.INFO,
        filename='TcpServer.log',
        encoding='utf-8'
    )

    # 创建一个TCP套接字
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 绑定服务器地址和端口
    server_socket.bind((host, port))

    # 监听连接
    server_socket.listen(1)
    print(f"Listening on {host}:{port}")

    count = 0
    while True:
        count = count + 1
        print("enter True " + str(count))
        # 接受客户端连接
        client_socket, client_address = server_socket.accept()
        print(f"Connected to {client_address}")

        try:
            # 接收数据
            data = client_socket.recv(1024)
            print("data " + str(count))
            if data:
                # 将接收到的数据返回给客户端
                rec_str = data.decode('utf-8')
                print("收到报文 {}".format(rec_str))

                time.sleep(1000)

                send_str = 'from server'
                print("发送报文 {}".format(send_str))
                client_socket.send(send_str.encode('utf-8'))
        except Exception as e:
            print(f"Error: {e}")
        finally:
            # 关闭客户端连接
            client_socket.close()

if __name__ == "__main__":
    main()
复制代码

客户端代码

复制代码
import socket

def main():
    # 设置服务器地址和端口
    host = '192.168.0.103'
    port = 54321
    # 创建一个TCP套接字
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        # 连接服务器
        client_socket.connect((host, port))
        print(f"Connected to {host}:{port}")

        # 发送数据
        message = "from client"
        client_socket.sendall(message.encode())
        print(f"Received: {message}")

        # 接收数据
        data = client_socket.recv(1024)
        print(f"Received: {data.decode()}")

    except Exception as e:
        print(f"Error: {e}")
    finally:
        # 关闭客户端套接字
        client_socket.close()

if __name__ == "__main__":
    main()
复制代码

测试过程

启动服务端后

发现服务端在监听54321端口

 启动客户端后

发现多了2个ESTABLISHED

 详解

关于2个ESTABLISHED

可以看到有两个不同的 TCP 连接,都与端口 54321 相关联。
这可能是因为一个典型的TCP连接,是由两个方向的数据流组成的: 一个是从客户端到服务端的数据流,另一个是从服务端到客户端的数据流。
这两个方向的数据流都被认为是一个独立的连接。

在一次典型的客户端 - 服务端通信中,不同的连接方向对应着不同的数据流。这是因为 TCP 是一个全双工协议,允许数据在两个方向上同时流动。当客户端与服务端建立连接后,客户端可以向服务端发送数据,同时服务端也可以向客户端发送数据。这样的设计使得双方能够实现双向的通信。

因此,从全双工的角度来看,两个连接分别代表了客户端到服务端和服务端到客户端的数据传输通道。客户端连接的 ESTABLISHED 状态表示客户端到服务端的数据流正常传输,而服务端连接的 ESTABLISHED状态表示服务端到客户端的数据流也正常传输。这两个连接共同构成了完整的双向通信。

 

这次客户端和服务端断开了,然后服务端还在监听54321端口。如果下次客户端继续使用49993端口和服务端进行连接。那么2次的连接的通道相同吗?
如果客户端和服务端断开连接,然后客户端再次使用相同的本地端口(49993)与服务端的54321端口进行连接,新的连接将被视为一个全新的连接,而不是复用之前的连接。
TCP连接的唯一标识由五元组(源IP地址、源端口、目标IP地址、目标端口、协议)组成。
在操作系统中,套接字(Socket)是一种用于实现网络通信的抽象概念。它是一种通信端点,可以用来在网络中传输数据。在这种情况下,我们主要关注TCP套接字,用于TCP协议的通信。
文件描述符(File Descriptor)是一种在类Unix操作系统中用于表示打开文件、套接字、管道等I/O资源的抽象概念。文件描述符是一个非负整数,它在程序中作为访问这些I/O资源的句柄。

 

在服务端和客户端的通信中,套接字和文件描述符之间存在关联:

服务端:
1、服务端创建一个监听套接字(listen socket)并绑定到特定的IP地址和端口(例如:192.168.0.103:54321)。
2、当客户端尝试连接时,服务端调用accept函数来接受连接请求,这会创建一个新的套接字,称为连接套接字(connection socket)。
3、连接套接字与客户端之间的通信会使用一个独立的文件描述符。这个文件描述符与连接套接字相关联。
客户端:
1、客户端创建一个套接字并使用connect函数尝试连接到服务端的IP地址和端口(例如:192.168.0.103:54321)。
2、连接成功后,客户端的套接字也会有一个相关联的文件描述符,用于在客户端程序中进行读写操作。

当客户端和服务端断开连接后,之前关联的连接套接字会被关闭,对应的文件描述符也会被释放。如果之后客户端再次使用相同的本地端口(例如:49993)连接到服务端的54321端口,这将创建一个全新的连接套接字,而这个新的连接套接字会分配一个新的文件描述符。

总之,每个连接(每个套接字)在操作系统中都会有一个独立的文件描述符,用于在程序中操作这个连接。即使使用相同的本地端口,新连接也会获得新的文件描述符。

 

客户端和服务端连接上了后,服务端识别客户端,是通过文件描述符的吗?
不完全正确。在TCP连接中,服务端并不是通过文件描述符来直接识别客户端的。文件描述符是在应用程序中用于操作套接字的一种抽象,而实际的连接管理是由操作系统内核处理的。

当客户端与服务端建立TCP连接时,操作系统内核会为每个连接分配一个唯一的套接字描述符,这个套接字描述符由内核维护,应用程序通过文件描述符访问它。内核使用五元组(源IP地址、源端口、目标IP地址、目标端口、协议)来标识每个连接,以便正确路由数据。

服务端通过监听套接字(listen socket)接受连接请求,当客户端连接请求到达时,内核会为该连接创建一个新的套接字描述符,代表这个连接。这个新的套接字描述符(文件描述符)与服务端程序中的对应变量关联,使服务端能够与特定客户端通信。

所以,服务端实际上是通过连接的套接字描述符(文件描述符)来与客户端进行通信,但识别连接所用的是内核维护的五元组标识。服务端通过文件描述符来操作这些连接,但连接的标识本质上是通过内核维护的连接信息来实现的。

 

posted @   szcj~  阅读(103)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示