当前位置: 首页 > news >正文

大学课设项目,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 intmemset(&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()){//没找到直接Passmes = "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"; //提前写好服务器IPstd::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;
}

三、运行图

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

在这里插入图片描述

相关文章:

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

文章目录 前言项目需求介绍一、服务端1.对Udp套接字进行一个封装2. UdpServer的编写3. Task.h4.protocol.h的编写5.线程池的编写6.main.cc 二、客户端1. Socket.h2.protocol.h3.UdpClient4.menu.h5.main.cpp 三、运行图 前言 本次项目可以作为之前内容的一个扩展&#xff0c;学…...

【5.x】ELK日志分析、集群部署

ELK日志分析 一、ELK概述 1、ELK简介 ELK平台是一套完整的日志集中处理解决方案&#xff0c;将ElasticSearch、Logstash和Kiabana三个开源工具配合使用&#xff0c;完成更强大的用户对日志的查询、排序、统计需求。 一个完整的集中式日志系统&#xff0c;需要包含以下几个主…...

揭秘创业加盟:豫腾助力,发掘商机,共赢未来

在我们生活的这个充满活力与机遇的世界里&#xff0c;商业活动如繁星点点&#xff0c;照亮着每个人的创业梦想。 在这个过程中&#xff0c;创业加盟作为一种独特且吸引人的模式&#xff0c;逐渐受到广大创业者的关注。 本文将深入解析创业加盟的精髓&#xff0c;以及如何在其…...

Linux操作系统以及一些操作命令、安装教程

Web课程完结啦&#xff0c;这是Web第一天的课程大家有兴趣可以传送过去学习 http://t.csdnimg.cn/K547r Linux-Day01 课程内容 Linux简介 Linux安装 Linux常用命令 1. 前言 1.1 什么是Linux Linux是一套免费使用和自由传播的操作系统。说到操作系统&#xff0c;大家比…...

树莓派4B_OpenCv学习笔记6:OpenCv识别已知颜色_运用掩膜

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 版本是4.5.1&#xff1a; 学了这些OpenCv的理论性知识&#xff0c;不进行实践实在…...

ZSH 配置

ZSH 配置 1. 安装 ZSH2. 安装 oh my zsh3. 安装插件3.1 autojump3.2 zsh-autosuggestions 1. 安装 ZSH sudo apt-get install zsh 完成安装后需设置当前用户使用 zsh&#xff1a; chsh -s /bin/zsh 重启后即可使用 2. 安装 oh my zsh 安装 oh my zsh 需先安装 git。 自动安装…...

LogicFlow 学习笔记——5. LogicFlow 基础 主题 Theme

主题 Theme LogicFlow 提供了设置主题的方法&#xff0c;便于用户统一设置其内部所有元素的样式。设置方式有两种&#xff1a; 初始化LogicFlow时作为配置传入初始化后&#xff0c;调用LogicFlow的 setTheme 方法 主题配置参数见主题API 配置 new LogicFlow 时作为将主题配…...

Centos7.9使用kubeadm部署K8S集群

Centos7.9使用kubeadm部署K8S集群 使用kubeadm部署一个k8s集群&#xff0c;单master2worker节点。 1. 环境信息 操作系统&#xff1a;CentOS 7.9.2009内存: 2GBCPU: 2网络: 能够互访&#xff0c;能够访问互联网 hostnameip备注k8s-master192.168.0.51masterk8s-node1192.16…...

VB.net调用VC DLL(二)

参考文献5&#xff0c;讲了在C程序中直接调用DLL和lib的函数方法&#xff0c;不是通过编译器连接方式 也讲了在非C程序中直接调用DLL和lib的函数方法。 实操了一下&#xff0c;建了win32dll项目 新建.h文件和.cpp文件 .h文件中加&#xff1a; void __stdcall funcA(double…...

社团管理系统

用Spring Boot、Vue.js和MyBatis实现社团管理系统 温馨提示&#xff1a;项目源代码获取方式见文末 摘要 本文探讨了如何使用Spring Boot作为后端框架&#xff0c;Vue.js作为前端框架&#xff0c;以及MyBatis作为数据库持久层框架&#xff0c;构建一个社团管理系统。该系统旨…...

网站的文章起到什么作用

1.便于用户了解产品服务 如果想要获得更多的用户访问或者转化率&#xff0c;那么网站就得需要高质量、高原创的文章&#xff0c;通过文章可以让用户更好的了解公司的产品和服务&#xff0c;用户会根据自己的需求去选择服务类型&#xff0c;从而可以给公司产生业务订单&am…...

Science | 稀土开采威胁马来西亚的生物多样性

马来西亚是一个生物多样性热点地区&#xff0c;拥有超过17万种物种&#xff0c;其中1600多种处于濒临灭绝的风险。马来西亚的热带雨林蕴藏了大部分的生物多样性&#xff0c;并为全球提供重要的生态系统效益&#xff0c;同时为土著社区带来经济和文化价值。同时马来西亚具有可观…...

pandas read_csv跳过有错的行

在使用Pandas的read_csv函数时&#xff0c;如果遇到格式错误或其他导致读取失败的行&#xff0c;Pandas默认会抛出异常。但如果你想让Pandas自动跳过这些错误行&#xff0c;可以使用error_bad_linesFalse参数。这将使Pandas在遇到格式错误的行时&#xff0c;忽略这些行而不是中…...

VRChat 2024年裁员原因与背景深度分析

VRChat&#xff0c;作为2022年元宇宙/VR社交领域的巨头&#xff0c;近期在2024年宣布裁员计划&#xff0c;其背后原因和背景值得业界尤其是仍在纯元宇宙虚拟空间创业的同仁们重点关注。 一、创始人决策失误 根据CEO的邮件披露&#xff0c;VRChat的创始人因缺乏经验和过度自信…...

mybatisplus 笔记

int isDelete userRoleMapper.delete(new LambdaQueryWrapper<UserRole>().in(UserRole::getUserId, roleUserDTO.getUserId()).in(UserRole::getRoleId, roleUserDTO.getRoleId()));LambdaQueryWrapper<UserRole>: LambdaQueryWrapper 是 MyBatis Plus 提供的一个…...

Shell脚本(.sh文件)如何执行完毕之后不自动关闭?

Shell脚本异常傲娇&#xff0c;出错后、执行完根本不给你机会让你查看报错信息、输出信息&#xff0c;直接闪退。 废话不多说&#xff0c;调教方法如下&#xff0c;直接在Shell脚本末尾加上如下代码&#xff1a; 1、实现方式一 1.1 使用read命令达到类似bat中的pause命令效果…...

苏州辰安塑业携塑料托盘、塑料物流箱解决方案亮相2024杭州快递物流展

苏州辰安塑业携塑料托盘、吹塑托盘、塑料卡板箱、塑料周转箱、塑料物流箱、塑料垃圾桶解决方案盛装亮相2024杭州快递物流展&#xff01; 展位号&#xff1a;3C馆A51 苏州辰安塑业有限公司&#xff0c;是一家专业从事塑料托盘、吹塑托盘、塑料卡板箱、塑料周转箱、塑料物流箱、…...

大模型应用开发技术:Multi-Agent框架流程、源码及案例实战(二)

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…...

Flutter 实现dispose探测控件

文章目录 前言一、什么是dispose探测控件&#xff1f;1、通常情况2、使用dispose探测控件 二、如何实现1、继承StatefulWidget2、定义dipose回调3、定义child4、重载Dispose方法5、build child 三、完整代码四、使用示例1、基本用法2、设置定义数据 总结 前言 开发flutter一般…...

如何定义和衡量一个产品的成功?

定义和衡量一个产品的成功是一个多维度的过程&#xff0c;涉及用户满意度、市场表现、商业成果和技术实现等多个方面。以下是几个关键点&#xff0c;可以帮助产品经理在面试中全面阐述这一话题&#xff1a; 用户层面的成功&#xff1a; 用户满意度&#xff1a;通过用户调研、N…...

微调大模型 - 面向学术论文的AI大模型

1、优化学术交流: 该项目专注于优化学术文献的阅读、润色和写作过程,提供了实用化的交互接口。 2、模块化和可扩展性: 项目设计模块化,支持自定义快捷按钮和函数插件,便于用户根据自己的需求进行扩展。 3、多语言和多模型支持: 支持多种语言的论文翻译和总结,同时可以并行…...

java溯本求源之基础(二十四)之--常见List的实现共同点

兄弟们终于到了上代码讲代码的环节了&#xff0c;之前的一些代码都是小打小闹&#xff0c;现在才是重头戏&#xff0c;今天来简单说说一些集合&#xff0c;首先这些都是基于数组实现的&#xff0c;当然Collections.emptyList不算奥&#xff0c;别犟。剩下的不多墨迹直接上重点&…...

gin连接redis

使用Gin框架连接Redis&#xff0c;需要先安装Redis客户端库&#xff0c;例如go-redis。然后&#xff0c;你可以创建一个Redis客户端实例&#xff0c;并在Gin路由处理函数中使用它。以下是一个简单的示例&#xff1a; 1. 首先&#xff0c;安装go-redis库&#xff1a; bash go g…...

深度学习训练——batch_size参数设置过大反而训练更耗时的原因分析

&#x1f4aa; 专业从事且热爱图像处理&#xff0c;图像处理专栏更新如下&#x1f447;&#xff1a; &#x1f4dd;《图像去噪》 &#x1f4dd;《超分辨率重建》 &#x1f4dd;《语义分割》 &#x1f4dd;《风格迁移》 &#x1f4dd;《目标检测》 &#x1f4dd;《暗光增强》 &a…...

jvm必知必会-类的生命周期图文详解

类的生命周期描述了一个从加载、使用到卸载的过程; 而其中的 连接 部分又分为一下三个阶段: 验证准备解析6.1 加载阶段 Loading阶段第一步是 类加载器 会根据类全限定名通过不同的渠道以二进制流的方式获取字节码信息,程序员可以使用Java代码扩展不同的渠道。 比如通过 …...

一文看懂人工智能、机器学习、深度学习是什么、有什么区别!

引言&#xff1a;走进智能的世界 曾经&#xff0c;人工智能&#xff08;AI&#xff09;是科幻小说中的概念&#xff0c;与飞船、外星人并肩而立。 然而&#xff0c;随着时间的推移&#xff0c;AI不再仅仅是幻想的产物&#xff0c;它已经成为我们日常生活中不可或缺的一部分。 在…...

Vue47-修改默认配置webpack.config.js文件

main.js是脚手架项目的入口文件&#xff0c;系统运行时&#xff0c;默认去找src下的main.js文件。这是webpack通过配置文件&#xff1a;webpack.config.js配置的。 脚手架把所有重要的配置文件都隐藏了&#xff0c;方式被开发者修改。 一、查看被隐藏的webpack配置 1-1、webpa…...

qss实现登录界面美化

qss实现登录界面美化 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);// 去掉头部this->setWindowFlag(Qt::FramelessWindowHint);// 去掉空白部分th…...

ROS系统中解析通过CAN协议传输的超声波传感器数据

CAN Bus接口设置&#xff1a;确保你的ROS系统可以通过CAN Bus接口与外部设备通信。这可能需要CAN卡或CAN适配器&#xff0c;以及相应的驱动程序和库。 CAN消息接收&#xff1a;配置ROS节点来监听特定的CAN ID&#xff0c;这通常是超声波传感器的标识符。 数据解析&#xff1a…...

SonarQube安全扫描常见问题

目录 一、SonarQube质量报告 二、SonarQube扫描常见问题和修复方法 三、SonarQube质量配置 最近小编在使用SonarQube工具进行代码扫描&#xff0c;检查代码异味&#xff0c;系统漏洞等&#xff0c;实际过程中也遇到了不少问题&#xff0c;这篇文章主要列举我遇到的常见问题和…...

营销推广活动方案/谷歌seo排名公司

1.1 判断本机是否联网 if(SystemInformation.Network) {//联网状态 } else {//未联网状态 } 1.2 获取特殊文件路径 1.2.1 获取Program Files路径 string FilePath Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); 1.2.2 获取桌面目录路径 string FilePat…...

网站栏目划分/永久免费自助建站平台

http://codeforces.com/contest/351/problem/E 题意&#xff1a; 给出一些数&#xff0c;可以改变任意数的正负&#xff0c;使序列的逆序对数量最少 因为可以任意加负号&#xff0c;所以可以先把所有数看作正数 序列中的数无论怎么改&#xff0c;都不会改变与绝对值最大的数的逆…...

创造与魔法官方网站-做自己喜欢的事/北京网

目录 文档用途 详细信息 文档用途 本文介绍向PostgreSQL中加载数据的几种方法。 详细信息 1.服务器端 1.1 COPY/COPY FROM PROGRAM COPY用于在PostgreSQL表和标准文件系统文件之间移动数据。COPY TO把一个表的内容复制到一个文件&#xff0c;而COPY FROM则从一个文件复…...

域名网站都有怎么进去建设/建站工具有哪些

2019独角兽企业重金招聘Python工程师标准>>> 花了一下午的时间&#xff0c;整理测试了一下Bean的生命周期行为. 首先排一个序。完整的测试代码后面贴出来。 加载当前Bean的构造器 加载Autowired注入的依赖关系的构造器。&#xff08;也就是加载依赖的Bean&#x…...

国内做的好的网站/电商网站开发需要多少钱

原来在Linux使用Tomcat不输工程使用的是新创建的用户非ROOT权限创建的&#xff0c;但可能会碰到一种情况&#xff0c;在这种用户身份下启动工程后&#xff0c;关闭SSH工具&#xff0c;工程便无法访问&#xff0c;于是需要换到root来执行&#xff0c;但会出现以下报错&#xff0…...

做网站给源码吗/如何做推广最有效果

AliNLP 自然语言技术平台 阿里AliNLP系统架构图 1. 词法分析&#xff08;分词、词性、实体&#xff09;&#xff1a; - 算法&#xff1a;基于Bi-LSTM-CRF算法体系&#xff0c;以及丰富的多领域词表 2. 句法分析&#xff08;依存句法分析、成分句法分析&#xff09;&#xff1…...