大家好,我是 同学小张,+v: jasper_8017 一起交流,持续学习C++进阶、OpenGL、WebGL知识和AI大模型应用实战案例,持续分享,欢迎大家点赞+关注,共同学习和进步。
网络编程在大多数的项目中都不可或缺。无论是多端多机器通信(客户端、服务端)还是跨语言通信(python — c++)等,都可以通过网络编程来解决。
在Python、javascript等语言中,集成一个网络模块相对简单,只需引入一个库便可直接用。而像C++这种比较底层的语言,引入一个库或实现网络通信模块相对比较笨重和复杂。
本文我们来学习下C++中的socket网络编程。
文章目录
1. C++的socket接口
C++ 中的 socket 接口是网络编程的基础,它允许程序进行网络通信。在 C++ 中,socket 编程通常使用 POSIX 标准库中的 socket API。以下是一些基本的 socket 接口和它们的使用方式:
(1) 创建 Socket
使用 socket()
函数来创建一个新的 socket。
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
(2) 配置 Socket 地址
使用 sockaddr_in
结构来配置服务器或客户端的地址信息。
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = INADDR_ANY; // 对于服务器,监听所有网络接口
(3) 绑定 Socket
使用 bind()
函数将 socket 绑定到特定的地址和端口。
bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
(4) 监听连接 (仅限服务器)
使用 listen()
函数让服务器开始监听传入的连接请求。
listen(sockfd, 5); // 5 是等待连接队列的长度
(5) 接受连接 (仅限服务器)
使用 accept()
函数接受一个传入的连接请求。
int connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);
(6) 连接到服务器 (仅限客户端)
使用 connect()
函数连接到服务器的地址和端口。
connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
(7) 发送数据
使用 send()
函数发送数据到连接的 socket。
send(sockfd, message, strlen(message), 0);
(8) 接收数据
使用 recv()
函数接收数据。
int nbytes = recv(sockfd, buffer, MAXLINE, 0);
(9) 关闭 Socket
使用 close()
函数关闭 socket。
close(sockfd);
2. 服务端和客户端的交互状态和流程
下图描述了服务端和客户端的交互过程、状态。
图片来源:https://www.geeksforgeeks.org/socket-programming-cc/
2.1 服务端建立连接的过程
(1)服务端通过socket()接口创建一个新的socket实例。
(2)bind 函数将套接字绑定到 addr(自定义数据结构)中指定的地址和端口号。
(3)listen 函数使服务器处于被动模式,等待客户端与服务器建立连接。
(4)accept 函数从监听 sockfd 的待处理连接队列中提取第一个连接请求,创建一个新的已连接socket,并返回指向该socket的新文件描述符。此时,客户端和服务器之间的连接已建立,它们已准备好传输数据。
2.2 客户端建立连接的过程
(1)客户端通过socket()接口创建一个新的socket实例。
(2)连接:connect() 将文件描述符sockfd指向的socket连接到addr指定的地址。服务器的地址和端口在addr中指定。
3. TCP的三次握手与四次挥手
了解了 socket 的建立和连接调用的接口,现在我们来看连接和断开的内部发生了什么,也就是所谓经典的面试题 – 说一说TCP连接的三次握手和四次挥手。
总体流程图(图片来源网络):
3.1 三次握手
(1)第一次握手
客户端将TCP报文标志位SYN置为1,随机产生一个序号值seq=x,保存在TCP首部的序列号(Sequence Number)字段里,指明客户端打算连接的服务器的端口,并将该数据包发送给服务器端,发送完毕后,客户端进入SYN_SENT状态,等待服务器端确认。
(2)第二次握手
服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将TCP报文标志位SYN和ACK都置为1,ack=x+1,随机产生一个序号值seq=y,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
(3)第三次握手
客户端收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给服务器端,服务器端检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
注意:我们上面写的ack和ACK,不是同一个概念:
小写的ack代表的是头部的确认号Acknowledge number, 缩写ack,是对上一个包的序号进行确认的号,ack=seq+1。
大写的ACK,则是我们上面说的TCP首部的标志位,用于标志的TCP包是否对上一个包进行了确认操作,如果确认了,则把ACK标志位设置成1。
- 参考:https://zhuanlan.zhihu.com/p/354882017
3.2 四次挥手
挥手请求可以是Client端,也可以是Server端发起的,我们假设是Client端发起:
(1)第一次挥手:Client端发起挥手请求,向Server端发送标志位是FIN报文段,设置序列号seq,此时,Client端进入FIN_WAIT_1状态,这表示Client端没有数据要发送给Server端了。
(2)第二次分手:Server端收到了Client端发送的FIN报文段,向Client端返回一个标志位是ACK的报文段,ack设为seq加1,Client端进入FIN_WAIT_2状态,Server端告诉Client端,我确认并同意你的关闭请求。
(3)第三次分手:Server端向Client端发送标志位是FIN的报文段,请求关闭连接,同时Client端进入LAST_ACK状态。
(4)第四次分手:Client端收到Server端发送的FIN报文段,向Server端发送标志位是ACK的报文段,然后Client端进入TIME_WAIT状态。Server端收到Client端的ACK报文段以后,就关闭连接。此时,Client端等待2MSL的时间后依然没有收到回复,则证明Server端已正常关闭,Client端也可以关闭连接了。
4. 相关面试题罗列和整理
罗列一下相关面试题,需要的可以按问题思考下:
(1)TCP 和 UDP 的区别是什么?
- TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它确保数据正确、按顺序到达,支持流量控制和拥塞控制。
- UDP(用户数据报协议)是一种无连接的、不可靠的传输层协议。它允许应用程序发送数据包,但不保证数据包的顺序或完整性,适用于需要快速传输的场景,如视频流或在线游戏。
(2)TCP 三次握手过程是什么?
- 见上文。
(3)TCP 四次挥手过程是什么?
- 见上文。
(4)TCP 协议的流量控制和拥塞控制是如何实现的?
- 流量控制:通过滑动窗口机制实现。接收方告诉发送方可以发送多少数据,防止接收方处理不过来。
- 拥塞控制:通过慢启动、拥塞避免、快速重传和快速恢复等算法实现。发送方根据网络状况调整数据发送速率,防止网络拥塞。
(5) 什么是 TCP 的粘包和拆包问题?如何解决?
- 粘包:TCP 协议按流传输数据,可能会导致多个数据包粘在一起,接收方无法确定数据包边界。
- 拆包:一个数据包被分成多个部分传输,接收方需要重新组装数据包。
- 解决方法:可以在应用层定义明确的数据包格式,如添加长度字段或使用特定的分隔符。
(6)如何使用 C++ 创建一个 TCP 服务器?
- 基本步骤:
- 创建 socket。
- 绑定地址和端口。
- 监听连接。
- 接受连接请求。
- 与客户端通信。
- 关闭连接。
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
listen(sockfd, 5);
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
int connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);
(7)如何使用 C++ 创建一个 TCP 客户端?
- 基本步骤:
- 创建 socket。
- 设置服务器地址和端口。
- 连接到服务器。
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
inet_pton(AF_INET, server_ip, &serv_addr.sin_addr);
connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
(8)描述一下 socket 的概念以及在 C++ 中如何使用 socket 进行网络通信?
- Socket 是一种端点,用于网络通信。在 C++ 中,可以通过系统调用创建 socket,然后使用
bind()
、listen()
、accept()
、connect()
、send()
、recv()
等函数进行通信。
(9)什么是 HTTP 协议?如何使用 C++ 实现一个简单的 HTTP 客户端?
- HTTP(超文本传输协议)是一种用于分布式、协作式、超媒体信息系统的应用层协议。它定义了客户端与服务器之间请求和响应的格式。
- 使用 C++ 实现 HTTP 客户端的基本步骤:
- 创建 socket。
- 连接到服务器。
- 发送 HTTP 请求。
- 接收响应。
- 解析响应内容。
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
inet_pton(AF_INET, server_ip, &serv_addr.sin_addr);
connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
send(sockfd, "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", 31, 0);
char buffer[1024];
int nbytes = recv(sockfd, buffer, 1024, 0);
(10)描述一下 DNS 协议的工作原理以及如何在 C++ 中实现域名解析?
- DNS(域名系统)是一个分布式数据库,用于将域名转换为 IP 地址。它通过查询 DNS 服务器来解析域名。
- 在 C++ 中,可以使用 gethostbyname()
函数进行域名解析:
struct hostent *host;
host = gethostbyname("www.example.com");
if (host == NULL) {
perror("gethostbyname");
exit(EXIT_FAILURE);
}
(11)什么是 ARP 协议?它在网络中扮演什么角色?
- ARP(地址解析协议)用于将网络层的 IP 地址解析为数据链路层的 MAC 地址。它在网络中扮演着地址解析的角色,确保数据包能够正确地在网络中传输。
(12)如何使用 C++ 处理网络中的多线程或异步 I/O?
-
使用多线程可以并行处理多个网络连接。可以使用 POSIX 线程库(pthread)来创建和管理线程。
-
异步 I/O 可以通过使用
select()
、poll()
或epoll()
等系统调用实现,这些调用允许应用程序在多个 socket 上等待 I/O 事件。
(13)什么是 FTP 协议?如何使用 C++ 实现一个简单的 FTP 客户端?
- FTP(文件传输协议)是一种用于在网络上传输文件的协议。它定义了客户端与服务器之间的命令和响应格式。
- 使用 C++ 实现 FTP 客户端的基本步骤:
- 创建 socket。
- 连接到服务器。
- 发送 FTP 命令(如 USER、PASS、RETR)。
- 接收响应和文件数据。
- 关闭连接。
- 使用 C++ 实现 FTP 客户端的基本步骤:
(14)什么是 SSL/TLS 协议?如何在 C++ 中实现安全的 TCP 连接?
- SSL(安全套接字层)和 TLS(传输层安全)是用于在网络通信中提供隐私和数据完整性的协议。它们通过加密数据来保护通信。
- 在 C++ 中,可以使用 OpenSSL 库来实现 SSL/TLS 加密的 TCP 连接:
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, sockfd);
SSL_accept(ssl);
(15)描述一下 ICMP 协议的作用和在网络中的应用。
-
ICMP(互联网控制消息协议)用于在 IP 网络中发送控制消息,如错误报告和网络查询。它在网络中用于诊断和测试网络连接。
-
在 C++ 中,可以使用
ping
命令或相关库来发送 ICMP 消息,检测网络连接状态。
最后,推荐一篇TCP知识,写的特别好的文章:https://zhuanlan.zhihu.com/p/591865232
如果觉得本文对你有帮助,麻烦点个赞和关注呗 ~~~
- 大家好,我是 同学小张,持续学习C++进阶、OpenGL、WebGL知识和AI大模型应用实战案例
- 欢迎 点赞 + 关注 👏,持续学习,持续干货输出。
- +v: jasper_8017 一起交流💬,一起进步💪。
- 微信公众号搜【同学小张】 🙏
本站文章一览: