计算机网络socket编程(2)_UDP网络编程实现网络字典
个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创计算机网络socket编程(2)_UDP网络编程实现网络字典
收录于专栏【计算机网络】
本专栏旨在分享学习计算机网络的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
功能介绍 :
dict.txt
1. nocopy.hpp
2. InetAddr.hpp
3. Log.hpp
4. LockGuard.hpp
5. Dict.hpp
6. UdpServer.hpp
7. UdpServerMain.cc
8. UdpClientMain.cc
9. 效果展示
功能介绍 :
实现一个简单的英译汉的网络字典
dict.txt
这里就放一些简单的单词, 方便测试~
1. nocopy.hpp
定义一个 nocopy 的类, 通过 C++11 delete 关键字的特性, 阻止该类的拷贝构造和拷贝赋值.
#pragma onceclass nocopy
{
public:nocopy(){}~nocopy(){}nocopy(const nocopy&) = delete;const nocopy& operator=(const nocopy&) = delete;
};
2. InetAddr.hpp
InetAddr 类它封装了网络地址 (IP 端口), 以及提供了访问这些信息的方法, 该类通过了 struct sockaddr_in 来存储 IP 地址和端口, 并提供了转换和获取信息的功能
#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;
};
成员变量 :
_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. Log.hpp
这段代码实现了一个简单的日志系统, 可以将日志信息输出到控制台或文件中.
#pragma once#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include <pthread.h>
#include "LockGuard.hpp"namespace log_ns
{enum{DEBUG = 1,INFO,WARNING,ERROR,FATAL};std::string LevelToString(int level){switch (level){case DEBUG:return "DEBUG";case INFO:return "INFO";case WARNING:return "WARNING";case ERROR:return "ERROR";case FATAL:return "FATAL";default:return "UNKNOWN";}}std::string GetCurrTime(){time_t now = time(nullptr);struct tm *curr_time = localtime(&now);char buffer[128];snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",curr_time->tm_year + 1900,curr_time->tm_mon + 1,curr_time->tm_mday,curr_time->tm_hour,curr_time->tm_min,curr_time->tm_sec);return buffer;}class logmessage{public:std::string _level;pid_t _id;std::string _filename;int _filenumber;std::string _curr_time;std::string _message_info;};#define SCREEN_TYPE 1
#define FILE_TYPE 2const std::string glogfile = "./log.txt";pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;// log.logMessage("", 12, INFO, "this is a %d message ,%f, %s hellwrodl", x, , , );class Log{public:Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE){}void Enable(int type){_type = type;}void FlushLogToScreen(const logmessage &lg){printf("[%s][%d][%s][%d][%s] %s",lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._curr_time.c_str(),lg._message_info.c_str());}void FlushLogToFile(const logmessage &lg){std::ofstream out(_logfile, std::ios::app);if (!out.is_open())return;char logtxt[2048];snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._curr_time.c_str(),lg._message_info.c_str());out.write(logtxt, strlen(logtxt));out.close();}void FlushLog(const logmessage &lg){// 加过滤逻辑 --- TODOLockGuard lockguard(&glock);switch (_type){case SCREEN_TYPE:FlushLogToScreen(lg);break;case FILE_TYPE:FlushLogToFile(lg);break;}}void logMessage(std::string filename, int filenumber, int level, const char *format, ...){logmessage lg;lg._level = LevelToString(level);lg._id = getpid();lg._filename = filename;lg._filenumber = filenumber;lg._curr_time = GetCurrTime();va_list ap;va_start(ap, format);char log_info[1024];vsnprintf(log_info, sizeof(log_info), format, ap);va_end(ap);lg._message_info = log_info;// 打印出来日志FlushLog(lg);}~Log(){}private:int _type;std::string _logfile;};Log lg;#define LOG(Level, Format, ...) \do \{ \lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \} while (0)
#define EnableScreen() \do \{ \lg.Enable(SCREEN_TYPE); \} while (0)
#define EnableFILE() \do \{ \lg.Enable(FILE_TYPE); \} while (0)
};
日志级别定义 :
定义了五个日志级别:DEBUG、INFO、WARNING、ERROR 和 FATAL,数字1到5代表不同的优先级。
DEBUG :
含义: DEBUG 级别的日志用于调试目的,主要记录程序运行中的详细信息,帮助开发人员理解程序的内部状态。这些信息通常对开发人员在开发、调试、分析问题时非常有用。
应用场景:
记录函数调用的输入输出参数。
输出变量的值、内存地址或其他详细的系统状态信息。
打印细节,像是某个功能执行的具体步骤、算法的中间结果等。
优先级: 最低的日志优先级,通常只在开发环境中启用。
INFO :
含义: INFO 级别的日志用于记录常规信息,表示系统按预期运行的正常操作。这些日志通常用于展示系统的状态或者某些操作的成功执行,提供程序执行过程中有用的背景信息。
应用场景:
系统启动或停止。
配置的加载。
用户执行操作的成功消息。
优先级: 相对较低,但仍然是一个有用的日志级别。适用于生产环境中记录系统的常规行为和关键事件。
WARNING :
含义: WARNING 级别的日志用于记录潜在的问题或不正常的情况,这些情况可能不会导致程序崩溃或立即失败,但可能会影响程序的执行或系统的稳定性。警告通常表明某些操作可能需要关注或修改,但并不一定立即需要处理。
应用场景:
配置文件中的可疑设置或过时的配置项。
资源的使用接近极限,比如内存占用接近最大值。
网络延迟、连接问题等。
优先级: 高于 INFO 级别,意味着它是需要关注的,但不至于影响程序的正常运行。
ERROR :
含义: ERROR 级别的日志用于记录系统中发生的错误,这些错误通常会导致某些功能或操作失败,但不会完全中断系统的运行。错误通常需要被开发人员注意和修复,或者需要采取措施来避免进一步的问题。
应用场景:
数据库连接失败。
文件读写错误。
网络请求失败。
输入参数错误,导致某个功能不能正常执行。
优先级: 高于 WARNING 级别,意味着这类问题更严重,但仍然不是致命的。通常会影响用户体验,需要开发人员快速处理。
FATAL :
含义: FATAL 级别的日志用于记录严重错误,这些错误通常会导致程序崩溃或系统完全无法继续运行。FATAL 级别的日志代表最严重的错误,需要立即处理。这些错误通常是程序中的致命缺陷,可能需要紧急修复或采取特殊措施来恢复系统的正常运行。
应用场景:
程序崩溃或内存泄漏导致系统无法继续运行。
系统无法启动或重要组件丢失。
某些关键操作失败,无法继续执行后续步骤。
优先级: 最高的日志优先级。需要立即采取措施,通常需要系统管理员或开发人员介入。
LevelToString 函数, 该函数用于将日志级别数字转换为对应的字符串:
GetCurrTime 函数, 获取当前系统时间并格式化为字符串。使用 strftime 格式化时间,返回一个格式化后的时间字符串:
logmessage 类用于封装日志消息的结构体,包含:
_level:日志级别
_id:进程ID
_filename:源代码文件名
_filenumber:行号
_curr_time:当前时间
_message_info:实际的日志消息内容
Log 类是日志管理的核心类,包含了日志的输出控制和处理方法。它的主要功能是:
构造函数:接受一个日志文件路径并初始化日志类型为屏幕输出(SCREEN_TYPE)。
Enable 方法:设置日志输出类型,支持屏幕输出或文件输出。
FlushLogToScreen:将日志信息输出到屏幕。
FlushLogToFile:将日志信息写入文件。默认文件路径为 ./log.txt。
FlushLog:根据日志类型决定将日志输出到屏幕还是文件。使用 LockGuard 实现线程安全。
logMessage 方法:日志记录的核心方法,接受文件名、行号、日志级别和日志内容格式化字符串。通过 va_list 支持可变参数。
析构函数:析构时没有特别的清理操作。
宏函数定义 :
#define LOG(Level, Format, ...)
LOG 是一个宏,用于简化日志记录的调用。这个宏接受以下参数:
Level: 日志的级别(如 INFO, DEBUG, ERROR 等)。
Format: 格式化字符串,类似于 printf 中使用的格式说明符(例如 %s, %d)。
...: 可变参数,可以传递给 logMessage 方法,具体取决于格式字符串和实际参数。
do { ... } while (0):这种结构常用于宏定义中,目的是将宏的多条语句包裹在一个代码块中,并确保宏使用时不受周围代码的影响。它保证了宏的调用语句总是作为一个完整的语句来执行。
__FILE__ 和 __LINE__:这两个预定义宏分别提供当前源文件的文件名和当前行号,用来记录日志发生的位置。
lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__):这是宏的核心部分,调用 Log 类的 logMessage 方法。它将当前文件名、行号、日志级别、格式字符串和变长参数传递给 logMessage。
__FILE__ 和 __LINE__ 提供日志的上下文信息。
Level 指定日志的级别(如 INFO、ERROR 等)。
Format 是日志的格式字符串。
##__VA_ARGS__ 用来传递实际的参数到 logMessage 中,## 语法在某些编译器中可以去掉多余的逗号(比如如果没有传递变长参数时)。
#define EnableScreen()
该宏用于启用屏幕日志输出。宏的定义如下:
lg.Enable(SCREEN_TYPE):调用 Log 类的 Enable 方法,传递一个常量 SCREEN_TYPE,该常量可能表示日志的输出目标是屏幕。具体来说,Enable 方法是配置日志输出的目标或启用某种日志记录模式。
do { ... } while (0):同样,使用这种方式包裹宏,确保宏调用的语法不会受到其他代码块的影响
#define EnableFILE()
lg.Enable(FILE_TYPE):调用 Log 类的 Enable 方法,传递一个常量 FILE_TYPE,表示日志的输出目标是文件。FILE_TYPE 是一个标志常量,表示日志应该写入到文件而不是其他地方(例如屏幕)。
do { ... } while (0):同样使用这个结构,以确保宏调用时不受外部代码的影响。
4. LockGuard.hpp
#pragma once#include <pthread.h>class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGuard(){pthread_mutex_unlock(_mutex);}
private:pthread_mutex_t *_mutex;
};
这段代码定义了一个名为 LockGuard 的 C++ 类,其目的是为多线程环境中的互斥锁(pthread_mutex_t)提供一种自动管理锁的机制。LockGuard 是一种 RAII(Resource Acquisition Is Initialization) 风格的锁管理类,用于简化锁的获取和释放。
构造函数:当 LockGuard 对象被创建时,它会接受一个指向互斥锁的指针 mutex,并将其存储在 _mutex 成员变量中。pthread_mutex_lock(_mutex):此语句通过 pthread_mutex_lock 函数请求对传入的互斥锁的独占访问权限。若锁已经被其他线程持有,当前线程将会被阻塞,直到锁变为可用。这样,在 LockGuard 对象被创建时,它会立即获取互斥锁,确保对共享资源的访问在 LockGuard 对象的生命周期内是安全的。
析构函数:当 LockGuard 对象超出作用域(即生命周期结束时),析构函数会被自动调用。pthread_mutex_unlock(_mutex):在析构函数中,调用 pthread_mutex_unlock 来释放锁。这样,互斥锁在 LockGuard 对象销毁时被自动解锁,避免了手动解锁的遗漏,保证了线程安全。
RAII 特性
LockGuard 类的核心是 RAII(资源获取即初始化)模式。通过这种模式,在对象创建时自动获取锁,在对象销毁时自动释放锁,避免了程序员忘记解锁的情况。RAII 确保了即使发生异常,锁也能被正确释放,因为栈上的局部对象(如 LockGuard)会在离开作用域时自动销毁,从而触发析构函数并释放资源。
5. Dict.hpp
// 实现一个简单的英译汉的网络字典
#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include <unistd.h>
#include "Log.hpp"using namespace log_ns;const static std::string sep = ": ";// sad: 悲伤的class Dict
{
private:void LoadDict(const std::string &path){std::ifstream in(path);if (!in.is_open()){LOG(FATAL, "open %s failed!\n", path.c_str());exit(1);}std::string line;while (std::getline(in, line)){LOG(DEBUG, "load info: %s , success\n", line.c_str());if (line.empty())continue;auto pos = line.find(sep);if (pos == std::string::npos)continue;std::string key = line.substr(0, pos);if (key.empty())continue;std::string value = line.substr(pos + sep.size());if (value.empty())continue;_dict.insert(std::make_pair(key, value));}LOG(INFO, "load %s done\n", path.c_str());in.close();}public:Dict(const std::string &dict_path) : _dict_path(dict_path){LoadDict(_dict_path);}std::string Translate(std::string word){if(word.empty()) return "None";auto iter = _dict.find(word);if(iter == _dict.end()) return "None";else return iter->second;}~Dict(){}private:std::unordered_map<std::string, std::string> _dict;std::string _dict_path;
};
const static std::string sep = ": ";
这里定义了一个常量 sep,它是字典文件中键值对之间的分隔符。在字典文件中,格式是 "word: translation",即单词和翻译之间由 ": " 分
Dict 类定义
私有成员 :
_dict:一个 unordered_map 类型的成员变量,用来存储词典数据。键是单词(std::string),值是对应的翻译(std::string)。
_dict_path:存储字典文件路径的成员变量。
LoadDict 方法 :
LoadDict 方法负责从指定路径加载字典文件,并将内容解析到 _dict 成员变量中
打开文件:使用 std::ifstream 打开字典文件。若打开失败,记录日志并退出程序。
逐行读取文件:使用 std::getline 逐行读取文件内容
解析字典内容 :
跳过空行。
查找 sep(即 ": ")在行中的位置,分割单词和翻译。
使用 substr 提取单词(key)和翻译(value)。
将有效的单词-翻译对插入到 _dict 中。
日志记录:
在加载每一行时记录调试级别日志。
在整个字典加载完成后记录信息级别日志。
最后关闭文件
公共方法
构造函数:在构造函数中调用 LoadDict 方法加载字典文件。
Translate 方法:
该方法用于翻译给定的单词(word)。
如果单词为空,返回 "None"。
使用 unordered_map 的 find 方法查找单词。如果找不到,返回 "None",否则返回对应的翻译
析构函数 : 析构函数目前没有特别的操作,因为 std::unordered_map 会自动管理内存。
6. UdpServer.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <functional>
#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
};using func_t = std::function<std::string(std::string)>;
// UdpServer user("192.1.1.1", 8899)
// 一般服务器主要是用来进行网络数据读取和写入的, IO的
// 服务器IO逻辑 和 业务逻辑 解耦class UdpServer : public nocopy
{
public:UdpServer(func_t func, uint16_t localport = glocalport):_func(func),_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", _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 = INADDR_ANY; // 服务器端, 进行任意 IP 地址绑定int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if(n < 0) {LOG(FATAL, "bind error");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 result = _func(inbuffer);sendto(_sockfd, result.c_str(), result.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;bool _isrunning;func_t _func;
};
这段代码实现了一个简单的 UDP 服务器类 UdpServer,能够接收客户端发送的消息,并通过用户提供的回调函数处理消息后返回响应。该类还实现了日志记录、套接字管理、地址绑定等基础的网络通信功能。
全局常量与类型定义
gsockfd:默认的套接字文件描述符,设置为 -1 作为无效值。
glocalport:默认的本地端口号,设置为 8888,如果未传递特定端口号,默认使用此端口。
错误码枚举:定义了 SOCKET_ERROR 和 BIND_ERROR,用于表示套接字创建和绑定失败的错误。
func_t 类型别名
func_t:定义了一个类型别名 func_t,表示一个回调函数类型。该函数接收一个 std::string 类型的参数,并返回一个 std::string 类型的结果。通过这个回调函数,服务器可以自定义处理接收到的数据。
1. using 关键字
using 是 C++11 引入的关键字,用于定义类型别名。它与传统的 typedef 类似,但更现代、更简洁。using 可以为复杂的类型提供更直观、更易读的别名。
例如,using 可以让我们更方便地为模板类型定义别名,这在某些情况下比 typedef 更易于理解。
这种写法在 C++11 和之后的标准中更常见,特别是在模板类型、函数类型等复杂类型定义时。
2. std::function<std::string(std::string)>
std::function 是 C++11 标准库提供的一个模板类,它用于封装任何可调用对象(例如函数指针、函数对象、Lambda 表达式、成员函数指针等),使它们能够被统一处理。
std::function 是一个通用的函数包装器,它提供了统一的接口来调用不同类型的可调用对象。具体来说,std::function 可以封装函数、函数指针、Lambda 表达式、绑定函数、成员函数等。
std::function<std::string(std::string)> 表示一个接受 std::string 类型参数并返回 std::string 类型结果的可调用对象。
解释一下 std::function<std::string(std::string)>:
std::string:表示返回类型,即被封装的函数调用时会返回一个 std::string 类型的结果。
std::string(std::string):表示封装的函数类型,即该函数接受一个 std::string 类型的参数,返回一个 std::string 类型的值。
简单来说,std::function<std::string(std::string)> 是一个可以封装任何接受 std::string 参数并返回 std::string 的可调用对象。
3. func_t 类型别名
func_t 是通过 using 定义的类型别名,它代表了一个类型为 std::function<std::string(std::string)> 的函数对象。
也就是说,func_t 类型可以表示:
普通函数
Lambda 表达式
函数对象(即重载了 operator() 的对象)
函数指针等
这些可调用对象的共同特点是,它们都能够接收一个 std::string 类型的参数,并返回一个 std::string 类型的值。
UdpServer 类
UdpServer 类是实现 UDP 服务器功能的核心部分。它继承自 nocopy,意味着不能拷贝该类的实例。
成员变量 :
_sockfd:存储套接字文件描述符,用于进行网络通信。
_localport:服务器监听的本地端口号。
_isrunning:标志服务器是否正在运行。
_func:存储一个回调函数,用于处理接收到的数据。
构造函数
func:构造函数接受一个回调函数 func,用于处理接收到的消息。
localport:设置监听端口,如果没有提供,默认使用 glocalport(8888)。
InitServer 方法
创建套接字:使用 ::socket() 创建一个 UDP 套接字。AF_INET 表示 IPv4 地址族,SOCK_DGRAM 表示 UDP 类型。
如果创建失败,记录错误日志并退出。
绑定套接字:::bind() 将套接字与指定的本地端口绑定。
local.sin_family = AF_INET:表示使用 IPv4 地址。
local.sin_port = htons(_localport):设置本地端口号(使用 htons() 转换为网络字节序)。
local.sin_addr.s_addr = INADDR_ANY:绑定到所有本地网络接口的 IP 地址。
Start 方法
循环接收消息:recvfrom() 从套接字接收数据,并将数据存储在 inbuffer 中。该方法会阻塞,直到接收到数据。
peer:存储客户端的地址信息(IP 和端口)。
len:保存 peer 地址的大小。
n:接收到的字节数。
如果接收到数据,则调用传入的回调函数 _func 对数据进行处理,并通过 sendto() 发送回响应数据。
处理异常:如果 recvfrom() 返回错误(n <= 0),则输出错误信息。
析构函数
关闭套接字:当服务器停止运行时,关闭套接字,释放资源。
7. UdpServerMain.cc
#include "UdpServer.hpp"
#include "Dict.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();Dict dict("./dict.txt");func_t translate = std::bind(&Dict::Translate, &dict, std::placeholders::_1);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(translate, port); //C++14的标准usvr->InitServer();usvr->Start();return 0;
}
这段代码实现了一个基于 UdpServer 的 UDP 服务器,目的是通过翻译字典(由 Dict 类提供的功能)处理客户端发送的请求。通过网络通信,服务器接收客户端发来的数据,根据字典内容进行翻译并返回结果。
int main(int argc, char *argv[])
命令行参数检查:main 函数首先检查传入的参数数量。如果参数不等于 2(即程序名和端口号),则打印使用说明并退出。参数的第一个是程序名,第二个是本地监听端口。
argv[0]:程序本身的路径或名称。
argv[1]:传入的本地端口号,用于 UDP 服务器的绑定。
端口号解析
端口号转换:通过 std::stoi() 函数将命令行输入的字符串 argv[1] 转换为 uint16_t 类型的端口号。这个端口号将用来绑定 UDP 套接字。
启用屏幕输出
这里调用了 EnableScreen() 函数,它用于初始化或启用屏幕输出/日志记录功能。
创建 Dict 对象
字典文件加载:创建一个 Dict 类的实例 dict,并从文件 dict.txt 加载字典内容。该文件路径传递给 Dict 的构造函数
创建翻译回调函数
创建回调函数:这里使用了 std::bind 来绑定 Dict 类的成员函数 Translate,将 dict 对象和 std::placeholders::_1 作为占位符传递给回调函数。std::placeholders::_1 代表传递给回调函数的第一个参数。
std::bind(&Dict::Translate, &dict, std::placeholders::_1) 会返回一个新的可调用对象 translate,它能够接收一个 std::string 类型的参数并调用 dict.Translate() 方法进行翻译。
创建 UdpServer 对象
创建 UDP 服务器:使用 std::make_unique<UdpServer> 创建一个 UdpServer 对象,并传递 translate 回调函数和端口号 port。
这意味着 UDP 服务器将在启动时使用传入的端口号进行绑定,并将 translate 作为数据处理的回调函数。当服务器接收到数据时,它将调用 translate 函数来处理收到的数据并返回结果。
初始化和启动服务器
初始化服务器:usvr->InitServer() 初始化服务器,创建套接字并绑定端口。这一步会创建 UDP 套接字并将其绑定到本地端口 port。
启动服务器:usvr->Start() 启动服务器,开始接收客户端的请求并处理数据。这个方法会进入一个循环,不断接收客户端数据,并调用 translate 函数进行处理后发送回响应。
8. 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. 程序输入检查
argc 是命令行参数的数量,argv 是一个字符串数组,其中存储了命令行输入的参数。
代码检查命令行参数数量是否正确。如果用户没有提供服务器的 IP 地址和端口号(即参数数量不等于 3),则输出使用帮助信息并退出程序。
2. 服务器地址和端口号的解析
serverip:获取用户输入的服务器 IP 地址(例如 "127.0.0.1")。
serverport:获取用户输入的服务器端口号,并将其转换为 uint16_t 类型。
3. 创建 UDP 套接字
使用 ::socket 函数创建一个套接字。
AF_INET:指定使用 IPv4 协议。
SOCK_DGRAM:指定使用 数据报 套接字,适用于 UDP。
0:通常表示选择默认的协议(对于 UDP,通常是 IPPROTO_UDP)。
如果创建套接字失败,socket 返回值小于零,程序会输出错误信息并退出。
4. 构造服务器地址结构
sockaddr_in 结构用于存储 IPv4 地址和端口信息。
memset(&server, 0, sizeof(server)):将 server 结构体初始化为 0,避免不必要的垃圾数据。
server.sin_family = AF_INET:指定协议族为 IPv4。
server.sin_port = htons(serverport):将端口号转换为网络字节顺序(htons 是 host to network short,将主机字节顺序转换为网络字节顺序)。
server.sin_addr.s_addr = inet_addr(serverip.c_str()):将字符串格式的 IP 地址转换为网络字节顺序的地址。
5. 发送和接收数据
发送数据
std::getline(std::cin, line):从标准输入读取用户输入的字符串。
sendto:通过 UDP 套接字向服务器发送数据。
sockfd:套接字描述符。
line.c_str():要发送的消息,c_str() 返回一个指向 std::string 数据的指针,sendto 需要 C 风格的字符串。
line.size():消息的长度。
0:标志位(通常为 0,没有特定的设置)。
(struct sockaddr*)&server:服务器的地址信息。
sizeof(server):地址结构的大小。
sendto 返回发送的字节数,若发送成功,返回大于零的值。
接收数据
recvfrom:接收来自服务器的响应。
buffer:接收数据的缓冲区。
sizeof(buffer)-1:指定缓冲区大小,recvfrom 会在收到数据后填充该缓冲区。
(struct sockaddr*)&temp:接收者的地址信息,虽然客户端在此不需要使用接收者的地址,但仍需传入一个 sockaddr_in 类型的变量来接收。
&len:地址结构的大小,用于接收 recvfrom 填充的信息。
如果 recvfrom 成功,返回实际接收的字节数;如果失败,返回负值。
数据处理
如果成功接收到数据,则将数据写入输出流(std::cout)并打印接收到的消息。
如果接收失败,输出错误信息并退出循环。
6. 关闭套接字
9. 效果展示
这里我们做的比较简陋, 只能英翻中~~ 不过我们实现还是没有问题的, 好了, 篇幅已经很长了, 网络编程是这样的, 我们要考虑的东西很多, 我们下期再见~~
相关文章:
计算机网络socket编程(2)_UDP网络编程实现网络字典
个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 计算机网络socket编程(2)_UDP网络编程实现网络字典 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记,欢迎大家在评论区交流讨…...
(Keil)MDK-ARM各种优化选项详细说明、实际应用及拓展内容
参考 MDK-ARM各种优化选项详细说明、实际应用及拓展内容 本文围绕MDK-ARM优化选项,以及相关拓展知识(微库、实际应用、调试)进行讲述,希望对你今后开发项目有所帮助。 1 总述 我们所指的优化,主要两方面: 1.代码大小(Size) 2.代码性能(运行时间) 在MDK-ARM中,优…...
mac2024 安装node和vue
以下是使用 Node.js 官方 .pkg 安装包 安装 Node.js 和 Vue CLI 的完整流程,包括如何重新设置 npm 的环境,以避免权限问题。 安装 Node.js 步骤 1.1:下载 Node.js 安装包 1. 打开 Node.js 官网。 2. 下载 LTS(长期支持…...
在win10环境部署opengauss数据库(包含各种可能遇到的问题解决)
适用于windows环境下通过docker desktop实现opengauss部署,请审题。 文章目录 前言一、部署适合deskdocker的环境二、安装opengauss数据库1.配置docker镜像源2.拉取镜像源 总结 前言 注意事项:后面docker拉取镜像源最好电脑有科学上网工具如果没有科学上…...
Docker1:认识docker、在Linux中安装docker
欢迎来到“雪碧聊技术”CSDN博客! 在这里,您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者,还是具有一定经验的开发者,相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导,我将…...
鸿蒙开发-音视频
Media Kit 特点 一般场合的音视频处理,可以直接使用系统集成的Video组件,不过外观和功能自定义程度低Media kit:轻量媒体引擎,系统资源占用低支持音视频播放/录制,pipeline灵活拼装,插件化扩展source/demu…...
Vue3学习笔记
目录 Vue3Vue3优势Vue3组合式API & Vue2选项式APIcreate-vue使用create-vue创建项目 项目目录和关键文件组合式API-setup选项组合式API-reactive和ref函数reactive()ref() 组合式API-computed组合式API-watch基础使用immdiate和deep配置精确侦听对象的某个属性 组合式API-生…...
node + Redis + svg-captcha 实现验证码
目录 前提说明 Redis链接与封装 svg-captcha使用步骤 封装中间件验证 前端接收 扩展【svg API】 svgCaptcha.create(options) svgCaptcha.createMathExpr(options) svgCaptcha.loadFont(url) svgCaptcha.options svgCaptcha.randomText([size|options]) svgCaptcha(…...
dubbo-go框架介绍
框架介绍 什么是 dubbo-go Dubbo-go 是 Apache Dubbo 的 go 语言实现,它完全遵循 Apache Dubbo 设计原则与目标,是 go 语言领域的一款优秀微服务开发框架。dubbo-go 提供: API 与 RPC 协议:帮助解决组件之间的 RPC 通信问题&am…...
玛哈特矫平机:工业制造中的平整利器
在日新月异的工业制造领域,每一个细节都至关重要。而在这其中,矫平机以其独特的功能和卓越的性能,成为了不可或缺的重要工具。它就像一位技艺高超的工匠,精心雕琢着每一件工业产品,赋予它们平整、光滑的表面。 矫平机…...
IDEA 2024安装指南(含安装包以及使用说明 cannot collect jvm options 问题 四)
汉化 setting 中选择插件 完成 安装出现问题 1.可能是因为之前下载过的idea,找到连接中 文件,卸载即可。...
Jmeter中的定时器
4)定时器 1--固定定时器 功能特点 固定延迟:在每个请求之间添加固定的延迟时间。精确控制:可以精确控制请求的发送频率。简单易用:配置简单,易于理解和使用。 配置步骤 添加固定定时器 右键点击需要添加定时器的请求…...
共享单车管理系统项目学习实战
前言 Spring Boot Vue前后端分离 前端:Vue(CDN) Element axios(前后端交互) BaiDuMap ECharts(图表展示) 后端:Spring Boot Spring MVC(Web) MyBatis Plus(数据库) 数据库:MySQL 验证码请求 git提交 cd C:/Users/Ustini…...
学Linux的第九天--磁盘管理
目录 一、磁盘简介 (一)、认知磁盘 (1)结构 (2)物理设备的命名规则 (二)、磁盘分区方式 MBR分区 MBR分区类型 扩展 GPT格式 lsblk命令 使用fdisk管理分区 使用gdisk管理分…...
CLIP-Adapter: Better Vision-Language Models with Feature Adapters 论文解读
abstract 大规模对比视觉-语言预训练在视觉表示学习方面取得了显著进展。与传统的通过固定一组离散标签训练的视觉系统不同,(Radford et al., 2021) 引入了一种新范式,该范式在开放词汇环境中直接学习将图像与原始文本对齐。在下游任务中,通…...
D74【 python 接口自动化学习】- python 基础之HTTP
day74 http基础定义 学习日期:20241120 学习目标:http定义及实战 -- http基础介绍 学习笔记: HTTP定义 HTTP 是一个协议(服务器传输超文本到浏览器的传送协议),是基于 TCP/IP 通信协议来传递数据&…...
维护表空间和数据文件(一)
学习目标 定义表空间和数据文件的用途创建表空间管理表空间使用Oracle管理文件(OMF)创建和管理表空间获取表空间信息 表空间和数据文件 Oracle逻辑上将数据存储在表空间中,物理上将数据存储在数据文件中。 Tablespaces: 一次只…...
H.265流媒体播放器EasyPlayer.js H5流媒体播放器关于如何查看手机端的日志信息并保存下来
现今流媒体播放器的发展趋势将更加多元化和个性化。人工智能的应用将深入内容创作、用户体验优化等多个方面,带来前所未有的个性化体验。 EasyPlayer.js H.265流媒体播放器属于一款高效、精炼、稳定且免费的流媒体播放器,可支持多种流媒体协议播放&#…...
Apple Vision Pro开发003-PolySpatial2.0新建项目
unity6.0下载链接:Unity 实时开发平台 | 3D、2D、VR 和 AR 引擎 一、新建项目 二、导入开发包 com.unity.polyspatial.visionos 输入版本号 2.0.4 com.unity.polyspatial(单独导入),或者直接安装 三、对应设置 其他的操作与之前的版本相同…...
解决 npm xxx was blocked, reason: xx bad guy, steal env and delete files
问题复现 今天一位朋友说,vue2的老项目安装不老依赖,报错内容如下: npm install 451 Unavailable For Legal Reasons - GET https://registry.npmmirror.com/vab-count - [UNAVAILABLE_FOR_LEGAL_REASONS] vab-count was blocked, reas…...
leetcode 面试150之 156.LUR 缓存
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -…...
Vue 动态给 data 添加新属性深度解析:问题、原理与解决方案
在 Vue 中,动态地向 data 中添加新的属性是一个常见的需求,但它也可能引发一些问题,尤其是关于 响应式更新 和 数据绑定 的问题。Vue 的响应式系统通过 getter 和 setter 来追踪和更新数据,但 动态添加新属性 时,Vue 并不会自动为这些新属性创建响应式链接。 1. 直接向 V…...
Unity类银河战士恶魔城学习总结(P141 Finalising ToolTip优化UI显示)
【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/ UI部分暂时完结!!! 本章节优化了UI中物品描述的显示效果,技能描述的显示效果 并且可以批…...
面试:请阐述MySQL配置文件my.cnf中参数log-bin和binlog-do-db的作用
大家好,我是袁庭新。星球里的小伙伴去面试,面试官问:MySQL配置文件my.cnf中参数log-bin和binlog-do-db的作用?一脸懵逼~不知道该如何回答。 在MySQL的配置文件my.cnf中,log-bin和binlog-do-db是与二进制日志…...
监控报警系统的指标、规则与执行闭环
随笔 从千万粉丝“何同学”抄袭开源项目说起,为何纯技术死路一条? 数据源的统一与拆分 监控报警系统的指标、规则与执行闭环 java 老矣,尚能饭否? 一骑红尘妃子笑,无人知是荔枝来! 有所依 我们如何知道系统交易…...
玩转数字与运算:用C语言实现24点游戏的扑克牌魅力
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
动态反馈控制器(DFC)和 服务率控制器(SRC);服务率和到达率简单理解
目录 服务率和到达率简单理解 服务率 到达率 排队论中的应用 论文解析:队列等待成本动态感知控制模型 动态反馈和队列等待成本意识: 服务速率调整算法: 动态反馈控制器(DFC)和 服务率控制器(SRC) SRC公式4的原理 算力资源分配系统中的调整消耗 举例说明 服务…...
Flutter-Web首次加载时添加动画
前言 现在web上线后首次加载会很慢,要5秒以上,并且在加载的过程中界面是白屏。因此想在白屏的时候放一个加载动画 实现步骤 1.找到web/index.html文件 2.添加以下<style>标签内容到<head>标签中 <style>.loading {display: flex;…...
stl 实现非容器类型元素和容器元素比较
在 C 标准模板库(STL)中,有许多算法可以接受自定义的比较函数(Compare)。这些算法通常涉及排序、查找、合并、集合操作等场景,允许用户通过 Compare 函数指定如何比较两个元素。 自定义compare的算法 排序…...
ChatGPT 桌面版发布了,如何安装?
本章教程教大家如何进行安装。 一、下载安装包 官网地址地址:https://openai.com/chatgpt/desktop/ 支持Windows和MacOS操作系统 二、安装步骤 Windows用户下载之后,会有一个exe安装包,点击运行安装即可。 注意事项,如果Windows操…...
【动手学电机驱动】STM32-FOC(8)MCSDK Profiler 电机参数辨识
STM32-FOC(1)STM32 电机控制的软件开发环境 STM32-FOC(2)STM32 导入和创建项目 STM32-FOC(3)STM32 三路互补 PWM 输出 STM32-FOC(4)IHM03 电机控制套件介绍 STM32-FOC(5&…...
JavaWeb后端开发知识储备2
目录 1.HttpClient 2.微信小程序开发 3.Spring Cache 4.Spring Task 4.1cron表达式 4.2入门案例 1.HttpClient 简单来说,HttpClient可以通过编码的方式在Java中发送Http请求 2.微信小程序开发 微信小程序的开发本质上是前端开发,对于后端程序员来…...
Java Stream API - 高效数据处理的核心工具_01
文章目录 概述:高效数据处理的核心工具1. 流的创建1.1 从集合创建流1.2 从数组创建流1.3 直接创建流 2. 中间操作:流的转换和处理2.1 map():映射操作2.2 filter():过滤操作2.3 flatMap():扁平化映射操作2.4 sorted()&a…...
【计算机网络】网段划分
一、为什么有网段划分 IP地址 网络号(目标网络) 主机号(目标主机) 网络号: 保证相互连接的两个网段具有不同的标识 主机号: 同一网段内,主机之间具有相同的网络号,但是必须有不同的主机号 互联网中的每一台主机,都要隶属于某一个子网 -&…...
HTML和CSS 表单、表格练习
HTML和CSS 表格练习 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>HTML表格练习</title>…...
ByteBuffer模拟拆包输出消息字符串
以下代码模拟网络编程中的粘包现象,用\n进行分割消息块 源码 public static void main(String[] args) {ByteBuffer byteBuffer1 ByteBuffer.allocate(60) ;byteBuffer1.put("Hello World\nWhat is you name?\nI am Licky!\nHo".getBytes());splice(byt…...
Java FastJson 踩坑记录
newtonsoft.json 第一次用。java 转的json给newtonsoft.json解析,一直解析不出来。 直到写这个文章的时候,我都还觉得是newtonsoft.json和fastjson都有问题。 先看java JSONField(name"RankValue") public long Rank11000; JSONField(na…...
深入理解 Java 阻塞队列:使用场景、原理与性能优化
在并发编程中,线程安全的队列是解决线程间任务传递和调度的关键工具之一。阻塞队列(BlockingQueue)作为一种线程安全的队列,实现了在并发环境下对共享数据的安全访问,广泛应用于生产者-消费者模型、任务调度和多线程计…...
Python常用高阶函数:map()、filter()、reduce()
Python常用高阶函数:map()、filter()、reduce() 高阶函数是一类以函数作为参数或者返回值的函数,能够显著提高代码的简洁性和灵活性。在Python中,map()、filter()和reduce()是三种非常常用的高阶函数,它们常被用来对列表或其他可…...
多目标优化算法:多目标极光优化算法(MOPLO)求解ZDT1、ZDT2、ZDT3、ZDT4、ZDT6,提供完整MATLAB代码
一、极光优化算法 极光优化算法(Polar Lights Optimization, PLO)是2024年提出的一种新型的元启发式优化算法,它从极光这一自然现象中汲取灵感。极光是由太阳风中的带电粒子在地球磁场的作用下,与地球大气层中的气体分子碰撞而产…...
【大数据学习 | Spark-Core】关于distinct算子
只有shuffle类的算子能够修改分区数量,这些算子不仅仅存在自己的功能,比如分组算子groupBy,它的功能是分组但是却可以修改分区。 而这里我们要讲的distinct算子也是一个shuffle类的算子。即可以修改分区。 scala> val arr Array(1,1,2,…...
ShuffleNet:一种为移动设备设计的极致高效的卷积神经网络
摘要 https://arxiv.org/pdf/1707.01083 我们介绍了一种名为ShuffleNet的计算效率极高的卷积神经网络(CNN)架构,该架构专为计算能力非常有限的移动设备(例如10-150 MFLOPs)而设计。新架构利用两种新操作:逐…...
AIGC-------AIGC在社交媒体内容生成中的应用
AIGC在社交媒体内容生成中的应用 引言 随着人工智能生成内容(AIGC)的快速发展,社交媒体平台上的内容创作方式发生了巨大变化。AIGC使得内容创作的门槛大大降低,从而让更多的人能够参与到社交媒体内容的创作中,同时也使…...
提取图像中的高频信息
三种方法 1. 傅里叶变换提取高频和低频【有损】2. 傅里叶变换提取振幅和相位【无损】3. 小波变换【不涉及恢复代码】代码1.代码2代码3 1. 傅里叶变换提取高频和低频【有损】 环境:集群210.30.98.11效果: 2. 傅里叶变换提取振幅和相位【无损】 环境:集…...
js函数声明
在 JavaScript 中,函数是一等公民(first-class citizen),这意味着函数可以作为变量、参数和返回值使用。JavaScript 提供了多种定义函数的方式,以下是几种常见的方法: 1. 函数声明(Function De…...
语言模型中的多模态链式推理
神经网络的公式推导 简介摘要引言多模态思维链推理的挑战多模态CoT框架多模态CoT模型架构细节编码模块融合模块解码模块 实验结果运行代码补充细节安装包下载Flan-T5数据集准备rougenltkall-MiniLM-L6-v2运行 简介 本文主要对2023一篇论文《Multimodal Chain-of-Thought Reason…...
【Java 解释器模式】实现高扩展性的医学专家诊断规则引擎
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...
CTF之密码学(Polybius密码)
棋盘密码,也称为Polybius密码或方格密码,是一种基于替换的加密方法。以下是对棋盘密码的详细解析: 一、加密原理 棋盘密码使用一个5x5的方格棋盘,其中填充了26个英文字母(通常i和j被视为同一个字母并放在同一个格子中…...
java excel 导入各种踩坑
在 Java 中处理 Excel 导入时,常见的问题(即“踩坑”)很多,下面列举了处理 Excel 导入时可能遇到的一些问题,并给出了解决方案和优化技巧。 1. POI 库与版本问题 Apache POI 是处理 Excel 的常用库,但是不…...
优化表单交互:在 el-select 组件中嵌入表格显示选项
介绍了一种通过 el-select 插槽实现表格样式数据展示的方案,可更直观地辅助用户选择。支持列配置、行数据绑定及自定义搜索,简洁高效,适用于复杂选择场景。完整代码见GitHub 仓库。 背景 在进行业务开发选择订单时,如果单纯的根…...