计算机网络socket编程(1)_UDP网络编程实现echo server

ops/2024/11/12 10:50:05/

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

计算机网络socket编程(1)_UDP网络编程实现echo server

收录于专栏【计算机网络
本专栏旨在分享学习计算机网络的一点学习笔记,欢迎大家在评论区交流讨论💌
 

目录

功能介绍 : 

1.  nocopy.hpp

2. InetAddr.hpp

3. UdpServer.hpp

4. UdpServerMain.cc

5. UdpClientMain.cc 

6. 效果展示

 


功能介绍 : 

简单的回显服务器和客服端代码, 能接受多个客服端的代码.

1.  nocopy.hpp

#pragma onceclass nocopy
{
public:nocopy(){}~nocopy(){}nocopy(const nocopy&) = delete;const nocopy& operator=(const nocopy&) = delete;
};

定义一个 nocopy 的类, 通过 C++11 delete 关键字的特性, 阻止该类的拷贝构造和拷贝赋值.

比如下面的例子都是不可以的 : 

nocopy obj1;
nocopy obj2 = obj1;  // 错误:拷贝构造函数被删除
nocopy obj1;
nocopy obj2;
obj2 = obj1;  // 错误:拷贝赋值运算符被删除

 为什么需要禁止对象的拷贝呢?

这样的设计常见于以下情况 :

1. 资源管理类 : 例如, 涉及底层资源 (如文件句柄, 网络连接等) 的类通常不希望被复制, 因为这些资源可能会导致资源管理上的混乱或错误~

2. 不想允许拷贝的类: 有些类可能设计上不希望被拷贝,  例如类的状态可能是唯一的, 拷贝它没有意义~~ 

我们这里显然是第一种~~

2. InetAddr.hpp

#pragma once#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
private:void ToHost(const struct sockaddr_in &addr){_port = ntohs(addr.sin_port);_ip = inet_ntoa(addr.sin_addr);}public:InetAddr(const struct sockaddr_in &addr):_addr(addr){ToHost(addr);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}private:std::string _ip;uint16_t _port;struct sockaddr_in _addr;
};

InetAddr 类它封装了网络地址 (IP 端口), 以及提供了访问这些信息的方法, 该类通过了 struct sockaddr_in 来存储 IP 地址和端口, 并提供了转换和获取信息的功能

成员变量 :

_ip : 存储 IP 地址, 使用 std::string 类型, 因为 IP 地址通常表示一个点分十进制的字符串

_port : 存储端口号, 使用 uint16_t 类型, 端口号是一个16位的无符号整数

_addr : 存储原始的 struct sockaddr_in 结构体, 它包含了 IP 地址和端口信息

ToHost():

ToHost() : 将 struct sockaddr_in 中的网络字节数据转换为主机字节序, 并提取 IP 和端口.

ntohs(addr.sin_port) : 网络字节序的端口号转换为主机字节序

inet_ntoa(addr.sin_addr) : 网络字节序的 IP 地址转换为点分十进制的字符串的形式 

构造函数 : 

构造函数 : 接受一个 struct sockaddr_in 类型的参数, 表示网络地址

构造函数初始化 _addr 成员, 存储传入的地址

然后调用 ToHost() 方法来从该地址中提取 IP 和端口, 并进行转换 

Ip() && Port()

Ip() : 返回存储的 IP 地址, 类型为 std::string

Port() : 返回存储的端口号, 类型为 uint16_t 

析构函数 :

由于没有动态分配资源, 所以没有必要进行额外的清理工作~

3. UdpServer.hpp

#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "nocopy.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"using namespace log_ns;static const int gsockfd = -1;
static const uint16_t glocalport = 8888;enum
{SOCKET_ERROR = 1,BIND_ERROR
};// UdpServer user("192.1.1.1", 8899);
// 一般服务器主要是用来进行网络数据读取和写入的。IO的
// 服务器IO逻辑 和 业务逻辑 解耦
class UdpServer : public nocopy
{
public:UdpServer(uint16_t localport = glocalport): _sockfd(gsockfd),_localport(localport),_isrunning(false){}void InitServer(){// 1. 创建socket文件_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG, "socket create success, _sockfd: %d\n", _sockfd); // 3// 2. bindstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_localport);// local.sin_addr.s_addr = inet_addr(_localip.c_str()); // 1. 需要4字节IP 2. 需要网络序列的IP -- 暂时local.sin_addr.s_addr = INADDR_ANY; // 服务器端,进行任意IP地址绑定int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind error\n");exit(BIND_ERROR);}LOG(DEBUG, "socket bind success\n");}void Start(){_isrunning = true;char inbuffer[1024];while (_isrunning){struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);if (n > 0){InetAddr addr(peer);inbuffer[n] = 0;std::cout << "[" << addr.Ip() << ":" << addr.Port() << "]# " << inbuffer << std::endl;std::string echo_string = "[udp_server echo] #";echo_string += inbuffer;sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&peer, len);}else{std::cout << "recvfrom ,  error"  << std::endl;}}}~UdpServer(){if(_sockfd > gsockfd) ::close(_sockfd);}private:int _sockfd;uint16_t _localport;// std::string _localip; // TODO:后面专门要处理一下这个IPbool _isrunning;
};

这段代码定义了一个 UdpServer 类, 用于创建和管理一个 UDP 服务器, 它能够接受客户端的消息并发送相应~

1. 常量和枚举

gsockfd : 设置为 -1, 用作初始的 sockfd, 代表一个无效的套接字描述符

glocalport : 服务器的默认接口, 默认值为 8888

SOCKET_ERROR BIND_ERROR : 自定义的错误代码, 分别代表套接字创建失败和绑定失败.

2. 成员变量

_sockfd : 存储套接字文件描述符

_localport : 服务器绑定的本地端口号

_isrunning : 一个布尔值, 指示服务器是否正在运行

3. 构造函数

UdpServer 类的构造函数的初始化了以下成员变量 : 

_sockfd : 默认初始化为 gsockfd (-1), 表示无效的套接字

_localport : 使用传入的端口号 localport 或默认端口 glocalport (8888) 

isrunning : 初始化为 false , 表示服务器的初始状态是未运行.

4. InitServer()

创建套接字 : 使用 socket() 函数创建一个 UDP 套接字, AF_INET 表示使用 IPv4 协议, SOCK_DGRAM 表示使用 UDP. 

1. 如果创建失败, 记录日志并退出

2. 成功后, 输出日志, 显示套接字描述符 _sockfd.

绑定套接字 : 使用 bind() 函数将套接字与本地地址 (IP 和端口) 绑定

local.sin_family = AF_INET : 设置为 IPv4 地址族

local.sin_port = htons(_localport) : 将端口号转换为网络字节序

local.sin_addr.s_addr = INADDR_ANY : 服务器绑定到任意 IP 地址 

band() 调用套接字与本地地址绑定, 如果绑定失败, 记录错误日志并退出

5. Start()

Start() 方法用于接收数据并发送响应

_isrunning = true : 启动服务器, 进入接收数据的循环

recvfrom() : 接收 UDP 数据包, 数据存放在 inbuffer 中, peer 存储发送方的地址信息

如果接收成功 (n > 0), 则:

1. 创建一个 InetAddr 对象, 解析发送对方的 IP 地址和端口

2. 输出接收到的消息及其来源 IP 和端口

3. 生成一个响应消息 ("[udp_server echo]#" + 接收的消息), 然后使用 sendto() 将其发送回客户端

如果 recvfrom() 失败, 打印错误消息.

6. 析构函数

在 UdpServer 对象销毁时, 关闭套接字 _sockfd, 如果 _sockfd 大于 gsockfd (即有效套接字), 则调用 close() 关闭套接字 

4. UdpServerMain.cc

#include "UdpServer.hpp"#include <memory>// ./udp_server local-port
// ./udp_server 8888
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]);EnableScreen();  std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port); //C++14的标准usvr->InitServer();usvr->Start();return 0;
}

std::stoi(argv[1]) : 将命令行参数中的端口号字符串 (argv[1]) 转换为 uint6_t 类型 (无符号16位整数), std::stoi 是 C++ 标准库函数, 用于将字符串转换为整数, 如果 argv[1] 不是一个有效的数组, std::stoi 将抛出异常

std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port) : 

这行代码使用了 C++14 引入的 std::make_unique, 它用来创建一个 std::unique_ptr, 智能指针类型, 用于管理动态分配的 UdpServer 对象.

std::make_unique<UdpServer>(port) : 动态创建一个 UdpServer 对象, 并将端口号 port 传递给它的构造函数.

std::unique_ptr<UdpServer> usvr : usvr 是一个智能指针, 指向创建的 Udpserver 对象, unique_ptr 确保在 usvr 离开作用域时自动销毁对象, 避免内存泄露.

UdpServer 类接收一个端口号作为构造参数, 表示该服务器将在指定的端口上运行

然后便直接 初始化服务器启动服务 

5. UdpClientMain.cc 

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// 客户端在未来一定要知道服务器的IP地址和端口号
// ./udp_client server-ip server-port
// ./udp_client 127.0.0.1 8888
int main(int argc, char *argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "create socket error" << std::endl;exit(1);}// client的端口号,一般不让用户自己设定,而是让client OS随机选择?怎么选择,什么时候选择呢?// client 需要 bind它自己的IP和端口, 但是client 不需要 “显示” bind它自己的IP和端口, // client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口struct sockaddr_in server;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());while(1){std::string line;std::cout << "Please Enter# ";std::getline(std::cin, line);// std::cout << "line message is@ " << line << std::endl;int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server)); // 你要发送消息,你得知道你要发给谁啊!if(n > 0){struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];int m = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&temp, &len);if(m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}else{std::cout << "recvfrom error" << std::endl;break;}}else{std::cout << "sendto error" << std::endl;break;}}::close(sockfd);return 0;
}

1. 从命令行获取目标服务器的 IP 地址和端口号。

2. 创建 UDP 套接字并配置服务器的地址。

3. 进入循环,等待用户输入数据。

4. 将用户输入的数据通过 sendto 发送给服务器

5. 接收服务器的响应数据并输出到控制台。

6. 出现错误时终止通信并关闭套接字。

6. 效果展示

 


http://www.ppmy.cn/ops/132495.html

相关文章

OpenCV C++ 计算两幅图像之间的多尺度结构相似性(MSSIM)

目录 一、定义与背景 二、计算流程 三、性质与特点 四、应用场景 五、代码实现 多尺度结构相似性(MSSIM)是一种用于衡量两幅图像之间相似度的指标,它基于结构相似性(SSIM)指数进行扩展,通过在不同尺度上计算SSIM来评估图像的整体质量。以下是对MSSIM的详细介…

【数据集】【YOLO】【目标检测】水面船只识别数据集 9798 张,YOLO船只识别算法实战训练教程!

一、数据集介绍 【数据集】水面船只识别数据集 9798 张&#xff0c;目标检测&#xff0c;包含YOLO/VOC格式标注。 数据集中包含1种分类&#xff1a;{0: ship}&#xff0c;代表水面船只。 数据集来自国内外图片网站和视频截图&#xff1b; 可用于无人机船只检测、监控灯塔船…

JavaWeb——Web入门(7/9)-Tomcat-介绍(Tomcat 的简介:轻量级Web服务器,支持Servlet/JSP少量JavaEE规范)

目录 Web服务器的作用 三个方面的讲解 Tomcat 的简介 小结 Web服务器的作用 封装 HTTP 协议操作&#xff1a;Web服务器是一个软件程序&#xff0c;对 HTTP 协议的操作进行了封装。这样开发人员就不需要再直接去操作 HTTP 协议&#xff0c;使得外部应用程序的开发更加便捷、…

ctfshow(328)--XSS漏洞--存储型XSS

Web328 简单阅读一下页面。 是一个登录系统&#xff0c;存在一个用户管理数据库。 那么我们注册一个账号&#xff0c;在账号或者密码中植入HTML恶意代码&#xff0c;当管理员访问用户管理数据库页面时&#xff0c;就会触发我们的恶意代码。 思路 我们向数据库中写入盗取管理员…

第四期书生大模型实战营(【基础岛】- 第2关 | 玩转书生「多模态对话」与「AI搜索」产品)

文章目录 1. 任务介绍[玩转书生「多模态对话」与「AI搜索」产品 - 任务](https://github.com/InternLM/Tutorial/blob/camp4/docs/L1/InternIntro/tasks.md)1.1. 基础任务 (完成此任务即完成闯关)1.2. 进阶任务 (优秀学员需要完成)1.3. 闯关材料提交 (完成材料提交视为闯关成功…

clickhouse自增id的处理

msyql 中创建数据表的时候可以通过AUTO_INCREMENT 来实现&#xff0c;clickhouse中可以通过其他方式来处理 一、 默认值 创建表时可以实用默认值&#xff0c;该列值可以自动递增。如下所示 CREATE TABLE my_table ( id UInt32 DEFAULT IDENTITY(AUTO_INCREMENT), name Strin…

月薪已炒到15w?强烈建议大家冲一冲设计新兴领域,工资高前景好,人才缺口极大!

2024年&#xff0c;你一定要学会用AI设计**。** 1年时间&#xff0c;AI证明了它的超强设计能力&#xff1a; 不用PS&#xff0c;一键完成抠图、扩图&#xff1b; 不用绘图&#xff0c;几个字描述&#xff0c;轻松生成****矢量图标、3D 插画****&#xff1b; 不用付费&#…

Python 小高考篇(2)字符串

目录 字符串字符串运算符格式化转义符字符串和整数间的转换字符串的一些其它知识点字符串切割获取字符串长度 自测总结结尾 本文由Jzwalliser原创&#xff0c;发布在CSDN平台上&#xff0c;遵循CC 4.0 BY-SA协议。 因此&#xff0c;若需转载/引用本文&#xff0c;请注明作者并附…