【Linux后端服务器开发】TCP通信设计

目录

一、TCP通信协议的封装

二、TCP多进程通信

三、TCP多线程通信


一、TCP通信协议的封装

简单的TCP一对一通信其实完全可以不进行封装,直接分别写server端和client端的源代码,按照TCP通信协议的规定调用socket接口即可完成,但是在通过TCP协议设计应用层协议的时候,将TCP协议进行封装并且将协议与服务任务进行解耦,是更方便编程和维护的。

对server服务端封装,我们只需要指明服务器的端口号,服务器IP是本机IP,服务器可以接收的IP是任何主机的IP。

对client客户端封装,我们需要指明服务器的ip和端口号,通过服务器的ip和端口号信息,构建sockaddr_in结构体,然后通过sockaddr_in结构体对服务器发起连接请求。

server服务器的类分为两个阶段:

  • 第一个阶段是Init()初始化阶段,创建socket套接字、bind绑定本地网络信息、listen设置监听
  • 第二阶段是Start()进行服务阶段,accept连接客户端、服务任务,在本次封装中,服务任务是简单一对一通信,故我直接将任务代码写在了Start()函数里面,在任务复杂的时候,我们可以再写一个Task()任务阶段,将执行服务和服务任务的具体内容进行解耦

client客户端也分为两个阶段:

  • 第一个阶段是Init()初始化阶段,创建socket套接字,不需要调用bind()函数,由os绑定
  • 第二个阶段是Run()运行阶段,通过服务器的sockaddr_in信息向服务器发起connect请求,连接成功后即可直接进行通信

在网络通信中,需要调用socket套接字的接口完成,这些接口的头文件是:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

Server.h头文件:

#pragma once#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <functional>
#include <cerrno>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;const string g_default_ip = "0.0.0.0";class TcpServer
{
public:TcpServer(const uint16_t port, const string& ip = g_default_ip): _port(port), _ip(ip), _listenfd(0){}void Init(){// 1. 创建socket套接字_listenfd = socket(AF_INET, SOCK_STREAM, 0);if (_listenfd < 0){cerr << "socket error " << errno << ": " << strerror(errno) << endl;exit(1);}// 2. bind绑定服务器网络信息struct sockaddr_in local;local.sin_family = AF_INET;local.sin_addr.s_addr = htonl(INADDR_ANY);local.sin_port = htons(_port);if (bind(_listenfd, (struct sockaddr*)&local, sizeof(local)) < 0){cerr << "bind error " << errno << ": " << strerror(errno) << endl;exit(1);}// 3. listen设置监听if (listen(_listenfd, 8) < 0){// 监听的连接队列长度与项目的线程数相关cerr << "listen error " << errno << ": " << strerror(errno) << endl;exit(1);}}void Start(){// 4. accept连接客户端struct sockaddr_in client;socklen_t client_len = sizeof(client);int client_sock = accept(_listenfd, (struct sockaddr*)&client, &client_len);if (client_sock < 0){cerr << "accept error " << errno << ": " << strerror(errno) << endl;exit(1);}// 5. 连接成功,进行通信string client_ip = inet_ntoa(client.sin_addr);uint16_t client_port = ntohs(client.sin_port);while (true){// 5.1 接收信息char recv_buf[1024];int n = read(client_sock, recv_buf, sizeof(recv_buf));if (n > 0)recv_buf[n] = 0;cout << "[" << client_ip << ":" << client_port << "]# " << recv_buf << endl;// 5.2 应答信息char sent_buf[1024];snprintf(sent_buf, sizeof(sent_buf), "服务器已收到信息: %s\n", recv_buf);write(client_sock, sent_buf, sizeof(sent_buf));}}private:string _ip;uint16_t _port;int _listenfd;
};

server.cpp源文件

#include "Server.h"
#include <memory>void Usage()
{cout << "Usage:\n\tserver port" << endl;exit(1);
}int main(int args, char* argv[])
{if (args != 2)Usage();uint16_t port = atoi(argv[1]);unique_ptr<TcpServer> tcp_server(new TcpServer(port));tcp_server->Init();tcp_server->Start();return 0;
}

Client.h头文件:

#pragma once#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <functional>
#include <cerrno>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;class TcpClient
{
public:TcpClient(const uint16_t server_port, const string server_ip): _server_port(server_port), _server_ip(server_ip), _sock(-1){}void Init(){// 1. 创建套接字_sock = socket(AF_INET, SOCK_STREAM, 0);if (_sock < 0){cerr << "socket error " << errno << ": " << strerror(errno) << endl;exit(1);}// 2. bind绑定,由OS绑定}void Run(){// 3. 向服务器发起连接请求struct sockaddr_in server;server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(_server_ip.c_str());server.sin_port = htons(_server_port);if (connect(_sock, (struct sockaddr*)&server, sizeof(server)) != 0){cerr << "connect error " << errno << ": " << strerror(errno) << endl;exit(1);}// 4. 连接成功,进行通信while (true){// 4.1 发送信息char sent_buf[1024];cout << "请输入信息:";gets(sent_buf);write(_sock, sent_buf, sizeof(sent_buf));// 4.2 接收应答信息char recv_buf[1024];int n = read(_sock, recv_buf, sizeof(recv_buf));if (n > 0)recv_buf[n] = 0;cout << recv_buf << endl;}}private:string _server_ip;uint16_t _server_port;int _sock;
};

client.cpp源文件:

#include "Client.h"
#include <memory>void Usage()
{cout << "Usage:\n\tclient ip port" << endl;exit(1);
}int main(int args, char* argv[])
{if (args != 3)Usage();string server_ip = argv[1];uint16_t server_port = atoi(argv[2]);unique_ptr<TcpClient> tcp_client(new TcpClient(server_port, server_ip));tcp_client->Init();tcp_client->Run();return 0;
}

二、TCP多进程通信

上面对于TCP的封装是实现了一对一的服务器与客户端通信,通信断开代码就运行结束。

但是在业务中,一个服务器通常是需要与多个客户端通信的,并且客户端断开通信时并不会影响服务端的运行。

为了实现一对多的服务,服务器通常会采用多进程或者多线程策略。

多进程相比于多线程,资源开销更大,但是更安全,因为进程之间是完全相互独立的。

要将上面的TCP封装修改成多进程版,我们只需要将Server.h中封装TcpServer类的Start()函数做出修改即可,将其改为可以循环accept接受不同的client连接,每新建一个连接,就创建一个子进程去完成通信任务,然后主进程继续accept等待之后的client连接。

为了使多进程接受连接与任务代码解耦,我们在TcpServer类中新建一个Task()函数。

    void Start(){while (true){// 4. accept连接客户端struct sockaddr_in client;socklen_t client_len = sizeof(client);int client_sock = accept(_listenfd, (struct sockaddr*)&client, &client_len);if (client_sock < 0){cerr << "accept error " << errno << ": " << strerror(errno) << endl;exit(1);}// 5. 连接成功,进行通信, 多进程string client_ip = inet_ntoa(client.sin_addr);uint16_t client_port = ntohs(client.sin_port);if (fork() == 0){// 子进程执行通信任务Task(client_sock, client_ip, client_port);}}}void Task(int client_sock, string client_ip, uint16_t client_port){while (true){// 5.1 接收信息char recv_buf[1024];int n = read(client_sock, recv_buf, sizeof(recv_buf));if (n > 0)recv_buf[n] = 0;cout << "[" << client_ip << ":" << client_port << "]# " << recv_buf << endl;// 5.2 应答信息char sent_buf[1024];snprintf(sent_buf, sizeof(sent_buf), "服务器已收到信息: %s\n", recv_buf);write(client_sock, sent_buf, sizeof(sent_buf));}}

三、TCP多线程通信

TCP通信的多线程和多进程非常相似,在相同的代码处做出修改即可,相比于多进程,多线程的对服务器来说资源开销更小,故在TCP通信中,更推荐使用多线程。

多线程策略和多进程策略总体类似,但是在代码还是存在差异,需要将Task()设置为静态函数,这其实也就是说,需要将Task()函数与TcpServer类再次解耦。当任务非常复杂的时候,我们甚至需要单独写一个Task头文件写通信任务。

在子线程执行与客户端的通信服务的时候,需要将子线程进行detach()线程分离,这样当客户端与服务器通信结束的时候,不会影响主线程,但是需要注意的事,在任务函数中当任务结束的时候,不能再用exit()退出,而是需要将exit()改为return,不然的话exit()会在退出任务的时候将退出信号传递给OS,OS会杀死整个进程。

还有就是此处我们调用C++11的线程库,不仅需要包含<thread>头文件,在编译的时候也要加上线程动态库的选项:-lpthread

makefile:

all: server clientserver: server.ccg++ -o $@ $^ -std=c++11 -pthreadclient: client.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -rf server client

Server.h代码修改处:

    static void Task(int client_sock, string client_ip, uint16_t client_port){while (true){// 5.1 接收信息char recv_buf[1024];int n = read(client_sock, recv_buf, sizeof(recv_buf));if (n == 0)return;recv_buf[n] = 0;cout << "[" << client_ip << ":" << client_port << "]# " << recv_buf << endl;// 5.2 应答信息char sent_buf[1024];snprintf(sent_buf, sizeof(sent_buf), "服务器已收到信息: %s\n", recv_buf);write(client_sock, sent_buf, sizeof(sent_buf));}}void Start(){while (true){// 4. accept连接客户端struct sockaddr_in client;socklen_t client_len = sizeof(client);int client_sock = accept(_listenfd, (struct sockaddr*)&client, &client_len);if (client_sock < 0){Log_Message(FATAL, "accept error");exit(ACCEPT_ERR);}Log_Message(NORMAL, "accept success");// 5. 连接成功,进行通信, 多线程string client_ip = inet_ntoa(client.sin_addr);uint16_t client_port = ntohs(client.sin_port);thread t(Task, client_sock, client_ip, client_port);    // 创建线程自动执行t.detach();                                             // 线程分离}}

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

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

相关文章

RocketMQ教程-安装和配置

Linux系统安装配置 64位操作系统&#xff0c;推荐 Linux/Unix/macOS 64位 JDK 1.8 Maven3.0 yum 安装jdk8 yum 安装maven 1.下载安装Apache RocketMQ RocketMQ 的安装包分为两种&#xff0c;二进制包和源码包。 点击这里 下载 Apache RocketMQ 5.1.3的源码包。你也可以从这…

Windows11的VS201x编译OpenCV+Contrib+CUDA

(1) CUDA下载&#xff0c;注意要和cudnn版本号相关。 我安装的是cuda11.0,注意VS2015不能编译CUDA11&#xff0c;所以用VS2015的话需要下载CUDA 10。因为更高的版本目前还没有cudnn。 (2) 下载和安装VS2015。 (3) 下载和解压CMake。 CMake地址&#xff1a; Releases Kitw…

Nginx代理nginx.conf配置——nginx对静态文件代理

1. 对根目录下的静态资源代理 Nginx代理nginx.conf配置——反向代理 2. 目录代理 如果需要将资源代理到不同的目录下&#xff0c;则在nginx.conf中的server节点下进行如下配置&#xff1a; location /image {root /opt/cache; }location vedio {root /opt/cache; } 修改后…

Android dp to pix resources.getDimension(R.dimen.xxx) ,kotlin

Android dp to pix resources.getDimension(R.dimen.xxx) ,kotlin <?xml version"1.0" encoding"utf-8"?> <resources><dimen name"my_size_dp">20dp</dimen><dimen name"my_size_px">20px</dime…

【微信小程序】从网络请求返回值res.data获取并解析一个Array

在微信小程序中&#xff0c;可以通过res.data获取到请求返回的数据。如果返回的数据是一个数组&#xff0c;您可以直接对其进行操作。 以下是一个示例代码&#xff0c;演示了如何从res.data中解析一个数组&#xff1a; wx.request({url: http://yuor-api:80/device/_query/no…

双线性插值算法缩放图片,部分图片出现黑边的解决办法

因工作需要使用软件方法缩放PNG图片&#xff0c;询问chatgpt拿到了c双线性插值算法&#xff0c;开始很顺利&#xff0c;整理一下代码&#xff0c;封装一下接口&#xff0c;就可以使用了&#xff0c;效果还不错&#xff0c;马上编译发给测试组测试&#xff0c;测试发现有一些图片…

数据仓库表设计理论

数据仓库表设计理论 数仓顾名思义是数据仓库&#xff0c;其数据来源大多来自于业务数据(例如:关系型数据库)&#xff0c;当设计数仓中表类型时(拉链表、增量表、全量表、流水表、切片表)时&#xff0c;应先观察业务数据的特点再设计数仓表结构 首先业务数据是会不断增长的-即…

练习——动态内存分配的笔试题

今天我们分享几道经典的笔试题&#xff0c;做完直接变成陈泽 第一题 ~~ --------------------------------------------------------------------------------------------------~~ void GetMemory(char* p) {p (char*)malloc(100); } void Test(void) {char* str NULL;Get…

阿里云容器镜像仓库(ACR)的创建和使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

使用spark进行hbase的bulkload

使用spark进行hbase的bulkload 一、 背景 HBase 是一个面向列&#xff0c;schemaless&#xff0c;高吞吐&#xff0c;高可靠可水平扩展的 NoSQL 数据库&#xff0c;用户可以通过 HBase client 提供的 put get 等 api 实现在数据的实时读写。在过去的几年里&#xff0c;HBase …

虚拟机 NAT模式联网 重启断网的解决方法

1、虚拟机NAT模式联网 这个博客写的很清楚:按照他的方法可以ping通www.baidu.com 三分钟完成虚拟机联网 小白看了都说好&#xff01;&#xff01;&#xff01; 虚拟机超详细联网教程/步骤 SDN软件定义网络实验_虚拟机联网教程_九号迷妹的博客-CSDN博客 但是虚拟机重启几次之后…

使用arm-none-eabi-gcc编译器搭建STM32的Vscode开发环境

工具 make&#xff1a;Windows中没有make&#xff0c;但是可以通过安装MinGW或者MinGW-w64&#xff0c;得到make。gcc-arm-none-eabi&#xff1a;建议最新版&#xff0c;防止调试报错OpenOCDvscodecubeMX VSCODE 插件 Arm Assembly&#xff1a;汇编文件解析C/C&#xff1a;c…

HTTP 请求走私漏洞(HTTP Request Smuggling)

一、什么是Http 请求走私漏洞&#xff1f; HTTP请求走私漏洞&#xff08;HTTP Request Smuggling&#xff09;是一种安全漏洞&#xff0c;利用了HTTP协议中请求和响应的解析和处理方式的不一致性。攻击者通过构造特定的恶意请求&#xff0c;以欺骗服务器和代理服务器&#xff0…

Godot 4 源码分析 - 增加管道通信

学习研究Godot 4&#xff0c;很爽&#xff0c;虽然很庞杂&#xff0c;但相对于自己的水平来说&#xff0c;很强大&#xff0c;尤其是vulkan这块直接打包可用&#xff0c;省得自己从头琢磨。 一点一点地消化、优化与完善&#xff0c;最终才能成为自己的。 这段时间就在Godot的…

【Matlab】基于随机森林算法的数据分类预测(Excel可直接替换数据)

【Matlab】基于随机森林算法的数据分类预测(Excel可直接替换数据) 1.模型原理2.数学公式3.文件结构4.Excel数据5.分块代码6.完整代码7.运行结果1.模型原理 基于随机森林算法的数据分类预测是一种集成学习方法,用于解决分类问题。它由多个决策树组成,通过对这些决策树的预测…

Spring期刊模板中,引用Appendix中的表格时,页面显示正确但链接跳转错误

错误描述&#xff1a; 正文中有Table1&#xff0c;Table2&#xff0c; Table3. Appendix中有Table A1&#xff0c; Table A2&#xff0c; Table A3 appendix中引用 Table A1&#xff0c;生成pdf后。appendix中显示Table A1&#xff0c;正确&#xff0c;但是点击Table A1以后&…

Pytorch迁移学习使用Resnet50进行模型训练预测猫狗二分类

目录 1.ResNet残差网络 1.1 ResNet定义 1.2 ResNet 几种网络配置 1.3 ResNet50网络结构 1.3.1 前几层卷积和池化 1.3.2 残差块&#xff1a;构建深度残差网络 1.3.3 ResNet主体&#xff1a;堆叠多个残差块 1.4 迁移学习猫狗二分类实战 1.4.1 迁移学习 1.4.2 模型训练 1.…

华为数通HCIP-ISIS基础

IS-IS的基本概念 isis&#xff08;中间系统到中间路由协议&#xff09; 链路状态路由协议、IGP、无类路由协议&#xff1b; IS-IS是一种链路状态路由协议&#xff0c;IS-IS与OSPF在许多方面非常相似:运行IS-IS协议的直连设备之间通过发送Hello报文发现彼此&#xff0c;然后建…

从零开始搭建vue3 + ts + pinia + vite +element-plus项目

前言&#xff1a;据说vue2将于 2023 年 12 月 31 日停止维护&#xff0c;最近打算搭建一个vue3项目来学习一下&#xff0c;以防忘记&#xff0c;记录一下搭建过程。 一、使用npm创建项目 前提条件&#xff1a;已安装 16.0 或更高版本的 Node.js 执行 “npm init vuelatest”…

【Java基础教程】(四十三)多线程篇 · 下:深入剖析Java多线程编程:同步、死锁及经典案例——生产者与消费者,探究sleep()与wait()的差异

Java基础教程之多线程 下 &#x1f539;本节学习目标1️⃣ 线程的同步与死锁1.1 同步问题的引出2.2 synchronized 同步操作2.3 死锁 2️⃣ 多线程经典案例——生产者与消费者&#x1f50d;分析sleep()和wait()的区别&#xff1f; &#x1f33e; 总结 &#x1f539;本节学习目标…