【计网】基于TCP协议的Echo Server程序实现与多版本测试

目录

前言:

1、InitServer类的实现

1.1. 创建流式套接字

1.2. bind 绑定一个固定的网络地址和端口号

1.3.listen监听机制

1.4.完整代码

2. 循环接收接口与服务接口

2.1.accept函数讲解

讲个商场拉客的故事方便我们理解:

2.2.服务接口实现

3.服务端和客户端的实现

3.1.服务端

3.2.客户端

connect函数讲解:

4.多版本测试运行

4.1.单进程处理

4.2.多进程处理

测试结果:

4.3.多线程处理

代码:​编辑

前言:

我们上一篇文章讲解了利用udp协议实现一个简单的echo_server程序,将客户端的数据在服务端打印出来UDP协议。今天我们来讲解利用TCP协议实现的一个简单的echo_server程序,认识并熟悉各个接口的功能以及如何使用!

1、InitServer类的实现

1.1. 创建流式套接字

socket函数讲解:

  • socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描述符;
  • 应用程序可以像读写文件一样用 read/write 在网络上收发数据;
  • 如果 socket()调用出错则返回-1;
  • 对于 IPv4, family 参数指定为 AF_INET;
  • 对于 TCP 协议,type 参数指定为 SOCK_STREAM, 表示面向流的传输协议
  • protocol 参数的介绍从略,指定为 0 即可。

该部分与udp唯一的区别就是type参数的不同,因为对于TCP协议是面向流的传输协议,所以参数指定为SOCK_STREAM

代码:

1.2. bind 绑定一个固定的网络地址和端口号

  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用 bind 绑定一个固定的网络地址和端口号;
  • bind()成功返回 0,失败返回-1。
  • bind()的作用是将参数 sockfd 和 myaddr 绑定在一起, 使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号;
  • struct sockaddr *是一个通用指针类型,myaddr 参数实际上可以接受多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数 addrlen指定结构体的长度;

注意这部分代码与udp是一模一样的。

代码:

1.3.listen监听机制

listen函数讲解

  • listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是 5)
  • listen()成功返回 0,失败返回-1;

tcp是面向连接的,所以通信之前,必须先建立连接。服务器是被连接的。tcpserver 启动,未来首先要一直等待客户的连接到来,所以需要listen函数进行监听。

代码:

1.4.完整代码

void InitServer(){// 1. 创建流式套接字_listensock = ::socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){LOG(FATAL, "socket error");exit(SOCKET_ERROR);}LOG(DEBUG, "socket create success, sockfd is : %d\n", _listensock);// 2. bindstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;int n = ::bind(_listensock, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind error");exit(BIND_ERROR);}LOG(DEBUG, "bind success, sockfd is : %d\n", _listensock);// 3. tcp是面向连接的,所以通信之前,必须先建立连接。服务器是被连接的//    tcpserver 启动,未来首先要一直等待客户的连接到来n = ::listen(_listensock, gbacklog);if (n < 0){LOG(FATAL, "listen error");exit(LISTEN_ERROR);}LOG(DEBUG, "listen success, sockfd is : %d\n", _listensock);}

2. 循环接收接口与服务接口

Loop()循环循环接收接口需要:

  1. 不断从套接字文件中accept获取连接流与客户端信息!
  2. 获取成功后,就可以进行服务了
  3. 服务就是从流中读取数据,然后处理之后再写回流中!!!使用的接口是read与write,文件流中我们对他们很熟悉!!!

2.1.accept函数讲解

  • 三次握手完成后, 服务器调用 accept()接受连接;
  • 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr 是一个传出参数,accept()返回时传出客户端的地址和端口号;
  • 如果给 addr 参数传 NULL,表示不关心客户端的地址;

accept返回的套接字和传入的参数套接字到底有什么关系?

传入的套接字这个参数不是真正参与通信的,只是用来建立连接的。我们真正用来与客户端通信的是返回的套接字

讲个商场拉客的故事方便我们理解:

就比如我们在商场逛街,很多门面前面有拉客的,拉客的人在马路上拉客,然后接到客人之后,会从店里再喊一个服务员用来服务新拉过来的客人。因此拉客的人就是我们的参数,而返回值的套接字才是真正服务我们的,也就是说如果有多个客人,就会有很多个返回值的套接字,分别用来服务。

部分代码:

2.2.服务接口实现

服务就是从流中读取数据,然后处理之后再写回流中。使用的接口是read与write。

void Service(int sockfd, InetAddr client){LOG(DEBUG, "get a new link, info %s:%d, fd : %d\n", client.Ip().c_str(), client.Port(), sockfd);std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.Port()) + "]# ";while (true){char inbuffer[1024];ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);if (n > 0){inbuffer[n] = 0;std::cout << clientaddr << inbuffer << std::endl;std::string echo_string = "[server echo]# ";echo_string += inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0){// client 退出&&关闭连接了LOG(INFO, "%s quit\n", clientaddr.c_str());break;}else{LOG(ERROR, "read error\n", clientaddr.c_str());break;}}std::cout << "server开始退出" << std::endl;shutdown(sockfd, SHUT_RD);std::cout << "shut _ rd " << std::endl;sleep(10);//shutdown(sockfd, SHUT_WR);//std::cout << "shut _ wr " << std::endl;//::close(sockfd); // 文件描述符泄漏}

3.服务端和客户端的实现

3.1.服务端

服务端简单的创建一个服务器类然后进行初始化和loop就可以了。

代码:

#include "TcpServer.hpp"
#include <memory>void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;
}// ./tcpserver port
int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);return 1;}EnableScreen();uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0;
}

3.2.客户端

  1. 首先根据传入的参数进行初始化服务器IP地址和端口号
  2. 然后创建套接字文件 ,并进行connect连接绑定bind,客户端回被动绑定一个端口号。
  3. 绑定成功之后就可以通过sockfd进行写入与读取了。

connect函数讲解:

  • 客户端需要调用 connect()连接服务器;
  • connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而connect 的参数是对方的地址;
  • connect()成功返回 0,出错返回-1;

代码:

#include <iostream>
#include <string>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}// ./tcp_client serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;exit(2);}// tcp client 要bind,不要显示的bind.struct sockaddr_in server;// 构建目标主机的socket信息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 error" << std::endl;exit(3);}while(true){std::cout << "Please Enter# ";std::string outstring;std::getline(std::cin, outstring);ssize_t s = send(sockfd, outstring.c_str(), outstring.size(), 0); //writeif(s > 0){char inbuffer[1024];ssize_t m = recv(sockfd, inbuffer, sizeof(inbuffer)-1, 0);if(m > 0){inbuffer[m] = 0;std::cout << inbuffer<< std::endl;}else{break;}}else{break;}}shutdown(sockfd, SHUT_WR);//::close(sockfd);return 0;
}

4.多版本测试运行

4.1.单进程处理

一次只能处理一个请求,显然这个版本是很废的,根本不满足用户需求,直接跳过

测试结果:

4.2.多进程处理

子进程会继承父进程的文件描述符表,所以子进程一定会看到父进程之前创建的sockfd,父子进程的文件描述符表是独立的,子进程会拷贝父进程的那一份,线程才会共享

  • 对于子进程:关心sockfd, 不关心listensock,所以对于子进程需要关闭listensock
  • 对于父进程:关心listensock,不关心sockfd,所以对于父进程需要关闭sockfd

为什么父进程要关闭sockfd,不然一直创建会导致sockfd一直减少,浪费资源

子进程还会创建子进程,就是孙子进程,我们不进行任何处理,那么这个孙子进程就变成了孤儿进程,系统自己领养进行运行,而父进程仍在源源不断的创建新的子进程,那么这个版本服务器就可以并发处理了!

代码:

            // Version 1: 采用多进程pid_t id = fork();if (id == 0){// child : 关心sockfd, 不关心listensock::close(_listensock); // 建议if(fork() > 0) exit(0); Service(sockfd, InetAddr(peer)); //孙子进程 -- 孤儿进程 --- 系统领养exit(0);}// father: 关心listensock,不关心sockfd::close(sockfd);waitpid(id, nullptr, 0);

测试结果:

测试成功!但是我们知道切换进程时,CPU会切换上下文和热点数据。在并发场景下多进程的不断切换会消耗大量的性能!

而作为轻量级进程的线程就可以避免这样的问题!

4.3.多线程处理

我们采用线程分离的方法来实现并发处理!

多线程禁止关闭文件描述符!因为多线程是共享文件描述符表的,如果直接将sockfd关掉了,那么先创建的线程可能无法通过文件描述符来读取数据。

在服务器编程中,为了处理大量的客户端请求,可以采用多线程技术来提高服务器的并发性能。在这种情况下,线程分离可以确保每个子线程在结束后自动释放资源,从而避免资源泄露,提高服务器的稳定性和性能

代码:

测试结果:非常nice!

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

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

相关文章

easyexcel实现自定义的策略类, 最后追加错误提示列, 自适应列宽,自动合并重复单元格, 美化表头

easyexcel实现自定义的策略类, 最后追加错误提示列, 自适应列宽,自动合并重复单元格, 美化表头 原版表头和表体字体美化自动拼接错误提示列自适应宽度自动合并单元格使用Easyexcel使用poi导出 在后台管理开发的工作中,离不开的就是导出excel了. 如果是简单的导出, 直接easyexce…

边缘计算的学习

文章目录 概要何为边缘计算&#xff1f;现阶段&#xff0c;企业使用边缘计算相对云计算 整体架构流程边缘网络组件边缘计算与云安全 研究方向结合引用 概要 edge 何为边缘计算&#xff1f; 边缘计算&#xff08;英语&#xff1a;Edge computing&#xff09;&#xff0c;是一种…

SpringBoot在城镇保障性住房管理中的应用

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理城镇保障性住房管理系统的相关信息成为必然…

算法|牛客网华为机试41-52C++

牛客网华为机试 上篇&#xff1a;算法|牛客网华为机试21-30C 文章目录 HJ41 称砝码HJ42 学英语HJ43 迷宫问题HJ44 SudokuHJ45 名字的漂亮度HJ46 截取字符串HJ48 从单向链表中删除指定值的节点HJ50 四则运算HJ51 输出单向链表中倒数第k个结点HJ52 计算字符串的编辑距离 HJ41 称砝…

粒子群优化双向深度学习!PSO-BiTCN-BiGRU-Attention多输入单输出回归预测

粒子群优化双向深度学习&#xff01;PSO-BiTCN-BiGRU-Attention多输入单输出回归预测 目录 粒子群优化双向深度学习&#xff01;PSO-BiTCN-BiGRU-Attention多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现PSO-BiTCN-BiGRU-Attention粒子…

「Mac玩转仓颉内测版1」入门篇1 - Cangjie环境的搭建

本篇详细介绍在Mac系统上快速搭建Cangjie开发环境的步骤&#xff0c;涵盖VSCode的下载与安装、Cangjie插件的离线安装、工具链的配置及验证。通过这些步骤&#xff0c;确保开发环境配置完成&#xff0c;为Cangjie项目开发提供稳定的基础支持。 关键词 Cangjie开发环境搭建VSC…

协程6 --- HOOK

文章目录 HOOK 概述链接运行时动态链接 linux上的常见HOOK方式修改函数指针用户态动态库拦截getpidmalloc 第一版malloc 第二版malloc/free通过指针获取到空间大小malloc 第三版strncmp 内核态系统调用拦截堆栈式文件系统 协程的HOOK HOOK 概述 原理&#xff1a;修改符号指向 …

ResNet 残差网络 (乘法→加法的思想 - 残差连接是所有前沿模型的标配) + 代码实现 ——笔记2.16《动手学深度学习》

目录 前言 0. 乘法变加法的思想 1. 函数类 2. 残差块 (讲解代码) QA: 残差这个概念的体现&#xff1f; 3. ResNet模型 (代码讲解) 补充&#xff1a;更多版本的ResNet 4. 训练模型 5. 小结 6. ResNet的两大卖点 6.1 加深模型可以退化为浅层模型 6.2 用加法解决梯度消…

iphone怎么删除重复的照片的新策略

Phone用户常常面临存储空间不足的问题&#xff0c;其中一个主要原因是相册中的重复照片。这些重复项不仅占用了大量的存储空间&#xff0c;还会影响设备的整体性能。本文将向您展示iphone怎么删除重复的照片的方法&#xff0c;包括一些利用工具来自动化这个过程的创新方法。 识…

软件缺陷等级评定综述

1. 前言 正确评估软件缺陷等级&#xff0c;在项目的生命周期中有着重要的作用&#xff1a; 指导缺陷修复的优先级和资源分配 在软件开发和维护过程中&#xff0c;资源&#xff08;包括人力、时间和资金&#xff09;是有限的。通过明确缺陷的危险等级&#xff0c;可以帮助团队合…

【Pikachu】Cross-Site Scripting跨站脚本攻击实战

只管把目标定在高峰&#xff0c;人家要笑就让他去笑&#xff01; 1.XSS&#xff08;跨站脚本&#xff09;概述 XSS&#xff08;跨站脚本&#xff09;概述 Cross-Site Scripting 简称为“CSS”&#xff0c;为避免与前端叠成样式表的缩写"CSS"冲突&#xff0c;故又称…

【SpringBoot】 黑马大事件笔记-day2

目录 用户部分 实体类属性的参数校验 更新用户密码 文章部分 规定josn日期输出格式 分组校验 上期回顾&#xff1a;【SpringBoot】 黑马大事件笔记-day1 用户部分 实体类属性的参数校验 对应的接口文档&#xff1a; 基本信息 请求路径&#xff1a;/user/update 请求方式&#…

大数据面试题--kafka夺命连环问

1、kafka消息发送的流程&#xff1f; 在消息发送过程中涉及到两个线程&#xff1a;一个是 main 线程和一个 sender 线程。在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给双端队列&#xff0c;sender 线程不断从双端队列 RecordAccumulator 中拉取…

QT信号和槽与自定义的信号和槽

QT信号和槽与自定义的信号和槽 1.概述 这篇文章介绍下QT信号和槽的入门知识&#xff0c;通过一个案例介绍如何创建信号和槽&#xff0c;并调用他们。 2.信号和槽使用 下面通过点击按钮关闭窗口的案例介绍如何使用信号和槽。 创建按钮 在widget.cpp文件中创建按钮代码如下 …

unity显示获取 年月日周几【日期】

unity显示获取 年月日周几【日期】 public void ShowDate(Text txt){//txt.text DateTime now DateTime.Now; // 获取当前时间int year now.Year; // 获取年份int month now.Month; // 获取月份&#xff08;1-12&#xff09;int day now.Day; // 获取天数&#xff08;1-31&…

emr上使用sparkrunner运行beam数据流水线

参考资料 https://time.geekbang.org/column/intro/167?tabcatalog Apache Beam和其他开源项目不太一样&#xff0c;它并不是一个数据处理平台&#xff0c;本身也无法对数据进行处理。Beam所提供的是一个统一的编程模型思想&#xff0c;而我们可以通过这个统一出来的接口来编…

AUTOSAR CP SocketAdaptor(SoAd)规范导读

《AUTOSAR_SWS_SocketAdaptor》规范的主要内容包括&#xff1a; 简介和功能概述&#xff1a;说明了 AUTOSAR 基本软件模块 Socket Adaptor&#xff08;SoAd&#xff09;的功能、API 和配置。数据传输的 TCP/IP 概念在计算和电信环境中已成为标准&#xff0c;应用程序的寻址等…

代码随想录-栈和队列-用栈实现队列

问题描述 题目描述中有说不存在空栈的pop和peek&#xff0c;所以无需判断这个 解析 重点在于思路&#xff0c;代码白给。 要用栈实现队列&#xff0c;肯定是两个栈才可以。一个做入队操作&#xff0c;一个做出队操作。 首先入队简单&#xff0c;往栈里加就完事了。 出队复…

【设计模式】结构型模式(四):组合模式、享元模式

《设计模式之结构型模式》系列&#xff0c;共包含以下文章&#xff1a; 结构型模式&#xff08;一&#xff09;&#xff1a;适配器模式、装饰器模式结构型模式&#xff08;二&#xff09;&#xff1a;代理模式结构型模式&#xff08;三&#xff09;&#xff1a;桥接模式、外观…

轻型民用无人驾驶航空器安全操控------理论考试多旋翼部分笔记

官网&#xff1a;民用无人驾驶航空器综合管理平台 (caac.gov.cn) 说明&#xff1a;一是法规部分&#xff1b;二是多旋翼部分 本笔记全部来源于轻型民用无人驾驶航空器安全操控视频讲解平台 目录 官网&#xff1a;民用无人驾驶航空器综合管理平台 (caac.gov.cn) 一、轻型民用无人…