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

文章目录

  • 前言
  • 项目需求介绍
  • 一、服务端
    • 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;
}

三、运行图

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

汇聚荣拼多多运营策略是怎么样的?

拼多多作为中国领先的电商平台&#xff0c;其运营策略一直备受关注。拼多多的成功不仅在于其创新的“社交电商”模式&#xff0c;更在于其精细化的市场定位和高效的用户增长策略。本文将深入分析拼多多的运营策略&#xff0c;探讨其如何在激烈的电商竞争中突围而出。 一、用户增…

Vue3-滑动到最右验证功能

1、思路 1、在登录页面需要启动向右滑块验证 2、效果图 3、文章地址&#xff1a;滑动验证码的实现-vue-simple-verify 2、成分分析 1、由三块构成&#xff0c;分别是底部条、拖动条、拖动移动部分 2、底部条&#xff1a;整体容器&#xff0c;包括背景、边框和文字&#xf…

企业级Flask项目移植记录【待补完】

背景 Flask项目A从老电脑C1移植到新电脑C2上 更新日志 1-240614上线第一版经验&#xff0c;博主关于这个项目包的问题还没有解决&#xff0c;正在解决中&#xff0c;但是整体思路大家在移植的时候可以借鉴。 思路 1-首先生产环境的python解释器等相关的东西最好严格保持一…

基于自编码器的心电信号异常检测(Pytorch)

代码较为简单&#xff0c;很容易读懂。 # Importing necessary libraries for TensorFlow, pandas, numpy, and matplotlib import tensorflow as tf import pandas as pd import numpy as np import matplotlib.pyplot as plt import copy# Importing the PyTorch library im…

项目管理进阶之EVM(挣值管理)

前言 项目管理进阶系列&#xff0c;终于有时间更新啦&#xff01;&#xff01;&#xff01;欢迎持续关注哦~ 上一节博主重点讲了一个环&#xff1a;PDCA&#xff0c;无论各行各业&#xff0c;上到航空航天、下到种地种菜&#xff0c;都离不开对质量的监督和改进。这个环既是一…

轻松4步!格式工厂怎么转换mp3教会你

在数字化时代&#xff0c;音频文件格式转换变得越发重要&#xff0c;而格式工厂作为一款强大而多功能的工具&#xff0c;为我们提供了便捷的音频转换解决方案。特别是在将音频文件转换为MP3的需求上&#xff0c;格式工厂以其简便易用的特点备受欢迎。格式工厂怎么转换mp3&#…

力扣172. 阶乘后的零

Problem: 172. 阶乘后的零 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 1.要使得末尾出现0&#xff0c;则乘式中必须出现因子2与5&#xff1b; 2.而由于对于一个数的阶乘&#xff0c;易知因子2的个数是大于因子5的个数&#xff08;因为只要出现偶数则可以分解出…

好的品牌营销策划方案需包含哪些?

现在很多企业家和创业者&#xff0c;对品牌营销知之甚少。作为一个多年的老营销人&#xff0c;可以来谈谈我们写一个品牌方案一般包含哪些内容。 首先&#xff0c;我们必须认识到&#xff0c;品牌策划的第一步其实是市场调研。 这不仅仅是对企业的简单了解&#xff0c;更包括…

计数排序(Counting Sort)

计数排序&#xff08;Counting Sort&#xff09; 计数排序是一个非基于比较的排序算法&#xff0c;该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时&#xff0c;快于任何比较排序算法。排序思路: 1.找出待排序数组最大值2.定义一个索引最大…

鸿蒙: 基础认证

先贴鸿蒙认证 官网10个类别总结如下 https://developer.huawei.com/consumer/cn/training/dev-cert-detail/101666948302721398 10节课学习完考试 考试 90分合格 3次机会 1个小时 不能切屏 运行hello world hvigorfile.ts是工程级编译构建任务脚本 build-profile.json5是工程…

裁剪图片的最简单方法?这四种裁剪方法真的超级简单!

裁剪图片的最简单方法&#xff1f;在丰富多彩的现代生活中&#xff0c;图片成为了我们表达、沟通甚至展示身份的重要媒介&#xff0c;然而&#xff0c;无论是出于个人审美还是专业需求&#xff0c;图片的格式和尺寸往往成为了我们不得不面对的问题&#xff0c;特别是那些未经雕…

小孟再接盲盒小程序,3天开发完!

大家好&#xff0c;我是程序员小孟。 前面开发了很多的商业的单子&#xff0c;私活联盟的小伙伴慢慢的逐渐搞自己的产品。 前面的话&#xff0c;开发了盲盒小程序&#xff0c;最近又接了一款盲盒小程序。因为前面有开发过&#xff0c;所以我们的成本也少了很多。 盲盒小程序…

2024年最佳插电式混合动力电动汽车

对电动汽车充满好奇和环保意识的司机们还没有准备好跨入纯电动汽车&#xff0c;他们可以找到一个折衷方案&#xff0c;即插电式混合动力车。 在过去的16年里&#xff0c;我一直在把握汽车行业的脉搏。试驾数百辆汽车、电动汽车、插电式混合动力车&#xff0c;跟踪汽车行业的新闻…

一行代码实现鼠标横向滚动

&#x1f9d1;‍&#x1f4bb; 写在开头 点赞 收藏 学会&#x1f923;&#x1f923;&#x1f923; 在项目中我们可能会遇到当鼠标在某个区域内&#xff0c;我们希望滚动鼠标里面的内容可以横向滚动&#xff1b; 比如我们一些常见的后台状态栏&#xff1a; 那这种该怎么写&…

GitLab安装部署以及bug修复

使用git&#xff0c;还需要一个远程代码仓库。常见的github、gitee这种远程代码仓库&#xff0c;公司中一般不会使用&#xff0c;因为他们是使用外网的&#xff0c;不够安全。一般企业都会搭建一个仅内网使用的远程代码仓库&#xff0c;最常见就是 GitLab 安装准备 需要开启s…

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

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

NATAPP-内网穿透工具----下载与配置

NATAPP-内网穿透工具 基于ngrok的国内高速内网穿透服务&#xff0c;natapp提供了一种便利的方式&#xff0c;使得开发和测试过程更加高效&#xff0c;尤其是在需要进行远程调试或展示时。无论是进行web开发、微信和支付宝的本地开发调试&#xff0c;还是简单地从外部网络访问家…

win10 修改远程桌面端口,win10 修改远程桌面端口详细步骤

在Windows 10中修改远程桌面端口是一个涉及系统配置和网络安全的任务&#xff0c;需要谨慎操作以确保系统的稳定性和安全性。 以下是详细的步骤内容&#xff0c;供您参考&#xff1a; 一、通过注册表编辑器修改远程桌面端口 1. 打开注册表编辑器&#xff1a; - 首先&#…

美国犹他州立大学《Nature Geoscience》(IF=18)!揭示草本植物对土壤有机碳的重要贡献!

随着全球变暖的影响越来越显著&#xff0c;碳固定成为了一个备受关注的话题。在这个背景下&#xff0c;热带草原被认为是一个潜在的碳固定区域。然而&#xff0c;目前的研究主要关注于在热带草原中种植树木&#xff0c;以期望增加土壤有机碳含量。但是&#xff0c;热带草原中的…

llamaindex原理与应用简介(宏观理解)

llamaindex原理与应用简介&#xff08;宏观理解&#xff09; 文章目录 llamaindex原理与应用简介&#xff08;宏观理解&#xff09; 这是我认为对于 llamaindex 应用的场景概述讲的相对比较好的视频&#xff1a;llamaindex原理与应用简介