从 0 开始的计网学习(一)Socket 通信入门

这个项目主要是记录我尝试使用代码来学习计网理论,起因是因为在找实习的过程中面试官问到我有没有做过udp实现文件传输,让我发掘自己没有动手实践过这一方面的知识。所以以此项目为基础开始学习。

Socket(套接字)

Socket:the part of a piece of equipment, especially electrical equipment, into which another part fits 插座;插槽  

        Socket在计算机网络中是操作系统提供给应用程序访问网络协议栈的编程接口。可以把它想象成程序与网络之间的“插座”,数据通过这个插座流入和流出。在 Unix/Linux 系统中,一切皆文件,一个套接字被抽象为一个文件描述符(非负整数);在 Windows 中则是 SOCKET 句柄。

 

  bind 将套接字与一个本地网络地址(IP + 端口)关联起来。只有服务端才必须显式调用 bind,因为服务端需要有一个众所周知的“营业地址”,让客户端能找到它。客户端通常不调用 bind,系统会自动为它分配一个临时端口。

  listen 将套接字从 CLOSED 状态转为 LISTEN 状态(将套接字用于接收客户端的连接请求,而非主动发起连接。),并创建一个连接请求队列。

  accept 从 listen 创建的已完成连接队列中取出一个客户端连接,并返回一个全新的套接字(以及客户端的地址信息)。这个新套接字专门用于与该客户端通信,而原来的监听套接字继续用于接收其他新连接。

     connect由客户端调用向指定 IP 和端口发起三次握手,请求建立 TCP 连接。成功返回后,这个套接字就与服务器建立了可靠的双向通信通道。

     连接成功后就可以收发消息:

  • send:将数据从应用程序缓冲区复制到内核发送缓冲区,由内核负责打包发送。

  • recv:从内核接收缓冲区中取出数据到应用程序缓冲区。

        它们的行为类似文件的 write / read,但有更丰富的选项(如 MSG_DONTWAIT 等)。TCP 是流式协议,无消息边界,所以 recv 一次读取的数据量可能与 send 发送的次数和大小没有固定对应关系。应用层需要自行处理粘包/半包问题。

"""
Server
"""

import socket

HOST = '127.0.0.1'   # 本地回环地址
PORT = 8888          # 监听的端口

# socket.socket(地址族, 套接字类型)
# AF_INET     = IPv4
# SOCK_STREAM = TCP(面向连接的流)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    # bind() 绑定地址。Python 里传元组 (host, port)
    server.bind((HOST, PORT))
    
    # listen(backlog) 开始监听连接请求
    server.listen(1)  #就听一个
    print(f"[Server] Listening on {HOST}:{PORT}...")
    print("[Server] Run 'python tcp_echo_client.py' in another terminal.\n")
    
    # accept() 阻塞等待客户端连接
    # 返回 (新套接字, 客户端地址)
    conn, addr = server.accept()
    
    # with conn: 确保这个客户端连接用完自动关闭
    with conn:
        print(f"[Server] Client connected from {addr}")
        
        while True:
            # recv(bufsize) 接收数据,返回 bytes 对象
            # 返回 b''(空字节)表示客户端已关闭连接
            data = conn.recv(1024)
            if not data:
                print("[Server] Client disconnected.")
                break
            
            text = data.decode('utf-8')
            print(f"[Server] Received ({len(data)} bytes): {text}")
            
            # sendall(data) 发送数据,会自动循环直到全部发送完毕
            # 比 send() 更省心,不用担心一次没发完
            conn.sendall(data)
            print(f"[Server] Echoed back ({len(data)} bytes)\n")

print("[Server] Bye!")
#Client
import socket

HOST = '127.0.0.1'
PORT = 8888

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    print(f"[Client] Connecting to {HOST}:{PORT}...")
    
    # connect(address) 发起连接
    sock.connect((HOST, PORT))
    print("[Client] Connected! Type messages and press Enter.")
    print("[Client] Type 'quit' to exit.\n")
    
    while True:
        msg = input("You> ")
        
        if msg == 'quit':
            print("[Client] Goodbye!")
            break
        
        # str.encode() 把字符串转成 bytes 才能发送
        sock.sendall(msg.encode('utf-8'))
        print(f"[Client] Sent ({len(msg)} bytes): {msg}")
        
        # 接收回显
        data = sock.recv(1024)
        if not data:
            print("[Client] Server closed the connection.")
            break
        
        print(f"[Client] Echo from server ({len(data)} bytes): {data.decode('utf-8')}\n")

    上面实现的只是一个简单的一对一的client-server,所以下面请自己尝试实现一个多对一的client-server使用Socket。

"""
任务:实现一个支持多客户端的 Ping-Pong 服务端 (Python 版)

任务要求(请务必全部实现,否则测试无法通过):

1. 服务端监听 127.0.0.1:8888
2. 使用 select.select() 同时处理多个客户端连接(不能用多线程/多进程)
3. 收到客户端消息 "ping",必须回复 "pong"
4. 收到客户端消息 "quit",断开与该客户端的连接
5. 收到客户端消息 "shutdown",服务端打印 "[Server] Shutting down..." 后退出
6. 收到任何其他消息,回复 "Echo: " + 原消息

运行命令:python ping_pong_server.py
"""

import socket
import select

HOST = '127.0.0.1'
PORT = 8888

# TODO 1: 创建监听套接字(socket.socket)
# 提示:AF_INET, SOCK_STREAM
server = None  # 请替换为实际代码

# TODO 2: 设置端口复用(可选但建议)
# server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# TODO 3: 绑定地址到 127.0.0.1:8888(bind)
# Python 中 bind 的参数是元组 (HOST, PORT)

# TODO 4: 开始监听(listen),backlog 设为 5

# TODO 5: 将服务端设为**非阻塞模式**
# 否则 accept 会卡住,导致 select 失去意义
# server.setblocking(False)

# TODO 6: 创建一个列表 inputs,存放所有需要 select 监视的套接字
# 初始时只有 server 自己

print(f"[Server] Ping-Pong server started on {HOST}:{PORT}")
print("[Server] Waiting for connections...")

while True:
    # TODO 7: 调用 select.select(inputs, [], []) 等待可读事件
    # 返回三个列表:(readable, writable, exceptional)
    # 我们只关心 readable
    
    # TODO 8: 遍历 readable
    # 如果当前套接字是 server 本身:
    #   a) 调用 server.accept() 接收新连接
    #   b) 将新连接 conn 设为非阻塞:conn.setblocking(False)
    #   c) 把 conn 加入 inputs 列表
    #   d) 打印:[Server] New connection from {addr}
    #
    # 如果当前套接字是客户端 conn:
    #   a) 调用 conn.recv(1024) 读取数据
    #   b) 如果没收到数据(返回 b''):客户端断开
    #        从 inputs 中移除该 conn,调用 conn.close()
    #        打印:[Server] Client disconnected
    #   c) 如果收到数据:
    #        msg = data.decode().strip()  # 去掉首尾空白和换行
    #        判断 msg:
    #        - "ping"     -> conn.sendall(b'pong')
    #        - "quit"     -> 从 inputs 移除,conn.close()
    #        - "shutdown" -> 打印 "[Server] Shutting down..."
    #                         关闭所有套接字(包括 server),break/exit
    #        - 其他       -> conn.sendall(f"Echo: {msg}".encode())
    
    pass  # 删除这行,替换为你的实现

运行python test.py,如果成功则得到:

参考文献

​​​​​​SOCKET中文(简体)翻译:剑桥词典

实验所用代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值