C++ 套接字编程
C++ 套接字编程是使用 C++ 在网络上的两个套接字之间建立通信的方法。在本教程中,我们将学习使用 C++ 中不同类型的套接字进行套接字编程的全部知识。
什么是套接字?
套接字充当网络数据交换的接触点,就像在网络上发送和接收数据的端点。它们允许应用程序使用 TCP(传输控制协议)和 UDP(用户数据报协议)等协议相互通信。它们是大多数互联网通信的支柱,因为它使我们能够进行网页浏览和实时聊天。
套接字有两种类型:
- 流套接字 (TCP):
它提供可靠的面向连接的通信,数据以连续的流形式发送,确保数据包按顺序无错误地到达。 - 数据报套接字 (UDP):
它提供无连接通信。它以数据包的形式独立传输数据,但不保证顺序或交付,因此发送速度较快,但可靠性较低。
C++ 套接字编程
C++ 套接字编程是一种强大的方法,可用于创建网络应用程序,允许使用套接字 API 通过网络在设备之间进行通信。此过程涉及在客户端和服务器之间建立连接,从而支持通过 TCP 或 UDP 等协议进行数据交换。
C++ 服务器端套接字(监听连接)
以下方法用于处理服务器端通信:
1. socket()
socket() 是网络编程中的一个系统调用,用于在 C++ 中创建一个新的 TCP 套接字,该套接字定义在 <sys/socket.h> 头文件中。
语法
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
其中
- int sockfd 声明一个整型变量,用于存储套接字文件描述符。
- AF_INET表示套接字将使用 IPv4 地址族。
- SOCK_STREAM指定套接字将使用 TCP(一种面向流的协议),
- 0让系统为指定的地址族和套接字类型选择默认协议(在本例中为 TCP)。
2. bind()
bind() 方法与一个套接字关联,该套接字具有特定的本地地址和端口号,允许套接字监听该地址上的传入连接。
语法
bind(sockfd, (struct sockaddr*)&address, sizeof(address));
其中:
- sockfd 是程序中代表套接字的文件描述符,用于执行各种套接字操作。
- (struct sockaddr)&address 将地址结构转换为 bind 函数的通用指针类型。
- sizeof(address) 指定地址结构的大小,以告知系统预期的数据量。
3. listen()
listen() 函数将套接字标记为被动套接字,该套接字准备接受传入的连接请求(针对服务器)。
语法
listen(sockfd, 10);
其中:
- sockfd 是程序中代表套接字的文件描述符,用于执行各种套接字操作。
- 10 是 backlog 参数,指定服务器繁忙时排队的最大待处理连接数。
4. accept()
accept() 函数接受来自客户端(对于服务器)的新连接。它提取待处理连接队列中的第一个连接请求,并为该连接创建一个新的套接字。
语法
int clientSocket = accept(sockfd, (struct sockaddr*)&clientAddress, &clientLen);
其中,
- sockfd:它是套接字的文件描述符,用于执行各种套接字操作。
- (struct sockaddr)&address:这是一个类型转换,将clientAddress的指针类型转换为structsockaddr*类型的指针。
- &lientLen:它是一个指向保存clientAddress大小的变量的指针。
C++ 客户端套接字(连接到服务器)
以下方法用于客户端通信:
1. connect()
此函数是一个系统调用,用于尝试使用套接字与指定服务器(对于客户端)建立连接。
语法
connect(sockfd, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
其中:
- sockfd 是程序中代表套接字的文件描述符,用于执行各种套接字操作。
- (struct sockaddr*)&serverAddress 将 serverAddress 转换为 struct sockaddr* 指针,从而与需要通用套接字地址类型的函数兼容。
- sizeof(serverAddress) 指定 serverAddress 的大小
2. send()
send() 函数是套接字编程中的系统调用,用于向已连接的套接字发送数据。
语法
send(sockfd, "Hello", strlen("Hello"), 0);
其中:
- sockfd 是程序中代表套接字的文件描述符,用于执行各种套接字操作。
- strlen("Hello") 函数返回字符串"Hello"的长度(5 个字节),显示要发送的数据字节数。
- 0 让系统为指定的地址族和套接字类型选择默认协议(在本例中为 TCP)。
3. recv()
recv() 函数是一个系统调用,用于从已连接的套接字接收数据,允许客户端或服务器读取传入的消息。
语法
recv(sockfd, buffer, sizeof(buffer), 0);
其中:
- sockfd 是程序中代表套接字的文件描述符,用于执行各种套接字操作。
- buffer 是指向存储接收数据的内存位置的指针。此缓冲区应足够大,以容纳传入的数据。
- sizeof(buffer) 指定从套接字读取的最大字节数,通常是缓冲区的大小。
关闭客户端套接字
close() 方法关闭打开的套接字。
语法
close(sockfd);
其中,
- close函数是一个系统调用,用于关闭与套接字关联的文件描述符。
套接字编程所需的头文件
使用 C 或 C++ 进行套接字编程时,必须包含特定的头文件以进行必要的声明。
对于 Linux/Unix系统
- <sys/socket.h>
- <netinet/in.h>
- <arpa/inet.h>
- <unistd.h>
- <string.h>
- <errno.h>
Windows 系统
- <winsock2.h>
- <ws2tcpip.h>
- <windows.h>
C++ 套接字编程示例
这里有一个简单的示例,用于说明 TCP 服务器和客户端C++:
TCP 服务器代码
#include <netinet/in.h> #include <sys/socket.h> #include <unistd.h> #include <cstring> #include <iostream> #define PORT 8080 int main() { int server_fd, new_socket; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); char buffer[1024] = {0}; // 创建套接字 server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 将套接字连接到端口 setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); // Bind if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } // Listen if (listen(server_fd, 3) < 0) { perror("listen"); exit(EXIT_FAILURE); } // 接受连接 new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen); if (new_socket < 0) { perror("accept"); exit(EXIT_FAILURE); } // 读取数据 read(new_socket, buffer, 1024); std::cout << "Message from client: " << buffer << std::endl; // 关闭套接字 close(new_socket); close(server_fd); return 0; }
TCP 客户端代码
#include <arpa/inet.h> #include <netinet/in.h> #include <sys/socket.h> #include <unistd.h> #include <cstring> #include <iostream> #define PORT 8080 int main() { int sock = 0; struct sockaddr_in serv_addr; const char *hello = "Hello from client"; // 创建套接字 sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { std::cerr << "Socket creation error" << std::endl; return -1; } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); // 将 IPv4 和 IPv6 地址从文本转换为二进制 if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { std::cerr << "Invalid address/ Address not supported" << std::endl; return -1; } // 连接到服务器 if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { std::cerr << "Connection Failed" << std::endl; return -1; } // 发送数据 send(sock, hello, strlen(hello), 0); std::cout << "Message sent" << std::endl; // 关闭套接字 close(sock); return 0; }
编译和运行步骤
编译和运行客户端套接字程序的步骤如下:
编译服务器和客户端代码文件:
g++ -o server server.cpp g++ -o client client.cpp
运行服务器:
./server
运行客户端(在另一个终端):
./client
最佳实践
- 错误处理:务必检查套接字函数的返回值,以便正确处理错误。
- 阻塞与非阻塞模式相比:默认情况下,套接字以阻塞模式运行。因此,请考虑使用非阻塞套接字或多路复用(例如 select 或 poll)来处理多个连接。
- 跨平台问题:此示例适用于 Unix/Linux。对于 Windows,您需要包含 <winsock2.h>并使用 WSAStartup() 初始化 Winsock。
实际应用
套接字在实际用途和工具等方面有各种各样的应用,这里仅列举其中几种。
以下示例演示了如何在不同的应用中使用套接字:
- 回显服务器:一个用于回显收到消息的简单服务器。
- 聊天应用程序:一个允许多个客户端聊天的多线程服务器。
- FTP 客户端/服务器:一个用于通过网络传输文件的简单实现。
- Web 服务器:套接字处理 HTTP 请求和响应,以提供 Web 内容。
- 在线多人游戏:套接字支持玩家和游戏服务器之间的实时通信。
- 远程访问工具:套接字为服务器的远程管理提供安全连接。
- VoIP 应用:套接字实时传输音频和视频数据以进行通信。
- 流媒体服务:套接字向用户提供连续的音频和视频内容。
- 物联网设备:套接字促进智能设备与服务器之间的通信。
- 实时协作工具:套接字允许用户之间即时共享编辑和消息。
- 数据同步服务:套接字管理设备和服务器之间的文件上传和下载。
- 天气监测系统:套接字将实时天气数据发送到中央服务器进行分析。
- 支付处理系统:套接字在客户端和银行。
- 聊天机器人:Sockets 可在对话界面中实现即时消息传递和响应。