欢迎光临
我们一直在努力

Linux网络(六):一文吃透 TCP Socket 编程:从监听状态到 accept 握手全流程

在这里插入图片描述

◆ 博主名称: 小此方-CSDN博客

大家好,欢迎来到小此方的博客。

⭐️网络系列个人专栏:
【主题曲】计算机网络

⭐️此方的GitHub:
github_此方

⭐️
我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)


文章目录

  • 概要&序論
  • 一、TCP 套接字 API 详解
    • 1.1 socket 函数
    • 1.2 bind 函数
    • 1.3 listen 函数与监听状态(重点讲,UDP没有)
      • 1.3.1 listen 函数解析
      • 1.3.2 查看网络进程监听状态
      • 1.3.3 连接建立与状态转换
    • 1.4 accept 函数与套接字的分工
      • 1.4.1 “好再来鱼庄”的故事
      • 1.4.2 accept 函数详解
      • 1.4.3 数据读取写入
  • 二、附上源码

概要&序論

  Hello大家好,我是此方,上一期我们讲解了UDP套接字编程的详细方法,本文是 Linux 网络编程系列的第六篇,重点剖析面向连接的 TCP 套接字编程基础与核心 API。   文章首先梳理 TCP 套接字创建与地址绑定的基础流程,深入解析 socket() 与 bind() 的参数含义及初始化规范;接着重点对比 UDP,详细讲解 TCP 独有的 listen() 函数、监听状态机制(LISTEN)以及使用 netstat 等工具查看网络连接状态的方法;最后通过通俗易懂的“鱼庄拉客”生活类比,深度拆解 accept() 函数的工作原理,厘清“监听套接字”与“服务套接字”的分工区别,并分析单进程 TCP 服务器模型的瓶颈所在。好的我们开始吧。

一、TCP 套接字 API 详解

  在进行 TCP 网络编程时,首先需要了解 Linux 系统提供的标准套接字接口(定义在 sys/socket.h 头文件中)。与无连接的 UDP 编程类似,TCP 通信的第一步也是通过创建套接字来建立通信端点。

1.1 socket 函数

  socket() 函数用于打开一个网络通讯端口。如果调用成功,它会像 open() 系统调用一样,返回一个套接字文件描述符(Socket Descriptor),后续的网络读写操作都可以围绕该描述符展开。

函数声明:

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

参数与返回值详细说明:

  • 和UDP唯一区别:type(套接字类型): 指定数据传输的服务类型。对于 TCP 协议,该参数必须指定为 SOCK_STREAM,表示面向连接的、可靠的字节流传输协议。

1.2 bind 函数

  不讲,和上一期讲的UDP完全一致

1.3 listen 函数与监听状态(重点讲,UDP没有)

  不同于 UDP 的无连接特性,TCP 是面向连接的传输层协议。   去餐馆吃饭,老板一定在守这个店。随时等待客户的到来,一直等待。——这就是监听。   我给你发邮件,你不用一直在等待——这就是面向数据报

  TCP是面向连接的。他需要处于监听状态等待你去连接。

1.3.1 listen 函数解析

  listen() 函数声明 sockfd 处于监听状态。当有多个客户端同时发起连接请求时,这些连接请求需要排队等待处理。listen() 决定了最多允许有多少个客户端处于连接等待队列(全连接队列)中。   函数签名:

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

参数与返回值解析:

  • sockfd: 已经绑定(bind)了地址和端口号的套接字文件描述符。
  • backlog: 指定提示队列的最大排队连接数。即最多允许有 backlog 个客户端处于连接等待状态;如果收到更多的连接请求则会直接忽略。该值通常不会设置得太大(通常设置为 5 或 128 等)。
  • 返回值: 成功返回 0,失败返回 -1。

1.3.2 查看网络进程监听状态

  只要 TCP 服务器调用 listen() 成功并进入 LISTEN 状态,它就已经具备了接受客户端连接的能力。   可以通过 Linux 系统命令行工具检查网络套接字的状态:

# 查看所有 TCP 和 UDP 监听及连接状态(显示 PID 及程序名)
netstat -tnap

# 仅查看处于 LISTEN 状态的套接字
netstat -tnlp

常用参数含义:

  • -t (tcp): 仅显示 TCP 相关的套接字。
  • -u (udp): 仅显示 UDP 相关的套接字。
  • -n (numeric): 直接显示数字形式的 IP 地址和端口号,不进行域名与服务名解析。
  • -l (listening): 仅显示处于 LISTEN(监听)状态的套接字。
  • -p (programs): 显示进程名称。

1.3.3 连接建立与状态转换

  以使用 telnet 工具向服务器发起连接为例:

  • 未连接前,服务器进程处于 LISTEN 状态:

    Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
    tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 1080682/./tcpserver

  • 客户端执行 telnet 127.0.0.1 8080 建立连接后,网络状态中会新增对应的 ESTABLISHED 状态记录(表示连接已成功建立):

    Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
    tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 1080682/./tcpserver
    tcp 0 0 127.0.0.1:58752 127.0.0.1:8080 ESTABLISHED 1080746/telnet
    tcp 0 0 127.0.0.1:8080 127.0.0.1:58752 ESTABLISHED –

  •   在同台主机测试时,netstat 输出中会出现两条 ESTABLISHED 记录。产生该现象的原因如下:

    • TCP 是全双工通信: 通信双方既是发送方也是接收方,双向通道独立存在。
    • 同机测试: 客户端(Client)与服务器(Server)部署在同一台机器上,系统在回环网卡(127.0.0.1)上同时记录了“客户端端口 -> 服务器端口”以及“服务器端口 -> 客户端端口”的两条对称链路连接信息。

    1.4 accept 函数与套接字的分工

      最容易让人产生困惑的概念莫过于 accept() 函数的返回值。为什么调用 accept() 之后,系统又返回了一个全新的套接字文件描述符?原来创建的那个套接字又去哪了?

    1.4.1 “好再来鱼庄”的故事

      假设在西湖边开着一家极其火爆的“好再来鱼庄”:

    在这里插入图片描述

    • 拉客员(张三): 站在鱼庄门口(马路边),专门负责热情招呼路过的游客。张三的工作非常纯粹——只要看到有顾客想用餐,就把顾客引导进店内,交给店内的服务员。张三不负责具体的点菜、端茶或倒水,引导完一位客人后,他会立刻回到门口继续拉客。
    • 服务员(李四、王二麻): 坐在店内,专门负责对接已经被张三带进来的顾客。他们负责点菜、上菜、陪聊、结账等具体的服务工作。

      在这个故事中:

  • 监听套接字(listensockfd) 就是拉客员张三:它唯一的使命就是在特定端口上监听外部请求,负责建立并“获取”连接,而不承担后续与客户端的数据通信(I/O)。
  • 服务套接字(accept() 返回的 sockfd) 就是服务员(如李四):当连接建立后,系统专门分配该套接字来与该具体的客户端进行 read/write 数据交互。
  • 1.4.2 accept 函数详解

      需要特别明确的是:TCP 三次握手建立连接的过程完全由操作系统内核自动完成,与是否调用 accept() 无关。 accept() 的本质作用是从内核的已完成连接队列中获取一个已经建立好的连接。   函数签名:

    #include <sys/types.h> /* See NOTES */
    #include <sys/socket.h>

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    参数与返回值解析:

    • sockfd: 监听套接字描述符(即 listensockfd,相当于拉客员张三),它专门负责传入 accept() 用于获取连接。
    • addr: 输出型参数,用于获取连接成功的客户端网络地址信息(包含客户端的 IP 和端口号)。
    • addrlen: 值-结果参数,传入时为 addr 结构体的长度,返回时为实际写入的地址结构体大小。
    • 返回值: 成功时返回一个全新的套接字文件描述符(服务套接字,相当于服务员);如果获取失败,则返回 -1。

      后续针对该客户端的所有数据传输(read/write 或 recv/send),均使用 accept() 返回的这个新套接字 进行操作。

    1.4.3 数据读取写入

      UDP 是面向数据报的,具有特定的收发接口(如 recvfrom/sendto);而 TCP 是面向字节流的,因此可以直接使用标准的 Linux 文件 I/O 系统调用 read() 与 write() 进行数据通信。

    二、附上源码

      都是手写代码,可能会有bug,如果有,非常欢迎大佬指出私信。量真的太大了,我把仓库贴一下:(目前仓库还在建设中,Readme还没有写。)Konata’s Network-Programming

    在这里插入图片描述


    好的本期内容就到这里,如果对你有帮助,还不要忘记点赞三联支持。我是此方,我们下期再见。bye! Linux、C++、算法持续连载中,欢迎关注WeChat Official Account 【此方的技术栈】。

    赞(0)
    未经允许不得转载:171主机测评 » Linux网络(六):一文吃透 TCP Socket 编程:从监听状态到 accept 握手全流程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址