实现基于UDP简易的英汉词典

news2024/12/24 2:15:37

文章目录

  • 实现目标
  • 认识相关接口
    • socket
    • bzero
    • bind
    • recvfrom
    • sendto
  • 实现思路和注意事项
  • 完整代码
    • Server.hpp
    • Server.cc
    • Client.hpp
    • Client.cc
  • 运行效果
  • END

实现目标

  1. 实现一个服务端和一个客户端,客户端负责发送一个单词,服务端接收到后将翻译后的结果返回发送到客户端。
  2. 使用UDP网络连接,可以跨主机实现通信
  3. 服务端读取文件中保存的单词及其翻译,通过发送信号使服务端更新词库,不需要重启。

认识相关接口

socket

创建套接字文件,在Linux一切皆文件。。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

参数一为需要选择的通信方式:

image-20230725153425886

通常是使用AF_UNIX AF_INET,分别表示为本地通信和网络通信

参数二为套接字提供服务的类型,通常使用SOCK_STREAM:流式服务TCP策略,SOCK_DGRAM:数据报服务,UDP策略

image-20230725153821653

参数三默认设为0即可,因为前面两个参数已经确定好通信的方式和策略

返回值:成功创建返回文件的文件描述符, 失败返回-1

 _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
assert(_sockfd != -1);
cout << "success : " << _sockfd << endl;

bzero

可以将结构体对象初始化,和memset同理

#include <strings.h>

void bzero(void *s, size_t n);

bind

绑定端口号

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,
        socklen_t addrlen);

参数一为:需要绑定的文件描述符

参数二为:sockaddr结构体对象的地址,通常使用sockaddr_in对象强转,这个结构体对象里面就包括了传输方式,端口号,和ip地址

参数三为:这个结构体对象的大小

成功返回0

assert(bind(_sockfd, (struct sockaddr *)&local, sizeof(local)) == 0);

recvfrom

读取数据。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

参数一为:文件描述符

参数二为:接收数据的存储对象

参数三为:接收数据的存储对象的大小

参数四默认为0,表示阻塞读取

参数五为:一个结构体对象,输入输出型参数,该对象接收到后里面包含了发送端的信息,以便在未来可以往这个位置发回信息。

参数六为:接收到这个结构体对象的大小

成功返回数据的字节数,失败返回-1

ssize_t s = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&peer, &len);

sendto

发送数据

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
              const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

参数一为:文件描述符

参数二为:发送的数据的缓冲区

参数三为:发送数据的长度

参数四默认为0,阻塞发送

参数五为:结构体对象,里面包含了接收端的属性,ip地址等

参数六为:结构体对象大小

sendto(sockfd, res.c_str(), res.size(), 0, (sockaddr *)&client, sizeof(client));

实现思路和注意事项

思路:

  1. 首先可以对客户端和服务端分别进行封装
  2. 两者都具有初始化,启动功能。初始化主要负责初始化自身的IP地址,端口号和通信方式等
  3. 两者的启动都必须要有发送和读取的功能,客户端先发送再读取,服务端先读取再发送
  4. 服务端要有一个接收到数据后的回调函数,对数据进行处理后再发送回去
  5. 使用C++文件操作,加载文件里的词库

注意事项:

  1. 运行服务端时必须带上端口号,运行客户端必须带上IP地址和端口号
  2. 服务端必须显示绑定端口号,客户端不需要。操作系统会帮客户端自动生产并绑定端口号,因为服务端是只有一个,而访问这个服务端的客户端却会有很多个。
  3. 服务端的IP地址不能够指定某个特定的IP地址,必须使用0.0.0.0,因为会有很多个客户端访问,如果指明一个特定的IP地址,那么就可能出现别的IP访问不了端口号
  4. 注意端口号必须要调用接口去转换一下大小端,因为很多情况下都不清楚机器的大小端,养成好习惯
  5. 所有接口的参数都是 sockaddr类型的结构体,但是在使用的时候往往都是定义 sockaddr_in 结构体,传参的时候再强转。sockaddr_in的属性分别为:sin_family 传输方式;sin_port 端口号;sin_addr.s_addr IP地址

完整代码

以下代码均有注释,上述不完整的代码的注释里都有解释

Server.hpp

#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cassert>
#include <unistd.h>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;

typedef function<void(int, string, uint16_t, string)> func_t;
class udpServer
{
public:
    udpServer(const uint16_t &port, const func_t &funcCall)
        : _port(port), _ip("0.0.0.0"), _funcCall(funcCall)
    {
    }

    // 初始化服务器端
    void initServer()
    {
        // 创建socket
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        assert(_sockfd != -1);
        cout << "success : " << _sockfd << endl;

        // 定义socket_in结构体变量
        struct sockaddr_in local;
        // 初始化这个变量
        bzero(&local, sizeof(local));
        // 填充这个变量里的属性
        local.sin_family = AF_INET; // 指定传输方式
        // 指定端口号,不明确大小端所以要调用一下转换函数
        local.sin_port = htons(_port);
        // 指定IP地址, 首先要把字符串类型转换成网络IP的整型再转换大小端
        // 一般而言不会指明一个特定的IP地址,而是会设为0.0.0.0
        // 因为如果只绑定一个明确的IP,最终的数据可能用别的IP来访问端口号就会访问不了
        // INADDR_ANY就是0.0.0.0
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());
        local.sin_addr.s_addr = INADDR_ANY;

        // 绑定端口号
        assert(bind(_sockfd, (struct sockaddr *)&local, sizeof(local)) == 0);
    }

    // 启动服务器端
    void start()
    {
        char buff[1024];

        // 服务器本质就是一个死循环,除非紧急情况否则不退出
        while (1)
        {
            struct sockaddr_in peer;
            // 保存这个结构体大小的变量
            socklen_t len = sizeof(peer);

            ssize_t s = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                // 记录数据是什么,哪个IP地址发的,发到哪个端口
                // 首先peer里的IP地址是网络序列,所以要转化为整形再转成点分制的字符串
                string clientip = inet_ntoa(peer.sin_addr);
                // 端口号也要利用函数调用转换为16位的整形
                uint16_t clientport = ntohs(peer.sin_port);
                // 保存数据
                buff[s] = 0;
                string message = buff;

                // 读取数据
                cout << clientip << "[ #: " << clientport << "] : " << message << endl;

                // 处理数据后再发回客户端
                _funcCall(_sockfd, clientip, clientport, message);
            }

            sleep(1);
        }
    }

    ~udpServer()
    {
    }

private:
    uint16_t _port; // 端口号
    string _ip;     // ip地址
    int _sockfd;    // 创建socket后的网络文件描述符
    func_t _funcCall; // 回调方法
};

Server.cc

#include "Server.hpp"
#include <memory>
#include <unordered_map>
#include <fstream>
#include <signal.h>

#define textfile "./dict.txt"
// 保存字典
unordered_map<string, string> dict;

// 输出命令错误函数
void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_ip local_port\n\n";
}

// 读取一行中的kv值
bool getString(const string &line, string *key, string *value)
{
    auto pos = line.find(":");
    if (pos == string::npos)
        return false;

    // 分割两段字符串 分别提取
    *key = line.substr(0, pos);
    *value = line.substr(pos + 1);

    return true;
}

// 初始化字典
void Initdict()
{
    string key, value, line;

    // 打开文件读取内容插入到dict中
    ifstream ifs(textfile, ios::binary);
    if (!ifs.is_open())
    {
        cerr << "open file error" << endl;
        exit(3);
    }

    while (getline(ifs, line))
    {
        if (getString(line, &key, &value))
            dict.insert(make_pair(key, value));
    }

    ifs.close();

    cout << "dict success" << endl;
}

// 如果收到2号信号则重新读取文件重新加载dict
void reload(int signal)
{
    Initdict();
}

// 设置接收数据后的回调函数
void CallMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    // 对接收到的数据进行自定义处理
    // 与通信解耦

    // 查询接收到的单词并查找
    auto it = dict.find(message);
    string res;
    if (it == dict.end())
        res = "未查询到";
    else
        res = it->second;

    // 将查询到的结果返回去
    struct sockaddr_in client;
    client.sin_family = AF_INET;
    client.sin_addr.s_addr = inet_addr(clientip.c_str());
    client.sin_port = htons(clientport);

    sendto(sockfd, res.c_str(), res.size(), 0, (sockaddr *)&client, sizeof(client));
}

int main(int argc, char *argv[])
{
    // 从命令行获取命令
    // 其中包括端口号
    // 如果分割不为两部分就说明命令有误,输出错误信息后退出
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(2);
    }

    // 拿到端口号
    uint16_t port = atoi(argv[1]);

    // 如果收到2号信号则重新读取文件重新加载dict
    signal(2, reload);
    // 初始化字典
    Initdict();

    unique_ptr<udpServer> us(new udpServer(port, CallMessage));

    us->initServer();
    us->start();

    return 0;
}

Client.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <strings.h>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;

class udpClient
{
public:
    udpClient(const string &server_ip, const uint16_t &server_port)
        : _server_ip(server_ip), _server_port(server_port)
    {
    }

    void clientInit()
    {
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd == -1)
            exit(2);
        cout << "success : " << _sockfd << endl;

        // 客户端也需要绑定IP地址和端口,但是不需要显示绑定,操作系统会自动绑定
        // 客户端的端口号对服务端而言并不重要,它只需要确定自己的唯一性即可
        // 相当于写服务器的是一家公司,写客户端的是无数家公司,无数家公司之间只需要不冲突即可
    }

    void run()
    {
        string buff;
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = inet_addr(_server_ip.c_str());
        server_addr.sin_port = htons(_server_port);

        while (1)
        {
            cout << "Please cin:";
            cin >> buff;

            // sendto自动帮客户端绑定端口
            ssize_t s = sendto(_sockfd, buff.c_str(), buff.size(), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));

            // 接收服务端发回来的数据
            char message[1024];
            struct sockaddr_in temp;
            bzero(&temp, sizeof(temp));
            socklen_t len = sizeof(temp);
            size_t n = recvfrom(_sockfd, message, sizeof(message) - 1, 0, (struct sockaddr *)&temp, &len);
            if (n > 0)
                message[n] = 0;
            cout << "翻译结果:" << message << endl;
        }
    }

    ~udpClient()
    {
    }

private:
    int _sockfd;
    string _server_ip;
    uint16_t _server_port;
};

Client.cc

#include "Client.hpp"
#include <memory>

// 输出命令错误函数
void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " server_ip server_port\n\n";
}

int main(int argc, char* argv[])
{   
    // 从命令行获取命令
    // 其中包括服务端的IP地址和对应的端口号
    // 如果分割不为两部分就说明命令有误,输出错误信息后退出
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(2);
    }

    // 保存服务端的IP地址和端口号
    string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);

    unique_ptr<udpClient> cs(new udpClient(server_ip, server_port));

    cs->clientInit();
    cs->run();

    return 0;
}

运行效果

初始词库

image-20230725161246600

运行效果:

更新后词库

image-20230725161612973

运行:不需要重启服务端,发送2号信号(ctrl + c)

END

以上就是本篇简易的UDP英汉词典了,期待各位佬们能够指点一二。

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

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

相关文章

学术研究 #可视化工具 #学术文献绘图 #研究利器 #Citespace #vosviewer

目录 专题一 文献计量学方法与应用简介 专题二 主题确定、检索与数据采集 专题三 VOSviewer可视化绘图精讲 专题四 Citespace可视化绘图精讲 专题五 R语言文献计量学绘图分析 专题六 论文写作 专题七 论文投稿 文献计量学是指用数学和统计学的方法&#xff0c;定量地分析…

Jmeter接口/性能测试,Jmeter使用教程(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、线程组 线程组…

数据结构的复杂度

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大一&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 望小伙伴们点赞&#x1f44d;收藏✨加关注哟&#x1f495;&#x1…

Web浪漫历程:揭秘二十年间与您“约会”的浏览器发展

&#x1f9d1;‍&#x1f4bc; 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; &#x1f5a5;️ Node专栏&#xff1a;Node.js从入门到精通 &#x1f5a5;️ TS知识总结&#xff1a;十万字TS知识点总结 &#x1f449; 你的一键三连是我更新的最大动力❤️&#xff01;…

保护客户信息,金融行业的 DNS 泄漏风险

在金融行业中&#xff0c;保护客户信息一直是重中之重。随着网络技术的发展&#xff0c;各种网络安全威胁也层出不穷。其中&#xff0c;DNS 泄漏风险是金融行业需要重视的一个问题。 DNS 是域名系统的缩写&#xff0c;它是互联网中用于将域名解析成 IP 地址的系统。在金融行业中…

Jenkins+Gitlab集成CI/CD

前提是Jenkins&#xff0c;Maven&#xff0c;Gitlab&#xff0c;Docker环境已经搭建完毕并测试无误&#xff01; maven环境 java环境 git环境 Gitlab集成 保存应用 准备一个SpringBoot项目 配置好git仓库&#xff0c;推送到gitlab服务器上 点击立即构建 构建成功 查看服务器是…

ajax axios json

一、ajax概述 Ajax即Asynchronous Javascript And XML&#xff08;异步JavaScript和XML&#xff09;在 2005年被Jesse James Garrett提出的新术语&#xff0c;用来描述一种使用现有技术集合的‘新’方法&#xff0c;包括: HTML 或 XHTML, CSS, JavaScript, DOM, XML, XSLT, 以…

软考开发思考(完善中)

软考开发思考 文章目录 软考开发思考1. 互联网媒体&#xff1a;新技术和新应用及当前的趋势和应用1.1 自动化报道1.2. 虚拟和增强现实1.3. 数据新闻1.4. 即时新闻推送1.5 智能助手和聊天机器人1.6 语音播报&#xff0c;语音检索&#xff0c;后台播放、播放倍速。1.6 机器人交互…

信息安全运维经验

1.备份系统 国外主流&#xff1a;veritas NetBackUp&#xff08;NBU&#xff09;、IBM&#xff08;TSM&#xff09; (191条消息) 【大数据-文摘笔记】Veritas NBU简介_weixin_30501857的博客-CSDN博客 虚拟机玩转 Veritas NetBackup&#xff08;NBU&#xff09;之服务端安装…

组件间嵌套与父子组件通信

1.组件的嵌套 比如在App.vue内使用注册的ShowInfo组件,这就是组件嵌套,其中ShowInfo是子组件,App是父组件 ◼ 前面我们是将所有的逻辑放到一个App.vue中&#xff1a;  在之前的案例中&#xff0c;我们只是创建了一个组件App&#xff1b;  如果我们一个应用程序将所有的逻…

第八次CCF计算机软件能力认证

第一题&#xff1a;最大波动 小明正在利用股票的波动程度来研究股票。 小明拿到了一只股票每天收盘时的价格&#xff0c;他想知道&#xff0c;这只股票连续几天的最大波动值是多少&#xff0c;即在这几天中某天收盘价格与前一天收盘价格之差的绝对值最大是多少。 输入格式 输入…

C++_01_初步认识C++语言

本人博客园亦可见 一、认识 “C语言” 一、首先聊聊什么是语言&#xff1f; 语言是一套具有“语法”、“词法”规律的系统&#xff0c;是思维的工具。   计算程序设计语言是计算机可以识别的语言&#xff0c;用于描述解决问题的方法&#xff0c;供计算机阅读和执行。 语言由…

火山引擎DataLeap的Data Catalog系统公有云实践 (上)

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 前言 Data Catalog 通过汇总技术和业务元数据&#xff0c;解决大数据生产者组织梳理数据、数据消费者找数和理解数的业务场景。本篇内容源自于火山引擎大数据研发治…

《合成孔径雷达成像算法与实现》Figure2.18与2.20

代码复现如下&#xff1a; xlinspace(-20,20,32); ylinspace(-20,20,32); SINC_1zeros(length(x),length(y)); for i1:length(x)for j1:length(y)SINC_1(i,j)sinc(x(i))*sinc(y(j));end end%SINC_1imrotate(SINC_1,8,bilinear,crop); %Zfftshift(fft2(SINC_1)); Zfft2(SINC_1)…

[Python进阶] 元类metaclass(type类)及object类

4.9 元类metaclass(type类)及object类 4.9.1 object类 在面向对象编程中&#xff0c;类是对象的蓝图或模板。类定义了一组属性和方法&#xff0c;并且根据该模板可以创建新的对象。由于每个对象都是基于类来创建的&#xff0c;因此它们共享相同的属性和方法。 object类是一个…

Docker私有仓库部署与管理

目录 Docker--harbor Harbor 简介 Harbor 部署 1. 部署 Docker-Compose 服务 2. 部署 Harbor 服务 维护管理Harbor 1. 通过 Harbor Web 创建项目 2. 创建 Harbor 用户 3. 查看日志 4. 修改 Harbor.cfg 配置文件 5. 移除 Harbor 服务容器同时保留镜像数据/数据库&…

解析数字孪生的现在和未来

数字孪生是一种将现实世界与数字世界相连接的技术&#xff0c;它可以通过建立数字化的物理模型来模拟和预测现实世界的行为和性能&#xff0c;随着技术的成熟逐渐在越来越多行业得以应用&#xff0c;那有没有人好奇数字孪生是怎么来的呢&#xff1f;今天就带大家来盘一盘数字孪…

Unity中的MonoBehaviour 及其生命周期

关于MonoBehaviour 类的类图的详细介绍&#xff1a; Unity中的MonoBehaviour脚本-基础知识和继承关系_拂面清风三点水的博客-CSDN博客 关于MonoBehaviour 类的生命周期&#xff1a; Unity - Manual: Order of execution for event functions&#xff1a; Awake&#xff1a;当…

脑电信号处理与特征提取——5.频谱分析和时频分析(张治国)

目录 五、频谱分析和时频分析 5.1 频谱估计 5.1.1 基本概念 5.1.2 频谱估计方法&#xff1a;周期图 5.1.3 频谱估计方法&#xff1a;Welch法 5.1.4 频谱估计方法的比较 5.1.5 频谱特征提取 5.2 时频分析 5.2.1 短时傅里叶变换 5.2.2 连续小波变换 5.3 事件相关同步…

24考研数据结构-线性表6

目录 2.4.8 静态链表2.4.9 顺序表和链表的比较2.4.9.1 逻辑结构2.4.9.2 存储结构2.4.9.3 基本操作 - 创建2.4.9.4 基本操作 - 销毁2.4.9.5 基本操作-增/删2.4.9.6 基本操作-查2.4.9.7 顺序、链式、静态、动态四种存储方式的比较2.4.9.8 存储密度的问题2.4.9.9 存储方式的选择以…