📝前言:
上篇文章我们讲解了UDP编程,这篇文章我们来讲讲TCP编程
🎬个人简介:努力学习ing
📋个人专栏:Linux
🎀CSDN主页 愚润求学
🌄其他专栏:C++学习笔记,C语言入门基础,python入门基础,C++刷题专栏
目录
一,TCP介绍
- 在 UDP 通信中,客户端不需要与服务端建立连接,可直接将数据封装成 UDP 数据报发送出去。如果服务端没有准备好接收(比如没有启动监听或者监听的端口错误等 ),那么发送过去的数据报就会被丢弃,从而导致数据丢失
- TCP(Transmission Control Protocol,传输控制协议)是面向连接的、可靠的传输层协议。在进行数据传输之前,需要通过 "三次握手" 建立连接,确保服务端处于可接收数据的状态
- UDP直接获取的套接字就是和客户端通信的套接字,但是TCP需要获取两个套接字,一个是监听的,一个是通信的。
二,重要接口
socket

socket()
打开一个网络通讯端口,如果成功的话,就像open()
一样返回一个文件描述符;- 应用程序可以像读写文件一样用
read/write
在网络上收发数据(写入套接字文件); - 如果 socket()调用出错则返回
-1
; - 对于 IPv4,
domain
参数指定为AF_INET
; - 对于 TCP 协议,
type
参数指定为SOCK_STREAM
, 表示面向流的传输协议 protocol
参数的介绍从略,指定为0
即可。
bind

- 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;服务器需要调用 bind 绑定一个固定的网络地址和端口号;
- 成功返回 0, 失败返回 - 1。
bind ()
的作用是将参数sockfd
和addr
绑定在一起,使sockfd
这个用于网络通讯的文件描述符监听addr
所描述的地址和端口号;struct sockaddr *
是一个通用指针类型,addr
参数实际上可以接受多种协议的sockaddr
结构体,而它们的长度各不相同,所以需要第三个参数addrlen
指定结构体的长度;
addr填充
addr 参数是这样初始化
cpp
memset(&_addr, 0, sizeof(_addr)); // 清空
_addr.sin_family = AF_INET; // 填协议家族
_addr.sin_port = htons(_port); // 端口
// 这个 _ip 是一个点分10进制的 string ip
inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr); // IP
// 或者:_addr.sin_addr.s_addr = INADDR_ANY; // 用于服务端监听所有IP
listen

- 用于将套接字设置为监听状态,等待客户端连接。
sockfd
:要监听的套接字文件描述符(通常是socket
创建并bind
绑定过的)。backlog
:等待连接队列的最大长度(即最多允许多少个客户端处于"等待连接"状态)。listen
后,套接字变为被动监听状态,不能直接收发数据,只能用来接收连接 。只有调用了listen
,后续才能用accept
接收客户端连接
accept
参数:
sockfd
:服务器端的监听 socket 描述符(通过 socket() 创建并通过 bind() 绑定、listen() 监听的 socket)。addr
:用于存储客户端的地址信息,是一个输出参数。addrlen
:输入输出参数,传入时表示addr
缓冲区的大小,返回时表示实际存储的地址信息长度。
返回值:
- 成功:返回一个新的
socket
描述符,用于与该客户端进行数据收发(读写操作)。 - 失败:返回
-1
,并设置errno
表示错误原因。
注意:
accept()
是阻塞函数,会一直等待直到有连接到来(非阻塞模式除外)。- 原监听
socket
用于监听新连接,而accept()
返回的新socket
才用于与特定客户端通信。 - 一个服务器通常会循环调用
accept()
来处理多个个客户端的连接请求。
accept()
就像服务器的 "接待员",负责接待新来的客户端并安排专门的 "服务员"(新的 socket
)为其服务,而自己则继续等待下一位客人
connect
connect()
函数主要用于客户端,用于向服务器发起连接请求
参数:
sockfd
:客户端创建的 socket 描述符(通过 socket() 函数生成,尚未连接)。addr
:指向服务器地址信息的结构体指针(需包含服务器的 IP 地址和端口号)addrlen
:addr 结构体的字节长度(例如sizeof(struct sockaddr_in)
)。
返回值:
- 成功:返回
0
,表示 TCP 三次握手完成,连接建立成功。 - 失败:返回
-1
,并设置全局变量 errno 指示错误原因(例如
ECONNREFUSED 表示连接被拒绝,ETIMEDOUT 表示超时)。
recv和send
建立好连接以后,我们用recv
来接受信息,有别于UDP的recvfrom
(发送也类似,TCP 用 send()
,UDP用sendto()
):
recv
不需要指定发送方地址,因为在面向连接的通信中,连接建立后已经知道对方地址recvfrom
必须提供src_addr
参数来存储发送方的地址信息,这对于无连接通信至关重要,因为每次接收数据可能来自不同的发送方
cpp
// recv 函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// recvfrom 函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
工作流程
- 服务器先创建监听 socket(
socket()
),绑定地址(bind()
),并开始监听端口(listen()
)。 - 调用
accept()
阻塞等待客户端连接。 - 当客户端调用
connect()
发起连接后,accept()
返回新的socket
描述符。 - 服务器使用新的 socket 与客户端通信(
recv()/send()
),而原监听 socket 继续等待其他客户端连接。
三,代码练习
简答的回声:Github链接
🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!