当前位置: 首页 > news >正文

计算机网络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、ERRORFATAL,数字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_ERRORBIND_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网络编程实现网络字典

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络socket编程(2)_UDP网络编程实现网络字典 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨…...

c#窗体列表框(combobox)应用——省市区列表选择实例

效果如下&#xff1a; designer.cs代码如下&#xff1a; using System.Collections.Generic;namespace 删除 {public partial class 省市区选择{private Dictionary<string, List<string>> provinceCityDictionary;private Dictionary<string,List<string&…...

Nginx 架构与设计

Nginx 是一个高性能的 HTTP 和反向代理服务器&#xff0c;同时也可以用作邮件代理和通用的 TCP/UDP 负载均衡器。它的架构设计以高并发、高可扩展性和高性能为目标&#xff0c;充分利用操作系统提供的多路复用机制和事件驱动模型。以下是 Nginx 的架构和设计特点&#xff1a; 1…...

python Flask指定IP和端口

from flask import Flask, request import uuidimport json import osapp Flask(__name__)app.route(/) def hello_world():return Hello, World!if __name__ __main__:app.run(host0.0.0.0, port5000)...

多线程 相关面试集锦

什么是线程&#xff1f; 1、线程是操作系统能够进⾏运算调度的最⼩单位&#xff0c;它被包含在进程之中&#xff0c;是进程中的实际运作单位&#xff0c;可以使⽤多线程对 进⾏运算提速。 ⽐如&#xff0c;如果⼀个线程完成⼀个任务要100毫秒&#xff0c;那么⽤⼗个线程完成改…...

【数据结构】—— 线索二叉树

引入 我们现在提倡节约型杜会&#xff0c; 一切都应该节约为本。对待我们的程序当然也不例外&#xff0c;能不浪费的时间或空间&#xff0c;都应该考虑节省。我们再观察团下图的二叉树&#xff08;链式存储结构)&#xff0c;会发现指针域并不是都充分的利用了&#xff0c;有许…...

uni-app 发布媒介功能(自由选择媒介类型的内容) 设计

1.首先明确需求 我想做一个可以选择媒介的内容&#xff0c;来进行发布媒介的功能 &#xff08;媒介包含&#xff1a;图片、文本、视频&#xff09; 2.原型设计 发布-编辑界面 通过点击下方的加号&#xff0c;可以自由选择添加的媒介类型 但是因为预览中无法看到视频的效果&…...

How to update the content of one column in Mysql

How to update the content of one column in Mysql by another column name? UPDATE egg.eggs_record SET sold 2024-11-21 WHERE id 3 OR id 4;UPDATE egg.eggs_record SET egg_name duck egg WHERE id 2;...

URL在线编码解码- 加菲工具

URL在线编码解码 打开网站 加菲工具 选择“URL编码解码” 输入需要编码/解码的内容&#xff0c;点击“编码”/“解码”按钮 编码&#xff1a; 解码&#xff1a; 复制已经编码/解码后的内容。...

Python3 爬虫 Scrapy的安装

Scrapy是基于Python的分布式爬虫框架。使用它可以非常方便地实现分布式爬虫。Scrapy高度灵活&#xff0c;能够实现功能的自由拓展&#xff0c;让爬虫可以应对各种网站情况。同时&#xff0c;Scrapy封装了爬虫的很多实现细节&#xff0c;所以可以让开发者把更多的精力放在数据的…...

QT中QString类的各种使用

大部分的QString使用可以参考:QT中QString 类的使用--获取指定字符位置、截取子字符串等_qstring 取子串-CSDN博客 补充一种QString类的分离:Qt QString切割(Split()与Mid()函数详解)_qstring split-CSDN博客 1. Trimmed和Simplified函数(去除空白) trimmed&#xff1a;去除了…...

linux 网络安全不完全笔记

一、安装Centos 二、Linux网络网络环境设置 a.配置linux与客户机相连通 b.配置linux上网 三、Yum详解 yum 的基本操作 a.使用 yum 安装新软件 yum install –y Software b.使用 yum 更新软件 yum update –y Software c.使用 yum 移除软件 yum remove –y Software d.使用 yum …...

uniapp将图片url转换成base64支持app和h5

uniapp将图片url转换成base64支持app和h5 imageToBase64支持app和h5, app内使用plus.io.resolveLocalFileSystemURL方法转换 h5内使用uni.request方法转换 // 图片转base64 export const imageToBase64 (path) > {// #ifdef APP-PLUSreturn new Promise((resolve, rejec…...

odoo17 档案管理之翻译2

翻译格式&#xff1a;#: model_terms:对象名称,arch_db:模块名.xml_id #. module: dms #: model_terms:ir.ui.view,arch_db:dms.view_dms_directory_kanban #: model_terms:ir.ui.view,arch_db:dms.view_dms_file_kanban #: model_terms:ir.ui.view,arch_db:dms.view_dms_tag_…...

风尚云网前端学习:制作一款简易的在线计算器

风尚云网前端学习&#xff1a;制作一款简易的在线计算器 简介 在前端开发的学习过程中&#xff0c;实现一个简单的在线计算器是一个常见的练习项目。它不仅能够帮助我们熟悉HTML、CSS和JavaScript的基本用法&#xff0c;还能够加深我们对事件处理和DOM操作的理解。今天&#…...

Android蓝牙架构,源文件目录/编译方式学习

Android 版本 发布时间 代号&#xff08;Codename&#xff09; Android 1.0 2008年9月23日 无 Android 1.1 2009年2月9日 Petit Four Android 1.5 2009年4月27日 Cupcake Android 1.6 2009年9月15日 Donut Android 2.0 2009年10月26日 Eclair Android 2.1 2…...

ubuntu中使用ffmpeg和nginx推流rtmp视频

最近在测试ffmpeg推流rtmp视频&#xff0c;单独安装ffmpeg是无法完成推流的&#xff0c;需要一个流媒体服务器&#xff0c;常用nginx&#xff0c;可以直接在ubuntu虚拟机里面测试一下。 测试过程不涉及编译ffmpeg和nginx&#xff0c;仅使用基本功能&#xff1a; 1 安装ffmpeg …...

strongswan测试流程

测试shell脚本文件testing/do-tests&#xff0c;测试配置文件testing/testing.conf。do-tests脚本不加参数&#xff0c;将依次执行testing/tests/目录下的所有测试用例。do-tests脚本有两个参数-v和-t&#xff0c;前者在测试中记录详细信息&#xff0c;后者在输出信息中增加时间…...

[CKS] CIS基准测试,修复kubelet和etcd不安全项

目前的所有题目为2024年10月后更新的最新题库&#xff0c;考试的k8s版本为1.31.1 ​ 专栏其他文章: [CKS] K8S Admission Set Up[CKS] CIS基准测试&#xff0c;修复kubelet和etcd不安全项[CKS] K8S NetworkPolicy Set Up[CKS] 利用Trivy对image进行扫描[CKS] 利用falco进行容器…...

Linux/Windows/OSX 上面应用程序重新启动运行。

1、Linux/OSX 上面重新运行程序&#xff0c;直接使用 execvp 函数就可以了&#xff0c;把main 函数传递来的 argv 二维数组&#xff08;命令行参数&#xff09;传进去就可以&#xff0c;注意不要在 fork 出来的子进程搞。 2、Windows 平台可以通过 CreateProcess 函数来创建新的…...

React拆分组件中的传值问题

在我们实际项目开发中&#xff0c;很多时候为为了项目后期便于维护&#xff0c;都会将相关的组件进行拆分&#xff0c;拆分过后&#xff0c;会将数据方法在父组件中进行编写&#xff0c;然后将一些逻辑拆分为组件&#xff0c;在这个过程中&#xff0c;最重要的就是数据的传递&a…...

RocketMQ的使⽤

初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种⽅式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要⻢上回复。 两种⽅式各有优劣&#xff0c;打电话可以⽴即得到响应&#xff0c;但…...

Android Studio 设置不显示 build-tool 无法下载

2024版本查看build-tool版本 File -> Settings -> Languages & Frameworks -> Android SDK 或者直接打开Settings后搜索“SDK” 解决方案 将 Android Studio 升级到2022.2.1以上的版本将 C:/Windows/System32/drivers/etc/hosts 文件用管理员身份打开&#xff0c…...

【Y20030007】基于java+servlet+mysql的垃圾分类网站的设计与实现(附源码 配置 文档)

网垃圾分类网站的设计与实现 1.摘要2.开发目的和意义3.系统功能设计4.系统界面截图5.源码获取 1.摘要 随着全球环境保护意识的提升&#xff0c;垃圾分类已成为一项紧迫且重要的任务。为了有效推动垃圾分类的实施&#xff0c;提升公众的环保意识和参与度&#xff0c;垃圾分类已…...

细说敏捷:敏捷四会之standup meeting

上一篇文章中&#xff0c;我们讨论了 敏捷四会 中 冲刺计划会 的实施要点&#xff0c;本篇我们继续分享敏捷四会中实施最频繁&#xff0c;团队最容易实施但往往也最容易走形的第二个会议&#xff1a;每日站会 关于每日站会的误区 站会是一个比较有标志性的仪式活动&#xff0…...

ThinkPHP8使用workerman

应用场景说明&#xff1a;通过建立通信&#xff0c;不同用户进行消息推送或数据更新&#xff0c;因为本身需要作为服务端进行主动消息推送&#xff0c;因此使用Gateway方式&#xff0c;如果不需要的可以不采用这种形式&#xff0c;以下内容仅为参考&#xff0c;具体业务场景&am…...

C语言超详细教程

系列文章目录 文章目录 系列文章目录1 运算符1.1 算术运算符:2 控制语句2.1 条件语句:2.2 循环语句:3 函数3.1 函数的定义与声明:3.2 递归函数:4 指针4.1 指针的定义与使用函数指针:5. 数组与字符串5.1 数组一维数组:相同类型元素的集合(如:多维数组:数组的数组(如:…...

[开源]3K+ star!微软Office的平替工具,跨平台,超赞!

大家好&#xff0c;我是JavaCodexPro&#xff01; 数字化的当下&#xff0c;高效的办公工具是提升工作效率的关键&#xff0c;然而大家想到的一定是 Microsoft Office 办公软件&#xff0c;然而价格也是相当具有贵的性价比。 今天JavaCodexPro给大家分享一款超棒的开源办公套…...

如何借助计算机视觉算法通过识别水尺精准识别水位

如何借助计算机视觉算法通过识别水尺精准识别水位 随着技术的发展&#xff0c;计算机视觉在多个领域得到了广泛的应用&#xff0c;尤其是在环境监测方面。本文将介绍一种利用计算机视觉算法通过识别水尺来精准识别水位的方法。这种方法可以用于河流、水库等场景的水位监测&…...

C++(进阶) 第1章 继承

C&#xff08;进阶) 第1章 继承 文章目录 前言一、继承1.什么是继承2.继承的使用 二、继承方式1.private成员变量的&#xff08;3种继承方式&#xff09;继承2. private继承方式3.继承基类成员访问⽅式的变化 三、基类和派生类间的转换1.切片 四、 继承中的作⽤域1.隐藏规则&am…...