UDP应用1:翻译软件¶
约 1395 个字 148 行代码 1 张图片 预计阅读时间 6 分钟
本篇介绍¶
本篇基于UDP编程接口基本使用中封装的服务器和客户端进行改写,基本功能如下:
- 从配置文件
dict.txt
读取到所有的单词和意思 - 客户端向服务端发送英文
- 服务端向客户端发送英文对应的中文意思
配置文件内容¶
下面的内容是本次实现使用的配置文件
Text Only | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
设计翻译软件¶
根据上面对功能描述,下面对功能实现进行具体分析
设计字典类¶
既然是字典类,那么对应的就需要一个便于查询的结构,本次以哈希表为例
因为服务端主要是接收客户端的数据,所以服务端本身不直接处理翻译功能,而是交给一个函数进行处理,但是如果这个函数直接裸露在外部就会导致字典本身和翻译函数分开,所以本次考虑将翻译函数作为字典类的成员函数
需要注意,本次默认使用的配置文件中英文单词和中文意思使用的是一个冒号和空格进行分割,即:
,可以考虑让用户自己提供配置文件和分割符,所以类基本结构如下:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
读取配置文件并分割¶
首先是读取配置文件,本次考虑将从配置文件中读取到的字符串通过分割成key
和value
依次存入到一个哈希表中。并且因为是字典类,所以考虑在创建字典类对象时就完成前面的读取和存储操作
根据上面的思路需要考虑两个步骤:
- 获取并读取配置文件
- 分割字符串获取到
key
和value
存储到哈希表
读取配置文件的操作就是打开指定的文件并读取其中的内容,在本次配置文件的内容中,一行为一组数据,所以在读取文件内容时需要按照行读取
读到一行数据后,需要将该行数据按照指定的分隔符进行切割存入key
和value
中,这里有两种思路:
- 自行实现分割逻辑
- 使用库函数
本次考虑使用第一种思路,那么对于分割逻辑来说,基本思路如下:
- 找到
:
第一次出现的位置,以该位置为终点,以字符串第一个字符为起点,切出第一个子串作为key
- 找到分隔符的下一个字符,以该位置为起点,以字符串结尾为终点,切出第二个子串作为
value
将获取到的key
和value
存入到哈希表中
重复上面的步骤直到读取完毕文件,将文件关闭,代码如下:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
翻译函数¶
所谓翻译就是在哈希表中根据指定字符串查找对应的value
并返回,基本代码如下:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
修改服务端类¶
本次服务端做的任务就是接收客户端发送的数据,再调用字典类的翻译函数将翻译结果返回给客户端,所以实际上需要修改的就是两点:
- 服务端需要拿到翻译函数
- 执行翻译功能并返回结果给客户端
首先修改第一点,因为服务端本身没有翻译函数,所以需要外界传递,此时可以考虑在构造服务端时要求外部传递翻译函数,对应地服务端就需要一个成员用于接收这个函数:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Note
需要注意,在构造函数中,没有默认参数的形式参数一定要写在有默认参数的形式参数之前,具体原因见C++中的缺省参数
接着修改第二点,执行翻译函数并将结果返回给客户端,这一步主要涉及到服务端的任务部分。在上一节中,主要任务是将客户端发送的信息再发给客户端,这次就是调用翻译函数再将结果返回给客户端,所以代码如下:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
修改服务端主函数¶
主要修改的地方就是创建服务端对象部分,因为需要传递一个翻译任务函数,所以需要先创建一个字典类对象,再调用字典类对象中的翻译方法,需要注意的是,不能直接将字典类的翻译函数作为参数传递给服务端对象的构造函数,因为该函数的参数列表除了需要显示传递的字符串对象外,还存在一个字典类对象,这并不符合服务端类构造函数要求的函数类型,这里提供两个方法:
- 使用绑定,将字典类对象作为固定参数绑定给翻译函数
- 使用lambda表达式,在表达式中调用翻译函数
即:
C++ | |
---|---|
1 2 3 4 |
|
C++ | |
---|---|
1 2 3 4 5 6 |
|
测试¶
以绑定为例,服务端整体代码如下:
C++ | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
客户端代码保持和上一节一样,此处不再展示,运行结果如下:
图表梳理¶
类间关系¶
classDiagram
class Dictionary {
-unordered_map~string, string~ _dict
-string _path
-string _file
-string _sep
+Dictionary(path, file, sep)
+string translate(word)
+void printDic()
}
class SockAddrIn {
-sockaddr_in _s_addr_in
-string _ip
-uint16_t _port
+SockAddrIn()
+SockAddrIn(sockaddr_in)
+SockAddrIn(port)
+SockAddrIn(port, ip)
+sockaddr* operator&()
+socklen_t getLength()
+string getIp()
+uint16_t getPort()
}
class UdpServer {
-int _socketfd
-SockAddrIn _sa_in
-bool _isRunning
-task_t _translate
+UdpServer(task_t, port)
+void start()
+void stop()
}
class UdpClient {
-int _socketfd
-SockAddrIn _sa_in
-bool _isRunning
+UdpClient(ip, port)
+void start()
+void stop()
}
UdpServer "1" *-- "1" SockAddrIn : 组合
UdpClient "1" *-- "1" SockAddrIn : 组合
UdpServer ..> Dictionary : 通过回调函数使用translate
执行逻辑¶
服务端¶
sequenceDiagram
participant 用户
participant main
participant Dictionary
participant UdpServer
participant SockAddrIn
用户->>main: 启动服务器
main->>Dictionary: 创建Dictionary对象
Dictionary->>Dictionary: 加载词典文件
Dictionary-->>main: 返回Dictionary对象
main->>UdpServer: 创建UdpServer对象(传入Dictionary.translate)
UdpServer->>SockAddrIn: 创建SockAddrIn对象(port)
SockAddrIn-->>UdpServer: 返回SockAddrIn对象
UdpServer->>UdpServer: 创建并绑定socket
UdpServer-->>main: 返回UdpServer对象
main->>UdpServer: start()
activate UdpServer
UdpServer->>UdpServer: 循环等待客户端请求
loop 处理客户端请求
UdpServer->>UdpServer: recvfrom()接收请求
UdpServer->>Dictionary: _translate(word)
Dictionary-->>UdpServer: 返回翻译结果
UdpServer->>UdpServer: sendto()发送翻译结果
end
deactivate UdpServer
客户端¶
sequenceDiagram
participant 用户
participant main
participant UdpClient
participant SockAddrIn
participant 服务器
用户->>main: 启动客户端
main->>UdpClient: 创建UdpClient对象(ip, port)
UdpClient->>SockAddrIn: 创建SockAddrIn对象(port, ip)
SockAddrIn-->>UdpClient: 返回SockAddrIn对象
UdpClient->>UdpClient: 创建socket
UdpClient-->>main: 返回UdpClient对象
main->>UdpClient: start()
activate UdpClient
loop 用户查询循环
用户->>UdpClient: 输入单词查询
UdpClient->>服务器: sendto()发送查询请求
服务器->>服务器: 处理查询请求
服务器-->>UdpClient: 返回翻译结果
UdpClient->>UdpClient: recvfrom()接收结果
UdpClient->>用户: 显示翻译结果
end
deactivate UdpClient