send
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
-
sockfd :已连接的 TCP 套接字(
socket()→connect()或accept()返回的 fd) -
buf/len:发送/接收缓冲区及其最大长度
-
flags :一般填 0;非阻塞模式下常加
MSG_DONTWAIT做"本次操作非阻塞"
返回值
| 值 | 含义 | 下一动作 |
|---|---|---|
| >0 | 实际拷贝字节数(可能 < len) | 继续 send剩余数据 |
| -1 | 出错;立即查 errno |
见第 5 节错误表 |
CP 读端关闭(本端 close/shutdown(RD_ONLY))后,内核立即给对端回 RST ;
对端若继续 write(),会收到 SIGPIPE 信号(默认终止进程),同时 write() 返回 -1 且 errno = EPIPE。显然不能因为对端关闭,服务器就直接被终止进程,因此要把sigpipe信号设置忽略。
常见错误码速查
| 错误码 | 要不要重试 | 说明/示例处理 |
|---|---|---|
EINTR |
✅ 立即重试 | 信号中断,数据未传输 |
EAGAIN/EWOULDBLOCK |
✅ 等事件再重试 | 非阻塞缓冲区空/满 |
| 其他 | ❌ 关闭连接 |
recv
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
-
sockfd :已连接的 TCP 套接字(
socket()→connect()或accept()返回的 fd) -
buf/len:发送/接收缓冲区及其最大长度
-
flags :一般填 0;非阻塞模式下常加
MSG_DONTWAIT做"本次操作非阻塞"
返回值
| 值 | 含义 | 下一动作 |
|---|---|---|
| >0 | 实际拷贝字节数(可能 < len) | 继续 send/recv 剩余数据 |
| 0 | 对端优雅关闭(TCP FIN 已收且内核缓冲区无数据) | 本方调用 close() 回收 fd |
| -1 | 出错;立即查 errno |
见第 5 节错误表 |
常见错误码速查
| 错误码 | 要不要重试 | 说明/示例处理 |
|---|---|---|
EINTR |
✅ 立即重试 | 信号中断,数据未传输 |
EAGAIN/EWOULDBLOCK |
✅ 等事件再重试 | 非阻塞缓冲区空/满 |
| 其他 | ❌ 关闭连接 |
事实上我们也可以使用write和read,他们和recv/send唯一的区别就是没有最后的flag标志位,write,read默认是阻塞模式。
代码v0
// Tcp_Socket客户端
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
using namespace std;
//服务端
int main() {
// ip 127.0.0.1
int fd = socket(AF_INET, SOCK_STREAM, 0); // ip4 ,tcp
if (fd < 0) {
cout << "socket faild" << endl;
}
//让操作系统自动bind端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8082);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
int i = 0;
int ret=connect(fd,(sockaddr*)&addr,sizeof addr);
if(ret<0){
cout<<"connect faild"<<endl;
}
while (true) {
i++;
char buff[1024];
size_t n = read(fd, buff, sizeof buff);
cout << "n:" << n << endl;
if(n==0){
cout<<"server close"<<endl;
break;
}
cout << "server say:" << buff << endl;
}
}
// Tcp_Socket
// socket编程
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstring> // C++ 推荐用 cstring
#include <cerrno> // errno 也需要
#include <csignal> // 增加信号处理
#include <iostream>
using namespace std;
//服务端
int main() {
// 忽略 SIGPIPE 信号,防止客户端关闭后 write 导致进程直接退出
signal(SIGPIPE, SIG_IGN);
// ip 127.0.0.1 端口8082 开启地址重用
int fd = socket(AF_INET, SOCK_STREAM, 0); // ip4 ,udp
if (fd < 0) {
cout << "socket faild" << endl;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8082);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
int ret = bind(fd, (sockaddr*)&addr, sizeof addr);
if (ret < 0) cout << "bind faild" << endl;
int i = 0;
ret = listen(fd, 10); //测试1会如何
if (ret < 0) {
cout << "listen faild" << endl;
}
struct sockaddr_in si;
socklen_t len = sizeof si;
int acfd = accept(fd, (sockaddr*)&si, &len);
if (acfd < 0) {
cout << "accept faild" << endl;
}
cout << "accept over" << endl;
cout<<"客户端端口:"<<ntohs(si.sin_port)<<endl;
char ip[16]={0};
inet_ntop(AF_INET,&si.sin_addr,ip,sizeof ip);
cout<<"客户端:"<<ip<<endl;
while (true) {
i++;
sleep(1);
char buff[1024];
sprintf(buff, "%s :%d", "hello client", i);
int n = write(acfd, buff, sizeof buff);
if(n==-1){
cout<<"write faild"<<endl;
cout<<strerror(errno)<<endl;
close(acfd);
break;
}
cout << "n:" << n << endl;
}
}
关闭读端,写段输出以下内容

关闭写段、读端输出以下内容

功能正常运行,但是有个明显的问题:我们的server只能接受一个连接!因为第一次accept成功后就进入死循环了。这显然不合理,所以我们可以用多线程。
代码V1
// Tcp_Socket
// socket编程
//多线程
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cerrno> // errno 也需要
#include <csignal> // 增加信号处理
#include <cstring> // C++ 推荐用 cstring
#include <iostream>
#include <thread>
using namespace std;
//服务端
int listen_fd;
void handle_client(int acfd) {
int count=0;
while(true)
{count++;
sleep(1);
char buff[1024];
sprintf(buff, "%s:%d:%lld", "hello client I am Thread", pthread_self(),count);
int ret = write(acfd, buff, sizeof buff);
if (ret == -1) {
cout << "write faild" << endl;
cout << strerror(errno) << endl;
close(acfd);
return;
}
}
}
int main() {
// 忽略 SIGPIPE 信号,防止客户端关闭后 write 导致进程直接退出
signal(SIGPIPE, SIG_IGN);
// ip 127.0.0.1 端口8082 开启地址重用
listen_fd = socket(AF_INET, SOCK_STREAM, 0); // ip4 ,tcp
if (listen_fd < 0) {
cout << "socket faild" << endl;
}
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8082);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
int ret = bind(listen_fd, (sockaddr*)&addr, sizeof addr);
if (ret < 0) cout << "bind faild" << endl;
int i = 0;
ret = listen(listen_fd, 10); //测试1会如何
if (ret < 0) {
cout << "listen faild" << endl;
}
struct sockaddr_in si;
socklen_t len = sizeof si;
while (true) {
int acfd = accept(listen_fd, (sockaddr*)&si, &len);
if (acfd < 0) {
cout << "accept faild" << endl;
}
cout << "accept over" << endl;
cout << "客户端端口:" << ntohs(si.sin_port) << endl;
char ip[16] = {0};
inet_ntop(AF_INET, &si.sin_addr, ip, sizeof ip);
cout << "客户端:" << ip << endl;
thread t(handle_client, acfd);
t.detach();
}
}
可以看到确实是可以响应多个客户端了。

地址重用
我们开启服务端,然后它运行了一会自己挂了或者被我们终止了,你直接重新启动会发现他不让你bind原来的端口了,大概过2分钟后才可以重新bind这个端口,原因先不讲解,在socket函数和bind函数中间加上
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
即可不受该影响。