【Linux】Socket编程-TCP构建自己的C++服务器

🌈 个人主页:Zfox_
🔥 系列专栏:Linux

目录

  • 一:🔥 Socket 编程 TCP
    • 🦋 TCP socket API 详解
    • 🦋 多线程远程命令执行
    • 🦋 网络版计算器(应用层自定义协议与序列化)
  • 二:🔥 共勉

一:🔥 Socket 编程 TCP

🦋 TCP socket API 详解

下面介绍程序中用到的 socket API,这些函数都在 sys/socket.h 中
socket

#include <sys/types.h>
#include <sys/socket.h>// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);domain:/ 协议家族AF_INET      IPv4 Internet protocolsAF_INET6     IPv6 Internet protocolstype: 报文类型SOCK_DGRAM      Supports datagrams (connectionless, unreliable messages of a fixed maximum length).SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based byte streams.  An out-of-band data transmission mechanism may be supported.protocol: 传输层类型默认为0On success, a file descriptor for the new socket is returned.  On error, -1 is returned, and errno is set appropriately.

bind

#include <sys/types.h>
#include <sys/socket.h>// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.// 2. 填充网络信息,并bind绑定
// 2.1 没有把socket信息设置进入内核
struct sockaddr_in local;
bzero(&local, sizeof(local));       // string.h
local.sin_family = AF_INET;
local.sin_port = ::htons(_port);   // 要被发送给对方,既要发送到网络中!   主机序列转换为网络序列 大小端转换 网络中都是大端 #include <arpa/inet.h>
local.sin_addr.s_addr = ::inet_addr(_ip.c_str());    // 1. string ip -> 4bytes 2. network order   #include <sys/socket.h>    #include <netinet/in.h>  #include <arpa/inet.h>
local.sin_addr.s_addr = INADDR_ANY;// 2.1 bind    这里设置进入内核
int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());

📚 我们之前在调用socket的时候,明明已经填充了一次 AF_INET, 为什么这里还需要一次呢?

创建套接字的时候填充的 AF_INET 是给操作系统文件系统里的网络文件接口,告诉我们的操作系统我们要创建一个网络的套接字。

这里则是用来填充 sockaddr_in 网络信息,只有套接字的结构和这里的结构一样,操作系统才能绑定成功。

📚 必带四件套

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

listen

#include <sys/types.h>          
#include <sys/socket.h>int listen(int sockfd, int backlog);sockfd:			 指定的套接字
backlog				 等待连接队列的最大长度。On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.

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

accept

#include <sys/types.h>      
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockfd:			 指定的套接字
  • 三次握手完成后, 服务器调用 accept() 接受连接;
  • 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr 是一个传出参数,accept()返回时传出客户端的地址和端口号;
  • 如果给 addr 参数传 NULL,表示不关心客户端的地址;
  • addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);

connect

#include <sys/types.h>       
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockfd:			 指定的套接字
  • 客户端需要调用 connect()连接服务器;
  • connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而 connect 的参数是对方的地址;
  • connect() 成功返回 0,出错返回-1

🦋 多线程远程命令执行

📚 代码结构

C++
CommandExec.hpp  Common.hpp  Cond.hpp  InetAddr.hpp  Log.hpp  Makefile  
Mutex.hpp  TcpClient.cc  TcpServer.cc  TcpServer.hpp  Thread.hpp  ThreadPool.hpp

TcpServer.hpp

#pragma once#include <iostream>
#include <cstring>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"using namespace LogModule;
using namespace ThreadPoolModule;static const uint16_t gport = 8080;
using handler_t = std::function<std::string(std::string)>;#define BACKLOG 8class TcpServer
{using task_t = std::function<void()>;struct ThreadData{int sockfd;TcpServer *self;};
public:TcpServer(handler_t handler, int port = gport): _handler(handler), _port(port), _isrunning(false){}bool InitServer(){// 1. 创建tcp socket_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0); // Tcp Socketif (_listensockfd < 0){LOG(LogLevel::FATAL) << "_listensockfd error";Die(SOCKET_ERR);}LOG(LogLevel::INFO) << "_listensockfd create success, _listensockfd is : " << _listensockfd;struct 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;// 2. bindint n = ::bind(_listensockfd, CONV(&local), sizeof(local));if (n < 0){LOG(LogLevel::FATAL) << "bind error";Die(BIND_ERR);}LOG(LogLevel::INFO) << "bind success, _listensockfd is : " << _listensockfd;// 3. cs tcp是面向连接的,就要求tcp随时随地等待被连接// tcp 需要将socket设置成为监听状态n = ::listen(_listensockfd, BACKLOG);if (n < 0){LOG(LogLevel::FATAL) << "listen error";Die(LISTEN_ERR);}LOG(LogLevel::INFO) << "listen success, _listensockfd is : " << _listensockfd;//::signal(SIGCHLD, SIG_IGN);     // 子进程退出,OS会自动回收资源,不用再wait了return true;}void HandlerRequest(int sockfd)   // TCP 也是全双工通信{LOG(LogLevel::INFO) << "HandlerRequest, sockfd is : " << sockfd;char inbuffer[4096];// 长任务while(true){ssize_t n = ::recv(sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if(n > 0){LOG(LogLevel::INFO) << inbuffer;inbuffer[n] = 0;// std::string echo_str = "server echo# ";// echo_str += inbuffer; std::string cmd_result = _handler(inbuffer);::send(sockfd, cmd_result.c_str(), cmd_result.size(), 0);}else if(n == 0){// read 如果读取返回值是0,表示client退出LOG(LogLevel::INFO) << "client quit: " << sockfd;break;}else {// 读取失败了break;}}::close(sockfd);   // fd泄露问题}static void *ThreadEntry(void *args){pthread_detach(pthread_self());ThreadData* data = (ThreadData*)args;data->self->HandlerRequest(data->sockfd);delete data;return nullptr;}void Start(){_isrunning = true;while (_isrunning){// 不能直接读取数据// 1. 获取新连接struct sockaddr_in peer;socklen_t peerlen = sizeof(peer);LOG(LogLevel::DEBUG) << "accept ing ...";// 我们要获取客户端的信息:数据(sockfd) + client socket信息(accept)int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);if (sockfd < 0){LOG(LogLevel::WARNING) << "accept error" << strerror(errno);continue;}// 获取连接成功了LOG(LogLevel::INFO) << "accept success, socket is : " << sockfd;InetAddr addr(peer);LOG(LogLevel::INFO) << "client info: " << addr.Addr();// version-0// HandlerRequest(sockfd);// version-1 多进程版本// pid_t id = fork();// if(id == 0)// {//     // child//     // 问题1: 父进程的文件描述符表子进程会继承 父子各一张共两张 //     // 1.关闭不需要的fd//     ::close(_listensockfd);//     if(fork() > 0) exit(0); // 子进程退出//     // 孙子进程 -> 孤儿进程 -> 1//     HandlerRequest(sockfd);//     exit(0);// }// ::close(sockfd);    // 父进程也关闭不需要的 已经交给子进程了// // 不会阻塞// pid_t rid = ::waitpid(id, nullptr, 0);// if(rid < 0)// {//     LOG(LogLevel::WARNING) << "waitpid error";// }// version-2 多线程版本// pthread_t tid;// ThreadData* data = new ThreadData;// data->sockfd = sockfd;// data->self = this;// pthread_create(&tid, nullptr, ThreadEntry, data);    // 主线程和新线程是如何看待,文件描述符表, 共享一张文件描述符表!!属于同一个进程 !// version-3 线程池版本  一般用于短任务(注册登录),少量用户// task_t f = std::bind(&TcpServer::HandlerRequest, this, sockfd);     // 构建任务// ThreadPool<task_t>::getInstance()->Equeue(f);ThreadPool<task_t>::getInstance()->Equeue([this, sockfd](){this->HandlerRequest(sockfd);});}}void Stop(){_isrunning = false;}~TcpServer(){}private:int _listensockfd; // 监听socketuint16_t _port;bool _isrunning;// 处理上层任务的入口handler_t _handler;
};

TcpServer.cc

#include "TcpServer.hpp"
#include "CommandExec.hpp"
#include <memory>using namespace LogModule;int main()
{ENABLE_CONSOLE_LOG();Command cmd;std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>([&cmd](std::string cmdstr){return cmd.Execute(cmdstr);});tsvr->InitServer();tsvr->Start();return 0;
}

TcpClient.cc

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>// ./client_tcp serverip serverport
int main(int argc, char *argv[])
{if(argc != 3){std::cout << "Usage: " << argv[0] << " serverip serverport" << std::endl;return 1;}std::string serverip = argv[1];int server_port = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){std::cout << "Create socket failed." << std::endl;return 2;}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(server_port);    server_addr.sin_addr.s_addr = inet_addr(serverip.c_str());// client 不需要显示的进行bind, tcp是面向连接的, connect 底层自动会进行bindint n = ::connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));if(n < 0){std::cout << "Connect to server failed." << std::endl;return 3;}// echo clientstd::string message;while(true){char inbuffer[1024];std::cout << "input message: ";std::getline(std::cin, message);n = ::write(sockfd, message.c_str(), message.size());if(n > 0){int m = ::read(sockfd, inbuffer, sizeof(inbuffer));if(m > 0){inbuffer[m] = 0;std::cout << inbuffer << std::endl;}else break;}else break;}::close(sockfd);return 0;   
}

CommandExec.hpp

#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <set>const int line_size = 1024;class Command
{
public:Command(){_white_list.insert("ls");_white_list.insert("pwd");_white_list.insert("ls -l");_white_list.insert("who");_white_list.insert("whoami");_white_list.insert("ll");}bool SafeCheck(const std::string& cmdstr){auto iter = _white_list.find(cmdstr);return iter == _white_list.end() ? false : true;}// 给你一个命令字符串"ls -l",执行它并返回执行结果std::string Execute(std::string cmdstr){// 1. pope// 2.fork + dup2(pipe[1], 1) + exec*, 执行结果给父进程, pipe[0]// 3. return // FILE *popen(const cahr *command, const char *type);// pclose(FILE *stream);if(!SafeCheck(cmdstr)){return std::string(cmdstr + "不支持");}FILE *fp = popen(cmdstr.c_str(), "r");if(fp == nullptr){return std::string("Failed");}char buffer[line_size];std::string result;while(true){char *ret = ::fgets(buffer, sizeof(buffer), fp);if(!ret) break;result += ret;}pclose(fp);return result.empty() ? std::string("Done") : result;}
private:std::set<std::string> _white_list;
};

CommandExec.hpp

#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <set>const int line_size = 1024;class Command
{
public:Command(){_white_list.insert("ls");_white_list.insert("pwd");_white_list.insert("ls -l");_white_list.insert("who");_white_list.insert("whoami");_white_list.insert("ll");}bool SafeCheck(const std::string& cmdstr){auto iter = _white_list.find(cmdstr);return iter == _white_list.end() ? false : true;}// 给你一个命令字符串"ls -l",执行它并返回执行结果std::string Execute(std::string cmdstr){// 1. pope// 2.fork + dup2(pipe[1], 1) + exec*, 执行结果给父进程, pipe[0]// 3. return // FILE *popen(const cahr *command, const char *type);// pclose(FILE *stream);if(!SafeCheck(cmdstr)){return std::string(cmdstr + "不支持");}FILE *fp = popen(cmdstr.c_str(), "r");if(fp == nullptr){return std::string("Failed");}char buffer[line_size];std::string result;while(true){char *ret = ::fgets(buffer, sizeof(buffer), fp);if(!ret) break;result += ret;}pclose(fp);return result.empty() ? std::string("Done") : result;}
private:std::set<std::string> _white_list;
};

🦋 网络版计算器(应用层自定义协议与序列化)

代码结构

C++
Calculator.hpp  Common.hpp  Cond.hpp  Deamon.hpp  InetAddr.hpp  Log.hpp  Makefile  Mutex.hpp  
Protocol.hpp  TcpClient.cc  TcpServer.cc  TcpServer.hpp  Thread.hpp  ThreadPool.hpp
// 简单起见, 可以直接采用自定义线程
// 直接 client<<->>server 通信, 这样可以省去编写没有干货的代码

网络版计算器(应用层自定义协议与序列化)

二:🔥 共勉

以上就是我对 【Linux】Socket编程-TCP构建自己的C++服务器 的理解,想要完整代码可以私信博主噢!觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
在这里插入图片描述

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

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

相关文章

C++(二十一)

前言&#xff1a; 本文承接上文&#xff0c;将详细讲解指针概念。 一&#xff0c;通过指针了解变量的数值。 在将变量地址存入指针后&#xff0c;从指针反推也可以知道原变量的值&#xff0c;若想进行反退&#xff0c;就需要使用间接引用运算符&#xff1a;*。 语法&#x…

Windows图形界面(GUI)-QT-C/C++ - Qt List Widget详解与应用

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 QListWidget概述 使用场景 常见样式 QListWidget属性设置 显示方式 (Display) 交互行为 (Interaction) 高级功能 (Advanced) QListWidget常见操作 内容处理 增加项目 删除项目…

一文了解如何使用 DBeaver 管理 DolphinDB

在日常的数据开发、分析和数据库运维中&#xff0c;一款优秀的 IDE 能够极大地提升工作效率。DBEaver 是一款由 Java 编写的一站式跨平台连接器&#xff0c;其社区版本已能支持连接近百种数据库&#xff0c;受到广大开发者的喜爱。近期。DolphinDB 与 DBeaver 团队共同努力&…

CSRF攻击XSS攻击

概述 ​在 HTML 中&#xff0c;<a>, <form>, <img>, <script>, <iframe>, <link> 等标签以及 Ajax 都可以指向一个资源地址&#xff0c;而所谓的跨域请求就是指&#xff1a;当前发起请求的域与该请求指向的资源所在的域不一样。这里的域指…

【机器学习实战入门】使用 Pandas 和 OpenCV 进行颜色检测

Python 颜色检测项目 今天的项目将非常有趣和令人兴奋。我们将与颜色打交道&#xff0c;并在项目过程中学习许多概念。颜色检测对于识别物体来说是必要的&#xff0c;它也被用作各种图像编辑和绘图应用的工具。 什么是颜色检测&#xff1f; 颜色检测是检测任何颜色名称的过程…

vue2 web 多标签输入框 elinput是否当前焦点

又来分享一点点工作积累及解决方案 产品中需要用户输入一些文字后按下回车键生成标签来显示在页面上&#xff0c;经过尝试与改造完成如下&#xff1a; <template><div class"tags-view" click"beginInput"><el-tag :key"index" …

SSE 实践:用 Vue 和 Spring Boot 实现实时数据传输

前言 大家好&#xff0c;我是雪荷。最近我在灵犀 BI 项目中引入了 SSE 技术&#xff0c;以保证图表的实时渲染&#xff0c;当图表渲染完毕服务端推送消息至浏览器端触发重新渲染。 什么是 SSE&#xff1f; SSE 全称为 Server-Send Events 意思是服务端推送事件。 SSE 相比于 …

记录一次 centos 启动失败

文章目录 现场1分析1现场2分析2搜索实际解决过程 现场1 一次断电,导致 之前能正常启动的centos 7.7 起不来了有部分log , 关键信息如下 [1.332724] XFS(sda3): Internal error xfs ... at line xxx of fs/xfs/xfs_trans.c [1.332724] XFS(sda3): Corruption of in-memory data…

【狂热算法篇】探秘图论之 Floyd 算法:解锁最短路径的神秘密码(通俗易懂版)

&#xff1a; 羑悻的小杀马特.-CSDN博客羑悻的小杀马特.擅长C/C题海汇总,AI学习,c的不归之路,等方面的知识,羑悻的小杀马特.关注算法,c,c语言,青少年编程领域.https://blog.csdn.net/2401_82648291?spm1010.2135.3001.5343 在本篇文章中&#xff0c;博主将带大家去学习所谓的…

【k8s面试题2025】3、练气中期

体内灵气的量和纯度在逐渐增加。 文章目录 在 Kubernetes 中自定义 Service端口报错常用控制器Kubernetes 中拉伸收缩副本失效设置节点容忍异常时间Deployment 控制器的升级和回滚日志收集资源监控监控 Docker将 Master 节点设置为可调度 在 Kubernetes 中自定义 Service端口报…

Ubuntu20.04取消root账号自动登录的方法,触觉智能RK3568开发板演示

Ubuntu20.04默认情况下为root账号自动登录&#xff0c;本文介绍如何取消root账号自动登录&#xff0c;改为通过输入账号密码登录&#xff0c;使用触觉智能EVB3568鸿蒙开发板演示&#xff0c;搭载瑞芯微RK3568&#xff0c;四核A55处理器&#xff0c;主频2.0Ghz&#xff0c;1T算力…

LeetCode | 解锁数组与字符串的秘密:经典题型详解与高效解法

1.理论 1. 1.核心概念 1.1.1.数组(Array) 定义&#xff1a;存储相同数据类型的元素的线性集合。 特点&#xff1a;支持随机访问&#xff08;通过索引&#xff09;;元素存储在连续内存中&#xff0c;支持高效的读写操作。 时间复杂度&#xff1a;访问&#xff1a;O(1);插入…

怎么修复损坏的U盘?而且不用格式化的方式!

当你插入U盘时&#xff0c;若电脑弹出“需要格式化才能使用”提示&#xff0c;且无法打开或读取其中的数据&#xff0c;说明U盘极有可能已经损坏。除此之外&#xff0c;若电脑在连接U盘后显示以下信息&#xff0c;也可能意味着U盘出现问题&#xff0c;需要修复损坏的U盘&#x…

数据结构漫游记:动态实现栈(stack)

嘿&#xff0c;各位技术潮人&#xff01;好久不见甚是想念。生活就像一场奇妙冒险&#xff0c;而编程就是那把超酷的万能钥匙。此刻&#xff0c;阳光洒在键盘上&#xff0c;灵感在指尖跳跃&#xff0c;让我们抛开一切束缚&#xff0c;给平淡日子加点料&#xff0c;注入满满的pa…

w163美食推荐商城

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

计算机网络 (47)应用进程跨越网络的通信

前言 计算机网络应用进程跨越网络的通信是一个复杂而关键的过程&#xff0c;它涉及多个层面和组件的协同工作。 一、通信概述 计算机网络中的通信&#xff0c;本质上是不同主机中的应用进程之间的数据交换。为了实现这种通信&#xff0c;需要借助网络协议栈中的各层协议&#x…

【Linux】Mysql部署步骤

一、JDK安装配置 在home目录下执行命令&#xff1a;mkdir Jdk 1.将JDK 上传至该文件夹&#xff0c;有些终端工具可以直接上传文件&#xff0c;比如&#xff1a;MobaXterm 可以看到安装包已经上传上来了 2.直接安装 命令&#xff1a;rpm -ivh jdk-8u311-linux-x64.rpm 3.安装成…

归子莫的科技周刊#2:白天搬砖,夜里读诗

归子莫的科技周刊#2&#xff1a;白天搬砖&#xff0c;夜里读诗 本周刊开源&#xff0c;欢迎投稿。 刊期&#xff1a;2025.1.5 - 2025.1.11。原文地址。 封面图 下班在深圳看到的夕阳&#xff0c;能遇到是一种偶然的机会&#xff0c;能拍下更是一种幸运。 白天搬砖&#xff0c;…

你需要什么样的资源隔离?丨TiDB 资源隔离最佳实践

导读 资源隔离是数据库性能优化的重要环节&#xff0c; TiDB 在当前版本已经实现了从数据级隔离到流控隔离的全面升级 &#xff0c;无论是多系统共享集群、复杂负载隔离&#xff0c;还是小型系统整合和 SQL 精细化控制&#xff0c;TiDB 都提供了灵活且高效的解决方案。 本文以…

w162体育馆管理系统

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…