【Linux】网络基础_4

文章目录


十、网络基础

5. socket编程

网络翻译服务

基于UDP,我们实现一个简单的翻译。

我们导入之前写的代码:
InetAddr.hpp:

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

class InetAddr
{
private:
    void GetAddress(std::string *ip, uint16_t *port)
    {
        *port = ntohs(_addr.sin_port);
        *ip = inet_ntoa(_addr.sin_addr);
    }

public:
    InetAddr(const struct sockaddr_in &addr)
        : _addr(addr)
    {
        // 根据套接字结构获取IP地址和端口号
        GetAddress(&_ip, &_port);
    }

    std::string Ip()
    {
        return _ip;
    }

    uint16_t Port()
    {
        return _port;
    }

    ~InetAddr()
    {}
private:
    // 套接字结构
    struct sockaddr_in _addr;
    // IP地址
    std::string _ip;
    // 端口号
    uint16_t _port;
};

LockGuard.hpp:

cpp 复制代码
#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__

#include <iostream>
#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;
};

#endif

Log.hpp:

cpp 复制代码
#pragma once

#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include "LockGuard.hpp"

// 宏定义,用于定义日志格式
#define LOG(level, format, ...) do{LogMessage(__FILE__, __LINE__, gIsSave, level, format, ##__VA_ARGS__);}while (0)
// 将日志输入到文件
#define EnableFile() do{gIsSave = true;}while (0)
// 将日志输出到显示器
#define EnableScreen() do{gIsSave = false;}while (0)

bool gIsSave = false;
// 日志文件名
const std::string logname = "log.txt";

// 枚举日志级别
enum Level
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

// 保存日志到文件
void SaveFile(const std::string &filename, const std::string &message)
{
    std::ofstream out(filename, std::ios::app);
    if (!out.is_open())
    {
        return;
    }
    out << message;
    out.close();
}

// 日志级别转字符串
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 GetTimeString()
{
    time_t curr_time = time(nullptr);
    struct tm *format_time = localtime(&curr_time);
    if (format_time == nullptr)
        return "None";
    char time_buffer[1024];
    snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
             format_time->tm_year + 1900,
             format_time->tm_mon + 1,
             format_time->tm_mday,
             format_time->tm_hour,
             format_time->tm_min,
             format_time->tm_sec);
    return time_buffer;
}

// 日志锁,同一时刻只能写一个日志
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

// 日志信息
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{
    // 日志级别
    std::string levelstr = LevelToString(level);
    // 时间
    std::string timestr = GetTimeString();
    // 进程id
    pid_t selfid = getpid();

    // 日志内容
    char buffer[1024];
    va_list arg;
    va_start(arg, format);
    vsnprintf(buffer, sizeof(buffer), format, arg);
    va_end(arg);

    // 日志格式化
    std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +
                          "[" + std::to_string(selfid) + "]" +
                          "[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer;
    LockGuard lockguard(&lock);

    // 输出日志
    if (!issave)
    {
        std::cout << message;
    }
    else
    {
        SaveFile(logname, message);
    }
}

UdpClient.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " local_ip local_prot\n" << std::endl;
}

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    // 获取本地IP地址和端口号
    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 << "socket error" << std::endl;
    }

    // 构建目标主机的socket信息
    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());
    std::string message;
    // 循环发送消息
    while (true)
    {
        // 输入消息
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));

        // 接收消息
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        char buffer[1024];
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
        if (n > 0)
        {
            buffer[n] = '\0';
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
    
    return 0;
}

首先我们需要一个翻译服务。我们先创建一个 词库 :
Dict.txt:

cpp 复制代码
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

这个词库代表当前我们所能翻译的能力范围,如果想要翻译更多单词,则需要往里面添加单词。

接着我们要将词库里的单词一一存入一个键值对当中。当客户端访问时以便能够提供服务。
Dict.hpp:

cpp 复制代码
#pragma once

#include <iostream>
#include <unordered_map>
#include <string>
#include <fstream>
#include "Log.hpp"

namespace dict_ns
{
    // 默认字典文件路径
    const std::string defaultpath = "./Dict.txt";
    // 字典文件中词条和汉字之间的分隔符
    const std::string sep = ": ";

    class Dict
    {
    private:
        bool Load()
        {
            // 读取文件内容
            std::ifstream in(_dict_conf_filepath);
            if (!in.is_open())
            {
                LOG(FATAL, "open %s error\n", _dict_conf_filepath.c_str());
                return false;
            }
            std::string line;
            // 逐行读取
            while (std::getline(in, line))
            {
                // 跳过空行
                if (line.empty()) continue;
                // 解析词条和汉字
                auto pos = line.find(sep);
                if (pos == std::string::npos) continue;
                std::string word = line.substr(0, pos);
                if (word.empty()) continue;
                std::string han = line.substr(pos + sep.size());
                if (han.empty()) continue;
                LOG(DEBUG, "load info, %s: %s\n", word.c_str(), han.c_str());
                // 加入字典
                _dict.insert(std::make_pair(word, han));
            }
            // 关闭文件
            in.close();
            LOG(DEBUG, "load %s success\n", _dict_conf_filepath.c_str());
            return true;
        }
    public:
        Dict(const std::string& filepath = defaultpath)
            : _dict_conf_filepath(filepath)
        {
            Load();
        }

        std::string Translate(const std::string& word, bool& ok)
        {
            ok = true;
            // 查找词条
            auto iter = _dict.find(word);
            // 检测词条存不存在
            if (iter == _dict.end())
            {
                ok = false;
                LOG(ERROR, "word %s not found\n", word.c_str());
                return "未找到";
            }
            // 返回汉字
            return iter->second;
        }

        ~Dict()
        {}
    private:
        // 词典映射
        std::unordered_map<std::string, std::string> _dict;
        // 词典配置文件路径
        std::string _dict_conf_filepath;
    };
}

有了翻译服务就可以让服务器提供这个服务。为了让网络与服务之间解耦合,我们不能直接在服务器上直接调用函数,我们最好使用包装器包装一下函数,然后通过回调的方法来访问服务。
UdpServer.hpp:

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"

// 错误码
enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR
};

// 默认套接字
const static int defaultfd = -1;
using func_t = std::function<std::string(const std::string&, bool&ok)>;

class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func)
        :_sockfd(defaultfd)
        ,_port(port)
        ,_isrunning(false)
        ,_func(func)
    {}

    void InitServer()
    {
        // 创建 UDP socket 套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno);
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "socket create success, sockfd: %d\n", _sockfd);

        // 创建 sockaddr_in 结构体
        struct sockaddr_in local;
        // 清空结构体
        bzero(&local, sizeof(local));
        // 设置IP协议地址族为 AF_INET 即IPv4
        local.sin_family = AF_INET;
        // 端口号要经过网络传输,需要转换成网络字节序
        local.sin_port = htons(_port);
        // 绑定到本地地址,IP地址设置为 INADDR_ANY 即 0.0.0.0, 表示接收所有地址的消息
        local.sin_addr.s_addr = INADDR_ANY;

        // 将套接字和结构体绑定
        int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        LOG(INFO, "socket bind success\n");
    }

    void Start()
    {
        // 启动服务器
        _isrunning = true;
        while (true)
        {
            // 接收数据
            char request[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 接收数据
            ssize_t n = recvfrom(_sockfd, request, sizeof(request) - 1, 0, (struct sockaddr*)&peer, &len);
            if (n > 0)
            {
                // 收到数据,打印
                request[n] = '\0';
                InetAddr addr(peer);
                LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), request);

                bool ok;
                // 将请求传给回调函数
                std::string response = _func(request, ok);
                (void)ok;

                // 发送数据
                sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&peer, len);
            }
            // sleep(1);
            // LOG(DEBUG, "server is running...\n");
        }
        _isrunning = false;
    }

    ~UdpServer()
    {}
private:
    int _sockfd;
    // 服务器所用端口号
    uint16_t _port;
    bool _isrunning;

    // 给服务器设置回调函数,即服务函数
    func_t _func;
};

主函数:
Main.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp"
#include "Dict.hpp"

using namespace dict_ns;

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " local_prot\n" << std::endl;
}

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERROR);
    }
    EnableScreen();
    // std::string local_ip = argv[1];

    Dict dict;

    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<UdpServer> usvr(new UdpServer(port, std::bind(&Dict::Translate, &dict, std::placeholders::_1, std::placeholders::_2)));
    // 初始化服务器
    usvr->InitServer();
    // 启动服务器
    usvr->Start();
    return 0;
}

Makefile:

cpp 复制代码
.PHONY:all
all:udpserver udpclient

udpserver:Main.cc
	g++ -o $@ $^ -std=c++11
udpclient:UdpClient.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f udpserver udpclient

OK,我们来使用一下。

服务端:./udpserver 8888

客户端:./udpclient 127.0.0.1 8888

成功实现了简陋的网络翻译服务。


未完待续

相关推荐
别挡25 分钟前
CentOS Stream 8中安装和使用 Docker
linux·docker·centos
日记成书34 分钟前
【无线通信发展史⑨】1791年路易吉·伽伐尼-关于动物电的研究与1800年亚历山大·伏打伯爵-电池:伏打电池
网络·人工智能·学习·职场和发展·信息与通信
贾saisai42 分钟前
Xilinx系FPGA学习笔记(四)VIO、ISSP(Altera)及串口学习
笔记·学习·fpga开发
月夕花晨3741 小时前
C++学习笔记(13)
c++·笔记·学习
人工智障调包侠1 小时前
Linux 目录介绍
linux·运维·服务器
probably1211 小时前
学习记录之Java学习笔记3
java·笔记·学习
愤怒的代码2 小时前
Centos使用阿里云镜像安装docker
linux·docker·centos
东华果汁哥2 小时前
【深度学习 CV方向】图像算法工程师 职业发展路线,以及学习路线
深度学习·学习·算法
Alan-Xia2 小时前
Vue响应式进阶常用API之effectScope、getCurrentScope、onScopeDispose学习
前端·vue.js·学习
Milo_K2 小时前
java学习笔记-IO流(韩顺平)
java·笔记·学习