打造自己的 C++ Socket 服务器:手把手教程
Socket 服务器技术文档
1. 概述
本文档描述了一个基于 C++ 的简单 TCP socket 服务器的实现和用法。该服务器监听指定的端口,接受客户端连接,读取客户端发送的消息,并向客户端发送一个固定的响应消息。
2. 环境要求
- C++ 编译器,支持 C++11 或更高版本
- POSIX 兼容操作系统
- 支持 POSIX socket API 的库
3. 编译和运行
6. 后续改进
- 将服务器代码保存为一个名为
server.cpp
的文件。 - 使用以下命令编译代码:
g++ server.cpp -o server
- 运行服务器:
-
./server
4. 服务器实现
4.1 头文件
#include
#include #include #include #include 这些头文件分别用于输入输出流、字符串操作、socket 编程、网络地址结构和 UNIX 标准函数。
4.2 定义端口号和缓冲区大小
#define PORT 8080 #define BUFFER_SIZE 1024
PORT
定义了服务器监听的端口号,BUFFER_SIZE
定义了用于读取和发送数据的缓冲区大小。4.3 主函数
int main() { // ... }
主函数是程序的入口点,包含了服务器的主要逻辑。
4.4 创建 socket
int server_fd, new_socket; server_fd = socket(AF_INET, SOCK_STREAM, 0);
创建一个 socket 文件描述符
server_fd
,用于监听客户端连接。AF_INET
表示 IPv4 地址族,SOCK_STREAM
表示 TCP 流套接字。4.5 绑定地址和端口
struct sockaddr_in address; bind(server_fd, (struct sockaddr*)&address, sizeof(address));
将 socket 绑定到本地地址和端口。
4.6 监听连接
listen(server_fd, 3);
开始监听是否有客户端连接。第二个参数
3
表示最大连接数。4.7 接受连接
new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
接受客户端的连接,并返回一个新的 socket 文件描述符
new_socket
。4.8 读取和发送数据
read(new_socket, buffer, BUFFER_SIZE); send(new_socket, hello, strlen(hello), 0);
从客户端读取数据到缓冲区,并向客户端发送固定的响应消息。
4.9 关闭 socket
close(server_fd);
关闭服务器 socket,释放资源。
5. 注意事项
- 本示例代码没有进行错误检查和异常处理,这在实际应用中是非常重要的。
- 服务器只能同时处理一个客户端连接,不支持并发。
- 服务器在发送响应消息后立即关闭连接,不进行持续通信。
- 添加错误检查和异常处理。
- 使用多线程或多进程支持并发连接。
- 实现完整的客户端-服务器通信协议。
#include
#include #include #include #include #define PORT 8080 #define BUFFER_SIZE 1024 int main() { int server_fd, new_socket; struct sockaddr_in address; int opt = 1; int addrlen = sizeof(address); char buffer[BUFFER_SIZE] = {0}; const char* hello = "Hello from server"; // 创建 socket 文件描述符 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 绑定 socket 到地址和端口 address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr*)&address, sizeof(address))<0) { perror("bind failed"); exit(EXIT_FAILURE); } // 监听是否有客户端连接 if (listen(server_fd, 3) < 0) { perror("listen"); exit(EXIT_FAILURE); } // 接受客户端的连接 if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen))<0) { perror("accept"); exit(EXIT_FAILURE); } // 读取数据 read(new_socket, buffer, BUFFER_SIZE); std::cout << "Message from client: " << buffer << std::endl; // 发送数据 send(new_socket, hello, strlen(hello), 0); std::cout << "Hello message sent "; // 关闭 socket close(server_fd); return 0; } 几个问题1.sockfd是什么?答:
在 socket 编程中,
sockfd
是一个术语,通常用于指代一个 socket 的文件描述符。在 Unix-like 系统中,几乎所有的 I/O 操作都是通过文件描述符进行的,包括网络通信。文件描述符是一个非负整数,它是内核为了高效管理已打开的文件或其他 I/O 资源(如 socket、管道、设备等)而提供的一个抽象句柄。当你创建一个 socket 时,系统会返回一个文件描述符,这个文件描述符就被称为
sockfd
。之后,你就可以使用这个文件描述符来进行各种 socket 操作,比如绑定地址(bind
)、监听连接(listen
)、接受连接(accept
)、读取数据(read
)、发送数据(write
或send
)、关闭 socket(close
)等。例如,在下面的代码中,
socket
函数创建了一个新的 socket,并返回了一个文件描述符,这个描述符被存储在变量sockfd
中:int sockfd = socket(AF_INET, SOCK_STREAM, 0);
在这里,
AF_INET
指定了地址族为 IPv4,SOCK_STREAM
指定了 socket 类型为 TCP 流套接字。如果socket
调用成功,sockfd
将是一个有效的文件描述符,用于后续的 socket 操作。如果调用失败,sockfd
将被设置为一个特定的错误值(通常是 -1),并且可以通过查看errno
变量来获取失败的原因。我们可以用一个类比来通俗易懂地解释
sockfd
。想象一下,你的电脑是一个大型的邮局,而你想通过邮局发送和接收信件。在邮局中,每个信箱都有一个唯一的编号,这个编号就像是文件描述符。当你创建一个 socket 时,就像是你在邮局申请了一个新的信箱,邮局会给你一个编号(即
sockfd
),这样你就可以通过这个编号来发送和接收信件了。当你想发送一封信时,你会把信放入对应编号的信箱中,然后邮局会帮你把信发送出去。同样地,当你创建了一个 socket 并获得了
sockfd
,你就可以使用这个sockfd
来进行网络通信,比如发送数据(send
)和接收数据(recv
)。当你不再需要这个信箱时,你可以通知邮局回收这个信箱,这样其他人就可以使用这个编号了。在 socket 编程中,当你完成所有的通信任务后,你需要关闭 socket,释放
sockfd
,这样系统就可以重新使用这个编号了。所以,
sockfd
就像是邮局信箱的编号,它帮助你管理和操作网络通信。