c++开发实战之网络编程(一)
C++ 网络编程可以使用多种库和技术,可以使用标准的 Berkeley Sockets API 来编写跨平台的 C++ 网络编程代码。通过使用不同的系统头文件和函数,我们可以实现一个 Linux 和 Windows 兼容的客户端和服务器。以下示例展示了如何使用 Berkeley Sockets
在 Linux 和 Windows 上实现 TCP 通信。
在 Windows 上,你需要使用 Winsock2
,而在 Linux 上使用标准的 POSIX Sockets API。
1. 编写跨平台代码的注意事项
在编写跨平台代码时,主要需要处理以下问题:
- 头文件:Linux 和 Windows 的头文件不同,Windows 需要包含
Winsock2.h
和初始化Winsock
。 - 网络库的链接:在 Windows 上需要链接
Ws2_32.lib
。 - 系统特有函数:如
close()
和closesocket()
的差异。
2. 服务器代码(支持 Linux 和 Windows)
#include <iostream>
#include <cstring>
// 处理跨平台的头文件和函数差异
#ifdef _WIN32
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") // Winsock 库
typedef int socklen_t;
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#define closesocket close
#endif
const int PORT = 8080;
const int BUFFER_SIZE = 1024;
void initialize_sockets()
{
#ifdef _WIN32
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
std::cerr << "Failed to initialize Winsock" << std::endl;
exit(EXIT_FAILURE);
}
#endif
}
void cleanup_sockets()
{
#ifdef _WIN32
WSACleanup();
#endif
}
int main()
{
initialize_sockets();
int server_fd, new_socket;
struct sockaddr_in server_addr, client_addr;
char buffer[BUFFER_SIZE] = {0};
// 创建套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1)
{
std::cerr << "Socket creation failed" << std::endl;
cleanup_sockets();
return -1;
}
// 初始化服务器地址结构
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 绑定套接字到端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
std::cerr << "Bind failed" << std::endl;
closesocket(server_fd);
cleanup_sockets();
return -1;
}
// 监听连接
if (listen(server_fd, 3) < 0)
{
std::cerr << "Listen failed" << std::endl;
closesocket(server_fd);
cleanup_sockets();
return -1;
}
std::cout << "Server listening on port " << PORT << std::endl;
socklen_t addr_len = sizeof(client_addr);
// 接受客户端连接
new_socket = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (new_socket < 0)
{
std::cerr << "Accept failed" << std::endl;
closesocket(server_fd);
cleanup_sockets();
return -1;
}
// 读取客户端消息
int valread = recv(new_socket, buffer, BUFFER_SIZE, 0);
if (valread > 0)
{
std::cout << "Message from client: " << buffer << std::endl;
}
// 发送回应
const char *response = "Hello from server";
send(new_socket, response, strlen(response), 0);
std::cout << "Response sent to client" << std::endl;
// 关闭套接字
closesocket(new_socket);
closesocket(server_fd);
cleanup_sockets();
return 0;
}
3. 客户端代码(支持 Linux 和 Windows)
#include <iostream>
#include <cstring>
// 处理跨平台的头文件和函数差异
#ifdef _WIN32
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") // Winsock 库
typedef int socklen_t;
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#define closesocket close
#endif
const int PORT = 8080;
const char *SERVER_IP = "127.0.0.1";
const int BUFFER_SIZE = 1024;
void initialize_sockets()
{
#ifdef _WIN32
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
std::cerr << "Failed to initialize Winsock" << std::endl;
exit(EXIT_FAILURE);
}
#endif
}
void cleanup_sockets()
{
#ifdef _WIN32
WSACleanup();
#endif
}
int main()
{
initialize_sockets();
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
std::cerr << "Socket creation failed" << std::endl;
cleanup_sockets();
return -1;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
// 将 IP 地址转换为网络格式
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0)
{
std::cerr << "Invalid address/Address not supported" << std::endl;
closesocket(sock);
cleanup_sockets();
return -1;
}
// 连接到服务器
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
std::cerr << "Connection failed" << std::endl;
closesocket(sock);
cleanup_sockets();
return -1;
}
// 发送消息给服务器
const char *message = "Hello from client";
send(sock, message, strlen(message), 0);
std::cout << "Message sent to server" << std::endl;
// 接收服务器的回应
char buffer[BUFFER_SIZE] = {0};
int valread = recv(sock, buffer, BUFFER_SIZE, 0);
if (valread > 0)
{
std::cout << "Response from server: " << buffer << std::endl;
}
// 关闭套接字
closesocket(sock);
cleanup_sockets();
return 0;
}
4. 编译与运行
Linux 上编译
在 Linux 上可以使用 g++
编译:
g++ -o server server.cpp
g++ -o client client.cpp
Windows 上编译
在 Windows 上使用 MinGW 或 Visual Studio 进行编译。
-
MinGW 编译: 使用 MinGW 的
g++
时需要链接Ws2_32.lib
:g++ -o server server.cpp -lws2_32 g++ -o client client.cpp -lws2_32
-
Visual Studio 编译:
- 创建新项目并添加源文件。
- 确保链接
Ws2_32.lib
。
-
5. 代码说明
-
跨平台支持:
- 使用
#ifdef _WIN32
来区分 Windows 和 Linux 平台。 - 在 Windows 上初始化和清理
Winsock
,使用WSAStartup()
和WSACleanup()
函数。
- 使用
-
服务器端:
- 服务器使用
socket()
、bind()
、listen()
、accept()
等函数来建立连接。 - 一旦客户端连接上,服务器接收消息并发送响应。
- 服务器使用
-
服务器端:
- 使用
socket()
创建 TCP 套接字。 - 使用
bind()
将套接字绑定到特定的端口。 - 使用
listen()
开始监听连接。 - 使用
accept()
接受客户端连接并与其通信。 - 使用
read()
和send()
接收客户端数据和发送响应。
- 使用
-
客户端:
- 使用
socket()
创建 TCP 套接字。 - 使用
connect()
连接到服务器。 - 使用
send()
向服务器发送消息。 - 使用
read()
接收服务器的回应。
- 使用
这些示例展示了基本的 TCP 网络通信。在生产环境中,你还需要处理更多的异常情况和错误处理,比如超时、并发连接等。
6. 注意事项
- 这段代码是同步的,适用于简单的网络通信。如果要支持并发或异步通信,可以使用线程或异步 I/O。
- 处理错误情况,例如
recv()
返回0
(表示连接已关闭)和超时等。-
客户端:
- 客户端使用
socket()
和connect()
连接到服务器。 - 发送消息给服务器并接收服务器的回应。
- 客户端使用
-