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

news2024/12/25 22:09:01

个人主页: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 once

class 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. bind
        struct 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:后面专门要处理一下这个IP
    bool _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.coloradmin.cn/o/2234807.html

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

相关文章

[安洵杯 2019]easy_web 详细题解

知识点: 编码转换 命令执行 linux空格_关键字绕过 打开页面 发现url 是 /index.php?imgTXpVek5UTTFNbVUzTURabE5qYz0&cmd 有img参数和cmd参数 cmd参数是没赋值的,随便赋值为123456 页面没有反应 鼠标移动到图片下面时发现有东西,当然直接查看页面源代码也可以发现 尝…

免费,基于React + ECharts 国产开源 IoT 物联网 Web 可视化数据大屏

文末查看开源项目地址 Light Chaser 是一款国产开源免费的基于 React18、Vite5、TypeScript5 技术栈实现的 Web 可视化大屏设计工具&#xff0c;支持Docker方式部署&#xff0c;支持MySQL、PostgreSQL、SQL Server、Oracle 数据源。 你可以简单快速地搭建数据可视化展示、数据报…

Linux服务管理-DHCP

DHCP DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09;是一个局域网的网络协议&#xff0c;它允许服务器自动地将IP地址和其他网络配置参数分配给网络中的计算机。DHCP极大地简化了网络管理&#xff0c;尤其是当大量设备需要接入…

如何使用Netdata部署高性能的服务器监控平台

简介 Netdata 是一个开源的、实时的性能和健康监控工具&#xff0c;专为系统、应用程序、SNMP 设备等而设计。它以其高度交互的 Web 仪表板和极低的资源开销而闻名。 主要特点&#xff1a;实时监控、全面监控、零配置、轻量级、交互式仪表板、可扩展性、警报和通知、分布式监…

【MySQL】深度学习与解析 : 库的操作知识整合

前言&#xff1a;本节内容是MySQL库的操作&#xff0c; 内容较少&#xff0c; 大体内容为创建库、删除库、修改库、库备份操作。 ps:本节内容适合安装了MySQL的友友们进行观看&#xff0c; 实操更有利于记住哦。 目录 创建数据库 查看数据库列表 创建数据库 删除数据库 …

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-11-01目录1. A Perspective for Adapting Generalist AI to Specialized Medical AI Applications and Their Challenges2. Synergi…

白杨SEO:百度在降低个人备案类网站搜索关键词排名和流量?怎样应对?【参考】

很久没有写百度或者网站这块内容了&#xff0c;一是因为做百度网站朋友越来越少&#xff0c;不管是个人还是企业&#xff1b;二是百度上用户搜索与百度给到网站的流量都越来越少。 为什么想到今天又来写这个呢&#xff1f;因为上个月有个朋友来咨询我说网站百度排名全没了&…

5分钟科普:AI网关是什么?应用场景是什么?有没有开源的选择?

AI网关的功能及其定义 AI网关位于企业应用与内外部大模型调用的交汇点&#xff0c;能够灵活地将请求转发给内部自建模型或外部大模型服务提供商&#xff0c;甚至海外的服务商。它管理着企业所有的AI出口流量&#xff0c;为企业内的不同团队提供了多方面的优势。 对于开发团队…

Java学习Day60:微服务总结!(有经处无火,无火处无经)

1、技术版本 jdk&#xff1a;17及以上 -如果JDK8 springboot&#xff1a;3.1及其以上 -版本2.x springFramWork&#xff1a;6.0及其以上 -版本5.x springCloud&#xff1a;2022.0.5 -版本格林威治或者休斯顿 2、模拟springcloud 父模块指定父pom <parent><…

登录功能设计(php+mysql)

一 登录功能 1. 创建一个登录页面&#xff08;login.php&#xff09;&#xff0c;包含一个表单&#xff0c;用户输入用户名和密码。 2. 在表单的提交事件中&#xff0c;使用PHP代码处理用户输入的用户名和密码。 3. 首先&#xff0c;连接MySQL数据库。然后&a…

【物联网技术】ESP8266 WIFI模块STA、AP、STA+AP、TCP/UDP透传工作模式介绍与AT指令介绍

前言:本文对ESP8266 WIFI模块STA、AP、STA+AP、TCP/UDP透传工作模式进行介绍;以及AT指令介绍,包括基础AT指令,WIFI功能AT指令、TCP/IP相关AT指令、常用AT指令实例进行介绍。 ESP8266 WIFI模块的接线及固件烧写可参考我的这篇博客:正点原子ATK-ESP8266 WIFI模块接线及固件…

【前端】JavaScript 方法速查大全-函数、正则、格式化、转换、进制、 XSS 转义(四)

&#x1f525; 前言 在现代前端开发中&#xff0c;JavaScript 是不可或缺的语言。无论是处理数据、操作 DOM&#xff0c;还是进行复杂的逻辑运算&#xff0c;掌握 JavaScript 的各种方法都是每位开发者的必修课。本文将为您提供一个全面、系统的 JavaScript 方法参考&#xff…

关于路由笔记

路由 定义&#xff1a; 在计算机网络中&#xff0c;路由是将数据包从源节点传输到目标节点的过程。这个过程涉及到网络中的多个设备&#xff0c;如路由器、交换机等&#xff0c;其中路由器起着关键的决策作用。 工作原理示例&#xff1a; 假设你在一个公司的局域网内&#…

25.停车场管理系统(基于web的Java项目)

目录 1.系统的受众说明 2.相关技术与方法 3.系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 经济可行性 3.1.3 操作可行性 3.2 需求分析 3.2.1 系统功能描述 3.2.2 用例图分析 4. 系统设计 4.1 系统类分析 5. 系统详细设计与实现 5.1 用户登录 5.2 系统信…

【含开题报告+文档+源码】基于SpringBoot+Vue智能居民健康检测系统设计与实现

开题报告 随着社会发展和人民生活水平的提高&#xff0c;人们对健康生活的要求越来越高。而广大居民由于条件限制&#xff0c;存在着健康管理服务不足的问题。本文基于JavaWeb技术&#xff0c;设计并实现了一种居民健康检测系统。首先&#xff0c;本文对该平台的需求进行了分析…

SCNU习题 总结与复习

1. P1:构建最大二叉树 【分治】 重点 构树函数需要注意的点&#xff1b; 前序遍历需要注意&#xff0c;本题的输出有点特点。若一个结点无左子&#xff0c;无右子就不再下去遍历&#xff1b; 其他情况都要下去遍历&#xff1b; 2. P2 寻找多数【分治】 没啥&#xff0c;注意…

ssm060基于SSM的高校共享单车管理系统的设计与实现+vue(论文+源码)_kaic

设计题目&#xff1a;高校共享单车管理系统的设计与实现 摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代&#xff0…

【算法】(Python)贪心算法

贪心算法&#xff1a; 又称贪婪算法&#xff0c;greedy algorithm。贪心地追求局部最优解&#xff0c;即每一步当前状态下最优选择。试图通过各局部最优解达到最终全局最优解。但不从整体最优上考虑&#xff0c;不一定全局最优解。步骤&#xff1a;从初始状态拆分成一步一步的…

记一次微信云托管搭建Redis服务

背景 最近在做一个微信小程序&#xff0c;规划服务全部部署在云托管上面&#xff0c;本次使用了对象存储、mysql、java服务、Redis服务&#xff08;pc端用的&#xff09;。 由于对部署Redis不理解&#xff0c;查看了官方文档&#xff0c;首先看到的是这个架构图&#xff0c;看…

基于SSM的校园美食交流系统【附源码】

基于SSM的校园美食交流系统 效果如下&#xff1a; 管理员主页面 用户主页面 美食信息页面 美食资讯页面 修改密码页面 论坛中心页面 研究背景 随着高校信息化建设的不断推进&#xff0c;校园生活日益丰富多样&#xff0c;学生对于美食的需求与探索也愈发旺盛。然而&#xff…