目录
[1. 基础篇:UdpEchoServer(V1)](#1. 基础篇:UdpEchoServer(V1))
[- 核心代码剖析](#- 核心代码剖析)
[1.1 UDPServer.hpp](#1.1 UDPServer.hpp)
[1.2 UDPServer.cc](#1.2 UDPServer.cc)
[1.3 UDPClient.cc](#1.3 UDPClient.cc)
[1.4 演示代码编译结果](#1.4 演示代码编译结果)
[2. 进阶篇:DictionaryServer(V2)](#2. 进阶篇:DictionaryServer(V2))
[- 功能扩展:单词查询服务](#- 功能扩展:单词查询服务)
[2.1 Dictionary.hpp](#2.1 Dictionary.hpp)
[2.2 配置文件dic.txt](#2.2 配置文件dic.txt)
[2.3 DicServer.hpp](#2.3 DicServer.hpp)
[2.4 DicServer.cc](#2.4 DicServer.cc)
[2.5 DicClient.cc](#2.5 DicClient.cc)
[2.6 演示代码实现结果](#2.6 演示代码实现结果)
[3. 实战篇:聊天室ChatServerDemo(V3)](#3. 实战篇:聊天室ChatServerDemo(V3))
[- 封装网络地址](#- 封装网络地址)
[3.1 InetAddr.hpp](#3.1 InetAddr.hpp)
[- 路由器设置](#- 路由器设置)
[3.2 Route.hpp](#3.2 Route.hpp)
[- 服务端设置](#- 服务端设置)
[3.3 ChatServer.hpp](#3.3 ChatServer.hpp)
[3.4 Server.cc](#3.4 Server.cc)
[- 客户端设置](#- 客户端设置)
[3.5 Client.cc](#3.5 Client.cc)
[- 代码实现结](#- 代码实现结)果
1. 基础篇:UdpEchoServer(V1)
- 核心代码剖析
1.1 UDPServer.hpp
我们采用面向对象方式封装socket相关操作,使用RAII思想管理资源,状态标志标识服务器的启停,并且我还使用到了前面文章讲过的日志实现:https://blog.csdn.net/whispers421/article/details/154695948?spm=1001.2014.3001.5502
cpp
#pragma once
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<strings.h>
#include<cstdlib>
#include"Log.hpp"
int default_sockfd = -1;
class UDPServer
{
public:
UDPServer(uint16_t port)
:_sockfd(default_sockfd)
,_port(port)
,_isrunning(false)
{}
//初始化socket
void Init()
{
//...
}
//开启服务器
void Start()
{
//...
}
//关闭服务器
void Stop()
{
//...
}
~UDPServer(){}
private:
int _sockfd; // socket 文件描述符
uint16_t _port; // 监听端口
bool _isrunning; // 运行状态标志
};
初始化socket:创建socket -> 绑定端口和IP地址
cpp
void Init()
{
//创建socketfd
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
//AF_INET:网络通信 SOCK_DGRAM:TCP通信
if(_sockfd<0)
{
LOG(4)<<" create socket error";
exit(1);
}
LOG(1)<<" create socket success,sockfd: "<<_sockfd;
//bind
//1. 填充IP和port
struct sockaddr_in local;
bzero(&local,sizeof(local));//把local全部清零
//重新开始填充
local.sin_family = AF_INET;//网络通信
local.sin_port = htons(_port);
//local.sin_addr.s_addr=inet_addr(_ip.c_str());//将字符串转整数IP,整数IP是网络序列的
//不明确具体IP,只要是发给对应的主机对应的port,都能收到!
local.sin_addr.s_addr = htonl(INADDR_ANY);//任意IP绑定
//和socket进行bind
int b = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(b<0)
{
LOG(4)<<" bind socket error";
exit(2);
}
LOG(1)<<" bind socket success,sockfd:"<<_sockfd;
}
网络字节序转换:
htons(): host to network short(16位端口转换)
htonl(): host to network long(32位IP转换)
INADDR_ANY= 0.0.0.0,表示接收任意来源的数据
服务器启动代码:
cpp
void Start()
{
_isrunning = true;
while(_isrunning)
{
char buffer[1024];
buffer[0] = 0;//清空缓存区
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//读取数据
ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
if(n>0)
{
//读取成功
//获取client的IP和port
uint16_t clientport = ntohs(peer.sin_port);
std::string clientip = inet_ntoa(peer.sin_addr);
buffer[n] = 0;
LOG(0)<<"[ip:"<<clientip<<" port:"<<clientport<<"]# "<<buffer;
std::string echo_string = " server encho# ";
echo_string+=buffer;
sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);
}
}
_isrunning = false;
}
recvfrom()会阻塞直到收到数据同时获取数据和客户端地址信息
UDP 是无连接的,每次通信都需要指定目标地址
1.2 UDPServer.cc
cpp
#include"UDPServer.hpp"
#include<memory>
//服务器使用手册------要输入端口号
void UDPServerManual()
{
std::cout<<"UDPServerManual"<<std::endl;
std::cout<<"Please enter the port"<<std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
UDPServerManual(); // 显示使用说明
return 1;
}
// 启用日志策略
EnableConsoleLogStrategy();
// 使用智能指针管理 UDPServer 对象
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<UDPServer> usvr = std::make_unique<UDPServer>(port);
// 启动服务器
usvr->Init();
usvr->Start();
return 0;
}
验证参数,提供使用提示手册
使用
unique_ptr避免内存泄漏模块化启动流程
1.3 UDPClient.cc
cpp
#include <iostream>
#include <cstring>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
//客户端使用手册
void UDPClientManual()
{
std::cout << "UDPClientManual" << std::endl;
std::cout << "Please enter the port and IP" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
UDPClientManual();
return 1;
}
//参数解析
std::string serverip = argv[1];//获取服务器ip
uint16_t serverport = std::stoi(argv[2]);//服务器端口号
// 创建客户端socketfd
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// AF_INET:网络通信 SOCK_DGRAM:TCP通信
if (sockfd < 0)
{
std::cout << "client create socket error" << std::endl;
exit(1);
}
// 创建并设置服务器地址
struct sockaddr_in server;
memset(&server, 0, sizeof(server));//清空
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
//循环交互
while (true)
{
std::cout << " Please Enter@ ";
// 获取要发的内容
std::string line;
std::getline(std::cin, line);
// 发送信息
sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr *)&server, sizeof(server));
// 回显服务端数据
// struct sockaddr_in tmp;
// socklen_t len = sizeof(tmp);
// char buffer[1024];
// int r = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&tmp,&len);
// if(r>0)
// {
// buffer[r] = 0;
// std::cout<<buffer<<std::endl;
// }
}
return 0;
}
客户端特点:
不需要
bind(),系统自动分配端口每次
sendto()都需要指定服务器地址可设计为同步或异步接收模式
1.4 演示代码编译结果
编译代码:

先启动服务器:

再开启客户端:

客户端发送消息:

服务器接收消息并回显:

2. 进阶篇:DictionaryServer(V2)
在基础的UdpEchoServer基础上增加了单词查询服务,也就是将前面我们服务器在接收到客户端信息直接回显的基础上,变成了回显为接收到的消息的翻译结果,达到翻译效果
- 功能扩展:单词查询服务
2.1 Dictionary.hpp
封装翻译功能的实现
cpp
#pragma once
#include<iostream>
#include<string>
#include<filesystem>
#include<unordered_map>
std::string sep = ": "; //分隔标识符
class Dictionary
{
private:
//加载配置文件
void LoadConf()
{
std::ifstream in(_path);
if(!in.is_open())
{
LOG(4)<<" open infile error";
return;
}
std::string line;
while(std::getline(in,line))
{
LOG(0)<<"load dic message:"<<line;
auto pos = line.find(sep);//line中找到sep开始下标
if(pos<0)
{
//如果没有找到sep分隔符
LOG(2)<<"find error";
continue;
}
std::string word = line.substr(0,pos);// [)
std::string value = line.substr(pos+sep.size());
if(word.empty()||value.empty())
{
LOG(2)<<"word || value empty, getline:"<<line;
continue;
}
_dic.insert(std::make_pair(word,value));
}
in.close();
}
public:
Dictionary(std::string path)
:_path(path)
{
LoadConf();
}
//实现翻译功能
std::string Translate(const std::string& word,const std::string& whoip,uint16_t& whoport)
{
auto pos = _dic.find(word);
if(pos == _dic.end())
{
return "Sorry, I can't find";
}
return pos->first+": "+pos->second;
}
~Dictionary(){}
private:
std::string _path; //配置文件路径
std::unordered_map<std::string,std::string> _dic; //相当于词典,存储{英文,中文}的结构
};
2.2 配置文件dic.txt

2.3 DicServer.hpp
在UDPServer.hpp的基础上添加了一个用于处理翻译任务的回调函数
cpp
#pragma once
#include<iostream>
#include<string>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<strings.h>
#include<cstdlib>
#include<functional>
#include"Log.hpp"
//回调函数------用于翻译
using callback_t = std::function<std::string \
(const std::string& word,const std::string& whoip,uint16_t& whoport)>;
int default_sockfd = -1;
class DicServer
{
public:
DicServer(uint16_t port,callback_t cb)
:_sockfd(default_sockfd)
,_port(port)
,_isrunning(false)
,_cb(cb)
{}
void Init()
{
//创建socketfd
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
//AF_INET:网络通信 SOCK_DGRAM:TCP通信
if(_sockfd<0)
{
LOG(4)<<" create socket error";
exit(1);
}
LOG(1)<<" create socket success,sockfd: "<<_sockfd;
//bind
//1. 填充IP和port
struct sockaddr_in local;
bzero(&local,sizeof(local));//把local全部清零
//重新开始填充
local.sin_family = AF_INET;//网络通信
local.sin_port = htons(_port);
//local.sin_addr.s_addr=inet_addr(_ip.c_str());//将字符串转整数IP,整数IP是网络序列的
//不明确具体IP,只要是发给对应的主机对应的port,都能收到!
local.sin_addr.s_addr = htonl(INADDR_ANY);//任意IP绑定
//和socket进行bind
int b = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(b<0)
{
LOG(4)<<" bind socket error";
exit(2);
}
LOG(1)<<" bind socket success,sockfd:"<<_sockfd;
}
void Start()
{
_isrunning = true;
while(_isrunning)
{
char buffer[1024];
buffer[0] = 0;//清空缓存区
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//读取数据
ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
if(n>0)
{
//读取成功
buffer[n] = 0;
//获取client的IP和port
uint16_t clientport = ntohs(peer.sin_port);
std::string clientip = inet_ntoa(peer.sin_addr);
// LOG(0)<<"[ip:"<<clientip<<" port:"<<clientport<<"]# "<<buffer;
std::string ret = _cb(buffer,clientip,clientport);
std::string echo_string = " server encho# ";
echo_string+=ret;
std::cout<<echo_string<<std::endl;
//把数据发给客户端
sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);
}
}
_isrunning = false;
}
void Stop()
{
_isrunning = false;
}
~DicServer(){}
private:
int _sockfd;
uint16_t _port;
std::string _ip;
bool _isrunning;
callback_t _cb;
};
2.4 DicServer.cc
cpp
#include"DicServer.hpp"
#include"Dictionary.hpp"
void DicServerManual()
{
std::cout<<"DicServerManual"<<std::endl;
std::cout<<"Please enter the port"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc != 2)
{
DicServerManual();
return 1;
}
EnableConsoleLogStrategy();
uint16_t port = std::stoi(argv[1]);
Dictionary dic("./dic.txt");
//std::unique_ptr<DicServer> usvr = std::make_unique<DicServer>(ip,port,Translate);
std::unique_ptr<DicServer> usvr = std::make_unique<DicServer>(port,[&dic]
(const std::string& word,const std::string& whoip,uint16_t& whoport)->std::string
{
return dic.Translate(word,whoip,whoport);
});
usvr->Init();
usvr->Start();
return 0;
}
2.5 DicClient.cc
cpp
#include<iostream>
#include<cstring>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
void DicClientManual()
{
std::cout<<"DicClientManual"<<std::endl;
std::cout<<"Please enter the port and IP"<<std::endl;
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
DicClientManual();
return 1;
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
//创建socketfd
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
//AF_INET:网络通信 SOCK_DGRAM:TCP通信
if(sockfd<0)
{
std::cout<<"client create socket error"<<std::endl;
exit(1);
}
//创建服务端结构体,并清空内容
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
while(true)
{
std::cout<<" Please Enter@ ";
//获取要发的内容
std::string line;
std::getline(std::cin,line);
//发送信息
sendto(sockfd,line.c_str(),line.size(),0,(struct sockaddr*)&server,sizeof(server));
//读取服务端数据
struct sockaddr_in tmp;
socklen_t len = sizeof(tmp);
char buffer[1024];
int r = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&tmp,&len);
if(r>0)
{
buffer[r] = 0;
std::cout<<buffer<<std::endl;
}
}
return 0;
}
2.6 演示代码实现结果
编译:

启动服务器,启动后会加载出配置文件内容:

启动服务器,就可以直接输入单词查询了。如果所查询的单词在配置文件里,则返回查询结果;如果查询的单词没有在配置文件里,服务器返回"Sorry,I can't find"

服务器端显示:

3. 实战篇:聊天室ChatServerDemo(V3)
上面我们都实现的是单线程,这次我们实现多线程,我们把之前写过的线程池拿过来使用,创建多线程。线程池的实现在这篇博客里有详细讲解:https://blog.csdn.net/whispers421/article/details/154848095?spm=1001.2014.3001.5502
这里就不过多介绍了,我们直接拿来使用
- 封装网络地址
3.1 InetAddr.hpp
cpp
#pragma once
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include <cstring>
#define Conv(addr) ((struct sockaddr*)&addr)
class InetAddr
{
private:
void NetToHost()
{
_port = ntohs(_addr.sin_port);
_ip = inet_ntoa(_addr.sin_addr);
}
void HostToNet()
{
memset(&_addr,0,sizeof(_addr));
_addr.sin_family = AF_INET;
_addr.sin_port = htons(_port);
_addr.sin_addr.s_addr = inet_addr(_ip.c_str());
}
public:
InetAddr(const struct sockaddr_in& addr)
:_addr(addr)
{
NetToHost();
}
InetAddr(uint16_t port,std::string ip = "0.0.0.0")//"0.0.0.0" -> INADDR_ANY
:_port(port)
,_ip(ip)
{
HostToNet();
}
std::string Ip()
{
return _ip;
}
uint16_t Port()
{
return _port;
}
struct sockaddr* Addr()
{
return Conv(_addr);
}
socklen_t Len()
{
return sizeof(_addr);
}
std::string ToString()
{
return _ip+"-"+std::to_string(_port);
}
bool operator==(const InetAddr& addr)
{
return (_ip == addr._ip&& _port == addr._port);
//return (_ip == addr._ip);
}
~InetAddr(){}
private:
struct sockaddr_in _addr;//网络风格地址
//主机风格地址
std::string _ip;
uint16_t _port;
};
- 路由器设置
3.2 Route.hpp
cpp
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Mutex.hpp"
class Route
{
private:
//判断该用户是否在聊天室内
bool IsExists(const InetAddr &addr)
{
for (auto &user : _online_user)
{
if (user == addr)
return true;
}
return false;
}
//添加新用户
void AddUser(const InetAddr &addr)
{
MutexGuard MG(&_lock);
if (!IsExists(addr))
{
_online_user.push_back(addr);
}
LOG(1)<<" AddUser success";
}
//删除用户
void DeleteUser(const std::string &message, const InetAddr &addr)
{
MutexGuard MG(&_lock);
if (message == "QUIT")
{
auto iter = _online_user.begin();
for (iter; iter != _online_user.end(); iter++)
{
if (*iter == addr)
{
_online_user.erase(iter);
LOG(1)<<" DeleteUser success";
break;
}
}
}
}
//发送消息
void SendMessageToAll(int sockfd, const std::string &message)
{
MutexGuard MG(&_lock);
// 将消息message转发给所有在线用户
for (auto &user : _online_user)
{
std::string info = user.ToString();
info += ("# " + message);
info.insert(info.begin(),'\n');
sendto(sockfd, info.c_str(), info.size(), 0, user.Addr(), user.Len());
}
}
public:
Route() {};
void RouteMessageToAll(int sockfd, const std::string &message, InetAddr &addr)
{
//首次发消息等同于登录
if(!IsExists(addr))
AddUser(addr);
SendMessageToAll(sockfd, message);
DeleteUser(message, addr);
}
~Route() {};
private:
std::vector<InetAddr> _online_user; // 在线用户
// 加锁
Mutex _lock;
};
- 服务端设置
3.3 ChatServer.hpp
cpp
#pragma once
#include<iostream>
#include<string>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<strings.h>
#include<cstdlib>
#include<functional>
#include"Log.hpp"
#include"InetAddr.hpp"
//回调函数
using callback_t = std::function<void(int sockfd,const std::string& message,InetAddr& addr)>;
int default_sockfd = -1;
class ChatServer
{
public:
ChatServer(uint16_t port,callback_t cb)
:_sockfd(default_sockfd)
,_port(port)
,_isrunning(false)
,_cb(cb)
{}
void Init()
{
//创建socketfd
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
//AF_INET:网络通信 SOCK_DGRAM:TCP通信
if(_sockfd<0)
{
LOG(4)<<" create socket error";
exit(1);
}
LOG(1)<<" create socket success,sockfd: "<<_sockfd;
//这里就可以直接使用我们封装好的网络地址
InetAddr local(_port);
//和socket进行bind
int b = bind(_sockfd,local.Addr(),local.Len());
if(b<0)
{
LOG(4)<<" bind socket error";
exit(2);
}
LOG(1)<<" bind socket success,sockfd:"<<_sockfd;
}
void Start()
{
_isrunning = true;
while(_isrunning)
{
char buffer[1024];
buffer[0] = 0;//清空缓存区
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//读取数据
ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,&len);
if(n>0)
{
//读取成功
buffer[n] =0;
//获取client的IP和port
InetAddr clientaddr(peer);
//输出日志信息及client信息
LOG(0)<<"get a client info# IP: "<<clientaddr.Ip()
<<", Port:"<<clientaddr.Port()<<", message:"<<buffer;
std::string message = buffer;
//回调函数_cb任务:把sockfd中收到的内容buffer,传给clientaddr
_cb(_sockfd,message,clientaddr);
}
}
_isrunning = false;
}
void Stop()
{
_isrunning = false;
}
~ChatServer(){}
private:
int _sockfd;
uint16_t _port;
std::string _ip;
bool _isrunning;
callback_t _cb;
};
3.4 Server.cc
cpp
#include"ChatServer.hpp"
#include"ThreadPool.hpp"
#include"Route.hpp"
void ServerManual()
{
std::cout<<"ServerManual"<<std::endl;
std::cout<<"Please enter the port"<<std::endl;
}
// //执行任务(for debug)
// void Chat(int sockfd,const std::string message,InetAddr addr)
// {
// LOG(0)<<"sockfd:"<<sockfd;
// LOG(0)<<"message:"<<message;
// LOG(0)<<"addrinfo:"<<addr.ToString();
// sendto(sockfd,message.c_str(),sizeof(message),0,addr.Addr(),addr.Len());
// }
using task_t = std::function<void()>;
int main(int argc,char* argv[])
{
if(argc != 2)
{
ServerManual();
return 1;
}
EnableConsoleLogStrategy();
uint16_t port = std::stoi(argv[1]);
//1.消息转发功能
std::unique_ptr<Route> r = std::make_unique<Route>();
//2. 线程池创建
auto tp = ThreadPool<task_t>::GetInstance();
//3. 构建服务器对象
std::unique_ptr<ChatServer> usvr = std::make_unique<ChatServer>(port,
[&r,&tp](int sockfd,const std::string message,InetAddr addr)
{
task_t task = std::bind(&Route::RouteMessageToAll,r.get(),sockfd,message,addr);
tp->Enqueue(task);
}
);
usvr->Init();
usvr->Start();
return 0;
}
- 客户端设置
3.5 Client.cc
cpp
#include <iostream>
#include <cstring>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <thread> //我们使用一下C++中的多线程
#include "InetAddr.hpp"
void ClientManual()
{
std::cout << "ClientManual" << std::endl;
std::cout << "Please enter the port and IP" << std::endl;
}
int sockfd = -1;
std::string serverip;
uint16_t serverport;
void InitClient(const std::string &serverip, uint16_t serverport)
{
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// AF_INET:网络通信 SOCK_DGRAM:TCP通信
if (sockfd < 0)
{
std::cout << "client create socket error" << std::endl;
}
}
// 接收消息
void recver()
{
while (true)
{
// 读取服务端数据
struct sockaddr_in tmp;
socklen_t len = sizeof(tmp);
char buffer[1024];
int r = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&tmp, &len);
if (r > 0)
{
buffer[r] = 0;
std::cerr << buffer << std::endl;
//将消息打印到err文件里,再把err文件输出重定向到另一个界面就可以实现收发消息在不同的界面了
}
}
}
// 发送消息
void sender()
{
// 创建服务端结构体
InetAddr server(serverport,serverip);
while (true)
{
std::cout << " Please Enter@ ";
// 获取要发的内容
std::string line;
std::getline(std::cin, line);
// 发送信息
int sr = sendto(sockfd, line.c_str(), line.size(), 0, server.Addr(), server.Len());
if (sr < 0)
{
perror("client sendto error");
}
}
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
ClientManual();
return 1;
}
serverip = argv[1];
serverport = std::stoi(argv[2]);
// 创建socketfd
InitClient(serverip, serverport);
std::thread trecv(recver); // 创建接收消息线程
std::thread tsend(sender); // 创建发送消息线程
trecv.join();
tsend.join();
return 0;
}
- 代码实现结果
编译生成可执行文件

启动服务器:

同时启动两个客户端:


只有一个客户端登录发信息时:


另一个客户端也登陆并发送消息,其他用户也能收到信息:



全文完整代码已上传到我的Gitee:https://gitee.com/da-bai-classmate/linux-test/tree/master/UdpSocket
大家感兴趣的话可以去看看哦!