文章目录

  • 一、TCP传输
  • 二、相关接口
  • 三、多进程版本
  • 四、多线程版本

一、TCP传输

TCP和UDP类似,但是在传输中TCP有输入,输出缓冲区,看下面的传输图片

在这里插入图片描述
可以理解为TCP之间的数据传输都是依赖各自的socket,socket就充当传输的中介吧。

而每个socket都对应两个缓冲区,一个输入缓冲区,一个输出缓冲区 。

二、相关接口

下⾯介绍程序中⽤到的socketAPI,这些函数都在sys/socket.h中。

socket函数的声明:

int socket(int domain, int type, int protocol);

功能:打开网络文件(套接字)。

  • 参数domain:确定IP地址类型,如IPv4还是IPv6。
    AF_INET: IPv4。

    AF_INET6:IPv6。

  • 参数type:确定数据的传输方式。
    SOCK_STREAM: 流式套接字(TCP)。

    SOCK_DGRAM: 数据报套接字(UDP)。

  • 参数protocol:确定协议类型,如果前面type已经能确定了,这里传入0即可。

  • 返回值:
    成功:文件描述符。
    失败:一个小于0的数。

bind函数的使用

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

功能:用来绑定端口。

  • 参数sockfd:要绑定的套接字描述符(由 socket() 函数创建)。
  • 参数sockaddr:指向地址结构体的指针,包含绑定的IP地址和端口号。
  • 参数addrlen:地址结构体的长度(单位:字节)。
  • 返回值:
    0:成功。
    非0:失败

listen函数的使用

int listen(int sockfd, int backlog);

功能:将主动套接字(SOCK_STREAM)转换为被动监听状态,等待客户端连接请求。

  • 参数sockfd: 已绑定(bind)但未连接的套接字描述符
  • 参数backlog:等待连接队列的最大长度
    listen()声明sockfd处于监听状态,并且最多允许有backlog个客⼾端处于连接等待状态,如果接收
    到更多的连接请求就忽略
  • 返回值:
    0:成功。
    非0:失败

accept函数的使用

int accept(int sockfd, struct sockaddr * addr,socklen_t * addrlen);

功能:三次握⼿完成后,服务器调⽤accept()接受连接;
如果服务器调⽤accept()时还没有客⼾端的连接请求,就阻塞等待直到有客⼾端连接上来

  • 参数sockfd:处于监听状态(LISTEN)的套接字描述符
  • 参数addr:addr是⼀个输出型参数,accept()返回时传出客⼾端的地址和端⼝号;
    如果给addr参数传NULL,表⽰不关⼼客⼾端的地址;
    参数addrlen:是⼀个传⼊传出参数(value-result argument),传⼊的是调⽤者提供的,缓冲区addr
    的⻓度以避免缓冲区溢出问题,传出的是客⼾端地址结构体的实际⻓度(有可能没有占满调⽤者提
    供的缓冲区);
  • 返回值
    -≥0 成功,返回新套接字描述符(与客户端通信用)
    -1 失败,错误码存储在errno中

connect函数的使用

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

功能:客⼾端需要调⽤connect()连接服务器;

  • connect和bind的参数形式⼀致,区别在于bind的参数是⾃⼰的地址,⽽connect的参数是对⽅的
    地址;

  • 成功返回0,出错返回-1;

下面就来根据缓冲区来说说,send()/write() 与 recv()/read() 两个函数吧。

注意: read(),write()是通用文件IO, recv(),send()是socket专用,在这里用哪一个都可以

send()/write()

ssize_t send(int sockfd, const void buf[.len], size_t len, int flags);
  • sockfd: socket套接字描述符

  • buf: 数据缓冲区

  • len: 要写入的字节数

  • flags: 控制标志

ssize_t write(int fd, const void *buf, size_t count);
  • fd: 任意文件描述符

  • buf: 数据缓冲区

  • count: 要写入的字节数

功能:都是用于数据发送或写入的函数

记住 ,我们只需要知道send()/write() 是将数据包发送到缓冲区里就行了,至于是什么时候将缓冲区里的数据发送到接收端的socket就不用我们应用层来管了,实际上想管也管不到。

有两种情况会将缓冲区里的数据全部发送出去

  • 情况1:缓冲区满了,很好理解吧,满了当然得发走啊,不然哪里有新的空间放新数据?

  • 情况2:隔到一定时间,发现没有数据再继续send到缓冲区里来了,即便没有满也会赶紧发过去,而这个时间是非常短的。

所以,不用担心说数据发送不及时,而为什么要这样,则是为了提高数据发送的效率了。

recv()/read()

ssize_t recv(int sockfd, void buf[.len], size_t len,  int flags);
  • sockfd: socket套接字描述符

  • buf: 数据缓冲区

  • len: 缓冲区可用容量(字节数)

  • flags: 控制标志

ssize_t read(int fd, void *buf, size_t count);
  • fd: 任意文件描述符

  • buf: 数据缓冲区

  • count: 期望读取的最大字节数

功能:都是用于接收或读取数据的函数

socket不论是发送还是接收缓冲区就相当于一个管道啊,先进先出,读取出来就不在管道里了。

简单的通信代码

client.cc

#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <strings.h>
#include <arpa/inet.h>int main(int argc,char* argv[])
{if(argc != 3){std::cout << "Usage " << argv[0] << " ip port" << std::endl;exit(1);}int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock < 0){perror("socket fail");exit(1);}uint16_t port = std::stoi(argv[2]);std::string ip = argv[1];sockaddr_in sd;bzero(&sd, sizeof(sd));sd.sin_family = AF_INET;sd.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &sd.sin_addr);int n = connect(sock, (const sockaddr*)&sd, sizeof(sd));if(n < 0){perror("connect fail");exit(1);}while(true){std::string message;std::cout << "Please Enter# ";std::getline(std::cin, message);ssize_t w = write(sock, message.c_str(), message.size());if(w <= 0){perror("write fail");break;}char buffer[4096];ssize_t r = read(sock, buffer, sizeof(buffer));if(r < 0){perror("read fail");break;}else if(r > 0){buffer[r] = '\0';std::cout << "接收服务端: " << buffer << std::endl;}else{break;}}close(sock);return 0;
}

server.cc

#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <strings.h>
#include <arpa/inet.h>const int default_backlog = 4;int main(int argc,char* argv[])
{if(argc != 2){std::cout << "Usage "<< argv[0] << " port" << std::endl;exit(1);}int sock = socket(AF_INET, SOCK_STREAM, 0);if(sock < 0){perror("socket fail");exit(1);}int opt = 1;setsockopt(sock, SOL_SOCKET,SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));uint16_t port = std::stoi(argv[1]);sockaddr_in sd;bzero(&sd, sizeof(sd));sd.sin_addr.s_addr = INADDR_ANY;sd.sin_family = AF_INET;sd.sin_port = htons(port);int b = bind(sock, (const sockaddr*)&sd, sizeof(sd));if(b < 0){perror("bind fail");exit(1);}int list = listen(sock, default_backlog);if(list < 0){perror("listen fail");exit(1);}while(true){sockaddr_in sd;socklen_t len = sizeof(sd);int sockfd = accept(sock, (sockaddr*)&sd, &len);if(sockfd < 0){std::cout << "accept fail" << std::endl;continue;}std::cout << "接收成功 ip: " << inet_ntoa(sd.sin_addr) << " port: " << ntohs(sd.sin_port) << std::endl;while(true){char buffer[1024];ssize_t n = read(sockfd, buffer, sizeof(buffer));if(n < 0){perror("read fail");break;}else if(n == 0){std::cout << "client quit" << std::endl;break;}else{buffer[n] = '\0';std::cout << "client say: " << buffer << std::endl;std::string echo = "server say: ";echo += buffer;ssize_t w = write(sockfd, echo.c_str(), echo.size());if(w <= 0)exit(1);}}close(sockfd);}close(sock);return 0;
}

测试多个连接的情况
再启动⼀个客⼾端,尝试连接服务器,发现第⼆个客⼾端,不能正确的和服务器进⾏通信

分析原因,是因为我们 accecpt 了⼀个请求之后,就在⼀直 while 循环尝试 read ,没有继续调⽤
到 accecpt ,导致不能接受新的请求

我们当前的这个 TCP ,只能处理⼀个连接,这是不科学的,我们需要引入多进程或者多线程

三、多进程版本

#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <strings.h>
#include <arpa/inet.h>#include <sys/wait.h>
#include <sys/types.h>const int default_backlog = 4;void Handler(int sockfd, const sockaddr_in& sd) 
{std::cout << "接收成功 ip: " << inet_ntoa(sd.sin_addr) << " port: " << ntohs(sd.sin_port) << std::endl;while (true){char buffer[1024];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n < 0){perror("read fail");break;}else if (n == 0){std::cout << "client quit" << std::endl;break;}else{buffer[n] = '\0';std::cout << "client say: " << buffer << std::endl;std::string echo = "server say: ";echo += buffer;ssize_t w = write(sockfd, echo.c_str(), echo.size());if (w <= 0)break;}}close(sockfd);exit(0);
}int main(int argc, char *argv[])
{if (argc != 2){std::cout << "Usage " << argv[0] << " port" << std::endl;exit(1);}int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){perror("socket fail");exit(1);}int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));uint16_t port = std::stoi(argv[1]);sockaddr_in sd;bzero(&sd, sizeof(sd));sd.sin_addr.s_addr = INADDR_ANY;sd.sin_family = AF_INET;sd.sin_port = htons(port);int b = bind(sock, (const sockaddr *)&sd, sizeof(sd));if (b < 0){perror("bind fail");exit(1);}int list = listen(sock, default_backlog);if (list < 0){perror("listen fail");exit(1);}while (true){sockaddr_in sd;socklen_t len = sizeof(sd);int sockfd = accept(sock, (sockaddr *)&sd, &len);if (sockfd < 0){std::cout << "accept fail" << std::endl;continue;}pid_t id = fork();if(id < 0){close(sockfd);continue;}else if(id == 0){if(fork() > 0){close(sockfd);exit(0);}Handler(sockfd, sd);}else{close(sockfd);waitpid(id, nullptr, 0);}}close(sock);return 0;
}

四、多线程版本

#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <strings.h>
#include <arpa/inet.h>#include <sys/wait.h>
#include <sys/types.h>#include <pthread.h>const int default_backlog = 4;struct Arg
{Arg(int sock, sockaddr_in s): sockfd(sock), sd(s){}int sockfd;sockaddr_in sd;
};void* Handler(void* arg) 
{Arg* a = static_cast<Arg*>(arg);int sockfd = a->sockfd;sockaddr_in sd = a->sd;delete a;std::cout << "接收成功 ip: " << inet_ntoa(sd.sin_addr) << " port: " << ntohs(sd.sin_port) << std::endl;while (true){char buffer[1024];ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n < 0){perror("read fail");break;}else if (n == 0){std::cout << "client quit" << std::endl;break;}else{buffer[n] = '\0';std::cout << "client say: " << buffer << std::endl;std::string echo = "server say: ";echo += buffer;ssize_t w = write(sockfd, echo.c_str(), echo.size());if (w <= 0)break;}}close(sockfd);return nullptr;
}int main(int argc, char *argv[])
{if (argc != 2){std::cout << "Usage " << argv[0] << " port" << std::endl;exit(1);}int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){perror("socket fail");exit(1);}int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));uint16_t port = std::stoi(argv[1]);sockaddr_in sd;bzero(&sd, sizeof(sd));sd.sin_addr.s_addr = INADDR_ANY;sd.sin_family = AF_INET;sd.sin_port = htons(port);int b = bind(sock, (const sockaddr *)&sd, sizeof(sd));if (b < 0){perror("bind fail");exit(1);}int list = listen(sock, default_backlog);if (list < 0){perror("listen fail");exit(1);}while (true){sockaddr_in sd;socklen_t len = sizeof(sd);int sockfd = accept(sock, (sockaddr *)&sd, &len);if (sockfd < 0){std::cout << "accept fail" << std::endl;continue;}Arg* a = new Arg(sockfd, sd);pthread_t id;int n = pthread_create(&id, nullptr, Handler, (void*)a);if(n < 0){close(sockfd);delete a;continue;}pthread_detach(id);}close(sock);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/91764.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/91764.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/91764.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GitHub使用小记——本地推送、外部拉取和分支重命名

GitHub 项目推送与拉取等操作使用随记 本小记适用于个人项目或组织项目&#xff0c;涵盖 GitHub 推送、拉取、分支管理、.gitignore 设置等常见需求。 1. 将已有本地工程推送至 GitHub 新仓库 1.1 前提条件 本地项目结构完整&#xff0c;已准备好&#xff1b;本地已安装 Git…

RabbitMQ 延时队列插件安装与使用详解(基于 Delayed Message Plugin)

RabbitMQ 延时队列插件安装与使用详解&#xff08;基于 Delayed Message Plugin&#xff09;&#x1f4cc; 一、什么是 RabbitMQ 延时队列&#xff1f;&#x1f680; 二、安装前准备✅ RabbitMQ 环境要求&#x1f527; 三、安装延时队列插件&#x1f9e9; 插件名称&#xff1a;…

Vue项目使用ssh2-sftp-client实现打包自动上传到服务器(完整教程)

告别手动拖拽上传&#xff01;本教程将手把手教你如何通过ssh2-sftp-client实现Vue项目打包后自动上传到服务器&#xff0c;提升部署效率300%。&#x1f680;一、需求场景与解决方案在Vue项目开发中&#xff0c;每次执行npm run build后都需要手动将dist目录上传到服务器&#…

《质光相济:Three.js中3D视觉的底层交互逻辑》

在Three.js搭建的虚拟维度中,光照与材质的关系远非技术参数的简单叠加,当光线以数字形态穿越虚空,与物体表面相遇的瞬间,便开始书写属于这个世界的物理叙事——每一缕光斑的形状、每一块阴影的浓淡、每一寸肌理的反光,都是对现实光学规律的转译与重构。理解这种交互的深层…

无刷电机在汽车领域的应用与驱动编程技术

文章目录引言一、核心应用场景1. 新能源汽车动力系统2. 底盘控制系统3. 车身与舒适系统4. 智能驾驶与安全系统二、无刷电机的技术优势解析三、无刷电机驱动编程基础1. 驱动原理2. 驱动架构四、核心控制算法与实现1. 六步换向法&#xff08;梯形波控制&#xff09;算法流程图C语…

【游戏引擎之路】登神长阶(十八):3天制作Galgame引擎《Galplayer》——无敌之道心

游戏引擎开发记录&#xff1a;2024年 5月20日-6月4日&#xff1a;攻克2D物理引擎。 2024年 6月4日-6月13日&#xff1a;攻克《3D数学基础》。 2024年 6月13日-6月20日&#xff1a;攻克《3D图形教程》。 2024年 6月21日-6月22日&#xff1a;攻克《Raycasting游戏教程》。 2024年…

kotlin kmp 跨平台环境使用sqldelight

欢迎访问我的主页: https://heeheeaii.github.io/ 1. 项目结构 SQLDelightKMPDemo/ ├── shared/ │ ├── src/ │ │ ├── commonMain/kotlin/ │ │ ├── androidMain/kotlin/ │ │ ├── desktopMain/kotlin/ │ │ └── commonMain/sqldel…

机器学习【五】decision_making tree

决策树是一种通过树形结构进行数据分类或回归的直观算法&#xff0c;其核心是通过层级决策路径模拟规则推理。主要算法包括&#xff1a;ID3算法基于信息熵和信息增益选择划分属性&#xff1b;C4.5算法改进ID3&#xff0c;引入增益率和剪枝技术解决多值特征偏差&#xff1b;CART…

简单记录一下VSCode中的一些学习记

在刚开始学习VSCode时&#xff0c;相信大家都会好奇VSCode底部区域那几个不同的状态栏具体有什么作用&#xff08;输出、调试控制台、终端、端口&#xff09;&#xff0c;貌似好像都是输出与代码相关的信息的&#xff1f;貌似代码运行结果既可以出现在输出中&#xff0c;也可以…

基于 Hadoop 生态圈的数据仓库实践 —— OLAP 与数据可视化(二)

目录 二、Hive、SparkSQL、Impala 比较 1. SparkSQL 简介 2. Hive、SparkSQL、Impala 比较 &#xff08;1&#xff09;功能 &#xff08;2&#xff09;架构 &#xff08;3&#xff09;场景 3. Hive、SparkSQL、Impala 性能对比 &#xff08;1&#xff09;cloudera 公司…

C++:std::array vs 原生数组 vs std::vector

&#x1f4cc; C&#xff1a;std::array vs 原生数组 vs std::vector 引用&#xff1a; C/C 标准库 std::vector、std::array、原生静态数组 的区别有哪些&#xff1f; 深度剖析&#xff1a;std::vector 内存机制与 push_back 扩容策略 今天过去了 还有许许多个明天 能和大…

Hyper-V + Centos stream 9 搭建K8s集群(二)

一、安装自动补全主节点安装就可以yum install -y bash-completion echo source <(kubectl completion bash) >>~/.bashrc kubectl completion bash >/etc/bash_completion.d/kubectl二、安装Calico网络插件&#xff08;主节点&#xff09;下载文件wget https://ca…

VBA代码解决方案第二十七讲:禁用EXCEL工作簿右上角的关闭按钮

《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程&#xff0c;目前已经是第三版修订了。这套教程定位于入门后的提高&#xff0c;在学习这套教程过程中&#xff0c;侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…

Spring AI 系列之三十一 - Spring AI Alibaba-基于Nacos的MCP

之前做个几个大模型的应用&#xff0c;都是使用Python语言&#xff0c;后来有一个项目使用了Java&#xff0c;并使用了Spring AI框架。随着Spring AI不断地完善&#xff0c;最近它发布了1.0正式版&#xff0c;意味着它已经能很好的作为企业级生产环境的使用。对于Java开发者来说…

sqli-labs:Less-12关卡详细解析

1. 思路&#x1f680; 本关的SQL语句为&#xff1a; $uname".$uname."; $passwd".$passwd."; $sql"SELECT username, password FROM users WHERE username($uname) and password($passwd) LIMIT 0,1";注入类型&#xff1a;字符串型&#xff0…

【SpringAI】8.通过json动态添加mcp服务

前言 官方示例的代码中&#xff0c;mcp一般是配置到yml中或者json文件中&#xff0c;使用自动装配的方式注入服务&#xff0c;这种方式不方便在程序启动后添加新的服务&#xff0c;这里参考cherry studio的方式动态添加mcp服务 1.确定方案 mcp服务的维护放到mysql业务数据库维…

【PDF + ZIP 合并器:把ZIP文件打包至PDF文件中】

B站链接 PDF ZIP 合并器&#xff1a;把ZIP文件打包至PDF文件中_哔哩哔哩_bilibiliz 加强作者的工具 https://wwgw.lanzn.com/i8h1C32k9bef 密码:30cv 新增c框架&#xff0c;加快运行速度

阿里云部署微调chatglm3

git Ifs install Git lfs 主要用于管理大型文件。在传统的Git仓库中&#xff0c;所有文件内容都会被完整记录在每一次提交中&#xff0c;这会导致仓库体积增大&#xff0c;克隆、拉取和推送操作变慢&#xff0c;甚至可能超出存储限额。Git LFS通过将大文件替换成文本指针&#…

Linux网络编程 ---五种IO模型

五种IO模型一、IO慢的原因二、五种IO模型三、如何设置非阻塞式IO&#xff1f;一、IO慢的原因 二、五种IO模型 阻塞式IO 非阻塞式IO 信号驱动IO 多路转接 异步IO 三、如何设置非阻塞式IO&#xff1f; &#xff08;一&#xff09;用法说明 &#xff08;二&#xff0…

Obsidian结合CI/CD实现自动发布

CI/CDQuickAddJS脚本bat脚本sh脚本实现自动发版Hugo文章 需求来源 每次手动执行Hugo的命令&#xff0c;手动把public文件夹上传到自己的服务器可以完成发版需求。 但是&#xff0c;作为一个内容创作者&#xff0c;我更希望的关注于自己的内容&#xff0c;而不是关注整个发版…