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.mfbz.cn/a/551599.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker部署Prometheus+AlertManager实现邮件告警

文章目录 一、环境准备1、硬件准备(虚拟机)2、关闭防火墙,selinux3、所有主机安装docker 二、配置Prometheus1、docker启动Prometheus 三、添加监控节点1、docker启动node-exporter 四、Prometheus配置node-exporter1、修改prometheus.yml配置…

Docker构建Golang项目常见问题

Docker构建Golang项目常见问题 1 dockerfile报错:failed to read expected number of bytes: unexpected EOF2 go mod tidy: go.mod file indicates go 1.21, but maximum supported version is 1.17 1 dockerfile报错:failed to read expected number o…

鸿蒙语言TypeScript学习第18天:【泛型】

1、TypeScript 泛型 泛型(Generics)是一种编程语言特性,允许在定义函数、类、接口等时使用占位符来表示类型,而不是具体的类型。 泛型是一种在编写可重用、灵活且类型安全的代码时非常有用的功能。 使用泛型的主要目的是为了处…

【Linux】详解如何利用共享内存实现进程间通信

一、共享内存(Shared Memory)的认识 共享内存(Shared Memory)是多进程间共享的一部分物理内存。它允许多个进程访问同一块内存空间,从而在不同进程之间共享和传递数据。这种方式常常用于加速进程间的通信,因…

JS打包工具 Vite

Vite是 JS 新一代的打包的工具,它所解决的问题,是前端打包慢的问题,随着前端应用复杂度越来越大,项目文件越来越多,通常项目中都是使用 Webpack 进行打包,Webpack是个静态的打包工具,每次改动都…

9.Jetson AGX Orin protobuf验证

Jetson AGX Orin protobuf验证 前提已经安装好grpc 1:进入目录grpc/examples/cpp/helloworld下编译 make如果出现错误,protoc: Command not found。 进入/usr/local/bin与/usr/local/lib 均没发现protoc与libprotobuf。原来/grpc/third_party/protob…

C语言【指针】

1. 基本语法 1.1 指针变量的定义和使用(重点) 指针是一种数据类型,指针变量指向谁 就把谁的地址赋值给指针变量 1.2 通过指针间接修改变量的值 指针变量指向谁 就把谁的地址赋值给指针变量 可以通过 *指针变量 间接修改变量的值 1.3 const修饰的指针变量 语法…

C语言 【函数】

1.函数概述 函数是一种可重用的代码块&#xff0c;用于执行特定任务或完成特定功能 函数作用&#xff1a;对具备相同逻辑的代码进行封装&#xff0c;提高代码的编写效率&#xff0c;实现对代码的重用 2. 函数的使用 2.1 无参无返回值 #include <stdio.h>// 函数名…

【AAAI2024】点云的自适应邻域提取

论文标题&#xff1a;Point Deformable Network with Enhanced Normal Embedding for Point Cloud Analysis 论文地址&#xff1a;https://ojs.aaai.org/index.php/AAAI/article/view/28497 两个创新点&#xff1a;可变邻域法向量提取 一、由固定邻居变为可变的邻域 二、最小二…

让一个元素在网页上跟随网页窗口大小变化始终保持上下左右居中

废话少说&#xff0c;直接上代码&#xff0c;懂的都懂&#xff1a; <!DOCTYPE html> <html style"font-size: 100px;"> <head><meta http-equiv"Content-Type" content"text/html;charsetUTF-8"><style type"te…

搭建第一个Web服务器(在eclipse或idea上部署Tomcat服务器)

&#x1f4bb;博主现有专栏&#xff1a; C51单片机&#xff08;STC89C516&#xff09;&#xff0c;c语言&#xff0c;c&#xff0c;离散数学&#xff0c;算法设计与分析&#xff0c;数据结构&#xff0c;Python&#xff0c;Java基础&#xff0c;MySQL&#xff0c;linux&#xf…

开关电源测试流程有哪些?如何让测试更简单?

NSAT-8000电源综合测试系统适用于AC-DC、DC-DC电源模块的研发和产线测试&#xff0c;为电源模块测试提供自动化测试方案。用该系统测试开关电源&#xff0c;只需以下操作即可完成&#xff1a; 1. 登录测试系统 2. 在方案运行界面找到已搭建好的开关电源测试方案&#xff0c;点击…

Learn SRP 02

3.Editor Rendering 3.1Drawing Legacy Shaders 因为我们的管线只支持无光照的着色过程&#xff0c;使用其他不同的着色过程的对象是不能被渲染的&#xff0c;他们被标记为不可见。尽管这是正确的&#xff0c;但是它还是隐藏了场景中一些使用错误着色器的对象。所以让我们来渲…

java -spring 图灵 03 各种核心组件

01.BeanDefinition 表示Bean定义&#xff0c;BeanDefinition中存在很多属性用来描述一个Bean的特点。比如&#xff1a; class&#xff0c;表示Bean类型 scope&#xff0c;表示Bean作用域&#xff0c;单例或原型等 lazyInit&#xff1a;表示Bean是否是懒加载 initMethodName&am…

postman 调试 传base64字符串 原来选xml

上个图 工具类 package org.springblade.common.utils;import com.alibaba.fastjson.JSONObject; import org.springblade.modules.tc.mas.Submit;import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStrea…

用示例说明序列化和反序列化

用示例说明序列化和反序列化 序列化和反序列化是将数据结构或对象转换为可存储或传输的格式&#xff0c;以便在需要时重新构建原始数据结构或对象的过程。常见的序列化格式包括 JSON、XML 和 Pickle。 序列化&#xff08;Serialization&#xff09;&#xff1a; 在计算机科学…

嵌入式中C++指针使用方法总结

各位开发者大家好,在分享指针之前,先来看一下int *p[3]和int (*p)[3] 的区别。 int *p[3] p是一个数组,此数组有3个元素,每个元素都是int*类型,也就是指向整型数据的指针类型。 int a=10,b=20,c=30; int*p[3]={&a,&b,&c}; 而int(*p)[3]中的p是一个指向数组的…

吐槽一下腾讯云TKE原生节点的降本增效

背景 作为一个10年腾讯云用户我本来是不想吐槽的&#xff0c;往常有问题都是第一时间在用户反馈群里面吐槽一下&#xff0c;这次我是忍不了吐槽了起因就是下面这种图&#xff1a; 恩 tke节点的新建&#xff0c;现在默认首个是原生节点&#xff0c;对没有看错&#xff0c;可以…

操作系统安全

操作系统属于软件安全的范畴。 什么是操作系统&#xff1f; 操作系统是硬件和软件应用程序之间接口的程序模块&#xff0c;是计算机资源的管理者。操作系统是保证安全的重要基础。 一、操作系统安全基础 操作系统保护的对象 操作系统的安全功能 用户认证存储器保护文件与I/O设…

初始监控工具--zabbix和安装

一、Zabbix 1. 监控系统的必要性 作为一个技术人员&#xff0c;需要会使用监控系统查看服务器状态以及网站流量指标&#xff0c;利用监控系统的数据去了解上线发布的结果和网站的健康状态。 2. 监控软件的作用 利用一个优秀的监控软件&#xff0c;我们可以: ● 通过一个友…
最新文章