【计网】从零开始使用TCP进行socket编程 --- 客户端与服务端的通信实现

在这里插入图片描述

阵雨后放晴的天空中,
出现的彩虹很快便会消失。
而人心中的彩虹却永不会消失。
--- 太宰治 《斜阳》---

从零开始使用TCP进行socket编程

  • 1 TCP与UDP
  • 2 TCP服务器类
    • 2.1 TCP基础知识
    • 2.2 整体框架设计
    • 2.3 初始化接口
    • 2.4 循环接收接口与服务接口
  • 3 服务端与客户端
  • 测试运行

1 TCP与UDP

我们之前实现了UDP协议下的客户端与服务端的通信。

UDP(用户数据报协议)和TCP(传输控制协议)都是网络通信中常用的传输层协议,它们在数据传输的方式和特性上存在以下特点:

TCP
  1. TCP 是面向连接的协议,意味着在数据传输之前,必须先建立一个连接,完成握手过程。这个连接在数据传输结束之后需要被断开。
  2. TCP 提供了可靠的服务。它确保数据包的顺序传输,并且通过确认(ACK)和重传机制保证数据的可靠性。
  3. TCP 因为需要建立连接、保证数据顺序和可靠性,所以传输速度相对较慢。
  4. TCP 将数据视为一个连续的数据流,确保数据按照发送的顺序到达。
  5. TCP 适用于要求高可靠性的应用,如网页浏览、文件传输(FTP)、电子邮件(SMTP)等。
  6. TCP 头部较大,因为它需要包含更多的信息来管理连接状态和保证数据的可靠性。
UDP
  1. UDP 是无连接的,它发送数据之前不需要建立连接,每个数据报文都是一个独立的信息传输单位。
  2. UDP 不保证数据包的顺序,也不保证数据包的可靠性。如果数据在传输过程中丢失,UDP不会进行重传。
  3. UDP 由于无需建立连接和保证可靠性,通常用于对实时性要求较高的应用,如视频会议和在线游戏,传输速度较快。
  4. UDP 将数据视为独立的、离散的数据包(datagrams),每个数据包独立处理,可能以不同的顺序到达。
  5. UDP 头部较小,处理起来更为高效。
  6. UDP 适用于实时性要求高的应用,如流媒体、实时视频会议(VoIP)、在线游戏等。

通俗理解的话:TCP的传输过程类似管道,数据从一端发送,然后在另一端按顺序接收。UDP传输数据的过程类似送快递,数据报文会一股脑包装在一起发送给接收者!

2 TCP服务器类

2.1 TCP基础知识

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

2.2 整体框架设计

下面我们就来设计一下TCP协议下的服务器类:

  1. 成员变量需要整体通信的_listensockfd和端口号_port,后续绑定网络通信接口,从中读取连接流。
  2. 初始化接口InitServer:对端口号进行绑定,将网络通信接口设置为"接听"模式,可以获取外部的链接。
  3. 循环读取接口Loop:从网络通信接口获取连接流与发送者的信息,之后进行数据接收。
  4. 服务端口Service:根据获取的连接流和发送者的信息开始读取接收数据
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include <memory>
#include <string>
#include <cstring>
#include <iostream>
#include <functional>
#include <unistd.h>#include "Log.hpp"
#include "InetAddr.hpp"using namespace log_ns;
//基础信息
const int gport = 8888;
const int gblocklog = 8;
//错误码
enum
{SOCKET_FD = 1,SOCKET_BIND,SOCKET_LISTNE
};class TcpServer
{
public:TcpServer(int port = gport) : _port(port),_listensockfd(-1),_isrunning(false){}// 进行初始化void InitServer(){   }void Loop(){}void Service(int sockfd, InetAddr addr){}~TcpServer(){}private:uint16_t _port;    // 服务器端口int _listensockfd; // 链接文件bool _isrunning;
};

这就是基础的框架。

2.3 初始化接口

InitServer()初始化接口进行的工作很好理解:

  1. 首先创建socket文件,获取到_listensockfd
  2. 然后将服务器结构体的成员进行初始化,将服务器端口与_listensockfd进行绑定
  3. 最后将_listensockfd通过listen函数进入监听状态。

初始化任务就完成了

// 进行初始化void InitServer(){// 创建socket文件 --- 字节流方式_listensockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){LOG(FATAL, "socket error!!!\n");exit(SOCKET_FD);}LOG(INFO, "socket create success!!! _listensockfd: %d\n", _listensockfd);// 建立server结构体struct sockaddr_in local;memset(&local , 0 , sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY; // 服务器IP一般设置为0local.sin_port = htons(_port); //一定注意主机序列转网络序列// 进行绑定if (::bind(_listensockfd, (struct sockaddr *)&local, sizeof(local)) < 0){LOG(FATAL, "bind error!!!\n");exit(SOCKET_BIND);}LOG(INFO, "bind success!!!\n");// 将_listensockfd文件转换为listening状态!!!if (::listen(_listensockfd, gblocklog) < 0){LOG(FATAL, "listen error!!!\n");exit(SOCKET_LISTNE);}LOG(INFO, "listen success!!!\n");}

2.4 循环接收接口与服务接口

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

  1. 不断从套接字文件中accept获取连接流与客户端信息!
  2. 获取成功后,就可以进行服务了
  3. 服务就是从流中读取数据,然后处理之后再写回流中!!!使用的接口是read与write,文件流中我们对他们很熟悉!!!
void Loop(){_isrunning = true;while (_isrunning){// accept接收sockfdstruct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = ::accept(_listensockfd, (struct sockaddr *)&client, &len);if(sockfd < 0){LOG(WARNING, "accept error\n");continue;}InetAddr addr(client);// 读取数据LOG(INFO, "get a new link, client info : %s, sockfd is : %d\n", addr.AddrStr().c_str(), sockfd);// version 0 --- 不靠谱版本Service(sockfd, addr);}_isrunning = false;}
void Service(int sockfd, InetAddr addr){LOG(INFO , "service start!!!\n");while (true){char buffer[1024];ssize_t n = ::read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;LOG(INFO , "sockfd read success!!! buffer: %s\n" , buffer);std::string str = "[server echo]#";str += buffer; write(sockfd, str.c_str(), str.size());}else if(n == 0){LOG(INFO , "client %s quit!\n" , addr.AddrStr().c_str());break;}else{LOG(ERROR, "read error: %s\n", addr.AddrStr().c_str());break;}}::close(sockfd);}

这样基础的服务器的通信工作就写好了

3 服务端与客户端

接下来我们来完善一下服务端和客户端的通信逻辑,让他们可以通信起来

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

#include "TcpServer.hpp"int main(int argc , char* argv[])
{if(argc != 2){std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0;
}

客户端稍微复杂一些:

  1. 首先根据传入的参数进行初始化服务器IP地址和端口号
  2. 然后创建套接字文件 ,并进行connect连接绑定bind,客户端回被动绑定一个端口号!!!
  3. 绑定成功之后就可以通过sockfd进行写入与读取了!!!
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>#include <memory>
#include <string>
#include <cstring>
#include <iostream>#include "Log.hpp"using namespace log_ns;int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;exit(0);}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);// 创建socket文件int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){LOG(FATAL, "sockfd create error!!!\n");exit(1);}struct sockaddr_in server;memset(&server, 0, sizeof(server)); // 数据归零server.sin_family = AF_INET;server.sin_port = htons(port); // 端口号 主机序列转网络序列!!!::inet_pton(AF_INET, ip.c_str(), &server.sin_addr);//安全写入// 进行发送数据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 line;std::cout << "Please Enter: ";std::getline(std::cin, line);::write(sockfd, line.c_str(), line.size());LOG(DEBUG , "write success !!!\n");// 读取数据char buffer[1024];int n = read(sockfd, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;std::cout << buffer << std::endl;}else{break;}}::close(sockfd);return 0;
}

测试运行

我们来测试一下服务端和客户端是否可以做到通信:
在这里插入图片描述
很好,可以完美的进行通信!!!

之后我们就可以加入多线程,加入回调函数逻辑,就可以进行业务处理了!!!

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

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

相关文章

JS落叶动画代码分析

秋天到了&#xff0c;秋高气爽的季节。我们来做一个落叶动画吧&#xff01;来迎接秋天的到来 文字可以更换。 1.目录如下 在线演示&#xff1a;点击我在线演示 images两张照片&#xff0c;首先&#xff0c;你得要准备一个vscode编辑器。和一个chorme浏览器或edge浏览器。 …

PyTorch 激活函数及非线性变换详解

激活函数是深度学习模型的重要组成部分&#xff0c;它们引入非线性&#xff0c;从而使模型能够更好地拟合复杂的数据模式。本文将详细介绍激活函数的作用、常见类型、经典应用示例&#xff0c;并比较它们的优缺点。 激活函数的作用 激活函数的主要作用是引入非线性变换&#…

理解高并发

文章目录 1、如何理解高并发2、高并发的关键指标3、高并发系统设计的目标是什么&#xff1f;1_宏观目标2_微观目标1.性能指标2.可用性指标3.可扩展性指标 4、高并发的实践方案有哪些&#xff1f;1_通用的设计方法1.纵向扩展&#xff08;scale-up&#xff09;2.横向扩展&#xf…

ROS组合导航笔记2:使用外部定位系统

在上一单元中&#xff0c;我们了解了如何合并不同传感器的数据以生成机器人的姿势估计。因此&#xff0c;基本上&#xff0c;我们介绍了图表的以下部分&#xff0c;其中向 robot_localization 节点提供了不同的传感器&#xff0c;以便通过卡尔曼滤波器进行合并。 但是...图表的…

背包问题 总结详解

就是感觉之前 dp 的 blog 太乱了整理一下。 0-1 背包 例题:P1048 朴素算法 思路 对于一个物品&#xff0c;我们可以选&#xff0c;也可以不选。 我们用表示第 i 件物品的重量&#xff0c;表示第 i 件物品的价值。 考虑表示前 i 件物品放入容量为j的背包中的最大价值。 如…

【图像匹配】基于Harris算法的图像匹配,matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于基于Harris算法的图像匹配&#xff0c;用matlab实现。 一、案例背景和算法介绍 …

Observability:日志管理的最佳实践 - 利用日志更快地解决问题

作者&#xff1a;来自 Elastic Luca Wintergerst•David Hope•Bahubali Shetti 在当今快速发展的软件开发环境中&#xff0c;高效的日志管理对于维护系统可靠性和性能至关重要。随着基础架构和应用程序组件的不断扩展和复杂化&#xff0c;运营和开发团队的职责也不断增加且越来…

yolov8区域入侵检测警报系统-pyside6可视化界面

yolov8区域入侵检测警报系统&#xff0c;是微智启软件工作室基于yolov8目标追踪和pyside6开发&#xff0c;在window的pycharm或者vscode里运行&#xff0c;可以应用于多个领域&#xff0c;检测统计物体个数以及入侵语音警报。 功能介绍 可以应用于 江河流域危险区域禁止游泳警…

利用AI技术提升ISP处理:图像质量的四大关键模块

随着智能手机和数码相机的飞速发展&#xff0c;图像质量成为了影响用户体验的关键因素之一。图像信号处理&#xff08;ISP&#xff0c;Image Signal Processing&#xff09;管道是将图像传感器捕捉到的原始数据转化为高质量输出的核心技术。然而&#xff0c;传统的ISP处理方法在…

螺丝、螺母、垫片等紧固件常用类型详细介绍

螺钉、螺母、垫片等紧固件介绍 螺钉 杯头内六角 首先介绍一下杯头内六角&#xff0c;杯头内六角是我们用的最常见的一种螺钉&#xff0c;如果你对选择螺钉没有什么想法&#xff0c;可以直接无脑选杯头内六角去使用。 比如说我们有一个零件加工了通孔&#xff0c;另一个零件加…

vmware,centos8(虚拟机) 的安装

安装vmware 点击下方网址 虚拟机安装地址https://www1.msc23.cn/vm/?bd_vid8829610582362807097选择VMware17 打开文件所在地&#xff0c;双击安装 同意条款 选择安装位置 不将VMware配置到环境变量path 不检查更新,不加入客户体验 创建桌面快捷方式 开始安装 安装完成…

CSP-J/S 考试介绍

CSP-J/S是由中国计算机学会&#xff08;CCF&#xff09;主办的非专业级别的软件能力认证考试。 CSP-J/S全称为CCF CSP-J/S&#xff0c;是CCF计算机软件能力认证&#xff08;简称CCFCSP认证&#xff09;中的一个部分&#xff0c;重点考察软件开发者实际编程能力。该项认证由CCF…

MTC完成右臂抓取放置任务\\放置姿态设置

#include "mtc_tutorial/mtc_glass_bottle.hpp" static const rclcpp::Logger LOGGER rclcpp::get_logger("mtc_glass_right"); // 获取节点基础接口的实现 rclcpp::node_interfaces::NodeBaseInterface::SharedPtr MTCTaskNode_Right::getNodeBaseInterf…

棋盘格角点检测-libcbdetect

libcbdetect libcbdetect 是一个用于自动子像素级别的棋盘格&#xff08;checkerboard&#xff09;、棋盘&#xff08;chessboard&#xff09;以及 Deltille 图案检测的库。它主要由 C 编写&#xff0c;旨在提供高精度、高鲁棒性的角点检测和图案组合功能&#xff0c;是一种基…

使用HTML和CSS制作网页的全面指南

目录 引言 一、理解HTML 1. 什么是HTML&#xff1f; 2. HTML文档的基本结构 3. 常用的HTML标签 4. 示例&#xff1a;创建一个简单的HTML页面 二、理解CSS 1. 什么是CSS&#xff1f; 2. CSS的使用方式 3. CSS选择器和属性 4. 常用的CSS属性 三、创建网页的步骤 1. 规…

【Java数据结构】二叉树

目录 树树的特征树的概念 二叉树两种特殊的二叉树二叉树的性质二叉树的基本操作4 种遍历二叉树的方式判断一棵树是不是完全二叉树获取二叉树总共的节点个数获取叶子节点的个数获取第 k 层的节点个数获取二叉树的高度检测值为 value 的元素是否存在 二叉树基本操作完整代码 树 …

VS code 安装使用配置 Continue

Continue 插件介绍 Continue 是一款高效的 VS Code 插件&#xff0c;提供类似 GitHub Copilot 的功能&#xff0c;旨在提升开发者的编程效率。其配置简单&#xff0c;使用体验流畅&#xff0c;深受开发者喜爱。 主要功能特点 智能代码补全 Continue 能够基于当前代码上下文生…

年化60.7%,最大回撤-16.5%,RSRS标准分择时效果差不多

原创内容第653篇&#xff0c;专注量化投资、个人成长与财富自由。 中秋节&#xff0c;祝大家中秋快乐&#xff01; 人有悲欢离合&#xff0c;月有阴晴圆缺&#xff0c;此事古难全。但愿人长久&#xff0c;千里共婵娟。 今天引入RSRS来择时&#xff0c;看下策略效果。 年化60.7…

Python编码系列—Python代理模式:为对象赋予超能力的魔法

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

C++掉血迷宫

目录 开头程序程序的流程图程序游玩的效果下一篇博客要说的东西 开头 大家好&#xff0c;我叫这是我58。 程序 #include <iostream> #include <string> #include <cstring> using namespace std; enum RBYG {R 1,B 2,Y 4,G 7, }; struct heal {int ix…