@bit::Shadow
✧(≖ ◡ ≖✿
目录
[listen 的直观理解(它在做什么?)](#listen 的直观理解(它在做什么?))
🔗listen错误时枚举的使用原理(区分与C是很有必要的)"C++进阶知识5.0"blog(待链接)
accept正常返回的套接字(sockfd)与listensockfd的对比(了解)
[1. 回顾 accept() 的函数原型](#1. 回顾 accept() 的函数原型)
[2. 为什么不需要初始化?因为它是"输出参数"](#2. 为什么不需要初始化?因为它是"输出参数")
[🔗netstat telnet使用(必看)blog](#🔗netstat telnet使用(必看)blog)
TCP与UDP差异
最核心差异
TCP是有状态 、有连接 的可靠 协议;UDP是无状态 、无连接 的不可靠协议;
| 对比维度 | UDP(你用的 SOCK_DGRAM) |
TCP(SOCK_STREAM) |
|---|---|---|
| 连接状态 | 无连接(发数据前不需握手) | 面向连接(需三次握手建立连接) |
| 可靠性 | 不可靠(丢包不重传,可能乱序) | 可靠(确认重传、保证顺序) |
| 数据边界 | 保留边界 (每次 sendto 对应一次 recvfrom) |
无边界(流式) (多次 send 可被一次 recv 读完) |
| 传输效率 | 高(头部开销小,无拥塞控制) | 低(头部大,有流量/拥塞控制) |
| 典型应用 | DNS、视频直播、实时游戏、VoIP | HTTP、FTP、SSH、邮件、数据库 |
| Socket 类型 | SOCK_DGRAM |
SOCK_STREAM |
| 核心函数 | sendto / recvfrom |
connect / accept / send / recv |
监听listen()
listen 的直观理解(它在做什么?)
继续用我们之前的"打电话"比喻:
-
socket()创建了一个"电话机"(创建套接字)。 -
bind()给电话机绑定了号码(绑定 IP 和端口)。 -
listen()则是把电话机切换到"响铃待机模式" 。它告诉操作系统内核:"我准备好了,现在开始监听这个端口,随时准备接听客户端的来电请求。"
在代码层面,它的函数原型通常是:
c
int listen(int sockfd, int backlog);
-
sockfd:刚才创建的那个套接字。 -
backlog:这是最核心的参数,它限制了"全连接队列(Accept 队列)"的最大长度。(不宜过大、过小)
🔗listen错误时枚举的使用原理(区分与C是很有必要的)"C++进阶知识5.0"blog(待链接)
listen造成accept的诞生原理
使用端(使用listen的)原套接字状态改变!
在listen调用之前,TCP套接字处于主动状态。(即类似UDP的状态)
在listen调用之后,TCP套接字处于被动状态 ,只能接受信息**链接,**无法被用以发送信息。
因此我们常使用listen的套接字命名为"_listenSockfd"
匿名服务端应该怎样接受与发送信息?
答案是"accept"再创建sockfd(因此就引入了多线程,多进程等版本(单进程版本在实际工程中不存在!))

accept正常返回的套接字(sockfd)与listensockfd的对比(了解)
|-----------------|-----------------------------------------|-------------------------------|
| | 监听套接字(Listening Socket) | 已连接套接字(Connected Socket) |
| 创建时机 | socket() + bind() + listen() 后产生 | accept() 从全连接队列中取出后返回 |
| 身份 | "前台接待员" | "专属客服代表" |
| 职责 | 只负责监听新的连接请求(三次握手) | 只负责收发数据(读写业务数据) |
| 能否 write? | 不能 (理论上强行写会报错 EPIPE 或 ENOTCONN) | 能(服务端通过它回复 HTTP 响应、文件数据等) |
| 数量 | 整个服务端通常只有 1 个 | 每个客户端连接对应 1 个,可能有成千上万个 |
TCP套接字的状态限定(待深入)
TCP是有严格状态机的。一个套接字在任意时刻只能处于一种状态:
| 套接字类型 | TCP状态 | 允许的操作 |
|---|---|---|
| 主动套接字(客户端) | CLOSED → SYN_SENT → ESTABLISHED |
只能connect()发起连接,成功后收发数据 |
| 被动套接字(监听) | CLOSED → LISTEN |
只能accept()接收新连接,不能收发数据 |
| 已连接套接字(服务端) | ESTABLISHED → CLOSE_WAIT → ... |
只能read()/write()收发数据,不能accept() |
accept()
阻塞等待对象链接。
1. 回顾 accept() 的函数原型
c
int accept(int _listensockfd, struct sockaddr *addr, socklen_t *addrlen);
-
第一个参数 :监听套接字(你已经
listen()好的那个)。 -
第二个参数 :
struct sockaddr *addr------ 这是一个"输出参数"。 -
第三个参数 :
socklen_t *addrlen------ 这是一个"输入输出参数"。
2. 为什么不需要初始化?因为它是"输出参数"
在C语言中,函数参数有两种用途:
| 参数类型 | 方向 | 调用者要做的事 | 函数要做的事 |
|---|---|---|---|
| 输入参数 | 调用者 → 函数 | 填充好数据再传进去 | 读取并使用 |
| 输出参数 | 函数 → 调用者 | 分配好内存即可 | 往里面写数据 |
| 输入输出参数 | 双向 | 既要初始化,又要读取函数写回的值 | 既要读取,也要修改 |
accept()的第二个参数 addr 是一个典型的"输出参数":
-
调用前 :你只需要提供一块足够大的内存 (
struct sockaddr_in或struct sockaddr_storage),里面的值无所谓,可以是随机垃圾。 -
调用后 :内核会把客户端的IP地址和端口号填进这个结构体,然后返回给你。
🔗netstat telnet使用(必看)blog
普通监视
部分进程PID隐藏(权限不足、可使用sudo提权)
bash
w@instance-qhw6x01g:~/e2026/Auguest/TCPServerClient/build$ netstat -tanp | grep LISTEN
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:45825 0.0.0.0:* LISTEN 10811/node
tcp 0 0 127.0.0.1:6010 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 72312/./tcpServer
tcp 0 0 127.0.0.1:43557 0.0.0.0:* LISTEN 10587/code-a5b50095
tcp 0 0 127.0.0.1:36239 0.0.0.0:* LISTEN 10558/code-a5b50095
tcp 0 0 0.0.0.0:428 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:35897 0.0.0.0:* LISTEN 9347/code-df53daabb
tcp6 0 0 :::781 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 ::1:6010 :::* LISTEN -
sudo提权
bash
w@instance-qhw6x01g:~/e2026/Auguest/TCPServerClient/build$ sudo netstat -tanp | grep LISTEN
[sudo] password for w:
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN 730/systemd-resolve
tcp 0 0 127.0.0.1:45825 0.0.0.0:* LISTEN 10811/node
tcp 0 0 127.0.0.1:6010 0.0.0.0:* LISTEN 72588/sshd: w@pts/0
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 72312/./tcpServer
tcp 0 0 127.0.0.1:43557 0.0.0.0:* LISTEN 10587/code-a5b50095
tcp 0 0 127.0.0.1:36239 0.0.0.0:* LISTEN 10558/code-a5b50095
tcp 0 0 0.0.0.0:428 0.0.0.0:* LISTEN 2793/has_agent
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 4337/sshd: /usr/sbi
tcp 0 0 127.0.0.1:35897 0.0.0.0:* LISTEN 9347/code-df53daabb
tcp6 0 0 :::781 :::* LISTEN 1037/./bcm-agent
tcp6 0 0 :::22 :::* LISTEN 4337/sshd: /usr/sbi
tcp6 0 0 ::1:6010 :::* LISTEN 72588/sshd: w@pts/0
🔗telnet的使用
你应该仔细观看动态的变化(待补充)
链接前

链接

链接后

客户端
connect()客户向服务打电话☎️以求建立链接
cpp
#include<sys/socket.h>
int connect(int sockfd, const sockaddr* __addr, socklen_t len);
|--------|----------------|-------|
| 参数 | 含义 | 方向 |
| sockfd | 客户端创建的套接字 | 一般输入型 |
| _addr | 服务端的IP与端口号 | 输入型参数 |
| len | 结构体大小 | 输入型参数 |
返回值:
-
成功:返回
0 -
失败:返回
-1,并设置errno
感谢支持,长期连载
欢迎关注
