Socket编程(TCP)

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() 返回 -1errno = 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

client.cc

复制代码
// 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;    
  }
}

server.cc

复制代码
// 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));

即可不受该影响。

相关推荐
tjjingpan2 小时前
HCIP-Datacom Core Technology V1.0_12流量过滤与转发路径控制
网络
qq. 28040339842 小时前
http 状态码
网络·网络协议·http
TG:@yunlaoda360 云老大2 小时前
华为云国际站代理商申请跨账号代维权限的流程复杂吗?
网络·数据库·华为云
小张的博客之旅2 小时前
“复兴杯”2025第五届大学生网络安全精英赛 (排位赛wp)
网络·安全·web安全
"YOUDIG"3 小时前
全能安全工具箱:智能密码生成、高强度文件加密与动态二维码生成的一站式平台
服务器·网络·安全
理智的煎蛋3 小时前
单节点 K8S IP 修改步骤
tcp/ip·云原生·容器·kubernetes
一颗青果3 小时前
序列化与反序列化
网络·网络协议·tcp/ip
安全渗透Hacker4 小时前
新一代特征扫描器afrog与经典引擎Xray深度解析
网络·安全·web安全·网络安全·自动化·系统安全·安全性测试
Xの哲學4 小时前
Linux IPC机制深度剖析:从设计哲学到内核实现
linux·服务器·网络·算法·边缘计算