秒懂Linux之Socket编程(二)

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

一.英汉词典

整体框架

加载模块

翻译模块

最终效果 

二.聊天室

核心代码

​编辑

答疑解惑

流程梳理

三.全部代码


一.英汉词典

整体框架

Dict.txt

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

Dicp.hpp 

namespace dict_ns
{
    const std::string defaultpath = "./Dict.txt";
    //用来分隔英汉的分隔符
    const std::string sep = ": ";
    class Dict
    {
    public:
        Dict(const std::string &path = defaultpath) : _dict_conf_filepath(path)
        {
            Load();
        }

        //加载模块 :将英汉词典加载进服务端
        bool Load()
        {
            
        }

        //翻译模块 :将输入的单词翻译成对应的中文
        std::string Translate(const std::string &word)
        {
        }
        ~Dict()
        {}
    private:
        // 存储词典的英汉映射
        std::unordered_map<std::string, std::string> _dict;
        // 用于配置英汉词典的路径
        std::string _dict_conf_filepath;
    };
}

加载模块

 //加载模块 :将英汉词典加载进服务端
        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 chinese = line.substr(pos+sep.size());
                if(chinese.empty()) continue;
                LOG(DEBUG, "load info, %s: %s\n", word.c_str(), chinese.c_str());
                //截取完毕,把它们一一映射进map里
                _dict.insert(std::make_pair(word,chinese));
            }

            //关闭文件
            in.close();
            LOG(DEBUG, "load %s success\n", _dict_conf_filepath.c_str());
            return true;
        }

翻译模块

 //翻译模块 :将输入的单词翻译成对应的中文
        std::string Translate(const std::string &word)
        {
            auto iter = _dict.find(word);
            if(iter == _dict.end())
            {
                return "not found";
            }
            // return _dict[word];
            return iter->second;
        }

最终效果 

最后关键的一步就是我们应该如何利用function让服务端接入翻译功能

最后需要理解的就是回调函数func的调用以及如何利用bind来解决参数问题~

二.聊天室

设计思路:通过服务端接收来自各个客户端的信息,然后把信息传递给通信模块,让通信模块把数据传递到线程池中,由多线程分发给各个客户端信息达到客户端之间共享信息的目的~

核心代码

#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <vector>
#include <sys/types.h>
#include <sys/socket.h>
#include <functional>
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include "LockGuard.hpp"

using task_t = std::function<void()>;

class MessageRoute
{
private:
    //判断现有用户是否已被记录为在线用户
    bool IsExists(const InetAddr &addr)
    {
        for (auto a : _online_user)
        {
            //因为InetAddr为自定义类,还需要重载一下==
            if (a == addr)
                return true;
        }
        return false;
    }
public:
    MessageRoute()
    {
        pthread_mutex_init(&_mutex, nullptr);
    }


    // 添加用户
    void AddUser(const InetAddr &addr)
    {
        LockGuard lockguard(&_mutex);
        if(IsExists(addr)) return ;
        _online_user.push_back(addr);
    }

    //删除用户
    void DelUser(const InetAddr &addr)
    {
        LockGuard lockguard(&_mutex);
        for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
        {
            if (*iter == addr)
            {
                _online_user.erase(iter);
                break;
            }
        }
    }

    void RouteHelper(int sockfd,const std::string &message,  InetAddr who)
    {
        LockGuard lockguard(&_mutex);
        //3. 遍历所有在线用户,把信息发送给所有在线用户
        for(auto user:_online_user)
        {
            //发送给用户的信息
            std::string send_message = "\n[" + who.Ip() + ":" + std::to_string(who.Port()) + "]# " + message + "\n";
            //把用户的自定义类转化为sockaddr_in
            struct sockaddr_in clientaddr   = user.Addr();
            ::sendto(sockfd, send_message.c_str(), send_message.size(), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
        }
    }

    //这里就作为接收数据的地方,因为func会把三种数据带出来
    void Route(int sockfd,const std::string &message,  InetAddr who)
    {
        //1. 我们的用户在首次发送信息时,还需要把自己添加进在线用户中
        AddUser(who);

        //2. 如果客户端要退出,删除其在线记录
        if(message == "Q"||message == "QUIT")
        {
            DelUser(who);
        }
        
        //构建任务对象
        task_t t = std::bind(&MessageRoute::RouteHelper, this, sockfd, message, who);
        //把函数对象放在线程池的任务队列中,最终线程池中的多线程就会去执行RouteHelper函数
        ThreadPool<task_t>::GetInstance()->Enqueue(t);
        
        // //3. 遍历所有在线用户,把信息发送给所有在线用户
        // for(auto user:_online_user)
        // {
        //     //发送给用户的信息
        //     std::string send_message = "\n[" + who.Ip() + ":" + std::to_string(who.Port()) + "]# " + message + "\n";
        //     //把用户的自定义类转化为sockaddr_in
        //     struct sockaddr_in clientaddr   = user.Addr();
        //     ::sendto(sockfd, send_message.c_str(), send_message.size(), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
        // }
    }

    ~MessageRoute()
    {
        pthread_mutex_destroy(&_mutex);
    }

private:
    // 记录各个客户端的socket
    std::vector<InetAddr> _online_user;
    pthread_mutex_t _mutex;
};
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Thread.hpp"
#include "Comm.hpp"

using namespace ThreadModule;
//客户端初始化
int InitClient(std::string &serverip, uint16_t serverport, struct sockaddr_in *server)
{
    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return -1;
    }
    // 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!
    // a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!
    // b. 什么时候bind呢?首次发送数据的时候

    // 构建目标主机的socket信息

    memset(server, 0, sizeof(struct sockaddr_in));
    server->sin_family = AF_INET;
    server->sin_port = htons(serverport);
    server->sin_addr.s_addr = inet_addr(serverip.c_str());

    return sockfd;
}

//客户端接收信息
void recvmessage(int sockfd, std::string name)
{
    // int fd = OpenDev("/dev/pts/2", O_WRONLY);
    while (true)
    {
        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;
            fprintf(stderr, "[%s]%s\n", name.c_str(), buffer); // stderr
            //write(fd, buffer, strlen(buffer));
        }
    }
}

//客户端发送信息
void sendmessage(int sockfd, struct sockaddr_in &server, std::string name)
{
    std::string message;
    while (true)
    {
        printf("%s | Enter# ", name.c_str());
        fflush(stdout);
        std::getline(std::cin, message);
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
    }
}

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

// ./udpclient serverip serverport  向目标主机中的某个端口号发送数据
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    struct sockaddr_in serveraddr;
    // 1. 创建socket
    int sockfd = InitClient(serverip, serverport, &serveraddr);
    if(sockfd ==-1)
    {
        return 1;
    }
    
    func_t r = std::bind(&recvmessage, sockfd, std::placeholders::_1);
    func_t s = std::bind(&sendmessage, sockfd, serveraddr, std::placeholders::_1);

    Thread Recver(r, "recver");
    Thread Sender(s, "sender");

    Recver.Start();
    Sender.Start();

    Recver.Join();
    Sender.Join();

    return 0;
}

#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 <functional>
#include "Log.hpp"
#include "InetAddr.hpp"


enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR
};
const static int defaultfd = -1;

// typedef std::function<std::string(const std::string&)> func_t;
//回调函数 那么回调出去的函数需要带哪些数据进行处理呢? sockfd:通过文件转发消息 message:消息 InetAddr:区分该消息属于谁
using hander_message_t = std::function<void (int sockfd,const std::string message,const InetAddr who)>;

class UdpServer
{
public:
    UdpServer(uint16_t port,hander_message_t hander_message):_sockfd(defaultfd),_port(port),_isrunning(false),_hander_message(hander_message)
    {}

    void InitServer()
    {
        //1.创建udp socket 套接字 本质就是创建了一个文件
        //模式:网络模式,UDP协议
        _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);
        //socket只是创建了一个文件,那么未来远端该如何找到呢?

        //2.0 填充sockaddr_in结构,里面是网络信息
        struct sockaddr_in local;// struct sockaddr_in 系统提供的数据类型。local是变量,用户栈上开辟空间。int a = 100; a = 20;
        bzero(&local, sizeof(local));//清空数据
        local.sin_family = AF_INET;//地址类型,标明为网络通信
        local.sin_port = htons(_port); // port要经过网络传输给对面,先到网络,_port:主机序列-> 主机序列,转成网络序列

        //a. 字符串风格的点分十进制的IP地址转成 4 字节IP,例如127.0.0.1,这是给人看的不是给机器看的
        //b. 主机序列,转成网络序列
        //in_addr_t inet_addr(const char *cp) //-> 同时完成 a & b

        //local.sin_addr.s_addr = inet_addr(_ip.c_str()); // "192.168.3.1" -> 字符串风格的点分十进制的IP地址 -> 4字节IP

        //默认IP为0
        local.sin_addr.s_addr = INADDR_ANY; // htonl(INADDR_ANY); 

        //2.1 bind,将文件与网络信息(ip+port)关联在一起

        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 message[64];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);//初始化
            //1.先让服务端接收/读取数据
            // 实际读取字节数            作为读取缓冲区  期望长度          对方的网路信息(ip+端口号)
            ssize_t n = recvfrom(_sockfd,message,sizeof(message)-1,0,(struct sockaddr*)&peer,&len);
            if (n > 0)
            {
                message[n] = 0;
                //因为客户端的网络信息是由bind自动绑定的,所以我们一般不清楚其网络信息,这里是用一个类把其解析出来
                InetAddr addr(peer);
                LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), message);

                //服务端仅读取数据,发送数据由通信模块进行
                _hander_message(_sockfd,message,addr);
                
                
                // 2. 我们要将server收到的数据,发回给对方
                //sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len);
            }
    
        }
        _isrunning = false;
    }

    ~UdpServer()
    {}

private:
    int _sockfd;     // socket所创的文件描述符
    //std::string _ip; // 服务器所用的协议
    uint16_t _port;  // 服务器所用的端口号
    bool _isrunning;
    
    hander_message_t _hander_message;
};

答疑解惑

目前疑点:

各个客户端是怎么保持通信即消息同步的?

如何正确理解多个bind?

在多线程的情况下线程应该是争抢任务?目前还无法理解,如同第一点

流程梳理

三.全部代码

英汉词典 · b53dec9 · 玛丽亚后/keep - Gitee.com

聊天室 · c878802 · 玛丽亚后/keep - Gitee.com

破防的一天呢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值