文章目录
- 客户端
- 服务端
- 需要修正的地方
- 1. 服务端没有处理“客户端断开”的逻辑(最严重)
- 2. 客户端发送速度过快(刷屏)
- 3. 客户端没有检查 `read()` 的返回值
- ✅ 修正后的服务端代码
- ✅ 修正后的客户端代码
- 📝 修正问题清单
客户端
#include<arpa/inet.h>#include<cstring>#include<iostream>#include<sys/socket.h>#include<unistd.h>usingnamespacestd;intmain(){// 创建 TCP 套接字,返回文件描述符intfd=socket(AF_INET,SOCK_STREAM,0);// 设置服务器地址结构体,初始化为全 0sockaddr_in addr{};// 设置地址族为 IPv4addr.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){// 向服务器发送消息constchar*msg="Hello from client";write(fd,msg,strlen(msg));cout<<"Sent: "<<msg<<endl;// 读取服务器的回复消息charbuf[64]={};read(fd,buf,sizeof(buf));cout<<"Received: "<<buf<<endl;}// 关闭套接字连接close(fd);return0;}服务端
#include<arpa/inet.h>#include<cstring>#include<iostream>#include<sys/socket.h>#include<unistd.h>usingnamespacestd;intmain(){// 1. 创建 TCP 套接字intfd=socket(AF_INET,SOCK_STREAM,0);if(fd==-1){cerr<<"socket() failed"<<endl;return-1;}// 2. 设置端口复用intopt=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// 查看已经连接的客户端信息intcfd=accept(fd,(sockaddr*)&client_addr,&len);if(cfd==-1){cerr<<"accept() failed"<<endl;close(fd);return-1;}// 6. 输出客户端信息(点分十进制格式)charip_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. 读取客户端消息charbuf[64]={};ssize_t n=read(cfd,buf,sizeof(buf)-1);if(n==-1){cerr<<"read() failed"<<endl;}elseif(n==0){cout<<"Client closed connection"<<endl;}else{buf[n]='\0';// 确保字符串终止cout<<"Received: "<<buf<<endl;}// 8. 发送回复消息constchar*msg="Hello from server";ssize_t sent=write(cfd,msg,strlen(msg));if(sent==-1){cerr<<"write() failed"<<endl;}}// 9. 关闭连接close(cfd);close(fd);return0;}服务端:在
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,但你的代码没有判断,会继续打印空数据或执行错误逻辑。
✅ 修正后的服务端代码
#include<arpa/inet.h>#include<cstring>#include<iostream>#include<sys/socket.h>#include<unistd.h>usingnamespacestd;intmain(){// 1. 创建 TCP 套接字intfd=socket(AF_INET,SOCK_STREAM,0);if(fd==-1){cerr<<"socket() failed"<<endl;return-1;}// 2. 设置端口复用intopt=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); // 调试用intcfd=accept(fd,(sockaddr*)&client_addr,&len);if(cfd==-1){cerr<<"accept() failed"<<endl;close(fd);return-1;}// 6. 输出客户端信息charip_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){charbuf[64]={};ssize_t n=read(cfd,buf,sizeof(buf)-1);if(n==-1){cerr<<"read() failed"<<endl;break;// 读取出错,退出循环}elseif(n==0){cout<<"Client closed connection"<<endl;break;// 客户端断开,退出循环}else{buf[n]='\0';cout<<"Received: "<<buf<<endl;// 发送回复constchar*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);return0;}✅ 修正后的客户端代码
#include<arpa/inet.h>#include<cstring>#include<iostream>#include<sys/socket.h>#include<unistd.h>usingnamespacestd;intmain(){// 创建 TCP 套接字intfd=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;// 循环通信intcount=0;// 添加计数器,避免无限发送(可选)while(1){// 发送消息constchar*msg="Hello from client";ssize_t sent=write(fd,msg,strlen(msg));if(sent==-1){cerr<<"write() failed"<<endl;break;}cout<<"Sent: "<<msg<<endl;// 读取回复charbuf[64]={};ssize_t n=read(fd,buf,sizeof(buf)-1);if(n==-1){cerr<<"read() failed"<<endl;break;}elseif(n==0){cout<<"Server closed connection"<<endl;break;}else{buf[n]='\0';cout<<"Received: "<<buf<<endl;}// 适当延时,避免刷屏(单位:秒)sleep(1);// 可选:发送一定次数后退出(避免无限循环)// if (++count >= 10) break;}close(fd);return0;}📝 修正问题清单
| 问题 | 原代码行为 | 修正内容 | 影响 |
|---|---|---|---|
| 服务端未处理客户端断开 | read()返回0时仅打印,不退出循环,导致死循环占满 CPU | 在n == 0时添加break | 避免空转,正常退出 |
| 服务端未处理读取错误 | read()返回-1时仅打印,继续循环 | 添加break退出 | 避免在错误状态下继续 |
| 服务端未处理写入错误 | write()失败时仅打印,继续循环 | 添加break退出 | 及时发现问题并退出 |
客户端未检查connect()返回值 | 直接连接,未判断失败 | 添加if判断并退出 | 避免后续操作无效 |
客户端未检查write()返回值 | 未判断发送是否成功 | 添加if判断并退出 | 检测网络异常 |
客户端未检查read()返回值 | 直接读取,未判断对方关闭或错误 | 添加n == -1和n == 0处理 | 正确处理服务端断开 |
| 客户端发送过快 | 无延时,疯狂发送导致刷屏 | 在循环末尾添加sleep(1) | 控制节奏,便于观察 |