跳到主要内容
Linux UDP 网络编程 | 极客日志
C++
Linux UDP 网络编程 Linux 环境下基于 UDP 的网络编程基础,涵盖 socket 套接字、地址结构体及常用网络接口函数。文章通过 Echo Server、Dict Server 和 Chat Server 三个实例,演示了如何使用 C++ 封装日志、锁、线程池等组件实现 UDP 通信。内容包含服务端与客户端的完整代码实现,涉及字节序转换、多线程并发处理及群聊逻辑路由,适合学习 Linux 网络编程基础。
忘忧 发布于 2026/3/22 更新于 2026/6/22 22K 浏览一。认识相关网络接口
1. socket 套接字
socket:
socket 套接字是网络编程的基础,它就类似于网络通信的传递数据的管道,我们可以通过不同的参数来塑造这个管道。
参数:
**domain:**指定套接字使用的地址族/协议族,套接字使用的类型决定了数据传输的网络协议层,常见的地址族/协议层
{
**AF_INET:**IPv4 地址族,使用32位IP地址
**AF_INET6:**IPv6 地址族,使用128位IP地址
**AF_UNIX/AF_LOCAL:**本地进程间通信,使用文件系统路径作为地址
}
**type:**指定套接字类型,决定传输层通信特性
{
**SOCK_DGRAM:**数据报套接字(基于UDP协议)
**SOCK_STREAM:**流式套接字(基于TCP协议)
}
**protocol:**指定具体传输层协议,我们通常使用默认,设置为0即可
返回值:
成功返回一个大于等于0的数,失败返回-1
2. sockaddr_in 网络地址结构体
sockaddr_in:
sockaddr_in 用于表示 IPv4 地址结构的核心结构体,主要用于存储和传递 IPv4 协议的地址信息。
参数:
**sin_family:**指定结构体存储的地址类型,AF_INET 固定为 IPv4 地址结构
**sin_port:**指定结构体存储的通信端口号
**sin_addr:**指定结构体存储的地址
**s_addr:**将地址转化为网络字节序大端进行存储
3. bind绑定
bind:
bind 的作用是将套接字与特定的 IP 和 port 进行绑定,我们已经将 IP 和端口号写入结构体 struct sockaddr_in 中,我们只需要传递结构体即可
参数:
**sockfd:**传递的套接字socket
**addr:**指向存储 IP 和 port 的结构体指针
**addrlen:**地址结构体的大小
返回值:
成功返回0,失败返回-1
4. recvfrom 接收网络数据
recvfrom:
主要用于UDP协议接收网络数据报内容,同时获取发送方的地址信息
参数:
**sockfd:**套接字描述符
**buf:**接收数据的数组指针
**len:**数组的大小
**flags:**接收方式标志,通常为0,
**src_addr:**输出参数,指向地址结构体,用于储存发送方的IP和port
**addrlen:**结构体大小
返回值:
成功返回接收到的字节数,失败返回-1
5. sendto 发送网络数据
sendto:
主要用于UDP协议发送网络数据报内容,同时发送发送方的IP和port
参数:
sockfd:套接字描述符
buf:储存发送信息的数组指针
len:数组的大小
flags:发送方式标志,默认为0
dest_addr:指向结构体存储的接收方的IP和端口号
addrlen:结构体的大小
返回值:
成功返回实际字节数,失败返回-1
6. ntohs / ntohl 网络字节序转换
ntohs 和 ntohl 都是将网络字节序转换为主机字节序,ntohs 将 16 位网络字节序转换为 16 位的主机字节序(Network to Host Short),ntohl 将 32 位网络字节序转换为 32 位的主机字节序(Network to Host Long)
参数:
**netshort / netlong:**需要转换的端口号
7. htons / htonl 主机字节序端口号转换
htons 和 htonl 都是将主机字节序转换为网络字节序,htons 将 16 位主机字节序转换为 16 位的网络字节序(Host to Network Short),htonl 将 32 位主机字节序转换为 32 位的网络字节序(Host to Network Long)
参数:
**hostshort / hostlong:**需要转换的端口号
8. inet_ntoa / inet_ntop 网络字节序IP地址转换
将 32位网络字节序的 IPv4 地址转换为点分十进制字符串,inet_ntoa 仅支持 IPv4 在多线程中容易出问题,现代编程更加推荐 inet_ntop
参数:
**af:**地址族
**src:**指向 IP 地址的指针
**dst:**指向数组的指针用于储存转换后的字符串
**size:**数组大小
二。Echo Server 实现
1. 前备日志和封装锁
(1)Mutex.hpp #pragma once
#include <iostream>
#include <pthread.h>
namespace MutexModule {
class Mutex {
public :
Mutex () { pthread_mutex_init (&_mutex, nullptr ); }
void Lock () { int n = pthread_mutex_lock (&_mutex); (void )n; }
void Unlock () { int n = pthread_mutex_unlock (&_mutex); (void )n; }
~Mutex () { pthread_mutex_destroy (&_mutex); }
pthread_mutex_t *Get () { return &_mutex; }
private :
pthread_mutex_t _mutex;
};
class LockGuard {
public :
LockGuard (Mutex &mutex):_mutex(mutex) { _mutex.Lock (); }
~LockGuard () { _mutex.Unlock (); }
private :
Mutex &_mutex;
};
}
(2)Log.hpp #ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem>
#include <sstream>
#include <fstream>
#include <memory>
#include <ctime>
#include <unistd.h>
#include "Mutex.hpp"
namespace LogModule {
using namespace MutexModule;
const std::string gsep = "\r\n" ;
class LogStrategy {
public :
~LogStrategy () = default ;
virtual void SyncLog (const std::string &message) = 0 ;
};
class ConsoleLogStrategy : public LogStrategy {
public :
ConsoleLogStrategy () { }
void SyncLog (const std::string &message) override { LockGuard lockguard (_mutex) ; std::cout << message << gsep; }
~ConsoleLogStrategy () { }
private :
Mutex _mutex;
};
const std::string defaultpath = "./log" ;
const std::string defaultfile = "my.log" ;
class FileLogStrategy : public LogStrategy {
public :
FileLogStrategy (const std::string &path = defaultpath, const std::string &file = defaultfile) : _path(path), _file(file) { LockGuard lockguard (_mutex) ; if (std::filesystem::exists (_path)) { return ; } try { std::filesystem::create_directories (_path); } catch (const std::filesystem::filesystem_error &e) { std::cerr << e.what () << '\n' ; } }
void SyncLog (const std::string &message) override { LockGuard lockguard (_mutex) ; std::string filename = _path + (_path.back () == '/' ? "" : "/" ) + _file;
~FileLogStrategy () { }
private :
std::string _path;
std::string _file;
Mutex _mutex;
};
enum class LogLevel { DEBUG, INFO, WARNING, ERROR, FATAL };
std::string Level2Str (LogLevel level) {
switch (level) {
case LogLevel::DEBUG: return "DEBUG" ;
case LogLevel::INFO: return "INFO" ;
case LogLevel::WARNING: return "WARNING" ;
case LogLevel::ERROR: return "ERROR" ;
case LogLevel::FATAL: return "FATAL" ;
default : return "UNKNOWN" ;
}
}
std::string GetTimeStamp () {
time_t curr = time (nullptr );
struct tm curr_tm;
localtime_r (&curr, &curr_tm);
char timebuffer[128 ];
snprintf (timebuffer, sizeof (timebuffer),"%4d-%02d-%02d %02d:%02d:%02d" , curr_tm.tm_year+1900 , curr_tm.tm_mon+1 , curr_tm.tm_mday, curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec );
return timebuffer;
}
class Logger {
public :
Logger () { EnableConsoleLogStrategy (); }
void EnableFileLogStrategy () { _fflush_strategy = std::make_unique <FileLogStrategy>(); }
void EnableConsoleLogStrategy () { _fflush_strategy = std::make_unique <ConsoleLogStrategy>(); }
class LogMessage {
public :
LogMessage (LogLevel &level, std::string &src_name, int line_number, Logger &logger) : _curr_time(GetTimeStamp ()), _level(level), _pid(getpid ()), _src_name(src_name), _line_number(line_number), _logger(logger) {
template <typename T> LogMessage &operator <<(const T &info) {
~LogMessage () { if (_logger._fflush_strategy) { _logger._fflush_strategy->SyncLog (_loginfo); } }
private :
std::string _curr_time;
LogLevel _level;
pid_t _pid;
std::string _src_name;
int _line_number;
std::string _loginfo;
Logger &_logger;
};
LogMessage operator () (LogLevel level, std::string name, int line) { return LogMessage (level, name, line, *this ); }
~Logger () { }
private :
std::unique_ptr<LogStrategy> _fflush_strategy;
};
Logger logger;
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}
#endif
2. 服务端封装 服务端封装划分为服务端初始化和服务端启动,初始化阶段需要进行套接字创建,地址结构体初始化,再将套接字与结构体进行绑定。启动阶段创建缓冲区,不断的接受客户端传递的数据,在服务端打印后,再将数据传回给客户端。
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <functional>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
using namespace std;
using namespace LogModule;
const int defaultnum = -1 ;
using func_t = function<string (const string &)>;
class UdpServer {
public :
UdpServer (uint16_t port, func_t func) : _isrunning(false ), _port(port), _func(func), _socket(defaultnum) { }
void Init () { _socket = socket (AF_INET, SOCK_DGRAM, 0 );
void Start () { _isrunning = true ; while (_isrunning) { char buffer[1024 ];
~UdpServer () { }
private :
bool _isrunning;
int _socket;
uint16_t _port;
func_t _func;
};
3. 客户端/服务端运行
(1)服务端 #include <iostream>
#include <memory>
#include "UdpServer.hpp"
string fun_c (const string&kk) {
string a = "hello," ;
a += kk;
return a;
}
int main (int argc, char *argv[]) {
if (argc != 2 ) {
cerr << "Usages:" << argv[0 ] << " port" << endl;
return 1 ;
}
uint16_t _port = stoi (argv[1 ]);
Enable_Console_Log_Strategy ();
unique_ptr<UdpServer> Udp = make_unique <UdpServer>(_port, fun_c);
Udp->Init ();
Udp->Start ();
return 0 ;
}
(2)客户端 客户端首先创建自己的地址结构体,进行地址 IP 和 port 初始化,接着创建字符串进行输入,发送给服务端,接着创建缓冲区接收服务端发送回来的信息。
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <functional>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
int main (int argc, char *argv[]) {
if (argc != 3 ) {
cerr << "Usage:" << argv[0 ] << " Client_ip Client_port" << endl;
return 1 ;
}
uint16_t _port = stoi (argv[2 ]);
string _ip = argv[1 ];
int sockfd = socket (AF_INET, SOCK_DGRAM, 0 );
if (sockfd < 0 ) {
cerr << "socket error" << endl;
return 2 ;
}
cout << "Client socket success" << endl;
struct sockaddr_in Client;
bzero (&Client, sizeof (Client));
Client.sin_family = AF_INET;
Client.sin_port = htons (_port);
Client.sin_addr.s_addr = inet_addr (_ip.c_str ());
while (true ) {
string message;
cout << "Please Enter:" ;
getline (cin, message);
sendto (sockfd, message.c_str (), message.size (), 0 , (sockaddr *)&Client, sizeof (Client));
char buffer[1024 ];
struct sockaddr_in peer;
socklen_t len;
int n = recvfrom (sockfd, &buffer, sizeof (buffer), 0 , (sockaddr *)&peer, &len);
if (n > 0 ) {
buffer[n] = 0 ;
cout << "recvfrom success:" << buffer << endl;
}
}
return 0 ;
}
三。Dict Server 实现
1. 前备日志,封装锁,地址结构体封装 这里的日志和锁封装就不再添加,详细参考上面的代码。
地址结构体封装接收 IP 和 port 并进行32位网络字节序转换以及端口号网络字节序转换为主机字节序
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
class InetAddr {
public :
InetAddr (struct sockaddr_in addr) : _addr(addr) { _port = ntohs (addr.sin_port); _ip = inet_ntoa (addr.sin_addr); }
uint16_t Port () { return _port; }
string Ip () { return _ip; }
~InetAddr () { }
private :
uint16_t _port;
string _ip;
struct sockaddr_in _addr;
};
2. 字典封装 字典主要实现两个功能,一个是'下载字典',将字典的内容打印到屏幕上,第二个功能是进行翻译,输入中文返回英文。
首先我们要对字典文本进行解析,以:为分隔,对左右两边的字符串进行截取并放入到字典数据结构中。翻译功能找到对应的单词打印即可
#pragma once
#include <istream>
#include <iostream>
#include <unordered_map>
#include <string>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace std;
using namespace LogModule;
const string defpath = "./dictionary.txt" ;
const string sep = ":" ;
class Dict {
public :
Dict (const string &path = defpath) : _dict_path(path) { }
bool LoadDict () {
ifstream in (_dict_path) ;
if (!in.is_open ()) {
LOG (LogLevel::DEBUG) << "打开字典:" << _dict_path << " 失败" ;
return false ;
}
LOG (LogLevel::INFO) << "打开字典:" << _dict_path << " 成功" ;
string line;
while (getline (in, line)) {
auto pos = line.find (sep);
if (pos == string::npos) {
LOG (LogLevel::WARNING) << "字典格式错误" ;
continue ;
}
auto english = line.substr (0 , pos);
auto chinese = line.substr (pos + sep.size ());
if (chinese.empty () || english.empty ()) {
LOG (LogLevel::WARNING) << "字典识别错误:" << line;
continue ;
}
_dict.insert (make_pair (english, chinese));
LOG (LogLevel::INFO) << "加载" << line;
}
in.close ();
return true ;
}
string translate (const string &word, InetAddr &client) {
auto iter = _dict.find (word);
if (iter == _dict.end ()) {
LOG (LogLevel::DEBUG) << "进入翻译 [" << client.Ip () << "," << client.Port () << "]#" << word << "->None" ;
return "unknown" ;
}
LOG (LogLevel::INFO) << "进入翻译 [" << client.Ip () << "," << client.Port () << "]#" << word << "->" << iter->second;
return iter->first + "->" + iter->second;
}
~Dict () { }
private :
string _dict_path;
unordered_map<string, string> _dict;
};
3. 服务端封装 服务端执行逻辑和上面的服务端逻辑一致,只是将端口和IP封装成了一个类进行处理
#pragma once
#include <iostream>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <functional>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace std;
using namespace LogModule;
using func_t = function<string (const string &, InetAddr &)>;
const int defaultnum = -1 ;
class UdpServer {
public :
UdpServer (uint16_t port, func_t func) : _port(port), _isrunning(false ), _func(func), _sockfd(defaultnum) { }
void Init () {
_sockfd = socket (AF_INET, SOCK_DGRAM, 0 );
if (_sockfd < 0 ) {
LOG (LogLevel::FATAL) << "fail to socket" ;
exit (1 );
}
LOG (LogLevel::INFO) << "sucess to socket,socket: " << _sockfd;
struct sockaddr_in local;
bzero (&local, sizeof (local));
local.sin_family = AF_INET;
local.sin_port = htons (_port);
local.sin_addr.s_addr = INADDR_ANY;
int m = bind (_sockfd, (struct sockaddr *)&local, sizeof (local));
if (m < 0 ) {
LOG (LogLevel::FATAL) << "bind error" ;
exit (2 );
}
LOG (LogLevel::INFO) << "bind success, socket:" << _sockfd;
}
void Start () {
_isrunning = true ;
while (_isrunning) {
char buffer[1024 ];
struct sockaddr_in peer;
socklen_t len = sizeof (peer);
ssize_t s = recvfrom (_sockfd, &buffer, sizeof (buffer), 0 , (struct sockaddr *)&peer, &len);
if (s > 0 ) {
InetAddr client (peer) ;
buffer[s] = 0 ;
string result = _func(buffer, client);
sendto (_sockfd, result.c_str (), result.size (), 0 , (struct sockaddr *)&peer, len);
}
}
}
~UdpServer () { }
private :
uint16_t _port;
bool _isrunning;
func_t _func;
int _sockfd;
};
4. 客户端/服务端运行
(1)客户端 #include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
using namespace std;
int main (int argc, char *argv[]) {
if (argc != 3 ) {
std::cerr << "Usage: " << argv[0 ] << " server_ip server_port" << std::endl;
return 1 ;
}
std::string server_ip = argv[1 ];
uint16_t server_port = std::stoi (argv[2 ]);
int sockfd = socket (AF_INET, SOCK_DGRAM, 0 );
if (sockfd < 0 ) {
std::cerr << "socket error" << std::endl;
return 2 ;
}
struct sockaddr_in server;
memset (&server, 0 , sizeof (server));
server.sin_family = AF_INET;
server.sin_port = htons (server_port);
server.sin_addr.s_addr = inet_addr (server_ip.c_str ());
while (true ) {
std::string input;
std::cout << "Please Enter# " ;
std::getline (std::cin, input);
int n = sendto (sockfd, input.c_str (), input.size (), 0 , (struct sockaddr*)&server, sizeof (server));
(void )n;
char buffer[1024 ];
struct sockaddr_in peer;
socklen_t len = sizeof (peer);
int m = recvfrom (sockfd, buffer, sizeof (buffer)-1 , 0 , (struct sockaddr*)&peer, &len);
if (m > 0 ) {
buffer[m] = 0 ;
std::cout << buffer << std::endl;
}
}
return 0 ;
}
(2)服务端 #include <iostream>
#include <memory>
#include "Dict.hpp"
#include "UdpServer.hpp"
int main (int argc, char *argv[]) {
if (argc != 2 ) {
cerr << "Usage: " << argv[0 ] << " port" << endl;
return 1 ;
}
uint16_t port = stoi (argv[1 ]);
Enable_Console_Log_Strategy ();
Dict dict;
dict.LoadDict ();
unique_ptr<UdpServer> udp = make_unique <UdpServer>(port, [&dict](const string &word, InetAddr addr) {
return dict.translate (word, addr);
});
udp->Init ();
udp->Start ();
return 0 ;
}
四。Chat Server 实现
1. 前备日志,封装锁,地址结构体封装,条件变量封装,线程封装,线程池封装 日志,锁,地址结构在上文已经完成,可对上文进行参考
(1)条件变量封装 #pragma once
#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"
using namespace MutexModule;
namespace CondModule {
class Cond {
public :
Cond () { pthread_cond_init (&_cond, nullptr ); }
void Wait (Mutex &mutex) { int n = pthread_cond_wait (&_cond, mutex.Get ()); (void )n; }
void Signal () {
void Broadcast () {
~Cond () { pthread_cond_destroy (&_cond); }
private :
pthread_cond_t _cond;
};
};
(2)线程封装 #ifndef _THREAD_H_
#define _THREAD_H_
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
namespace ThreadModlue {
static uint32_t number = 1 ;
class Thread {
using func_t = std::function<void ()>;
private :
void EnableDetach () { _isdetach = true ; }
void EnableRunning () { _isrunning = true ; }
static void *Routine (void *args)
{
Thread *self = static_cast <Thread *>(args);
self->EnableRunning ();
if (self->_isdetach) self->Detach ();
pthread_setname_np (self->_tid, self->_name.c_str ());
self->_func();
return nullptr ;
}
public :
Thread (func_t func) : _tid(0 ), _isdetach(false ), _isrunning(false ), res (nullptr ), _func(func) {
_name = "thread-" + std::to_string (number++);
}
void Detach () {
if (_isdetach) return ;
if (_isrunning) pthread_detach (_tid);
EnableDetach ();
}
bool Start () {
if (_isrunning) return false ;
int n = pthread_create (&_tid, nullptr , Routine, this );
if (n != 0 ) {
return false ;
} else {
return true ;
}
}
bool Stop () {
if (_isrunning) {
int n = pthread_cancel (_tid);
if (n != 0 ) {
return false ;
} else {
_isrunning = false ;
return true ;
}
}
return false ;
}
void Join () {
if (_isdetach) {
return ;
}
int n = pthread_join (_tid, &res);
if (n != 0 ) {
} else {
}
}
pthread_t Id () {
return _tid;
}
~Thread () { }
private :
pthread_t _tid;
std::string _name;
bool _isdetach;
bool _isrunning;
void *res;
func_t _func;
};
}
#endif
(3)线程池封装 #pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"
namespace ThreadPoolModule {
using namespace ThreadModlue;
using namespace LogModule;
using namespace CondModule;
using namespace MutexModule;
static const int gnum = 5 ;
template <typename T>
class ThreadPool {
private :
void WakeUpAllThread () {
LockGuard lockguard (_mutex) ;
if (_sleepernum) _cond.Broadcast ();
LOG (LogLevel::INFO) << "唤醒所有的休眠线程" ;
}
void WakeUpOne () {
_cond.Signal ();
}
ThreadPool (int num = gnum) : _num(num), _isrunning(false ), _sleepernum(0 ) {
for (int i = 0 ; i < num; i++) {
_threads.emplace_back ( [this ]() { HandlerTask (); });
}
}
void Start () {
if (_isrunning) return ;
_isrunning = true ;
for (auto &thread : _threads) {
thread.Start ();
}
}
ThreadPool (const ThreadPool<T> &) = delete ;
ThreadPool<T> &operator =(const ThreadPool<T> &) = delete ;
public :
static ThreadPool<T> *GetInstance () {
if (inc == nullptr ) {
LockGuard lockguard (_lock) ;
LOG (LogLevel::DEBUG) << "获取单例...." ;
if (inc == nullptr ) {
LOG (LogLevel::DEBUG) << "首次使用单例,创建之...." ;
inc = new ThreadPool <T>();
inc->Start ();
}
}
return inc;
}
void Stop () {
if (!_isrunning) return ;
_isrunning = false ;
WakeUpAllThread ();
}
void Join () {
for (auto &thread : _threads) {
thread.Join ();
}
}
void HandlerTask () {
char name[128 ];
pthread_getname_np (pthread_self (), name, sizeof (name));
while (true ) {
T t;
{
LockGuard lockguard (_mutex) ;
while (_taskq.empty () && _isrunning) {
_sleepernum++;
_cond.Wait (_mutex);
_sleepernum--;
}
if (!_isrunning && _taskq.empty ()) {
LOG (LogLevel::INFO) << name << " 退出了,线程池退出&&任务队列为空" ;
break ;
}
t = _taskq.front ();
_taskq.pop ();
}
t ();
}
}
bool Enqueue (const T &in) {
if (_isrunning) {
LockGuard lockguard (_mutex) ;
_taskq.push (in);
if (_threads.size () == _sleepernum) WakeUpOne ();
return true ;
}
return false ;
}
~ThreadPool () { }
private :
std::vector<Thread> _threads;
int _num;
std::queue<T> _taskq;
Cond _cond;
Mutex _mutex;
bool _isrunning;
int _sleepernum;
static ThreadPool<T> *inc;
static Mutex _lock;
};
template <typename T>
ThreadPool<T> *ThreadPool<T>::inc = nullptr ;
template <typename T>
Mutex ThreadPool<T>::_lock;
}
2. 执行路径封装 我们实现群聊的逻辑是服务端接收到客户端发来的信息,将信息添加上发送者的信息之后,再打包发送给每一个群聊用户,这样就实现了群聊。
原理类似,我们用 vector 容器存储地址结构体,从地址结构体当中提取出发送的信息,接着把信息发送给容器中每一个成员。同时为了解决线程并发冲突问题这里引入了锁
#pragma once
#include <iostream>
#include <vector>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "Mutex.hpp"
using namespace std;
using namespace LogModule;
using namespace MutexModule;
class Route {
private :
bool IsExit (InetAddr &peer) {
for (auto &e : _online_user) {
if (e == peer) {
return true ;
}
}
return false ;
}
void User_Add (InetAddr &peer) {
LOG (LogLevel::INFO) << "添加一个聊天用户" ;
_online_user.push_back (peer);
}
void User_Delete (InetAddr &peer) {
for (auto iter = _online_user.begin (); iter < _online_user.end (); iter++) {
if (*iter == peer) {
LOG (LogLevel::INFO) << "成功删除用户:" << peer.StringAddr ();
_online_user.erase (iter);
break ;
}
}
}
public :
Route () { }
void MessageRoute (int _sockfd, const string &messages, InetAddr &addr) {
LockGuard lockguard (_lock) ;
if (!IsExit (addr)) {
User_Add (addr);
}
string result_messeage = "[" + addr.StringAddr () + "]" + messages;
LOG (LogLevel::INFO)<<result_messeage;
for (auto &e : _online_user) {
sendto (_sockfd, result_messeage.c_str (), result_messeage.size (), 0 , (const struct sockaddr *)&e.Addr (), sizeof (e.Addr ()));
}
if (messages == "QUIT" ) {
LOG (LogLevel::INFO) << "删除一个用户" << addr.StringAddr ();
User_Delete (addr);
}
}
~Route () { }
private :
vector<InetAddr> _online_user;
Mutex _lock;
};
3. 服务端封装 #pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
using func_t = std::function<void (int sockfd, const std::string&, InetAddr&)>;
const int defaultfd = -1 ;
class UdpServer {
public :
UdpServer (uint16_t port, func_t func) : _sockfd(defaultfd),
void Init () {
_sockfd = socket (AF_INET, SOCK_DGRAM, 0 );
if (_sockfd < 0 ) {
LOG (LogLevel::FATAL) << "socket error!" ;
exit (1 );
}
LOG (LogLevel::INFO) << "socket success, sockfd : " << _sockfd;
struct sockaddr_in local;
bzero (&local, sizeof (local));
local.sin_family = AF_INET;
local.sin_port = htons (_port);
local.sin_addr.s_addr = INADDR_ANY;
int n = bind (_sockfd, (struct sockaddr *)&local, sizeof (local));
if (n < 0 ) {
LOG (LogLevel::FATAL) << "bind error" ;
exit (2 );
}
LOG (LogLevel::INFO) << "bind success, sockfd : " << _sockfd;
}
void Start () {
_isrunning = true ;
while (_isrunning) {
char buffer[1024 ];
struct sockaddr_in peer;
socklen_t len = sizeof (peer);
ssize_t s = recvfrom (_sockfd, buffer, sizeof (buffer) - 1 , 0 , (struct sockaddr *)&peer, &len);
if (s > 0 ) {
InetAddr client (peer) ;
buffer[s] = 0 ;
_func(_sockfd, buffer, client);
}
}
}
~UdpServer () { }
private :
int _sockfd;
uint16_t _port;
bool _isrunning;
func_t _func;
};
4. 服务端/客户端运行
(1)服务端运行 此处我们引入线程池,为的是解决信息的传递和接收无法并发的问题,我们将任务放到线程池当中,由线程池来分配线程来执行接收发送的工作。服务端的逻辑也很简单,就是将数据打包成任务,放到线程池中。
#include <iostream>
#include <memory>
#include "Route.hpp"
#include "ChatServer.hpp"
#include "ThreadPool.hpp"
using namespace ThreadPoolModule;
using task_t = function<void ()>;
int main (int argc, char *argv[]) {
if (argc != 2 ) {
std::cerr << "Usage: " << argv[0 ] << " port" << std::endl;
return 1 ;
}
uint16_t port = std::stoi (argv[1 ]);
Enable_Console_Log_Strategy ();
Route r;
auto tp = ThreadPool<task_t >::GetInstance ();
unique_ptr<UdpServer> udp = make_unique <UdpServer>(port,[&r,&tp] (int sockfd, const std::string& messages, InetAddr& addr){
task_t t = std::bind (&Route::MessageRoute,&r, sockfd, messages, addr);
tp->Enqueue (t);
});
udp->Init ();
udp->Start ();
return 0 ;
}
(2)客户端运行 客户端的操作无疑是接收信息和发送信息,我们将接受信息发送信息打包成两个线程进行管理
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Thread.hpp"
int sockfd = 0 ;
std::string server_ip;
uint16_t server_port = 0 ;
pthread_t id;
using namespace ThreadModlue;
void Recv () {
while (true ) {
char buffer[1024 ];
struct sockaddr_in peer;
socklen_t len = sizeof (peer);
int m = recvfrom (sockfd, buffer, sizeof (buffer) - 1 , 0 , (struct sockaddr *)&peer, &len);
if (m > 0 ) {
buffer[m] = 0 ;
std::cerr << buffer << std::endl;
}
}
}
void Send () {
struct sockaddr_in server;
memset (&server, 0 , sizeof (server));
server.sin_family = AF_INET;
server.sin_port = htons (server_port);
server.sin_addr.s_addr = inet_addr (server_ip.c_str ());
const std::string online = "inline" ;
sendto (sockfd, online.c_str (), online.size (), 0 , (struct sockaddr *)&server, sizeof (server));
while (true ) {
std::string input;
std::cout << "Please Enter# " ;
std::getline (std::cin, input);
int n = sendto (sockfd, input.c_str (), input.size (), 0 , (struct sockaddr *)&server, sizeof (server));
(void )n;
if (input == "QUIT" ) {
pthread_cancel (id);
break ;
}
}
}
int main (int argc, char *argv[]) {
if (argc != 3 ) {
std::cerr << "Usage: " << argv[0 ] << " server_ip server_port" << std::endl;
return 1 ;
}
server_ip = argv[1 ];
server_port = std::stoi (argv[2 ]);
sockfd = socket (AF_INET, SOCK_DGRAM, 0 );
if (sockfd < 0 ) {
std::cerr << "socket error" << std::endl;
return 2 ;
}
Thread recv (Recv) ;
Thread send (Send) ;
recv.Start ();
send.Start ();
id = send.Id ();
recv.Join ();
send.Join ();
return 0 ;
}
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online