【计算机网络】TCP实战

news2024/11/25 13:05:58

其实有了UDP的基础,TCP不管怎么说学习起来都还是比较舒服的,至少是比直接就学习TCP的感觉好。

这篇文章最多就是介绍一下起手式,如果想带业务的话和UDP那篇是完全一样的,就不进行演示了。
总的来说还是很简单的。

目录

  • Echo
    • 服务端起手式
    • 服务端LOOP
    • 客户端起手
    • 客户端LOOP
    • 验证
    • 改进
      • 方案一
      • 方案二
    • 验证

Echo

我们还是从最简单的不带业务的Echo开始。

服务端起手式

服务器起手式
首先要说明的一点是,TCP是面向字节流,有连接。
而UDP创建好套接字后不管连接,直接recvfrom,sendto就可以发送。

在TCP编码中会体现出有连接的特点,面向字节流会在理论中提及,代码中实践。


创建套接字
首先与UDP不同的是,UDP的第二个参数是SOCK_DGRAM

_listenfd = ::socket(AF_INET, SOCK_STREAM, 0);

关于这两个参数不同含义更详细的放在下图中了。
在这里插入图片描述
另外,这次使用listenfd接收,以前我们获得的就是sockfd,直接使用这个描述符进行收发,那么叫listenfd肯定是有特别用意。


进行bind
bind没什么好说的,仍旧是要注意填参问题。

int n = ::bind(_listenfd, (struct sockaddr *)&local, len);

进入listen状态

在这里插入图片描述

从这里就可以看到面向连接的痕迹了。
那么什么叫listen状态?
我们举一个小例子:
UDP就是无人售货店,不许用老板看店即可买东西。
但是TCP就是有人售货店,需要老板在才可以买东西,而TCP就是那个有人售货店,listen就是设置为老板模式,这样就可以接客了。

第一个参数就是你得到的listenfd。
第二个参数是连接队列长度,这个值我们暂时先不用管,一般默认设置为4, 8, 16即可,不需要太大。

n = ::listen(_listenfd, gbacklog);

服务端LOOP

accept
来了新的连接我们就要去accept了
换句话说就是在有人售货店中现在如果来客人了老板就需要去接客。

int sockfd = ::accept(_listenfd, (sockaddr *)&peer, &len);

参数是输出型参数,来一个连接就可以获得这个连接的基本信息。
但是要注意填参时一定要正确填参,虽然addrlen是一个输出型参数,但仍然需要正确初始化,不能不初始化。
我就犯了这个错误,导致有时错误(accept或者connect错误,connect在客户端会提到),甚至搞的我以为这是tcp特性…在这里插入图片描述
但是我们注意到返回值是一个文件描述符!?那么他是什么?与listenfd有何区别?在这里插入图片描述我们举一个例子进行理解:
在这里插入图片描述
上图可以形象的帮助理解返回值。
其中饭店 = 服务器
客户 = 新连接
揽客员 = listenfd(socket返回值)
服务员 = sockfd (accept返回值)

listenfd不直接提供服务,sockfd才直接提供服务!
所以在TCP服务端我们一般将socket返回值叫做监听套接字。

没有连接时就会陷入阻塞状态,accept失败返回-1(这里可以理解为揽客员都把路人拉到店里面,但是路人又临时有事离开了,导致拉客失败~)。


下段代码是TCP的框架代码,与UDP有很大的不同。

void Loop()
{
    _isrunning = true;
    while (_isrunning)
    {
        sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int sockfd = ::accept(_listenfd, (sockaddr *)&peer, &len);
        if (sockfd < 0)
        {
            continue;
        }
        // version 0 :提供长服务。
        service(sockfd);
    }
    _isrunning = false;
}

与UDP有很大的不同:
每一次循环都对应着一个新连接,我们随后要对这个新连接进行长服务;
而UDP是不管来的是谁,都统一处理。


读写操作
我们在长服务中进行读写操作。

因为我们是面向字节流,而管道、文件也都是流。
且Linux下一切皆文件,管道、文件都是文件,所以都是可以用文件操作read,write进行读写,所以这里也可以使用文件操作!

void service(int sockfd)
{
    // 长服务
    while (true)
    {
        char inbuffer[1024];
        int n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
        if (n > 0)
        {
            inbuffer[n] = 0;
            int m = write(sockfd, inbuffer, n);
            if (m < 0)
            {
                break;
            }
        }
        else if (n == 0)
        {
            break;
        }
        else
        {
            break;
        }
    }
    ::close(sockfd);
}

但是这里有一个细节要注意:read的返回值>0是表示读到的字节数(无\0),==0时表示读到文件结尾,在这里表示客户端结束。
就想吃在管道那里,写端管了,读端读出的自然都是0了。

客户端起手

socket

int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);

注意客户端仍旧是用socket的返回值进行通信。


connect
同样,客户端还是不用bind,在UDP那是sendto时OS进行绑定,而在这就是connect时帮我们自动绑定。

sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
inet_pton(AF_INET, ip.c_str(), &peer.sin_addr);
peer.sin_port = htons(port);
peer.sin_family = AF_INET;
int n = ::connect(sockfd, (sockaddr *)&peer, sizeof(peer));

客户端LOOP

这里没啥好说的,就是普通的write + read。
但是要注意:由于是面向字节流,所以这里的处理是有问题的!具体如何操作请看用户自定义协议与序列化

while (true)
{
    std::cout << "Please enter#";
    std::string line;
    getline(std::cin, line);
    n = write(sockfd, line.c_str(), line.size());
    if (n < 0)
    {
        break;
    }
    char inbuffer[1024];
    n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
    if (n > 0)
    {
        inbuffer[n] = 0;
        std::cout << inbuffer << std::endl; 
    }
}

完整代码链接

验证

在这里插入图片描述
但是当我们多开一个客户端就会发现:一个服务端只能服务一个客户端。
原因在于我们当前是串行,必须等当前客户端退出才能accept下一个客户端,并没有处理并发客户端的能力。

所以改进就来了

改进

方案一

多进程方案。
我们知道子进程是会继承父进程的代码和数据的,同样的文件描述符表也会继承,因此我们就可以利用这个特点让子进程去执行代码和数据。

在这里插入图片描述
首先我们fork之后就要将不需要的文件描述符关闭
对于子进程来说,是怕误操作;
对于父进程来说,是为了防止文件描述符泄露,因为进程具有独立性,当子进程继承了后,就与父进程相互不影响了。若是父进程不关闭,一直申请fd却步关闭那么就造成了资源泄露。

但是注意,由于父进程要进行等待,所以此时仍然是串行。
想要解决有两种方法

  1. 由于子进程结束时会发送SIGCHLD信号,于是进行信号忽略signal(SIGCHLD, SIG_IGN)另外这也是最佳方案
  2. 下段代码所示:我们进行再次fork,并让子进程退出,所以此时孙子进程成为了孤儿进程,孤儿进程的父进程是OS,结束归OS管,完美的利用了OS的特性。
pid_t pid = fork();
if (pid == 0)
{
    if (fork() > 0) exit(0);
    // 子进程关闭不需要的fd,防止误操作。
    ::close(_listenfd);
    service(sockfd, inetaddr);
    exit(0);
}
waitpid(pid, nullptr, 0);
// 父进程关闭不需要的fd,防止内存泄漏。
::close(sockfd);

方案二

多线程
我们创建一个新线程,让新线程去执行service即可。

验证

此时同时启动两个客户端也可以完美的进行并发了。
在这里插入图片描述

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

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

相关文章

魔方远程时时获取短信内容APP 前端Vue 后端Ruoyi框架(含搭建教程)

前端Vue 后端Ruoyi框架 APP原生JAVA 全兼容至Android14(鸿蒙 澎湃等等) 前后端功能&#xff1a; ①后端可查看用户在线状态(归属地IP) ②发送短信(自定义输入收信号码以及短信内容&#xff0c;带发送记录) ③短信内容分类清晰(接收时间、上传时间等等) ④前后端分离以及A…

Doris与StarRocks

目录 Doris Doris 架构 存储引擎 查询引擎 索引结构 存储模型 物化视图 使用场景 StarRocks 架构设计 架构选择 存算一体 节点 FE BE 存算分离 节点 存储 缓存 适用场景 OLAP 多维分析 实时数据仓库 高并发查询 统一分析 Doris和StarRocks对比 大规模…

Vue3中组件的多种写法

SFC单文件组件&#xff0c;一个vue写一个组件 使用 defineComponent h函数 去进行组件编写 使用 defineComponent JSX/TSX 去进行组件编写 需要安装插件pnpm i vitejs/plugin-vue-jsx -D 引入 配置 使用组件

Android的OkHttp使用和原理

前言 OkHttp的出现代替了HttpUrlConnection&#xff0c;被谷歌官方收纳为底层的网络框架。特点如下&#xff1a; 支持HTTP/2框架下的socket复用通过连接池减少连接的延时使用GZIP进行数据压缩使用缓存技术避免重复请求 当网络出现问题时&#xff0c;OkHttp会静默重新恢复连接…

uniapp组件使用

uni-popup 默认z-index是99 https://uniapp.dcloud.net.cn/component/uniui/uni-popup.html#uni-popup-%E5%BC%B9%E5%87%BA%E5%B1%82 uni-icons uniapp自带图标&#xff1a;https://hellouniapp.dcloud.net.cn/pages/extUI/icons/icons <uni-icons type"left"…

基于JAVA的在线教育系统设计与实现,源码、部署+讲解

摘 要 随着信息化的日益发展&#xff0c;互联网信息技术的发展日新月异。互联网在线教育模式也在不断的被革新。从传统的线下辅导授课&#xff0c;转变成现在的线上教育遍地开花。线上教育已经犹如雨后春笋一般冒芽而出&#xff0c;这为我们的生活带来了许多变动。 基于网络…

江协科技STM32学习笔记(第12章 PWR电源控制)

第12章 PWR电源控制 12.1 PWR电源控制 12.1.1 PWR简介 芯片在3种低功耗模式下&#xff0c;是没法直接再下载程序的。这是因为芯片在睡眠&#xff0c;不会关注调试端口了。解决办法就是&#xff1a;1.按住复位键不动&#xff1b;2.点下载按钮&#xff1b;3.及时从开复位键。这…

怎样使用sudo的时候不需要输入密码?

在Ubuntu等Linux系统下&#xff0c;经常要在个人账户使用sudo命令来执行一些需要root权限的命令&#xff0c;但是需要输入该账户的密码&#xff0c;有时候显得很繁琐&#xff0c; 那么怎样使用sudo的时候不需要输入密码呢&#xff1f; 有如下两种方法&#xff1a; 常规方法1…

颠覆传统 北大新型MoM架构挑战Transformer模型,显著提升计算效率

挑战传统的Transformer模型设计 在深度学习和自然语言处理领域&#xff0c;Transformer模型已经成为一种标准的架构&#xff0c;广泛应用于各种任务中。传统的Transformer模型依赖于一个固定的、按深度排序的层次结构&#xff0c;每一层的输出都作为下一层的输入。这种设计虽然…

Spring Boot集成selenium实现自动化测试

1.什么是selenium&#xff1f; Selenium 是支持web 浏览器自动化的一系列工具和 库的综合项目。 它提供了扩展来模拟用户与浏览器的交互&#xff0c;用于扩展浏览器分配的分发 服务器&#xff0c; 以及用于实现W3C WebDriver 规范 的基础结构&#xff0c; 该规范允许您为所有主…

全国各地认可再+4,美创入选ZJCERT等多省市网络数据安全支撑单位

近一个月以来&#xff0c;美创科技连获多省市认可&#xff0c;相继入选&#xff1a; ZJCERT网络安全应急服务支撑单位 杭州市委网信办网络安全技术服务单位 南通市网络和数据安全技术支撑单位 济南市卫生健康系统网络和数据安全应急技术支撑单位 ZJCERT第三届网络安全应急服…

力扣3148. 矩阵中的最大得分

题目 给你一个由 正整数 组成、大小为 m x n 的矩阵 grid。你可以从矩阵中的任一单元格移动到另一个位于正下方或正右侧的任意单元格&#xff08;不必相邻&#xff09;。从值为 c1 的单元格移动到值为 c2 的单元格的得分为 c2 - c1 。 你可以从 任一 单元格开始&#xff0c;并…

Ubuntu+QT编译QTXlsx库

1.在GitHub上下载QT Xlsx 的源码&#xff0c;网站链接如下&#xff08;需要科学上网&#xff09; https://github.com/dbzhang800/QtXlsxWriter 下载好的内容如下 然后在目录下右击启动终端 输入如下命令 先输入qmake qtxlsx.pro再输入make最后sudo make install 注意&…

医药企业如何选择数字化营销模式

有产品&#xff0c;有市场&#xff0c;便有了窜货这一现象&#xff0c;经销商之间窜货不仅伤害了生产企业的渠道和价格体系&#xff0c;还影响企业的形象&#xff0c;降低了企业品牌的价值。而这一问题的根源就是企业对产品的营销管理信息不对称&#xff0c;而数字化营销被视为…

【常见算法题】斐波那契数列(矩阵快速幂)

一、题目描述 大家都知道斐波那契数列&#xff0c;现在要求输入一个正整数 n &#xff0c;请你输出斐波那契数列的第 n 项。 斐波那契数列满足如下 二、解题思路 2.1 普通处理方式 使用递归直接计算 int fib(int n) {if (n 1 || n 2) return 1;return fib(n - 1) fib(n…

实现信创Linux麦克风摄像头录制(源码,银河麒麟、统信UOS)

随着信创国产化浪潮的来临&#xff0c;在国产操作系统上的应用开发的需求越来越多&#xff0c;其中一个就是需要在银河麒麟或统信UOS上实现录制摄像头视频和麦克风声音&#xff0c;将它们录制成一个mp4文件。那么这个要如何实现了&#xff1f; 一. 技术方案 要完成这些功能&a…

北大研究生公选课资料现已公开,数据库学习秘籍速来get!

为促进基础软件在中国高校的传播&#xff0c;进一步提高在校研究生对基础软件的学习和开发实践能力&#xff0c;拓数派与开源联盟 PG 分会携手合作&#xff0c;走进北京大学&#xff0c;进行了北大软件与微电子学院 2024 年《北京大学 PostgreSQL 内核开发&#xff1a;从入门到…

构建高效沃尔玛自养号测评系统:技术策略与实战指南

搭建沃尔玛自养号测评技术系统是一个涉及多方面技术和资源投入的过程&#xff0c;旨在通过自行构建和掌控测评环境&#xff0c;利用真实国外买家的信息和资料来创建买家账号&#xff0c;模拟真实的购买和评价过程&#xff0c;从而提升商品权重和销量。以下是搭建该系统的主要步…

mysql Ubuntu安装与远程连接配置

一、安装&#xff08;Ubuntu22环境安装mysql8&#xff09; 这里使用Xshell链接Ubuntu和mysql windows进行操作&#xff0c;特别提醒&#xff1a;安装之前建议对Ubuntu快照处理备份&#xff0c;避免安装中出错导致Ubuntu崩溃。 查看是否安装的有可以用指令&#xff1a;ps -ef|…

IOS 05 OC和Swift混合编程

为什么需要使用OC和Swift混合编程&#xff1f; 在真实项目开发过程中&#xff0c;大部分时候我们往往都会使用到OC和Swift混合编程&#xff0c;主要原因如下&#xff1a; 老项目是OC语言实现的&#xff0c;但需要引用Swift的框架&#xff1b;新项目是Swift实现的&#xff0c;…