socket编程——tcp

在我这篇博客:网络——socket编程中介绍了关于socket编程的一些必要的知识,以及介绍了使用套接字在udp协议下如何通信,这篇博客中,我将会介绍如何使用套接字以及tcp协议进行网络通信。

1. 前置准备

在进行编写代码之前,我们要明白一点:udp协议是面向数据报的,而tcp协议是面向字节流的,同时tcp是面向连接的,它要进行数据通信,就必须先连接,这些我们日后再解释。
udp协议和tcp协议所使用的socket文件是全双工的,也就是可以同时对该文件进行读写,而不会有任何影响,这一点在我上面的那篇博客中udp协议的套接字编程也有所体现。
那么我们现在就来开始tcp协议式的套接字编程。

2. 开始编码

a. 整体框架

整体框架还是一如既往:
Server.hpp:
在这里插入图片描述
客户端main函数:
在这里插入图片描述
服务端main函数:
在这里插入图片描述

b. Server.hpp

1). sockfd和bind

我们仍需要一个socket文件来被获取,以及需要bind成为网络进程:
在这里插入图片描述
在这里插入图片描述

2). listen

我们前面说过,要想使用tcp协议进行网络通信,就必须先让客户端与服务端连接,而这个连接申请一般是由客户端发起的,所以我们的服务端应该时刻 “监听” 客户端发过来的连接请求,所以我们要认识一个接口listen:
在这里插入图片描述
在这个接口中,我们第一个参数就是我们现在创建的所使用的_sockfd,第二个参数现在不做解释,我们给它填成5,这个函数也是成功返回0,失败返回-1,错误码被设置:
在这里插入图片描述
至此我们的tcp协议的初始化就完成了,当你使用tcp协议进行通信时,这段代码是始终不变的。

3). accept

接下来就是我们的Start函数了,上面说到,我们需要先建立连接,然后再进行通信,我们现在光知道客户端要连接服务端了(listen),我们还得接收它(accept):
在这里插入图片描述
这个函数的参数的第一个就是我们的_sockfd, 第二个和第三个参数就如同udp协议进行通信时的recvfrom中的那两个字段一样,作为输入输出型参数,主要是输出型参数来接收客户端的套接字信息。
这个函数我们需要重点关注它的返回值:
在这里插入图片描述
这句话的大致意思就是,当这个函数成功的话会给我们返回一个文件描述符。为什么这里要返回一个文件描述符呢?我们可以这样理解:
在这里插入图片描述

而其中,我们listen中的_sockfd就是那个招揽客人的人,这个accept返回的文件描述符就是服务员,而我们进行tcp通信时,客户端和服务端通信服务端使用的文件描述符就是这个accept返回的文件描述符:
在这里插入图片描述
所以我们应该,将成员变量_sockfd更名为_listenfd更合适一点。

4). echo server

现在我们开始编写如和接收客户端的信息,以及如何返回去,我们前面说过tcp是面向字节流的,所以我们的读写接口使用read和write接口也不令人意外了:
在这里插入图片描述
在这里插入图片描述
接下来我们的服务端就完成了。
接下来就是客户端的编写。

c. client

开始的套路不变:
在这里插入图片描述
这里我们再认识一个接口,那就是客户端如何向服务端发起连接请求:
在这里插入图片描述
内容不做解释了:
在这里插入图片描述

效果如下:
在这里插入图片描述
当有一端退出时,另一端目前也会退出:
在这里插入图片描述
还有一点,那就是当对这段代码测试时连续的运行server程序,会出现场这种情况:
在这里插入图片描述
这样的原因是因为程序退出后,并不会立即释放连接。我们只需要在Init函数中写这么一段代码就可以了:
在这里插入图片描述
这也是一种固定写法,原理是什么日后再说。至此我们就可以使用tcp协议进行简单的网络套接字通信了,由此可见tcp协议使用的文件描述符也是全双工的。

d. 引入多进程

上面的代码在面对一个客户时,没有什么问题,但是如果是多个用户要连接服务器呢?
在这里插入图片描述
我们发现只有上面的客户端可以正常进行通信,下面的客户端甚至都没有连接到服务器,当我们上面的客户端退出之后:
在这里插入图片描述
我们发现,下面客户端可以连接服务器了,并且一瞬间把消息返回来了。这其中的原因就是,我们的服务器是单进程的,在给第一个客户端提供服务时,我们的服务器进程就进入一个死循环了:
在这里插入图片描述
在这里插入图片描述

所以我们要将我们的服务器改为多进程的:
在这里插入图片描述

这样一来,当我们accept返回一个sockfd之后,我们就创建子进程,让子进程进行执行对该客户端的服务,然后然父进程回收子进程就可以了,但是这里还有个问题。当我们创建好子进程之后,子进程去执行任务去了,但是父进程需要等待子进程,等子进程结束之后父进程才能成功回收,然后继续接收切他客户端的连接,这样一来,这个代码不还是串行的,不能支持多个用户同时连接服务器嘛,所以我们还需要再改造一下:
在这里插入图片描述
在这里我们写了这样一段代码,当这段代码执行之后,子进程就又创建一个孙子进程,然后两者会继续执行后续的代码,但是此时我们让子进程直接退出,这个时候,父进程会直接回收子进程,而孙子进程由于它的父进程exit了,那么它就会成为孤儿进程被操作系统领养。这样我们就不需要让父进程一直等待了,而是直接对子进程回收之后就可以。
但是,还有问题。这个问题我使用图来表示一下:
在这里插入图片描述
在这个过程中,我们发现当我们的的孙子进程被创建好之后,执行完服务任务之后,关闭sockfd,退出之后,实际上这个sockfd并没有关闭,因为这个sockfd实际的指向还有一个父线程。这个文件结构体并不会被销毁,那么这样的话,当客户端连接的越来越多的话,我们父进程的文件描述符就会越来越多,这一点我们可以通过执行程序来看到:
在这里插入图片描述

但是要知道,一个进程的文件描述符数量是有限的,这样的话,就会出现文件描述符泄露的问题,所以我们需要,在各自的进程中,关闭不需要的文件描述符:
在这里插入图片描述
我们再来看程序运行:
在这里插入图片描述
从此以后我的们的父进程在接收返回来的sockfd之后就永远都是4了。也不会出现文件描述符泄露的问题。
上面的代码中我们的父进程还是需要等待子进程的并且我们接受一个客户端就需要创建两次子进程。这样效率有点低,所以我们再次改造:
在这里插入图片描述
我们直接对子进程退出时会给父进程发出的SIGCHLD信号进行忽略,这样的话,我们的子进程退出之后就不需要让父进程等待了,而是直接被操作系统释放:
在这里插入图片描述
我们发现,我们的代码在服务端被多个客户端连接后无法分辨客户端,所以我们再优化一下代码:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

e. 多线程

我们知道创建进程就意味着,一整套内核资源要被重新创建,这样的效率还是较低下的,所以我们使用多线程,再次修改代码:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
可以看到也可以进行多个客户端同时通信,并且也不需要关闭文件描述符了,因为文件描述符表也是多线程的共享资源。
但是,线程的创建也是有代价的,为什么不提前创建好一批线程呢?所以就有了线程池。

f. 线程池

在引入线程池之前,我要先更改代码中的一个地方:
在这里插入图片描述
在这个类中,我们将IP地址的网络字节序列转化为字符串,但是这个接口实际上不是那么安全:
在这里插入图片描述
我们可以看到,这个函数的返回值是一个char*类型的,这意味着,我们的返回的IP字符串,之前是被存在一个地方的,这个地方不是我们提供的,因为我们没有写,这个地方是C语言提供的,那就意味着,假如这个地方是固定的话,那就会导致多线程下的线程安全问题,所以我们需要换一个接口:
在这里插入图片描述

在这里插入图片描述
这么一改之后,这个函数的字符串的最终存储地址是我们单独在栈上提供的,就不用担心上面的问题了。
现在我们再来向代码中引入线程池:
在这里插入图片描述

这里我设置的线程池的数量是两个。

在这里插入图片描述
可以看到是可以正常通信的,但是:
在这里插入图片描述
当我再次连接一个客户端时,第三个客户端卡住了,这其实就是因为,我们的Service服务是一个死循环,我们线程池中的线程只有两个线程,而当第三个客户端连接过来之后,仅有的两个线程还在死循环中,所以就会导致第三个连接的客户端不能通信。
我们现在服务端提供的Service服务是一个死循环,是一个长任务,我们上面的线程池代码明显是不支持的,所以我们要改造代码,将Service长服务改为一个短服务,例如只完成一个功能(大小写转换,翻译,Ping服务器等):
首先服务器能提供一些服务:
在这里插入图片描述
在这里插入图片描述
注册服务通过服务端main函数中硬编码。
在这里插入图片描述

然后我们具体的服务应该是先让用户输入一个操作符,然后再接收一段用户想要被操作的内容,这段内容经过操作符对应的处理,将结果发送给用户:
在这里插入图片描述
在这里插入图片描述
接收用户操作符
在这里插入图片描述
接收用户想被操作的内容
在这里插入图片描述
根据操作符处理内容并返回结果
在这里插入图片描述
最后将结果发给用户
在这里插入图片描述
其中我们对于没有的服务以及ping功能是不需要用户再次输入内容的,所以我做了特殊处理。
在服务端mian函数中我们提供服务功能:
在这里插入图片描述
对于客户端我们也肯定要做出修改:
在这里插入图片描述
这其中本地文件内容如下:
在这里插入图片描述
而获取服务端服务列表是为了用户知道有哪些服务:
在这里插入图片描述
在这里插入图片描述
其中服务器中有一个翻译功能,我使用一个类对该功能进行封装:
在这里插入图片描述
在这里插入图片描述
这就是关于tcp和线程池实现的一个简单的短服务功能的代码。

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

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

相关文章

C语言学习/复习30--结构体的声明/初始化/typedef改名/内存对齐大小计算

一、自定义数据类型 二、结构体 1.结构体的定义(与数组相对比) 2.结构体全局/局部变量的定义 3.typedef对结构体改名 4.匿名结构体类型的声明 注意事项1: 匿名后必须立即创建结构体变量 、 5.结构体与链表节点定义 注意事项1&…

Datawhale ChatGPT基础科普

根据课程GitHub - datawhalechina/hugging-llm: HuggingLLM, Hugging Future. 摘写自己不懂得一些地方,具体可以再到以上项目地址 LM:这是ChatGPT的基石的基石。 Transformer:这是ChatGPT的基石,准确来说它的一部分是基石。 G…

nodejs工具模块学习

util 是一个Node.js 核心模块,提供常用函数的集合; util.inspect(object,[showHidden],[depth],[colors]) 是一个将任意对象转换 为字符串的方法,通常用于调试和错误输出; 如果只有一个参数 object,是要转换的对象&…

英伟达AI系列免费公开课

英伟达公开课官网地址 Augment your LLM Using Retrieval Augmented Generation Building RAG Agents with LLMs langchain的workflow: ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c90cb157c9c84bb5b3da380ec56f5c2a.png Generative AI Explained

函数的使用

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 前面已经介绍了函数的创建以及调用,下面就通过范例学习函数的使用 创建一个函数,如果是偶数则计算其平方,如果是奇数则计算其平方根 分…

如何在 Flutter 中制作多种颜色的 TextField

TextField widget 本身并不施加任何样式。相反,它会要求 TextEditingController 生成一个样式化的 TextSpan 对象,即一段带有样式的文本。 TextField 将其样式传递给 TextEditingController ,默认实现只是将其放入 TextSpan 对象中&#xff0…

abide数据集时间序列获取

1.http://preprocessed-connectomes-project.org/abide/ 2. 3.windows批量下载 (1)创建links.txt,写入链接,例如 https://s3.amazonaws.com/fcp-indi/data/Projects/ABIDE_Initiative/Outputs/dparsf/filt_global/rois_cc400/K…

(十六)call、apply、bind介绍、区别和实现

函数中的this指向: 函数中的this指向是在函数被调用的时候确定的,也就是执行上下文被创建时确定的。在一个执行上下文中,this由调用者提供,由调用函数的方式来决定。 类数组对象arguments: arguments只在函数&#…

谷歌收录工具有什么好用的?

如果是想促进谷歌的收录,其实能用的手段无非就两个,谷歌GSC以及爬虫池 谷歌gsc就不用说了,作为谷歌官方提供的工具,他能提供最准确的数据,并且可以提交每天更新的链接,进而促进收录,只要你的页面…

模块三:二分——69.x的平方根

文章目录 题目描述算法原理解法一:暴力查找解法二:二分查找 代码实现暴力查找CJava 题目描述 题目链接:69.x的平方根 算法原理 解法一:暴力查找 依次枚举 [0, x] 之间的所有数 i (这⾥没有必要研究是否枚举到 x /…

【后端】python2和python3的安装与配置

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、python是什么二、python环境的安装与配置Python 2的安装与配置Python 3的安装与配置注意事项 三、总结 前言 随着开发语言及人工智能工具的普及&#xff0…

洗地机哪个牌子好?推荐这四款热销品牌

随着科技的不断发展,洗地机已经成为了家庭中不可或缺的智能家居设备。它们能够帮助我们轻松地完成地面清洁工作,节省时间和精力。但是,面对市场上琳琅满目的洗地机品牌,我们该如何选择呢?本文将为大家介绍四大口碑洗地…

Jackson 2.x 系列【31】Spring Boot 集成之字典翻译

有道无术,术尚可求,有术无道,止于术。 本系列Jackson 版本 2.17.0 本系列Spring Boot 版本 3.2.4 源码地址:https://gitee.com/pearl-organization/study-jaskson-demo 文章目录 1. 场景描述2. 案例演示2.1 修改枚举2.2 定义注解…

使用PlantUML绘制活动图、泳道图

最近在学PlantUML 太漂亮了 给大家欣赏一下 我也记录一下 startuml |使用前| start :用户打开旅游App; |#LightSkyBlue|使用后| :用户浏览旅游信息; |#AntiqueWhite|登机前| :用户办理登机手续; :系统生成登机牌; |使用前| :用户到达机场; |登机前| :用户通过安检; |#Light…

2024华中杯A题|太阳能路灯光伏板的朝向设计问题(思路、代码.....)

太阳能路灯由太阳能电池板组件部分(包括支架)、LED灯头、控制箱(包含控制器、蓄电池)、市电辅助器和灯杆几部分构成。太阳能电池板通过支架固定在灯杆上端。太阳能电池板也叫光伏板,它利用光伏效应接收太阳辐射能并转化为电能输出,经过充放电控制器储存在蓄电池中。太阳能…

使用vue3+ts+vite从零开始搭建bolg(三)(持续更新中)

三、axios,API和路由封装 3.1 axios二次封装 pnpm i axios在src下建立如图文件夹 在request下配置请求拦截器,响应拦截器 import axios from axios import { ElMessage } from element-pluslet request axios.create({baseURL: import.meta.env.VITE…

DFS与回溯专题:路径总和问题

DFS与回溯专题:路径总和问题 一、路径总和 题目链接: 112.路径总和 题目描述 代码思路 对二叉树进行dfs搜索,递归计算每条路径的节点值之和,当某个节点的左右子节点都为空时,说明已经搜索完成某一条路径&#xff0…

牛客NC195 二叉树的直径【simple DFS C++ / Java /Go/ PHP】

题目 题目链接: https://www.nowcoder.com/practice/15f977cedc5a4ffa8f03a3433d18650d 思路 最长路径有两种情况: 1.最长条路径经过根节点,那么只需要找出根节点的左右两棵子树的最大深度然后相加即可。 2.最长路径没有经过根节点&#xf…

js some对比forEach

some&#xff1a;return true可以停止循环 forEach&#xff1a;return true无法停止循环 <!DOCTYPE html> <html ng-app"my_app"><head><script type"text/javascript">const array [10, 20, 30];const targetValue 10;// 检测…

Vue3的监听属性watch和计算属性computed

监听属性watch 计算属性computed 一、监听属性watch watch 的第一个参数可以是不同形式的“数据源&#xff0c;watch 可以监听以下几种数据&#xff1a; 一个 ref (包括计算属性)、 一个响应式对象、 一个 getter 函数、 或多个数据源组成的数组 watch 的参数:监视的回调&…