【简单的UDP网络程序】实现一个简单的英译汉的功能

封装 UdpSocket:udp_socket.hpp:

这是一个UdpSocket类,它封装了一些UDP套接字函数的操作。该类提供以下公共方法:

  • 构造函数UdpSocket:初始化UdpSocket对象并将其文件描述符初始化为-1;
  • Socket方法:创建UDP套接字;
  • Close方法:关闭UDP套接字;
  • Bind方法:将UDP套接字绑定到指定的IP地址和端口号;
  • RecvFrom方法:从UDP套接字接收数据,并将其放入指定的缓冲区。如果指定了IP和端口参数,则还会将远程IP和端口信息返回;
  • SendTo方法:向指定的IP和端口发送数据。

在实现这些方法时,使用了一些基本的网络编程函数,如socket、bind、recvfrom和sendto。其中,recvfrom函数用于从UDP套接字接收数据,sendto函数用于向指定IP和端口发送数据。还使用了一些其他函数,如inet_addr、htons、ntohs、inet_ntoa等,用于处理IP地址和端口号的字节序和字符串表示之间的转换。

#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cassert>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
class UdpSocket {
public:
UdpSocket() : fd_(-1) {
}
bool Socket() {
fd_ = socket(AF_INET, SOCK_DGRAM, 0);
if (fd_ < 0) {
perror("socket");
return false;
}
return true;
}
bool Close() {
close(fd_);
return true;
}
bool Bind(const std::string& ip, uint16_t port) {
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind");
return false;
}
return true;
}
bool RecvFrom(std::string* buf, std::string* ip = NULL, uint16_t* port = NULL) {
char tmp[1024 * 10] = {0};
sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t read_size = recvfrom(fd_, tmp,
sizeof(tmp) - 1, 0, (sockaddr*)&peer, &len);
if (read_size < 0) {
perror("recvfrom");
return false;
}
// 将读到的缓冲区内容放到输出参数中
buf->assign(tmp, read_size);
if (ip != NULL) {
*ip = inet_ntoa(peer.sin_addr);
}
if (port != NULL) {
*port = ntohs(peer.sin_port);
}
return true;
}
bool SendTo(const std::string& buf, const std::string& ip, uint16_t port) {
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
ssize_t write_size = sendto(fd_, buf.data(), buf.size(), 0, (sockaddr*)&addr, sizeof(addr));
if (write_size < 0) {
perror("sendto");
return false;
}
return true;
}
private:
int fd_;
};

UDP通用服务器:udp_server.hpp

该段代码定义了一个UDP服务器类UdpServer,它包含了一个成员变量sock_,它是一个UdpSocket对象,用于管理UDP套接字的创建、绑定、发送和接收操作。

UdpServer类提供了一个Start方法,它接受三个参数:ip、port和handler,分别表示服务器绑定的IP地址、端口号和请求处理函数。Start方法执行以下步骤:

  • 调用UdpSocket对象的Bind方法绑定IP地址和端口号,如果绑定失败则返回false;
  • 进入事件循环,不断读取请求并进行处理;
  • 尝试从套接字中读取请求,如果读取失败则跳过此次循环;
  • 调用handler函数处理请求,并得到响应;
  • 将响应发送回客户端;
  • 输出日志,记录请求和响应的内容。

其中,handler是一个函数对象,它接受一个const
std::string&类型的请求参数和一个std::string*类型的响应参数,用于计算请求的响应。在该代码中,Handler类型定义了handler的函数签名,使用了std::function模板,它支持函数指针、仿函数和lambda表达式等多种函数对象类型。

#pragma once
#include "udp_socket.hpp"
// C 式写法
// typedef void (*Handler)(const std::string& req, std::string* resp);
// C++ 11 式写法, 能够兼容函数指针, 仿函数, 和 lamda
#include <functional>
typedef std::function<void (const std::string&, std::string* resp)> Handler;
class UdpServer {
public:
UdpServer() {
assert(sock_.Socket());
}
~UdpServer() {
sock_.Close();
}
bool Start(const std::string& ip, uint16_t port, Handler handler) {
// 1. 创建 socket
// 2. 绑定端口号
bool ret = sock_.Bind(ip, port);
if (!ret) {
return false;
}
// 3. 进入事件循环
for (;;) {
// 4. 尝试读取请求
std::string req;
std::string remote_ip;
uint16_t remote_port = 0;
bool ret = sock_.RecvFrom(&req, &remote_ip, &remote_port);
if (!ret) {
continue;
}
std::string resp;
// 5. 根据请求计算响应
handler(req, &resp);
// 6. 返回响应给客户端
sock_.SendTo(resp, remote_ip, remote_port);
printf("[%s:%d] req: %s, resp: %s\n", remote_ip.c_str(), remote_port,
req.c_str(), resp.c_str());
}
sock_.Close();
return true;
}
private:
UdpSocket sock_;
};

实现英译汉服务器:dict_server.cc

以上代码是对 udp 服务器进行通用接口的封装. 基于以上封装, 实现一个查字典的服务器就很容易了。

这段代码实现了一个简单的 UDP 词典服务器。具体的实现步骤如下:

  • 在全局作用域中定义一个 std::unordered_map 类型的变量 g_dict,用于存储单词和对应翻译的键值对。
  • 定义一个名为 Translate 的函数,该函数接受一个 std::string 类型的请求参数 req 和一个 std::string*类型的响应参数 resp,并根据 req 参数在 g_dict 中查找对应的翻译,并将翻译结果写入 resp
    参数中。
  • 在 main 函数中,首先判断命令行参数个数是否为 3,如果不是,则打印用法并返回 1。
  • 调用 g_dict.insert 函数初始化 g_dict 变量。
  • 创建一个 UdpServer 类型的 server 变量,并调用其 Start 函数,传入命令行参数中指定的 IP地址和端口号,以及之前定义的 Translate 函数。
  • Start 函数中进入事件循环,循环中每次尝试从客户端读取请求,然后调用 Translate 函数计算响应,最后将响应发送给客户端,并在控制台上打印客户端
    IP地址、端口号、请求内容和响应内容。循环会一直运行直到服务器关闭,即在函数最后调用 sock_.Close() 关闭 socket。

注:此处的代码只实现了最简单的 UDP 词典服务器,没有实现线程池、错误处理等一些必要的功能。

#include "udp_server.hpp"
#include <unordered_map>
#include <iostream>
std::unordered_map<std::string, std::string> g_dict;
void Translate(const std::string& req, std::string* resp) {
auto it = g_dict.find(req);
if (it == g_dict.end()) {
*resp = "未查到!";
return;
}
*resp = it->second;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./dict_server [ip] [port]\n");
return 1;
}
// 1. 数据初始化
g_dict.insert(std::make_pair("hello", "你好"));
g_dict.insert(std::make_pair("world", "世界"));
g_dict.insert(std::make_pair("c++", "最好的编程语言"));
g_dict.insert(std::make_pair("bit", "特别NB"));
// 2. 启动服务器
UdpServer server;
server.Start(argv[1], atoi(argv[2]), Translate);
return 0;
}

UDP通用客户端:udp_client.hpp

  • 这段代码实现了一个UDP客户端,包含了一个构造函数,一个析构函数,以及两个公共方法RecvFrom和SendTo。
  • 在构造函数中,传入一个IP地址和端口号,创建一个UDPSocket,并将传入的IP地址和端口号保存在成员变量ip_和port_中。在析构函数中,关闭UDP
    Socket。
  • RecvFrom方法尝试从UDP Socket接收数据,并将接收到的数据存入传入的字符串指针buf中。如果接收数据成功,返回true,否则返回false。
  • SendTo方法将传入的字符串buf发送到ip_和port_指定的服务器地址。如果发送数据成功,返回true,否则返回false。
#pragma once
#include "udp_socket.hpp"
class UdpClient {
public:
UdpClient(const std::string& ip, uint16_t port) : ip_(ip), port_(port) {
assert(sock_.Socket());
}
~UdpClient() {
sock_.Close();
}
bool RecvFrom(std::string* buf) {
return sock_.RecvFrom(buf);
}
bool SendTo(const std::string& buf) {
return sock_.SendTo(buf, ip_, port_);
}
private:
UdpSocket sock_;
// 服务器端的 IP 和 端口号
std::string ip_;
uint16_t port_;
};

实现英译汉客户端

#include "udp_client.hpp"
#include <iostream>
//程序入口函数,通过命令行参数获取服务器 IP 和端口号。
int main(int argc, char* argv[]) {  
//检查命令行参数是否正确,如果不正确则输出使用帮助信息并退出程序。
if (argc != 3) {
printf("Usage ./dict_client [ip] [port]\n");
return 1;
}
//创建一个 UDP 客户端对象,指定服务器 IP 和端口号。
UdpClient client(argv[1], atoi(argv[2]));
for (;;) {
//定义一个字符串变量存储用户输入的单词。
std::string word;
std::cout << "请输入您要查的单词: ";
//从标准输入流中读取用户输入的单词。
std::cin >> word;
//如果读取失败,说明用户输入已经结束,输出提示信息并退出程序。
if (!std::cin) {
std::cout << "Good Bye" << std::endl;
break;
}
//将用户输入的单词发送到服务器。
client.SendTo(word);
//定义一个字符串变量存储服务器返回的单词解释。
std::string result;
//等待服务器返回数据并将数据存储到 result 变量中。
client.RecvFrom(&result);
//输出查询结果,包括输入的单词和服务器返回的单词解释。
std::cout << word << " 意思是 " << result << std::endl;
}
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值