文章目录
客户端
cpp
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
using namespace std;
int main() {
// 创建 TCP 套接字,返回文件描述符
int fd = socket(AF_INET, SOCK_STREAM, 0);
// 设置服务器地址结构体,初始化为全 0
sockaddr_in addr{};
// 设置地址族为 IPv4
addr.sin_family = AF_INET;
// 设置服务器 IP 为本地回环地址
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 设置服务器端口 8888,转为网络字节序
addr.sin_port = htons(8888);
// 连接到服务器
connect(fd, (sockaddr *)&addr, sizeof(addr));
cout << "Connected to server" << endl;
while (1) {
// 向服务器发送消息
const char *msg = "Hello from client";
write(fd, msg, strlen(msg));
cout << "Sent: " << msg << endl;
// 读取服务器的回复消息
char buf[64] = {};
read(fd, buf, sizeof(buf));
cout << "Received: " << buf << endl;
}
// 关闭套接字连接
close(fd);
return 0;
}
服务端
cpp
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
using namespace std;
int main() {
// 1. 创建 TCP 套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
cerr << "socket() failed" << endl;
return -1;
}
// 2. 设置端口复用
int opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
cerr << "setsockopt() failed" << endl;
close(fd);
return -1;
}
// 3. 绑定地址和端口
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8888);
if (bind(fd, (sockaddr *)&addr, sizeof(addr)) == -1) {
cerr << "bind() failed (port 8888 may be in use)" << endl;
close(fd);
return -1;
}
// 4. 开始监听
if (listen(fd, 5) == -1) {
cerr << "listen() failed" << endl;
close(fd);
return -1;
}
cout << "Server listening on port 8888..." << endl;
// 5. 接受客户端连接
sockaddr_in client_addr{};
socklen_t len = sizeof(client_addr);
// sleep(50); //调试代码使用,便于调试的使用使用 ss -lntp
// 查看已经连接的客户端信息
int cfd = accept(fd, (sockaddr *)&client_addr, &len);
if (cfd == -1) {
cerr << "accept() failed" << endl;
close(fd);
return -1;
}
// 6. 输出客户端信息(点分十进制格式)
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str));
cout << "Client connected from " << ip_str << ":"
<< ntohs(client_addr.sin_port) << endl;
while (1) {
// 7. 读取客户端消息
char buf[64] = {};
ssize_t n = read(cfd, buf, sizeof(buf) - 1);
if (n == -1) {
cerr << "read() failed" << endl;
} else if (n == 0) {
cout << "Client closed connection" << endl;
} else {
buf[n] = '\0'; // 确保字符串终止
cout << "Received: " << buf << endl;
}
// 8. 发送回复消息
const char *msg = "Hello from server";
ssize_t sent = write(cfd, msg, strlen(msg));
if (sent == -1) {
cerr << "write() failed" << endl;
}
}
// 9. 关闭连接
close(cfd);
close(fd);
return 0;
}
-
服务端 :在
accept()之后套了一个while(1),意味着处理完一条消息后会继续等待下一条,而不会关闭连接。 -
客户端 :在
connect()之后套了一个while(1),意味着发完一条并收到回复后,会立即发送下一条。
这样,双方就形成了一个"客户端发送 -> 服务端接收并回复 -> 客户端接收并再发送"的无限循环
需要修正的地方
虽然功能实现了,但你的代码中存在几个严重的逻辑隐患,如果不修复,程序可能会崩溃或占满 CPU:
1. 服务端没有处理"客户端断开"的逻辑(最严重)
当客户端主动关闭连接(或崩溃)时,服务端的 read(cfd, ...) 会返回 0(表示对方关闭)。
但你的代码里,n == 0 时只是打印了语句,并没有 break 跳出循环 。
这会导致服务端陷入死循环 ,疯狂调用 read(),每次都返回 0,导致 CPU 占用飙到 100%。
建议修改:
cpp
if (n == -1) {
cerr << "read() failed" << endl;
break; // 出错退出
} else if (n == 0) {
cout << "Client closed connection" << endl;
break; // 对方断开,退出循环
} else {
// ... 正常处理数据
}
2. 客户端发送速度过快(刷屏)
客户端在 while(1) 中没有任何延时,会疯狂地 往内核缓冲区塞数据。如果服务端处理稍慢,客户端的 write() 最终会被阻塞。虽然不会报错,但控制台会疯狂刷屏,不方便观察。
建议修改 :在客户端的循环末尾加个短暂延时,比如 sleep(1) 或 usleep(500000),让观察更清晰。
3. 客户端没有检查 read() 的返回值
如果服务端崩溃或主动关闭,客户端的 read() 会返回 0 或 -1,但你的代码没有判断,会继续打印空数据或执行错误逻辑。
✅ 修正后的服务端代码
cpp
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
using namespace std;
int main() {
// 1. 创建 TCP 套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
cerr << "socket() failed" << endl;
return -1;
}
// 2. 设置端口复用
int opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
cerr << "setsockopt() failed" << endl;
close(fd);
return -1;
}
// 3. 绑定地址和端口
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(8888);
if (bind(fd, (sockaddr *)&addr, sizeof(addr)) == -1) {
cerr << "bind() failed (port 8888 may be in use)" << endl;
close(fd);
return -1;
}
// 4. 开始监听
if (listen(fd, 5) == -1) {
cerr << "listen() failed" << endl;
close(fd);
return -1;
}
cout << "Server listening on port 8888..." << endl;
// 5. 接受客户端连接
sockaddr_in client_addr{};
socklen_t len = sizeof(client_addr);
// sleep(50); // 调试用
int cfd = accept(fd, (sockaddr *)&client_addr, &len);
if (cfd == -1) {
cerr << "accept() failed" << endl;
close(fd);
return -1;
}
// 6. 输出客户端信息
char ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str));
cout << "Client connected from " << ip_str << ":"
<< ntohs(client_addr.sin_port) << endl;
// 7. 循环通信
while (1) {
char buf[64] = {};
ssize_t n = read(cfd, buf, sizeof(buf) - 1);
if (n == -1) {
cerr << "read() failed" << endl;
break; // 读取出错,退出循环
} else if (n == 0) {
cout << "Client closed connection" << endl;
break; // 客户端断开,退出循环
} else {
buf[n] = '\0';
cout << "Received: " << buf << endl;
// 发送回复
const char *msg = "Hello from server";
ssize_t sent = write(cfd, msg, strlen(msg));
if (sent == -1) {
cerr << "write() failed" << endl;
break; // 写入失败,退出循环
}
}
}
// 8. 关闭连接
close(cfd);
close(fd);
return 0;
}
✅ 修正后的客户端代码
cpp
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
using namespace std;
int main() {
// 创建 TCP 套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
cerr << "socket() failed" << endl;
return -1;
}
// 设置服务器地址
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8888);
// 连接到服务器
if (connect(fd, (sockaddr *)&addr, sizeof(addr)) == -1) {
cerr << "connect() failed" << endl;
close(fd);
return -1;
}
cout << "Connected to server" << endl;
// 循环通信
int count = 0; // 添加计数器,避免无限发送(可选)
while (1) {
// 发送消息
const char *msg = "Hello from client";
ssize_t sent = write(fd, msg, strlen(msg));
if (sent == -1) {
cerr << "write() failed" << endl;
break;
}
cout << "Sent: " << msg << endl;
// 读取回复
char buf[64] = {};
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n == -1) {
cerr << "read() failed" << endl;
break;
} else if (n == 0) {
cout << "Server closed connection" << endl;
break;
} else {
buf[n] = '\0';
cout << "Received: " << buf << endl;
}
// 适当延时,避免刷屏(单位:秒)
sleep(1);
// 可选:发送一定次数后退出(避免无限循环)
// if (++count >= 10) break;
}
close(fd);
return 0;
}
📝 修正问题清单
| 问题 | 原代码行为 | 修正内容 | 影响 |
|---|---|---|---|
| 服务端未处理客户端断开 | read() 返回 0 时仅打印,不退出循环,导致死循环占满 CPU |
在 n == 0 时添加 break |
避免空转,正常退出 |
| 服务端未处理读取错误 | read() 返回 -1 时仅打印,继续循环 |
添加 break 退出 |
避免在错误状态下继续 |
| 服务端未处理写入错误 | write() 失败时仅打印,继续循环 |
添加 break 退出 |
及时发现问题并退出 |
客户端未检查 connect() 返回值 |
直接连接,未判断失败 | 添加 if 判断并退出 |
避免后续操作无效 |
客户端未检查 write() 返回值 |
未判断发送是否成功 | 添加 if 判断并退出 |
检测网络异常 |
客户端未检查 read() 返回值 |
直接读取,未判断对方关闭或错误 | 添加 n == -1 和 n == 0 处理 |
正确处理服务端断开 |
| 客户端发送过快 | 无延时,疯狂发送导致刷屏 | 在循环末尾添加 sleep(1) |
控制节奏,便于观察 |