C++中的socket网络编程,兼看TCP的三次握手与四次挥手过程(附常见面试题整理)

大家好,我是 同学小张,+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 服务器?

  • 基本步骤:
    1. 创建 socket。
    2. 绑定地址和端口。
    3. 监听连接。
    4. 接受连接请求。
    5. 与客户端通信。
    6. 关闭连接。
   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 客户端?

  • 基本步骤:
    1. 创建 socket。
    2. 设置服务器地址和端口。
    3. 连接到服务器。
   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 客户端的基本步骤:
    1. 创建 socket。
    2. 连接到服务器。
    3. 发送 HTTP 请求。
    4. 接收响应。
    5. 解析响应内容。
   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 客户端的基本步骤:
      1. 创建 socket。
      2. 连接到服务器。
      3. 发送 FTP 命令(如 USER、PASS、RETR)。
      4. 接收响应和文件数据。
      5. 关闭连接。

(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 一起交流💬,一起进步💪。
  • 微信公众号搜同学小张 🙏

本站文章一览:

在这里插入图片描述

  • 44
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

同学小张

如果觉得有帮助,欢迎给我鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值