TCP网络套接字
一、创建套接字
#include <sys/types.h>
#include <sys/socket.h> int socket(int domain, int type, int protocol);
参数:
- domain:指定使用的协议族。常见的取值有AF_INET(IPv4)和AF_INET6(IPv6)。这个参数决定了地址的格式和类型。
- type:指定套接字的类型。常见的取值有SOCK_STREAM(流套接字,提供有序的、可靠的、双向的和基于连接的字节流,通常使用TCP协议)和SOCK_DGRAM(数据报套接字,支持无连接的、不可靠的和使用固定大小缓冲区的数据报服务,通常使用UDP协议)。
- protocol:指定协议编号。通常可以设置为0,让系统根据domain和type自动选择合适的协议。
返回值:
- 成功:socket函数成功执行时,返回一个非负整数,续的socket编程中用于标识和操作该套接字即套接字的文件描述符。这个描述符在后。
- 失败:如果socket函数调用失败,将返回-1,并设置相应的errno以指示错误原因。
二、绑定
#include <sys/types.h>
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind函数的作用是将一个套接字与一个具体的地址(包括IP地址和端口号)绑定。这样,当套接字进行通信时,就可以使用这个指定的地址作为通信的源地址。
参数:
- sockfd:标识一个已经创建但尚未绑定的套接字的文件描述符。
- addr:指向一个包含地址信息的结构体的指针。对于IPv4,通常使用struct sockaddr_in;对于IPv6,则使用struct sockaddr_in6。
- addrlen:addr结构体的大小,通常可以使用sizeof操作符获取。
返回值:
- 成功时,bind函数返回0。
- 失败时,返回-1,并设置相应的errno以指示错误原因。
void Init(){// 创建tcp套接字_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(FATAL, "sockfd create fall!\n");exit(SOCKET_ERROR);}LOG(INFO, "create sockfd success,sockfd:%d\n", _listensockfd);// bindstruct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind false!\n");exit(BIND_ERROR);}LOG(INFO, "bind success!\n");}
三、将套接字设置为监听状态
对于UDP来说绑定完就可以正常进行读取发送数据了,因为UDP协议是无连接的,但是TCP协议是可连接的,所以我们需要先将套接字设置为监听状态
int listen(int sockfd, int backlog);
参数:
- sockfd:这是需要设置为监听状态的套接字的文件描述符。这个套接字之前应该已经通过 socket函数创建,并通过bind函数绑定到了一个特定的IP地址和端口上。
- backlog:这个参数定义了内核应该为相应套接字排队的最大连接数。这个值至少为0,但实际的最大值取决于系统。如果设置为0,则系统会根据其配置来决定一个合适的值。这个值并不是限制同时连接客户端的数量,而是限制在套接字处于listen状态时,尚未被accept调用的连接请求队列的最大长度。
返回值:
- 成功时,函数返回0。
- 失败时,返回-1,并设置错误码。
void Init(){// 创建tcp套接字_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(FATAL, "sockfd create fall!\n");exit(SOCKET_ERROR);}LOG(INFO, "create sockfd success,sockfd:%d\n", _listensockfd);// bindstruct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind false!\n");exit(BIND_ERROR);}LOG(INFO, "bind success!\n");// listenn = ::listen(_listensockfd, gbacklog);if (n < 0){LOG(FATAL, "listen false!\n");exit(LISEN_ERROR);}LOG(INFO, "listen success!\n");}
四、服务端获取连接
accept函数是网络编程中用于TCP服务器的一个关键系统调用,它用于从完成连接队列中取出下一个已完成连接请求,并创建一个新的套接字来与该客户端进行通信。这个函数通常与 listen函数一起使用,在TCP服务器程序中扮演着接收客户端连接的角色。
#include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
- sockfd:这是之前通过 socket函数创建的,并且已经通过bind和listen函数准备就绪以接受连接的监听套接字的文件描述符。
- addr这是一个指向 sockaddr结构的指针,该结构将被填充以表示连接客户端的地址信息。如果调用者对客户端的地址不感兴趣,可以将此参数设置为NULL。
- addrlen:这是一个指向socklen_t变量的指针,该变量在调用前应该包含 addr缓冲区的大小,在调用后,它将包含实际存储在addr中的地址信息的实际字节数。
返回值:
- 成功时,返回一个新的套接字文件描述符,该描述符用于与连接的客户端进行通信。
- 失败时,返回-1,并设置错误码。
注意:到目前为止一共出现了两个套接字,一个是我们用socket函数创建的,另一个是accept返回的,其中我们创建的套接字是用来监听的,所以我们才把他命名为listensockfd,而accept返回的套接字是我们用来进行数据接收发送的。
void Start(){_isrunning = true;while (_isrunning){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sockfd = accept(_listensockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){LOG(ERROR, "accept false!\n");continue;}LOG(INFO, "connect a link! sockfd:%d\n", sockfd);InetAddr addr(client);//通过accept返回的套接字我们就可以进行网络通信了Service(sockfd, addr);}}
同样我们实现的Service函数就是实现一个简单的回显函数
void Service(int sockfd, InetAddr addr){while (true){char buffer[1024];int n = read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;LOG(DEBUG, "[%s]#%s\n", addr.AddrStr().c_str(), buffer);std::string echo_message = "[udpserver echo]#";echo_message += buffer;n = write(sockfd, echo_message.c_str(), sizeof(echo_message));if (n < 0){LOG(ERROR, "server write false!\n");}}else if (n == 0) // 读到文件结尾{LOG(INFO, "%s quit!\n", addr.AddrStr().c_str());break;}else{LOG(INFO, "read error!\n");break;}}::close(sockfd);}
五、读取发送数据
由于tcp协议是面向字节流的,所以我们可以直接使用read、write向文件描述符读取发送数据,而为了与UDP协议的函数相类似,OS还提供了recv和send函数
recv
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
- sockfd/s:socket文件描述符或套接字描述符,它指定了要从中读取数据的socket。
- buf:指向缓冲区的指针,用于存储接收到的数据。这个缓冲区应该足够大,以存储预期的数据量。
- len:指定了buf缓冲区的大小,即函数最多可以读取的字节数。
- flags:用于指定接收操作的行为,这个参数通常是0,表示阻塞读取
返回值:
- 如果成功,recv返回实际读取的字节数,该值可能小于请求读取的字节数(例如,如果数据不足或对方关闭了连接)。
- 如果连接被对方正常关闭,并且已经读取了所有可用的数据,recv将返回0。
- 如果出现错误,recv 将返回-1
send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
六、客户端
客户端首先需要创建一个套接字,并将服务端的ip地址端口号等信息录入sockaddr_in结构体中,与UDP套接字的使用一样,客户端不需显示的调用bind函数,其次由于TCP协议是需要连接的,所以客户端需要先调用connect函数与服务端构建联系
#include <sys/types.h>
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//使用方式:./tcpclient ip地址 端口号
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage:" << argv[0] << " serverip serverport" << std::endl;exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){std::cerr << "connect socket error" << std::endl;exit(2);}while (true){std::string sendmessage;std::cout << "Please Enter#";std::getline(std::cin, sendmessage);write(sockfd, sendmessage.c_str(), sizeof(sendmessage));char echo_buffer[1024];n = read(sockfd, echo_buffer, sizeof(echo_buffer));if (n > 0){echo_buffer[n] = 0;std::cout << echo_buffer << std::endl;}else{break;}}::close(sockfd);return 0;
}
上述代码其实存在一个问题,由于我们上述的代码是单进程的,而Service业务是一个长服务,他不会主动退出,当第一个客户端进来以后是可以正常执行业务的,但是如果第二个客户端进来以后由于只有一个执行流,第二个客户端无法正常执行业务,只有当第一个客户端执行完退出以后第二个客户端才能正常执行,所以我们继续改进一下
七、多进程版
我们可以利用fork函数创建出一个子进程,让子进程来执行Service,父进程等待子进程退出后,就可以继续执行循环,接收别的客户端,但是我们还要考虑阻塞等待的问题,我们可以将等待方式设置为非阻塞等待的,但是这种方式有点麻烦。我们知道子进程退出后会给父进程发送SIGNALCHLD信号,最简单的方式就是可以将这个信号设置为忽略,signal(SIGCHLD,SIG_IGN);这里还有另一种方式,我们可以再创建一个孙子进程,并将子进程退出,子进程就直接会被父进程等待,这样的话孙子进程就会变成孤儿进程,被bash管理,让孙子进程执行我们的业务,这样我们就不需要考虑等待的问题了。
这里还有一个小细节,子进程会继承父进程的文件描述符表,我们知道每一个客户端会对应一个sockfd,而文件描述符表本质就是一个数组,也是有数量大小的,当客户端的数量比较多了的话文件描述符表可能就会被占满,其次父进程不关心业务执行什么,也就是父进程不关心sockfd,所以每当创建一个子进程建议父进程将sockfd关掉,也建议子进程将listenfd也关掉方式误操作,这样不论有多少个客户端,其对应的文件描述符永远是4
void Start(){_isrunning = true;while (_isrunning){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sockfd = accept(_listensockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){LOG(ERROR, "accept false!\n");continue;}LOG(INFO, "connect a link! sockfd:%d\n", sockfd);InetAddr addr(client);// version1:有缺陷,Service是长服务,由于这个代码是单进程的,第二个客户端进来会执行不了// Service(sockfd, addr);// version2: 多进程pid_t id = fork();if (id == 0){// 子进程::close(_listensockfd); // 防止误操作// 由于是阻塞等待,service不退出父进程就不会向后继续执行循环,简单的方式是signal(SIGCHLD,SIG_IGN);,// 也可以这样写,创建一个孙子进程并直接exit这样子进程就变成了孤儿进程交给bash处理,这样我们就不需要管子进程了if (fork() > 0)exit(0);Service(sockfd, addr);exit(0);}::close(sockfd); // 父进程不关心执行什么任务pid_t rid = waitpid(id, nullptr, 0);if (rid > 0){LOG(INFO, "wait success!\n");}}}
八、多线程版
创建一个多线程要求我们在pthread_create时传入一个参数为void*返回值为void*的函数,所以我们可以在类内设计一个静态的Execute函数,而我们想要调用这个函数就需要一个对象,其次我们的业务Service需要传入套接字sockfd和客户端信息Inet_Addr,所以我们可以将这三个元素封装成一个类,将这个类对象作为参数传给Execute
class ServerData{public:ServerData(int sockfd, InetAddr &addr, TcpServer *td): _sockfd(sockfd), _addr(addr), _td(td){}public:int _sockfd;InetAddr _addr;TcpServer *_td;};
void Start(){_isrunning = true;while (_isrunning){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sockfd = accept(_listensockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){LOG(ERROR, "accept false!\n");continue;}LOG(INFO, "connect a link! sockfd:%d\n", sockfd);InetAddr addr(client);// version 3:多线程版pthread_t tid;ServerData* sd=new ServerData(sockfd,addr,this);pthread_create(&tid, nullptr, Excute, sd);}}
九、线程池版
虽然这种方式可以,但是很不建议这样写,因为我们写的线程池中的线程也是有限的,而我们的业务是长服务,这样就会导致如果我们的客户端数量大于线程数量时,有些客户端因为没有执行流可能无法获取正常的业务,所以不建议线程池执行长服务
void Start(){_isrunning = true;while (_isrunning){struct sockaddr_in client;memset(&client, 0, sizeof(client));socklen_t len = sizeof(client);int sockfd = accept(_listensockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){LOG(ERROR, "accept false!\n");continue;}LOG(INFO, "connect a link! sockfd:%d\n", sockfd);InetAddr addr(client);// version4:线程池// ps:线程池不适合做长服务,因为线程池的线程数量也是有限的,如果客户端数量超过线程数量,再有客户端加进来也得不到服务task_t t = std::bind(&TcpServer::Service, this, sockfd, addr);ThreadPool<task_t>::GetInstance()->Enqueue(t);}}
上述所有代码可以参考:
张得帅c/Linux
相关文章:
TCP网络套接字
一、创建套接字 #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); 参数: domain:指定使用的协议族。常见的取值有AF_INET(IPv4)和AF_INET6(IPv6&a…...

Element学习(axios异步加载数据、案例操作)(5)
1、这次学习的是上次还未完成好的恶element案例,对列表数据的异步加载,并渲染展示。 ——>axios来发送异步请求 (1) (2)在vue当中安装axios (注意在当前的项目目录,并且安装完之后…...

大数据-65 Kafka 高级特性 分区 Broker自动再平衡 ISR 副本 宕机恢复再重平衡 实测
点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…...

html+css+js网页设计 软通动力网站2个页面(带js)首页轮播图+置顶导航
htmlcssjs网页设计 软通动力网站2个页面(带js)首页轮播图置顶导航 网页作品代码简单,可使用任意HTML编辑软件(如:Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及…...

【经验分享】ShardingSphere+Springboot-04:自定义分片算法(COMPLEX/STANDARD)
文章目录 3.4 CLASS_BASED 自定义类分片算法3.4.1 复杂分片自定义算法(strategyCOMPLEX )3.4.2 STANDARD 标准分片自定义算法## 进阶:star: 自定义算法范围查询优化 3.4 CLASS_BASED 自定义类分片算法 3.4.1 复杂分片自定义算法(strategyCOM…...
如何设置RabbitMQ和Redis消息队列系统
设置RabbitMQ和Redis作为消息队列系统时,需要分别进行安装、配置和测试,以确保它们能够正常工作并满足你的应用需求。以下是一个基于这两个系统的设置指南: RabbitMQ的设置 1. 安装Erlang 由于RabbitMQ是用Erlang语言编写的,因…...
白骑士的Matlab教学高级篇 3.3 工具箱与扩展
MATLAB 提供了丰富的工具箱(Toolbox)和扩展功能,这些工具箱涵盖了各个领域的专业计算需求,如信号处理、图像处理、统计与机器学习等。利用工具箱,用户可以快速实现复杂的计算和分析任务。本文将介绍常用的工具箱及其使…...

bug: 配置flyway.locations多个脚本位置不生效
文章目录 业务场景场景一场景二 业务场景 随着项目版本迭代,数据库结构也会变动。如果一个项目引用其他项目的jar包,并且需要执行对应jar包的flyway脚本,就需要配置flyway.locations 场景一 正常情况下,在一个项目中可以在yml文件…...
8月5日SpringBoot学习笔记
今日内容:搭建mybatis ORM 配置数据源 $#的区别 增删改查 搭建mybatis 在原有maven项目基础配置上进行: pom文件添加依赖 <!-- Mybatis --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-…...

Java学习笔记(二十):反射、动态代理、日志、类加载器、xml、单元测试Junit、注解
目录 一、反射 1.1 反射的概述: 1.2 学习反射到底学什么? 1.3 获取字节码文件对象的三种方式 1.4 字节码文件和字节码文件对象 1.5 获取构造方法 1.6 获取构造方法并创建对象 1.7 获取成员变量 1.8 获取成员变量并获取值和修改值 1.9 获取成员…...

如何快速从文本中找到需要的信息,字典和正则灵活运用
import re #打开文本文件 f open("stock_data.txt",encoding"utf-8") #单独读取第一行数据处理进行分割,末尾换行符去掉 headers f.readline().strip().split(,) print(headers) #定义一个字典,以股标代码做为KEY,每个行做为值 st…...

springboot3整合redis
来源于https://www.bilibili.com/video/BV1UC41187PR/?spm_id_from333.1007.top_right_bar_window_history.content.click&vd_source865f32e12aef524afb83863069b036aa 一、整合redis 1.创建项目文件 2.添加依赖 <dependencies><dependency><groupId>…...

VUE基础快速入门
VUE 和 VUE-Cli VUE 是一种流行的渐进式JavaScript框架,用于构建Web用户界面它具有易学、轻量级、灵活性强、高效率等特点,并且可以与其他库和项目集成是目前最流行的前端框架之一VUE-Cli 称为“VUE脚手架”,它是由VUE官方提供的客户端,专门为…...
用Python实现特征工程之特征提取——数值特征提取、类别特征提取、文本特征提取、时间特征提取
特征提取是特征工程中的关键步骤,它从原始数据中提取有意义的特征,以便机器学习模型能够更好地理解和学习数据。根据数据类型,特征提取可以分为数值特征提取、类别特征提取、文本特征提取和时间特征提取。下面详细讲解每种特征提取方法&#…...

按图搜索新体验:阿里巴巴拍立淘API返回值详解
阿里巴巴拍立淘API是一项基于图片搜索的商品搜索服务,它允许用户通过上传商品图片,系统自动识别图片中的商品信息,并返回与之相关的搜索结果。以下是对阿里巴巴拍立淘API返回值的详细解析: 一、主要返回值内容 商品信息 商品列表…...
vue跨域问题
本地调试 可以通过在vue.config.js中配置devServer来实现跨域请求。 module.exports {publicPath: ./,productionSourceMap: false, // 生产环境是否生成 sourceMap 文件devServer: {proxy: {/bi: {target: http://1.11.113.20:1234/bi, // 后台接口域名ws: false, //…...

【NLP】文本处理的基本方法【jieba分词、命名实体、词性标注】
文章目录 1、本章目标2、什么是分词3、jieba的使用3.1、精确模式分词3.2、全模式分词3.3、搜索引擎模式分词3.4、中文繁体分词3.5、使用用户自定义词典 4、什么是命名实体识别5、什么是词性标注6、小结7、jieba词性对照表⭐ 🍃作者介绍:双非本科大三网络…...

unity 本地使用Json(全套)
提示:文章有错误的地方,还望诸位大神不吝指教! 文章目录 前言一、Json是什么?二、创建Json文件1.在线编辑并转实体类(C#)2.Json文件 三、解析Json并使用四、报错:JsonError:JsonExce…...

java消息队列ActiveMQ
安装 前置条件 activemq的运行依赖于jdk,需要提前安装jdk如果已经安装了jdk,需要根据jdk的版本来选择对应的版本进行安装activemq版本对应在官网上,使用java -version 看jdk的版本注意:jdk和mq的版本不一致会报错,电脑…...
Android SurfaceFlinger——信号同步原理(五十一)
经过前面系列文章的学习,我们的已经理解了 SurfaceFlinger 运行机制以及同步机制,但是SurfaceFlinger 又是以什么方法是把需要刷新的信号发送给 App 进程的。 一、VSync简介 垂直同步(Vertical Synchronization,简称 VSync)是一种用于同步视频信号和显示设备刷新率的技术…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...