大学课设项目,Windows端基于UDP的网络聊天程序的服务端和客户端

news2024/11/27 1:28:00

文章目录

  • 前言
  • 项目需求介绍
  • 一、服务端
    • 1.对Udp套接字进行一个封装
    • 2. UdpServer的编写
    • 3. Task.h
    • 4.protocol.h的编写
    • 5.线程池的编写
    • 6.main.cc
  • 二、客户端
    • 1. Socket.h
    • 2.protocol.h
    • 3.UdpClient
    • 4.menu.h
    • 5.main.cpp
  • 三、运行图


前言

本次项目可以作为之前内容的一个扩展,学会在Windows端进行网络通信。
该项目需要用到的知识手段较多,在编写该项目的同时也可以对之前C++方面的知识进行一个不错的回顾。

项目需求介绍

在这里插入图片描述

本次项目用到 Windows网络套接字编程,多线程,线程池,线程安全,互斥锁,IO流,文件管理,数据结构设计,序列化,反序列化,自定义协议,STL等相关知识和技术。


提示:以下是本篇文章正文内容,下面案例可供参考

一、服务端

1.对Udp套接字进行一个封装

//Socket.h
#pragma once
#include<iostream>
#include<string>
#include<WinSock2.h>
#include<Windows.h>
#include<thread>
#include<functional>
#pragma comment(lib, "ws2_32.lib") // 链接库文件

#pragma warning(disable:4996)      //防止VS发出4996号警告

enum Erro
{
	Sock_Error = 1,
	Bind_Error,
	Listen_Error
};
class Socket
{
public:
	
	Socket(){}

	void Init()
	{

		_listensock = socket(AF_INET, SOCK_DGRAM, 0);
		if (_listensock == SOCKET_ERROR)
		{
			//套接字创建失败
			std::cout << "Socket Create Error..." << std::endl;
			exit(Sock_Error);
		}
	}

	void Bind(const std::string& ip, const int port)
	{
		memset(&_sockaddr, 0, sizeof _sockaddr);
		_sockaddr.sin_family = AF_INET;
		_sockaddr.sin_addr.s_addr = inet_addr(ip.c_str());
		_sockaddr.sin_port = htons(port);
		int n = bind(_listensock, (const struct sockaddr*)&_sockaddr, sizeof _sockaddr);
		if (n < 0)
		{
			std::cout << "Bind Error..." << std::endl;
			//std::cout << errno << " " << strerror(errno) << std::endl;
			exit(Bind_Error);
		}
	}


	~Socket()
	{
		closesocket(_listensock);
	}

public:
	SOCKET _listensock;  
	struct sockaddr_in _sockaddr;
};

Windows OS 和 Linux OS 所提供的网络套接字接口函数有一点点的不同,但是实际的使用差别并不是很大! 所以这里就不细讲了。

2. UdpServer的编写

// UdpServer.h
class UdpServer
{
public:
	UdpServer()
	{
		_tp = ThreadPool<Task>::GetInstance();
        _user_map = new std::unordered_map<std::string, std::string>;
        _online_map = new std::unordered_map<std::string, struct sockaddr_in>;
        _buffer = new std::unordered_map<std::string, std::string>;
        _mutex = new std::mutex;
	}

	void Init()
	{
        //初始化网络环境
        WSADATA wsd;
        WSAStartup(MAKEWORD(2, 2), &wsd);


		_host.Init();
		_host.Bind(server_ip, server_port);
        ReadUser();
	}

    void ReadUser()
    {
        std::ifstream in("User.txt");
        if (!in.is_open())
        {
            //文件打开失败
            std::cout << "Open FIle Error..." << std::endl;
        }
        std::string line;
        while (std::getline(in, line))
        {
            //"username passwd\n"
            size_t pos = line.find(' ');
            std::string username = line.substr(0, pos);
            std::string passwd = line.substr(pos + 1);

            _user_map->insert(std::pair<std::string,std::string>(username,passwd));
        }


        std::cout << "--------------------------------------------------------------" << std::endl;
        std::cout << "所有已注册账号密码" << std::endl;
        for (auto& e : *_user_map)
        {
            std::cout << e.first << " " << e.second << std::endl;
        }
        std::cout << "--------------------------------------------------------------" << std::endl;
    }


	void Start()
	{
		_tp->Start();
		struct sockaddr_in client;
        char inbuffer[1024];
        std::string recvmes;
        while (true)
        {
            int len = sizeof client; // unsigned int
            memset(&client, 0, sizeof client);
            // 服务器接受数据
            //std::cout << "开始等待数据" << std::endl;
            int n = recvfrom(_host._listensock, inbuffer, sizeof inbuffer - 1, 0, (sockaddr*)&client, &len);
            if (n > 0)
            {
                inbuffer[n] = '\0';
                recvmes = inbuffer;

                while (!recvmes.empty())
                {
                    Request rq;
                    if (!rq.deserialize(recvmes))
                    {
                        //报文错误,丢弃所有报文
                        std::cout << "报文错误" << std::endl;
                        break;
                    }
                    Task task(rq, client,_user_map,_mutex,_host._listensock,_online_map,_buffer);
                    _tp->Push(task);
                }
            }
            else
            {
                std::cout << "Read Error..." << std::endl;
                exit(1);
            }
        }
	}

	~UdpServer()
    {
        WSACleanup();   //清理网络环境
    }
private:
	Socket _host;
	ThreadPool<Task>* _tp;
    std::unordered_map<std::string, std::string>* _user_map;
    std::unordered_map<std::string, struct sockaddr_in>* _online_map;

    std::unordered_map<std::string, std::string>* _buffer;
    std::mutex* _mutex;
};

我们也对UdpServer也进行了一个封装,这里如果想要的话还可以继续接着完善,加入守护线程单例模式,大家感兴趣可以下来试一试。

主线程老老实实打印数据,其他线程就去处理客户端发来的命令。

我们可以看到类里面有许多成员变量, 这里对这些变量都进行解释一下。

Socket _host; 毫无疑问,服务端必须要申请一个套接字
ThreadPool* _tp; 这个是线程池的实例指针
std::unordered_map<std::string, std::string>* _user_map; 这个是服务器维护所有注册用户的哈希map
std::unordered_map<std::string, struct sockaddr_in>* _online_map; 这个是服务器维护所有在线用户的哈希map
std::unordered_map<std::string, std::string>* _buffer; 这个是存放所有注册用户离线消息的缓冲区
std::mutex* _mutex; 这个是为了保证线程安全提供的互斥锁

既然服务器想要维护所有用户信息,那么文件管理的IO就必不可少,所以这里也提供了 ReadUser函数

    void ReadUser()
    {
        std::ifstream in("User.txt");
        if (!in.is_open())
        {
            //文件打开失败
            std::cout << "Open FIle Error..." << std::endl;
        }
        std::string line;
        while (std::getline(in, line))
        {
            //"username passwd\n"
            size_t pos = line.find(' ');
            std::string username = line.substr(0, pos);
            std::string passwd = line.substr(pos + 1);

            _user_map->insert(std::pair<std::string,std::string>(username,passwd));
        }


        std::cout << "--------------------------------------------------------------" << std::endl;
        std::cout << "所有已注册账号密码" << std::endl;
        for (auto& e : *_user_map)
        {
            std::cout << e.first << " " << e.second << std::endl;
        }
        std::cout << "--------------------------------------------------------------" << std::endl;
    }

作为初始化_user_map的函数。

3. Task.h

//Task.h
#pragma once

#include<iostream>
#include<string>
#include<unordered_map>
#include<mutex>
#include<fstream>

#include"protocol.h"
#include"Socket.h"

std::string offline_message =
"-------------------------------------------------------------------------------\n\
                                   离线消息\n";


enum Code {
    Login_Err = -1,
    Normal = 0,
    Online_User,
    All_User
};
class Task
{
public:

    Task(){}

    Task(const Request& rq, struct sockaddr_in client, std::unordered_map<std::string, std::string>* user_map 
        ,std::mutex* mutex, SOCKET host, std::unordered_map<std::string, struct sockaddr_in>* online_map,
        std::unordered_map<std::string, std::string>* buffer)
        :_rq(rq)
        , _client(client)
        ,_user_map(user_map)
        ,_mutex(mutex) 
        ,_host(host)
        ,_online_map(online_map)
        ,_buffer(buffer) {}


    void SendOneMessage(int code, const std::string& info, const struct sockaddr_in client)
    {
        Respond rs(info, code);
        std::string mes;
        rs.serialize(&mes);
        sendto(_host, mes.c_str(), mes.size(), 0, (const struct sockaddr*)(&client), sizeof client);
    }

    void SendEveryoneMessage(const std::string& info)
    {
        Respond rs(info);
        std::string mes;
        rs.serialize(&mes);
        for (auto& user : *_online_map)
        {
            sendto(_host, mes.c_str(), mes.size(), 0, (const struct sockaddr*)&(user.second), sizeof(user.second));
        }
    }

    bool CheckUser(const std::string& name, const std::string& mes)
    {
        auto a_it = _user_map->find(name);
        if (a_it == _user_map->end())
        {
            //不存在该用户
            SendOneMessage(Login_Err, std::string("该用户未注册,请输入/quit退出聊天框"), _client);
            return false;
        }
        auto o_it = _online_map->find(name);
        if (o_it == _online_map->end())
        {
            //该用户不在线
            SendOneMessage(0, std::string("该用户未上线,您可以继续发送离线消息,对方在上线后可查看"), _client);

            Respond rs(mes,0);
            std::string tmp;
            rs.serialize(&tmp);

            _mutex->lock();
            (*_buffer)[name] += tmp;
            _mutex->unlock();

            return false;
        }
        return true;
    }

    void SendSpecifiedUser(const std::string& client_name, const std::string& name, const std::string& info)
    {
        std::string mes = "<";
        mes += client_name;
        mes += ">: ";
        mes += info;
        if (!CheckUser(name,mes))
        {
            return;
        }


        struct sockaddr_in spc_user = (*_online_map)[name];
        SendOneMessage(0, mes, spc_user);
    }

    void GetOnlineUser(std::string* out)
    {
        std::string tmp;
        for (auto& e : *_online_map)
        {
            tmp += e.first;
            tmp += '\n';
        }
        *out = tmp;
    }

    void GetAllUser(std::string* out)
    {
        std::string tmp;
        for (auto& e : *_user_map)
        {
            tmp += e.first;
            tmp += '\n';
        }
        *out = tmp;
    }


    void WriteUser(const std::string& name, const std::string& passwd)
    {
        std::ofstream file("User.txt", std::ios::app);

        if (file)
        {
            file << name << " " << passwd << std::endl;
            file.close();
        }
        else
        {
            std::cout << "写入失败" << std::endl;
        }
    }

    
    bool UserRegisterCheck(std::string& name, std::string& passwd)
    {
         auto it = _user_map->find(name);
         if (it == _user_map->end())
         {
             //没找到,可以进行注册
             _mutex->lock();
             (*_user_map)[name] = passwd;
             _mutex->unlock();

             WriteUser(name, passwd);

             std::string mes = "Sign Up Succeed!";
             SendOneMessage(0, mes,_client);
             mes = "用户<";
             mes += name;
             mes += "> 已上线,快来找他聊天吧";
             SendEveryoneMessage(mes);
             _mutex->lock();
             (*_online_map)[name] = _client;
             _mutex->unlock();

             return true;
         }
         else
         {
             std::string mes = "Sign Up Failed, The Same UserName Already Exists";
             SendOneMessage(Login_Err, mes, _client);
             return false;
         }
    }

    bool UserLoginCheck(std::string& name, std::string& passwd)
    {
        std::string mes;
        auto it = _user_map->find(name);

        if (it == _user_map->end())
        {
            //没找到直接Pass
            mes = "Sign In Failed, Your Account Is Wrong";
            SendOneMessage(Login_Err, mes, _client);
            return false;
        }

        if ((*_user_map)[name] != passwd)
        {
            //密码错误
            mes = "Sign In Failed, Your Password Is Wrong";
            SendOneMessage(Login_Err, mes, _client);
            return false;
        }
        if (_online_map->find(name) != _online_map->end())
        {
            //当前用户已经在线了
            mes = "The User has Signed In";
            SendOneMessage(Login_Err, mes, _client);
            return false;
        }
        mes = "Sign In Succeed! Weclome Back ";
        mes += name;
        mes += "!";
        SendOneMessage(0, mes, _client);
        mes = "用户<";
        mes += name;
        mes += "> 已上线,快来找他聊天吧";
        SendEveryoneMessage(mes);

        _mutex->lock();
        (*_online_map)[name] = _client;
        _mutex->unlock();

        //发送离线消息
        if (_buffer->find(name) != _buffer->end())
        {
            //离线buffer有它的信息
            SendOneMessage(Normal, offline_message, _client);
            sendto(_host, (*_buffer)[name].c_str(), (*_buffer)[name].size(), 0, (const struct sockaddr*)(&_client), sizeof _client);
            
            _mutex->lock();
            _buffer->erase(name);
            _mutex->unlock();
        }
        return true;
    }

    void LogOut(const std::string& name)
    {
        auto o_it = _online_map->find(name);
        if (o_it == _online_map->end())
        {
            //该用户不在线
            return;
        }

        _mutex->lock();
        _online_map->erase(name);
        _mutex->unlock();

        std::string mes;
        mes = "用户<";
        mes += name;
        mes += "> 已下线";
        SendEveryoneMessage(mes);

    }


    void run()
    {
        //根据类型处理信息
        if (_rq._type == "/signup")
        {
            //注册流程
            UserRegisterCheck(_rq._info1, _rq._info2);

        }
        else if (_rq._type == "/signin")
        {
            //登录流程
            UserLoginCheck(_rq._info1, _rq._info2);
        }

        else if (_rq._type == "/getonline")
        {
            //给客户端发在线用户表
            std::string online;
            GetOnlineUser(&online);
            SendOneMessage(Online_User, online, _client);
        }
        else if (_rq._type == "/getall")
        {
            std::string all;
            GetAllUser(&all);
            SendOneMessage(All_User, all, _client);

        }
        else if (_rq._type == "/exit")
        {
            //下线
            LogOut(_rq._info1);
        }
        else if (_rq._type.find("/info") != std::string::npos)
        {
            std::string client_name = _rq._type.substr(5);
            SendSpecifiedUser(client_name,_rq._info1, _rq._info2);
        }

    }
    void operator()()
    {
        run();
    }

    ~Task()
    {}

private:
    Request _rq;
    struct sockaddr_in _client;

    std::unordered_map<std::string, std::string>* _user_map;
    std::unordered_map<std::string, struct sockaddr_in>* _online_map;
    std::unordered_map<std::string, std::string>* _buffer;


    std::mutex* _mutex;
    SOCKET _host;

};

Task.h其实才是重中之重,所有服务器的需求我都写在了这里面,可以根据函数名和成员变量来分析每个函数都是实现了一个怎样的功能。 该项目服务器所有功能的实现,我都进行了接近完美的封装。

4.protocol.h的编写

#pragma once

#include <iostream>
#include <string>

const char blank_space_sep = ' ';
const char protocol_sep = '&';


class Request
{
public:
    Request() {} // 提供一个无参构造

    Request(const std::string& type, const std::string& info1 = "", const std::string& info2 = "")
        : _type(type), _info1(info1), _info2(info2) {}

    Request(const char* type, const  char* info1, const  char* info2)
        : _type(type), _info1(info1), _info2(info2) {}

    bool serialize(std::string* out_str)
    {
        // 协议规定 字符串格式应序列化为"len\n_type _info1 _info2\n"
        std::string main_body = _type;
        main_body += blank_space_sep;
        main_body += _info1;
        main_body += blank_space_sep;
        main_body += _info2;
        *out_str = std::to_string(main_body.size());
        *out_str += protocol_sep;
        *out_str += main_body;
        *out_str += protocol_sep;
        return true;
    }

    bool deserialize(std::string& in_str)
    {
        // 协议规定 in_str的格式应为"len&_type _info1 _info2&"
        size_t pos = in_str.find(protocol_sep);
        if (pos == std::string::npos)
        {
            // 说明没找到'&'
            return false;
        }
        std::string sl = in_str.substr(0, pos);
        int len = std::stoi(sl); // 如果这里的sl不是一串数字,stoi就会抛异常! BUG?  严格限制客户端行为!
        size_t total_len = sl.size() + 1 + len + 1;
        if (in_str.size() < total_len)
        {
            return false;
        }
        if (in_str[total_len - 1] != protocol_sep)
        {

            return false;
        }

        std::string main_body = in_str.substr(pos + 1, len);
        // main_body"_type _info1 _info2"
        size_t left = main_body.find(blank_space_sep);
        if (left == std::string::npos)
        {
            // 说明没找到' '
            return false;
        }
        size_t right = main_body.rfind(blank_space_sep);
        if (left == right)
        {
            // 说明只有一个' '
            return false;
        }
        _type = main_body.substr(0, left);   
        _info2 = main_body.substr(right + 1);
        _info1 = main_body.substr(left + 1, right - left - 1);
        in_str.erase(0, total_len);
        return true;
    }

    void print()
    {
        std::cout << _type << " " << _info1 << " " << _info2 << std::endl;
    }
    ~Request() {}

public:
    std::string _type;
    std::string _info1;
    std::string _info2;
};

class Respond
{
public:
    Respond() {} // 提供一个无参构造

    Respond(std::string info, int code = 0)
        : _info(info), _code(code) {}

    bool serialize(std::string* out_str)
    {
        // 协议规定 字符串格式应序列化为"len&_code _info"
        std::string main_body = std::to_string(_code);
        main_body += blank_space_sep;
        main_body += _info;

        *out_str = std::to_string(main_body.size());
        *out_str += protocol_sep;
        *out_str += main_body;
        *out_str += protocol_sep;

        return true;
    }

    bool deserialize(std::string& in_str)
    {
        // 协议规定 in_str的格式应为"len&_code _info"
        size_t pos = in_str.find(protocol_sep);
        if (pos == std::string::npos)
        {
            // 说明没找到'&'
            return false;
        }
        std::string sl = in_str.substr(0, pos);
        int len = std::stoi(sl); // 如果这里的sl不是一串数字,stoi就会抛异常! BUG?  严格限制客户端行为!
        size_t total_len = sl.size() + 1 + len + 1;
        if (in_str.size() < total_len)
        {
            return false;
        }
        if (in_str[total_len - 1] != protocol_sep)
        {

            return false;
        }

        std::string main_body = in_str.substr(pos + 1, len);

        // main_body"_code _info"
        size_t blank = main_body.find(blank_space_sep);
        if (blank == std::string::npos)
        {
            // 说明没找到' '
            return false;
        }
        _code = std::stoi(main_body.substr(0, blank));
        _info = main_body.substr(blank + 1);
        in_str.erase(0, total_len);
        return true;
    }

    void print()
    {
        std::cout << _code << " " << _info << std::endl;
    }

    ~Respond() {}

public:
    int _code; // 表示结果可信度  0表示可信
    std::string _info;
};

序列化和反序列化,我们之前也有写过。 因为我们这里服务器跟客户端的需求过多,所以就需要对提交来的数据进行分析。
class Request
{
_type 作为客户端想要执行的命令
_info1 作为客户端发来的信息1
_info 2 作为客户端发来的信息2
}

class Respond{
_code 作为服务端向客户端数据的标识符(解释)
_info 作为服务端向客户端发送的信息
}

5.线程池的编写

#pragma once

#include<iostream>
#include<thread>
#include<string>
#include<mutex>
#include<vector>
#include<queue>
#include<condition_variable>



static const int defalutnum = 10;

template <class T>
class ThreadPool
{
public:



    void Wakeup()
    {
        _cond.notify_one();
    }

    void ThreadSleep(std::unique_lock<std::mutex>& lock)
    {
        _cond.wait(lock);
    }

    bool IsQueueEmpty()
    {
        return _tasks.empty();
    }


public:
    static void HandlerTask(ThreadPool<T>* tp)
    {
        while (true)
        {
            T t;
            {
                std::unique_lock<std::mutex>lock(_mutex);
                while (tp->IsQueueEmpty())
                {
                    tp->ThreadSleep(lock);
                }
                t = tp->Pop();
            }
            t();
        }
    }
    void Start()
    {
        size_t num = _threads.size();
        for (int i = 0; i < num; i++)
        {
            _threads[i] = std::thread(HandlerTask, this);
        }
    }
    T Pop()
    {
        T t = _tasks.front();
        _tasks.pop();
        return t;
    }
    void Push(const T& t)
    {
        std::unique_lock<std::mutex>lock(_mutex);
        _tasks.push(t);
        Wakeup();
    }
    static ThreadPool<T>* GetInstance()
    {
        if (nullptr == _tp) 
        {
            std::unique_lock<std::mutex>lock(_lock);
            if (nullptr == _tp)
            {
                _tp = new ThreadPool<T>();
            }
        }

        return _tp;
    }

private:
    ThreadPool(int num = defalutnum) : _threads(num)
    {}
    ~ThreadPool() {}
    ThreadPool(const ThreadPool<T>&) = delete;
    const ThreadPool<T>& operator=(const ThreadPool<T>&) = delete;
private:
    std::vector<std::thread> _threads;
    std::queue<T> _tasks;

    static std::mutex _mutex;
    std::condition_variable _cond;

    static ThreadPool<T>* _tp;
    static std::mutex _lock;
};

template <class T>
ThreadPool<T>* ThreadPool<T>::_tp = nullptr;

template <class T>
std::mutex ThreadPool<T>::_lock;

template <class T>
std::mutex ThreadPool<T>::_mutex;

这里的线程池,我直接采用了C++11提供的Thread类,并进行了完美的封装。

6.main.cc

这个我是不太想贴出来的,有点侮辱大家的智商,不过考虑到广大大学生…

#include"UdpServer.h"

int main()
{
	UdpServer us;
	us.Init();
	us.Start();
	return 0;
}

二、客户端

1. Socket.h

与服务端Socket.h代码一致

2.protocol.h

与服务端protocol.h代码一致

3.UdpClient

#pragma once


#include<string>
#include<thread>

#include"protocol.h"
#include"Socket.h"
#include"menu.h"
#pragma comment(lib, "ws2_32.lib") // 链接库文件
#pragma warning(disable:4996)      //防止VS发出4996号警告

const int server_port = 8080;
const std::string server_ip = "127.0.0.1"; //提前写好服务器IP

std::string yourname = "";

enum Code {
    Login_Err = -1,
    Normal = 0,
    Online_User,
    All_User
};

struct Thread_Data
{
    SOCKET socket_fd;
    struct sockaddr_in server;
};

void GetOnlineUser(const Thread_Data& data)
{
    Request rq("/getonline");
    std::string info;
    rq.serialize(&info);
    sendto(data.socket_fd, info.c_str(), (int)info.size(), 0, (const struct sockaddr*)&data.server, sizeof(data.server));
}

void GetAllUser(const Thread_Data& data)
{
    Request rq("/getall");
    std::string info;
    rq.serialize(&info);
    sendto(data.socket_fd, info.c_str(), (int)info.size(), 0, (const struct sockaddr*)&data.server, sizeof(data.server));
}

void Exit(const Thread_Data& data)
{
    Request rq("/exit", yourname);
    std::string info;
    rq.serialize(&info);
    sendto(data.socket_fd, info.c_str(), (int)info.size(), 0, (const struct sockaddr*)&data.server, sizeof(data.server));
    exit(0);
}

void recv_mes(const Thread_Data& data)
{

    char buffer[1024];
    std::string message;
    while (true)
    {
        memset(buffer, 0, sizeof(buffer));
        struct sockaddr_in tmp;
        int tmp_len = sizeof(tmp);
        int n = recvfrom(data.socket_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&tmp, &tmp_len);
        if (n > 0)
        {
            buffer[n] = 0;
            message = buffer;

            while (!message.empty())
            {
                Respond rs;
                if (!rs.deserialize(message))
                {
                    //报文错误,丢弃所有报文
                    std::cout << "报文错误" << std::endl;
                    break;
                }
                if (rs._code == Online_User)
                {
                    PrintOnline(rs._info);
                    continue;
                }
                else if (rs._code == All_User)
                {
                    PrintAll(rs._info);
                    continue;
                }
                std::cout << rs._info << std::endl;
            }

        }
        else
        {
            std::cout << "Recv Error..." << std::endl;
            exit(1);
        }

    }
}

void LoginPage(const Thread_Data& data)
{
    std::string name;
    while (true)
    {
        std::cout << sign_page << std::endl;
        std::string login;
        std::cin >> login;
        std::string passwd;
        if (login == "1")
        {
            //登录
            std::cout << "请输入你的账号@";
            std::cin >> name;
            std::cout << "请输入你的密码@";
            std::cin >> passwd;
            Request rq("/signin", name.c_str(), passwd.c_str());
            std::string mes;
            rq.serialize(&mes);
            sendto(data.socket_fd, mes.c_str(), (int)mes.size(), 0, (const struct sockaddr*)&data.server, sizeof(data.server));
            break;
        }
        else if (login == "2")
        {
            //注册
            while (true)
            {
                std::cout << "请输入你要注册的账号@";
                std::cin >> name;

                std::cout << "请输入你要注册的密码@";
                std::cin >> passwd;

                std::string confirm;
                std::cout << "请重新输入你的密码@";
                std::cin >> confirm;
                if (confirm != passwd)
                {
                    std::cout << "两次输入的密码不正确,请重新注册" << std::endl;
                    continue;
                }
                break;
            }
            Request rq("/signup", name.c_str(), passwd.c_str());
            std::string mes;
            rq.serialize(&mes);
            sendto(data.socket_fd, mes.c_str(), (int)mes.size(), 0, (const struct sockaddr*)&data.server, sizeof(data.server));

            break;
        }
        else
        {
            //用户输入错误
            std::cout << "输入错误,请重新输入" << std::endl;
            continue;
        }
    }
    yourname = name;
}

void HandRequest(const Thread_Data& data)
{
    std::string message;
    std::cout << ico << std::endl;
    while (true)
    {
        LoginPage(data);

        //回应请求
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        struct sockaddr_in tmp;
        int tmp_len = sizeof tmp;
        int n = recvfrom(data.socket_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&tmp, &tmp_len);
        if (n > 0)
        {
            buffer[n] = 0;
            message = buffer;
            Respond rs;
            rs.deserialize(message);
            std::cout << rs._info << std::endl;
            if (rs._code == Login_Err)
            {
                std::cout << "请重新登录/注册" << std::endl;
                continue;
            }
            break;
        }
        else
        {
            std::cout << "Hand Recv Error..." << std::endl;
            exit(1);
        }
    }

}




void ConnectUser(const Thread_Data& data, const std::string& name)
{
    std::string mes;
    std::cout << "------------------------------------------------------------------------------------------" << std::endl;
    std::cout << "                                          聊天框" << std::endl;
    std::cout << "小帮手: 想要退出聊天框请输入/quit" << std::endl;
    while (true)
    {
        std::cout << "Send A Message@";
        std::cin >> mes;
        if (mes == "/help")
        {
            HelpMenu();
            continue;
        }
        else if (mes == "/quit")
        {
            break;
        }
        else if (mes == "/online")
        {
            GetOnlineUser(data);
            continue;
        }

        else if (mes == "/all")
        {
            GetAllUser(data);
            continue;
        }

        else if (mes == "/clear")
        {
            system("cls");
        }

        else if (mes == "/exit")
        {
            Exit(data);
        }

        std::string cmd = "/info";
        cmd += yourname;
        Request rq(cmd.c_str(), name, mes.c_str());
        std::string info;
        rq.serialize(&info);
        sendto(data.socket_fd, info.c_str(), (int)info.size(), 0, (const struct sockaddr*)&data.server, sizeof(data.server));
        std::cout << "成功发送消息" << std::endl;

    }
    std::cout << "------------------------------------------------------------------------------------------" << std::endl;
}

void send_mes(const Thread_Data& data)
{
    HelpMenu();
    std::string command;
    std::string name;
    while (true)
    {
        std::cin >> command;
        //根据
        if(command == "/online")
        { 
            GetOnlineUser(data);
            continue;
        }
        else if (command == "/all")
        {
            GetAllUser(data);
            continue;
        }
        else if (command == "/help")
        {
            HelpMenu();
            continue;
        }
        else if (command == "/go")
        {
            GetOnlineUser(data);
            std::cout << "你想要和谁聊天?" << std::endl;
            std::cin >> name;
            if (name == yourname)
            {
                std::cout << "不可与自己聊天,退出" << std::endl;
                continue;
            }
            ConnectUser(data, name);
            std::cout << "你已离开与 " << name << " 的聊天," << "可以输入/online ,查看当前在线用户" << std::endl;
        }
        else if (command == "/clear")
        {
            system("cls");
        }
        else if (command == "/exit")
        {
            Exit(data);
        }
        else
        {
            std::cout << "未知命令,输入/help来查看所有命令" << std::endl;
        }


    }
}




class UdpClient {
public:
	UdpClient() {}

	void Init()
	{
        WSADATA wsd;
        WSAStartup(MAKEWORD(2, 2), &wsd);

		_host.Init();
	}

	void Start()
	{
        struct sockaddr_in server;
        memset(&server, 0, sizeof server);
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());
        server.sin_port = htons(server_port);
        std::thread threads[2];
        Thread_Data data;
        data.server = server;
        data.socket_fd = _host._listensock;

        //等待第一次握手请求
        HandRequest(data);

        threads[1] = std::thread(send_mes, std::ref(data));

        threads[0] = std::thread(recv_mes, std::ref(data));


        threads[0].join();
        threads[1].join();
	}

	~UdpClient() {
        WSACleanup();   //清理网络环境
    }
private:
	Socket _host;
};




客户端的设计,我是分为两个部分,第一个部分我称为客户端的握手请求,
让客户端进行登录或注册。 然后再进入第二个部分,从这里开始读取数据和写数据就使用多线程让两个模块进行分离。

写数据的进程 可以让用户自由选择交互命令,读数据的进程分析服务器发来的数据,并进行响应。

4.menu.h

#pragma once

#include<string>
#include<iostream>
#include<vector>
//#pragma execution_character_set("utf-8")


const std::string ico =
"  ______                      _                 _   _        _____ _           _  \n\
 |  ____|                    | |               (_) ( )      / ____| |         | | \n\
 | |__ ___ _ __   __ _       | |_   _ _ __  _____  |/ ___  | |    | |__   __ _| |_ \n\
 |  __/ _ \\ '_ \\ / _` |  _   | | | | | '_ \\|_  / |   / __| | |    | '_ \\ / _` | __|\n\
 | | |  __/ | | | (_| | | |__| | |_| | | | |/ /| |   \\__ \\ | |____| | | | (_| | |_ \n\
 |_|  \\___|_| |_|\\__, |  \\____/ \\__,_|_| |_/___|_|   |___/  \\_____|_| |_|\\__,_|\\__|\n\
                  __/ |                                                            \n\
                 |___/                                                             \n";

const std::string sign_page =
" ------------------------------------------------------------------------------------------\n\
|           1.sign in(登录)                                2. sign up(注册)               |\n\
|                                       请输入序号                                        |\n\
|                                                                                         |\n\
 ------------------------------------------------------------------------------------------";


const std::vector<std::string> OKword = { "" };


void PrintOnline(const std::string& online)
{
    std::cout << "------------------------------------------------------------------------------------------" << std::endl;
    std::cout << "                                        当前在线用户" << std::endl;
    std::cout << online << std::endl;
    std::cout << "------------------------------------------------------------------------------------------" << std::endl;
}

void PrintAll(const std::string& all)
{
    {
        std::cout << "------------------------------------------------------------------------------------------" << std::endl;
        std::cout << "                                        所有注册用户" << std::endl;
        std::cout << all << std::endl;
        std::cout << "------------------------------------------------------------------------------------------" << std::endl;
    }

}

void HelpMenu()
{
    printf(" --------------------------------------------------------------------------------------------------------------------\n\
|                                                  小帮手                                                            |\n\
| 输入/online  可以查看当前在线用户                                                                                  |\n\
| 输入/all     可以查看所有注册用户                                                                                  |\n\
| 输入/help    可以再次召唤小帮手                                                                                    |\n\
| 输入/go      进入一个指定用户的聊天窗口,在聊天窗口内不可使用/go命令,期间可以收到其他用户发来的消息               |\n\
| 输入/quit    可以离开与当前用户的聊天窗口                                                                          |\n\
| 输入/clear   可以清理界面                                                                                          |\n\
| 输入/exit    关闭客户端,下线                                                                                      |\n\
|                                                                                                                    |\n\
| WARNING: 由于该程序采用UDP协议,为无连接传输协议,请登录后务必使用/exit退出程序,不要直接关闭客户端,否则后果自负  |\n\
| WARNING: 由于该程序采用UDP协议,为无连接传输协议,请登录后务必使用/exit退出程序,不要直接关闭客户端,否则后果自负  |\n\
| WARNING: 由于该程序采用UDP协议,为无连接传输协议,请登录后务必使用/exit退出程序,不要直接关闭客户端,否则后果自负  |\n\
 --------------------------------------------------------------------------------------------------------------------\n");
}

这个头文件就主要是在命令行界面一定程度上做一些仿图形界面,方便美观。

5.main.cpp

#include"UdpClient.h"

int main()
{
    UdpClient uc;
    uc.Init();
    uc.Start();

    return 0;
}

三、运行图

这里我就不放出服务器运行的图片了,因为服务器运行的时候,我没有写什么输出屏幕语句,所以啥也没有。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1832388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows 11 之 下载安装 curl

1. 背景 需要执行一个 curl 命令运行一个定时任务&#xff0c;之前博主用的mac&#xff0c;无需担心这个下载安装问题&#xff0c;现在转为 windows&#xff0c;需要下载安装crul命令。 2. 出现问题 3. 解决办法 3.1 下载最新的包 下载地址&#xff1a;https://curl.se/win…

文档项目:攻坚克难

鉴于交流离心机存在的缺点&#xff1a;转速相对偏差、稳定精度不够高&#xff1b;带负载能力受外界扰动后&#xff0c;波动较大&#xff1b;寿命短&#xff0c;研究所各相关部门成立组成技术攻关团队&#xff0c;齐心协力&#xff0c;攻坚克难&#xff0c;在摸索中突破创新&…

推荐 3 款小巧的文件压缩、投屏和快速启动软件,请收藏,避免找不到

Maya Maya是一款由博主25H开发的体积小巧、简单易用的快速启动工具。它的操作逻辑和界面设计几乎复刻了Rolan早期版本&#xff0c;功能上与Rolan几乎别无二致。Maya支持多文件拖拽添加启动、快捷键呼出、自动多列显示等功能。此外&#xff0c;Maya还具备lnk文件解析功能。 May…

“Jedis与Redis整合指南:实现高效的Java应用与Redis交互“

目录 #. 概念 1. 导入jedis依赖 2. 写一个类&#xff08;ping通redis&#xff09; 3. String字符串使用 3.1 set&#xff0c;get方法使用&#xff08;设值&#xff0c;取值&#xff09; 3.2 mset&#xff0c;mget方法使用&#xff08;设置多个值&#xff0c;取多个值&…

【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【10】【仓库管理】【分布式基础篇总结】

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【10】【仓库管理】【分布式基础篇总结】 采购简要流程采购单采购人员的接口分布式基础篇总结参考 采购简要流程 采购单 可以搞个枚举&#xff1a; public class WareConstant {public enu…

2024中国应急(消防)品牌巡展成都站成功召开!

汇聚品牌力量&#xff0c;共同相聚成都。6月14日&#xff0c;由中国安全产业协会指导&#xff0c;中国安全产业协会应急创新分会、应急救援产业网联合主办&#xff0c;四川省消防协会协办的“一切为了安全”2024年中国应急(消防)品牌巡展-成都站成功举办。该巡展旨在展示中国应…

特殊医学用途配方食品注册数据库

在这个追求健康的时代&#xff0c;特殊医学用途配方食品&#xff08;简称特医食品&#xff09;已成为众多特殊需求人群的膳食选择。它们不仅满足了特定疾病状态下的营养需求&#xff0c;更是病患康复之路上的重要伴侣。然而&#xff0c;面对市场上琳琅满目的特医食品&#xff0…

如何完成独立接口测试

有时我们需要完成独立的接口测试工作&#xff0c;在经过上面的接口测试工作准备后&#xff0c;下面我们可以这样开展独立的接口测试工作。先快速的学习接口设计&#xff0c;有一个整体的认识&#xff0c;再确定接口测试工作目标&#xff0c;再经过第一阶段确认接口的功能能够正…

2-8 基于matlab的ESMD(Extreme-Point Symmetric Mode Decomposition)信号分解算法

基于matlab的ESMD&#xff08;Extreme-Point Symmetric Mode Decomposition&#xff09;信号分解算法&#xff0c;其基本思想是通过寻找数据序列中的极大值点和极小值点&#xff0c;并以此为基础进行信号分解。该方法在观测数据的趋势分离、异常诊断和时-频分析方面具有独特优势…

Redis 网络模型

一、用户空间和内核空间 1.1 linux 简介 服务器大多采用 Linux 系统&#xff0c;这里我们以 Linux 为例来讲解&#xff0c;下面有两个不同的 linux 发行版&#xff0c;分别位 ubuntu 和 centos&#xff0c;其实发行版就是在 Linux 系统上包了一层壳。 任何 Linux 发行版&#…

【排序算法】希尔排序详解(C语言)

文章目录 前言希尔排序的原理原理思路 代码实现希尔排序的相关问题效率算法稳定性 前言 为什么会有希尔排序&#xff0c;要从插入排序说起&#xff0c;希尔排序一开始设计出来是为了改进插入排序&#xff0c;因为插入排序在处理大量数据时效率不高&#xff0c;特别是对于近乎有…

结合Boosting理论与深度ResNet:ICML2018论文代码详解与实现

代码见&#xff1a;JordanAsh/boostresnet: A PyTorch implementation of BoostResNet 原始论文&#xff1a;Huang F, Ash J, Langford J, et al. Learning deep resnet blocks sequentially using boosting theory[C]//International Conference on Machine Learning. PMLR, 2…

Bagging与Boosting的应用与优势

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

深浅拷贝以及正则表达式(python)

浅拷贝和深拷贝&#xff1a; 浅拷贝&#xff1a; copy函数是浅拷贝&#xff0c;支队可变类型的第一层对象进行拷贝&#xff0c;对拷贝的对象开辟显得内存空间进行存储&#xff0c;不会拷贝对象内部的子对象 不可变类型的浅拷贝示例&#xff1a; 浅拷贝不会对不可变类型进行…

KVB:怎么样选择最优交易周期?

摘要 在金融交易中&#xff0c;周期的选择是影响交易成败的重要因素之一。不同的交易周期对应不同的市场环境和交易策略&#xff0c;选择合适的周期可以提高交易的成功率。本文将详细探讨交易中如何选择最优周期&#xff0c;包括短周期、中周期和长周期的特点及适用情况&#…

ssm宠物网站系统-计算机毕业设计源码07183

摘 要 在信息飞速发展的今天&#xff0c;网络已成为人们重要的信息交流平台。宠物网站每天都有大量的信息需要通过网络发布&#xff0c;为此&#xff0c;本人开发了一个基于B/S&#xff08;浏览器/服务器&#xff09;模式的宠物网站系统。 该系统以JJava编程语言、MySQL和SSM框…

Linux自旋锁

面对没有获取锁的现场&#xff0c;通常有两种处理方式。 互斥锁&#xff1a;堵塞自己&#xff0c;等待重新调度请求自旋锁&#xff1a;循环等待该锁是否已经释放 本文主要讲述自旋锁 自旋锁其实是一种很乐观的锁&#xff0c;他认为只要再等一下下锁便能释放&#xff0c;避免…

最适合程序员的编程字体,漂亮、独特、优雅!(2024-06-17)

Monaco Monaco 字体是一款专为编程和代码编辑设计的等宽字体&#xff0c;以其简洁明了的无衬线设计风格、高可读性和清晰的字符区分度&#xff0c;受到开发者们的青睐&#xff0c;Mac 自带 Monaco 字体。 Consolas Consolas 是一款等宽无衬线字体&#xff0c;专为编程和代码编…

vscode c++ 开发环境配置

今天各位同学已经安装了mingw环境&#xff0c;但部分同学vscode开发环境又问题&#xff0c;究其原因&#xff0c;还是vscode 编译环境配置错误&#xff0c;有问题的同学 按如下步骤处理&#xff1a; 1、卸载相关插件&#xff0c;特别是中文插件&#xff0c;原因是暂时回避中文…