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));

即可不受该影响。

相关推荐
HelloWorld1024!3 分钟前
C++中链表的虚拟头结点:应用场景与使用时机
网络·c++·链表
久违 °7 分钟前
【安全开发】Nmap主机探测技术详解(一)
网络·安全·网络安全
txinyu的博客9 分钟前
DNS 协议
网络
小小代码狗16 分钟前
【无标题】
网络·sql·php
qq_3363139320 分钟前
java基础-网络编程-UDP
网络
乾元23 分钟前
范式转移:从基于规则的“特征码”到基于统计的“特征向量”
运维·网络·人工智能·网络协议·安全
txinyu的博客24 分钟前
手写 C++ 高性能 Reactor 网络服务器
服务器·网络·c++
华普微HOPERF27 分钟前
BLE6.0规范,如何助力智能门锁突破性能极限?
网络·智能家居·解决方案·智能门锁·芯片模组·蓝牙6.0
程序猿编码40 分钟前
无状态TCP技术:DNS代理的轻量级实现逻辑与核心原理(C/C++代码实现)
c语言·网络·c++·tcp/ip·dns
小二·44 分钟前
Python Web 开发进阶实战:可验证网络 —— 在 Flask + Vue 中实现去中心化身份(DID)与零知识证明(ZKP)认证
前端·网络·python