目录

一、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();                                             // 线程分离}}

【Linux后端服务器开发】TCP通信设计相关推荐

  1. C/C++ Linux后台服务器开发高级架构师学习知识点路线总结(2021架构师篇完整版)

    C/C++ Linux后台服务器开发高级架构师学习知识点路线总结(2021架构师篇完整版) 前言: 小编之前有跟大家分享过一篇架构师体系知识点总结的文章,今天在原来的基础上有所改变更新(2021版). ...

  2. C/C++ Linux后台服务器开发高级架构师学习知识点路线总结(2022架构师篇完整版)

    C/C++ Linux后台服务器开发高级架构师学习知识点路线总结(2021架构师篇完整版) 前言: 小编之前有跟大家分享过一篇架构师体系知识点总结的文章,今天在原来的基础上有所改变更新(2021版). ...

  3. ngrok服务器搭建_C/C++ Linux 后台服务器开发高级架构师学习知识路线总结

    前言: 小编也是从事c方面10多年的工作经验.今天跟大家分享一下我总结出来的一系列 C/C Linux后台服务器开发的学习路线.从Linux开发工程师-Linux后台开发工程师-Linux高级互联网架 ...

  4. C/C++ Linux 后台服务器开发高级架构师学习知识路线总结

    前言: 小编也是从事c方面10多年的工作经验.今天跟大家分享一下我总结出来的一系列 C/C Linux后台服务器开发的学习路线.从Linux开发工程师-Linux后台开发工程师-Linux高级互联网架 ...

  5. C/C++ Linux 后台服务器开发高级架构师学习知识路(架构师篇)

    @[前言: 小编从事c方面10多年的工作经验.今天跟大家分享一下我总结出来的一系列 C/C Linux后台服务器开发的学习路线.从Linux开发工程师-Linux后台开发工程师-Linux高级互联网架 ...

  6. Linux - 网络服务器开发(全)

    文章目录 Linux - 网络服务器开发(Ubuntu) Linux - 服务端开发 服务端开发 测试连接 Linux - 客户端开发 客户端开发 测试连接 补充 套接字socket 流程图: 网络字 ...

  7. Linux高性能服务器开发——进程篇

    本文主要是学习Linux高性能服务器开发需要提前了解的知识,后续还会涉及到虚拟内存方面的内容,各位看官可以多了解了解,看到文章内有将的不清楚或者讲错的地方请各位一定留言,我看到后会第一时间验证并修正的 ...

  8. Linux 高性能服务器开发笔记:Reactor 模型定时器 | 网络编程定时器

    本文主要根据游双书本 Linux 高性能服务器开发 学习分析 linux 网络编程常用到的定时器模型,配备详细理解和分析,同时分析了 Linux 内核中定时器的低精度时间轮和高精度定时器实现思路还有 ...

  9. 基于labview的tcp通信设计简要教程

    前言:本文参考了网上的一些资料和论述,在此表示感谢! TCP/IP最适合在两台电脑间进行大量的资料传输,但是如何使用labview进行tcp通信设计呢? 有一个基本的观念必需要先厘清,那就是LabVI ...

最新文章

  1. [gic]-linux和optee的中断处理流程举例(gicv3举例)
  2. 75. Sort Colors - LeetCode
  3. 476. 数字的补数
  4. [导入]在C++ Builder3下实现程序自动运行的方法
  5. 编写高质量代码的50条黄金守则-Day 02(首选readonly而不是const)
  6. 为ASP.NET MVC Client-side Resource Combine 添加中文支持
  7. 7-24 求集合数据的均方差 (15 分)
  8. ce能修改mc服务器吗,CE怎么修改DNF私服的装备。
  9. verilog实现格雷码(Gray Code)与二进制编码转换
  10. CANoe软件中制作DBC文件的小教程
  11. 航摄像片生成DSM(Pix4d)2021.5.21
  12. head first系列pdf下载
  13. 一个简单的acm竞赛题
  14. Linux7密钥,centos7秘钥验证
  15. pinyin4j:拼音与汉字的转换实例
  16. 解决,微信网页开发,网页授权域名数量不足问题
  17. Python 实现延时队列
  18. 用户喜欢什么样的内容?
  19. 学习大数据的第48天(zookeeper篇)
  20. 一起自学SLAM算法:7.4 基于贝叶斯网络的状态估计

热门文章

  1. 概率生成函数(probability-generating function)
  2. Java学习手册:thissuper
  3. ResNeXt结构的pytorch实现
  4. python如何重新开始程序_如何使Python程序自动重新启动
  5. Linux批量拉黑ip,在Linux下实现批量屏蔽IP地址的方法
  6. [收藏]该怎么做,才能取得转型的成功呢?[人力资本]
  7. 面向对象五大原则_1.单一职责原则amp;2.里氏替换原则
  8. 19python_类和对象
  9. 基于惯性传感器的上肢康复训练评估
  10. 湘潭职校学计算机的,湘潭计算机职业技术学校怎么样?好不好?