深入解析:【Linux】UDP 网络编程

news/2025/9/27 11:06:31/文章来源:https://www.cnblogs.com/tlnshuju/p/19114853

深入解析:【Linux】UDP 网络编程

2025-09-27 11:02  tlnshuju  阅读(0)  评论(0)    收藏  举报

目录

一. 认识相关网络接口

1. socket 套接字

2. sockaddr_in 网络地址结构体

3. bind绑定

4. recvfrom 接收网络数据

5. sendto 发送网络数据

6. ntohs / ntohl 网络字节序转换

7. htons / htonl 主机字节序端口号转换

8. inet_ntoa / inet_ntop 网络字节序IP地址转换

二. Echo Server 实现

1. 前备日志和封装锁

(1)Mutex.hpp

(2)Log.hpp

2. 服务端封装

3. 客户端/服务端运行

(1)服务端

(2)客户端

三. Dict Server 实现

1. 前备日志,封装锁,地址结构体封装

2. 字典封装

3. 服务端封装

4. 客户端/服务端运行

(1)客户端

(2)服务端

四. Chat Server 实现

1. 前备日志,封装锁,地址结构体封装,条件变量封装,线程封装,线程池封装

(1)条件变量封装

(2)线程封装

(3)线程池封装

2. 执行路径封装

3. 服务端封装

4. 服务端/客户端运行

(1)服务端运行

(2)客户端运行


UDP 的使用比较格式化,首先先绑定 socket 套接字,接着进行端口的绑定(一般客户端不需要绑定),然后进行发送或者接收数据,最后关闭 socket 套接字。

一. 认识相关网络接口

1. socket 套接字

socket:

socket 套接字是网络编程的基础,它就类似于网络通信的传递数据的管道,我们可以通过不同的参数来塑造这个管道。

#include
#include
int socket(int domain,int type,int protocol);

参数:

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 协议的地址信息。

#include
#include
struct sockaddr_in
{
sa_family_t  sin_family;    // 地址族
in_poet_t    sin_port;      // 16位端口
struct in_addr sin_addr;    // 32位 IPv4 地址
unsigned char  sin_zero[8]; // 填充字段,常为0
};
struct in_addr
{
in_addr_t s_addr;           // 专门存储 IPv4 地址
};

参数:

sin_family:指定结构体存储的地址类型,AF_INET 固定为 IPv4 地址结构

sin_port:指定结构体存储的通信端口号

sin_addr:指定结构体存储的地址

s_addr:将地址转化为网络字节序大端进行存储

3. bind绑定

bind:

bind 的作用是将套接字与特定的 IP port 进行绑定,我们已经将 IP 和端口号写入结构体 struct sockaddr_in 中,我们只需要传递结构体即可

#include
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

参数:

sockfd:传递的套接字socket

addr:指向存储 IP 和 port 的结构体指针

addrlen:地址结构体的大小

返回值:

成功返回0,失败返回-1

4. recvfrom 接收网络数据

recvfrom:

主要用于UDP协议接收网络数据报内容,同时获取发送方的地址信息

#include
ssize_t recvfrom(int sockfd,void *buf,size_t len,int flags,
struct sockaddr *src_addr,socklen_t *addrlen);

参数:

sockfd:套接字描述符

buf:接收数据的数组指针

len:数组的大小

flags:接收方式标志,通常为0,

src_addr:输出参数,指向地址结构体,用于储存发送方的IP和port

addrlen:结构体大小

返回值:

成功返回接收到的字节数,失败返回-1

5. sendto 发送网络数据

sendto:

主要用于UDP协议发送网络数据报内容,同时发送发送方的IP和port

#include
ssize_t sendto(int sockfd,const void *buf,size_t len,int flags,
const struct sockaddr *dest_addr,socklen_t addrlen);

参数:

sockfd:套接字描述符

buf:储存发送信息的数组指针

len:数组的大小

flags:发送方式标志,默认为0

dest_addr:指向结构体存储的接收方的IP和端口号

addrlen:结构体的大小

返回值:

成功返回实际字节数,失败返回-1

6. ntohs / ntohl 网络字节序转换

nthos / ntohl:

nthos ntohl 都是将网络字节序转换为主机字节序,nthos 将 16 位网络字节序转换为 16 位的主机字节序(Network to Host Short),nthol 将 32 位网络字节序转换为 32 位的主机字节序(Network to Host Long)

#include
uint16_t ntohs(uint16_t netshort);
uint16_t ntohl(uint16_t netlong);

参数:

netshort / netlong:需要转换的端口号

7. htons / htonl 主机字节序端口号转换

htons / htonl:

htons htonl 都是将主机字节序转换为网络字节序,htons 将 16 位主机字节序转换为 16 位的网络字节序(Host to Network Short),htonl 将 32 位主机字节序转换为 32 位的网络字节序(Host to Network Long)

#include
uint16_t htons(uint16_t hostshort);
uint16_t htonl(uint16_t hostlong);

参数:

hostshort / hostlong:需要转换的端口号

8. inet_ntoa / inet_ntop 网络字节序IP地址转换

inet_ntoa / inet_ntop:

将 32位网络字节序的 IPv4 地址转换为点分十进制字符串,inet_ntoa 仅支持 IPv4 在多线程中容易出问题,现代编程更加推荐 inet_ntop

#include
char *inet_ntoa(struct in_addr in);
const char *inet_ntop(int af,const void *src,char *dst,socklen_t size);

参数:

af:地址族

src:指向 IP 地址的指针

dst:指向数组的指针用于储存转换后的字符串

size:数组大小

二. Echo Server 实现

主要实现简单的回显服务端和客户端发送的信息

1. 前备日志和封装锁

(1)Mutex.hpp

我们对锁接口进行封装

#pragma once
#include
#include
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
#include
#include
#include  //C++17
#include
#include
#include
#include
#include
#include "Mutex.hpp"
namespace LogModule
{
using namespace MutexModule;
const std::string gsep = "\r\n";
// 策略模式,C++多态特性
// 2. 刷新策略 a: 显示器打印 b:向指定的文件写入
//  刷新策略基类
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 ();
}
void EnableConsoleLogStrategy()
{
_fflush_strategy = std::make_unique();
}
// 表示的是未来的一条日志
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)
{
// 日志的左边部分,合并起来
std::stringstream ss;
ss
LogMessage &operatorSyncLog(_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 _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
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
using namespace std;
using namespace LogModule;
const int defaultnum = -1;
using func_t = function;
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); // IPv4协议  UDP模式  默认值
if (_socket  0)
{
int peer_port = ntohs(peer.sin_port); //
string peer_ip = inet_ntoa(peer.sin_addr); //将 IP转换为string类格式
buffer[s] = 0;
string result = _func(buffer);  //将信息执行函数
cout<

3. 客户端/服务端运行

(1)服务端

进行基础信息的创建调用函数即可

#include
#include
#include "UdpServer.hpp"
string fun_c(const string&kk)
{
string a = "hello,";
a += kk;
return a;
}
// 输入  ./UdpServer  port
int main(int argc, char *argv[])
{
if (argc != 2)
{
cerr  Udp = make_unique(_port, fun_c);
Udp->Init();
Udp->Start();
return 0;
}

(2)客户端

客户端首先创建自己的地址结构体,进行地址 IP 和 port 初始化,接着创建字符串进行输入,发送给服务端,接着创建缓冲区接收服务端发送回来的信息。

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//  客户端
//  输入  ./UdpClient ip  port
int main(int argc, char *argv[])
{
//  先通过输入拿到 ip 和 port
if (argc != 3)
{
cerr  0)
{
buffer[n] = 0;
cout << "recvfrom success:" << buffer << endl;
}
}
return 0;
}

三. Dict Server 实现

1. 前备日志,封装锁,地址结构体封装

这里的日志和锁封装就不再添加,详细参考上面的代码。

我们这里来看看地址结构的封装

地址结构体封装接收 IP 和 port 并进行32位网络字节序转换以及端口号网络字节序转换为主机字节序

#pragma once
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#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) None";
return "unknown";
}
LOG(LogLevel::INFO) " second;
return iter->first + "->" + iter->second;
}
~Dict()
{
}
private:
string _dict_path;
unordered_map _dict;
};

3. 服务端封装

服务端执行逻辑和上面的服务端逻辑一致,只是将端口和IP封装成了一个类进行处理

#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace std;
using namespace LogModule;
using func_t = function;
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)
{
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
#include
#include
#include
#include
#include
#include
using namespace std;
// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cerr  0)
{
buffer[m] = 0;
std::cout << buffer << std::endl;
}
}
return 0;
}

(2)服务端

服务端需要增加字典结构

#include
#include
#include "Dict.hpp"      // 翻译的功能
#include "UdpServer.hpp" // 网络通信的功能
//  ./UdpServer  port
int main(int argc, char *argv[])
{
if (argc != 2)
{
cerr  udp = make_unique(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
#include
#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()
{
// 唤醒在条件变量下等待的一个线程
int n = pthread_cond_signal(&_cond);
(void)n;
}
void Broadcast()
{
// 唤醒所有在条件变量下等待的线程
int n = pthread_cond_broadcast(&_cond);
(void)n;
}
~Cond()
{
pthread_cond_destroy(&_cond);
}
private:
pthread_cond_t _cond;
};
};

(2)线程封装

#ifndef _THREAD_H_
#define _THREAD_H_
#include
#include
#include
#include
#include
#include
namespace ThreadModlue
{
static uint32_t number = 1; // bug
class Thread
{
using func_t = std::function; // 暂时这样写,完全够了
private:
void EnableDetach()
{
_isdetach = true;
}
void EnableRunning()
{
_isrunning = true;
}
static void *Routine(void *args) // 属于类内的成员函数,默认包含this指针!
{
Thread *self = static_cast(args);
self->EnableRunning();
if (self->_isdetach)
self->Detach();
pthread_setname_np(self->_tid, self->_name.c_str());
self->_func(); // 回调处理
return nullptr;
}
// bug
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
#include
#include
#include
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"
// .hpp header only
namespace ThreadPoolModule
{
using namespace ThreadModlue;
using namespace LogModule;
using namespace CondModule;
using namespace MutexModule;
static const int gnum = 5;
template
class ThreadPool
{
private:
void WakeUpAllThread()
{
LockGuard lockguard(_mutex);
if (_sleepernum)
_cond.Broadcast();
LOG(LogLevel::INFO)  &) = delete;
ThreadPool &operator=(const ThreadPool &) = delete;
public:
static ThreadPool *GetInstance()
{
if (inc == nullptr)
{
LockGuard lockguard(_lock);
LOG(LogLevel::DEBUG) ();
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);
// 1. a.队列为空 b. 线程池没有退出
while (_taskq.empty() && _isrunning)
{
_sleepernum++;
_cond.Wait(_mutex);
_sleepernum--;
}
// 2. 内部的线程被唤醒
if (!_isrunning && _taskq.empty())
{
LOG(LogLevel::INFO)  _threads;
int _num; // 线程池中,线程的个数
std::queue _taskq;
Cond _cond;
Mutex _mutex;
bool _isrunning;
int _sleepernum;
// bug??
static ThreadPool *inc; // 单例指针
static Mutex _lock;
};
template
ThreadPool *ThreadPool::inc = nullptr;
template
Mutex ThreadPool::_lock;
}

2. 执行路径封装

我们实现群聊的逻辑是服务端接收到客户端发来的信息,将信息添加上发送者的信息之后,再打包发送给每一个群聊用户,这样就实现了群聊。

原理类似,我们用 vector 容器存储地址结构体,从地址结构体当中提取出发送的信息,接着把信息发送给容器中每一个成员。同时为了解决线程并发冲突问题这里引入了锁

#pragma once
#include
#include
#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;
Mutex _lock;
};

3. 服务端封装

服务端处与原先逻辑类似

#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
using func_t = std::function;
const int defaultfd = -1;
// 你是为了进行网络通信的!
class UdpServer
{
public:
UdpServer(uint16_t port, func_t func)
: _sockfd(defaultfd),
//   _ip(ip),
_port(port),
_isrunning(false),
_func(func)
{
}
void Init()
{
// 1. 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd 网络序列
local.sin_port = htons(_port);
// IP也是如此,1. IP转成4字节 2. 4字节转成网络序列 -> in_addr_t inet_addr(const char *cp);
//local.sin_addr.s_addr = inet_addr(_ip.c_str()); // TODO
local.sin_addr.s_addr = INADDR_ANY;
// InetAddr addr("0", _port);
// addr.NetAddr();
// 那么为什么服务器端要显式的bind呢?IP和端口必须是众所周知且不能轻易改变的!
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n  0)
{
InetAddr client(peer);
buffer[s] = 0;
// TODO
_func(_sockfd, buffer, client);
// LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port<< "]# " << buffer; // 1. 消息内容 2. 谁发的??
// 2. 发消息
// std::string echo_string = "server echo@ ";
// echo_string += buffer;
// sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
}
}
}
~UdpServer()
{
}
private:
int _sockfd;
uint16_t _port;
// std::string _ip; // 用的是字符串风格,点分十进制, "192.168.1.1"
bool _isrunning;
func_t _func; // 服务器的回调函数,用来进行对数据进行处理
};

4. 服务端/客户端运行

(1)服务端运行

此处我们引入线程池,为的是解决信息的传递和接收无法并发的问题,我们将任务放到线程池当中,由线程池来分配线程来执行接收发送的工作。服务端的逻辑也很简单,就是将数据打包成任务,放到线程池中。

#include
#include
#include "Route.hpp"
#include "ChatServer.hpp" // 网络通信的功能
#include "ThreadPool.hpp"
using namespace ThreadPoolModule;
using task_t = function;
// ./Chat_Server  port
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cerr ::GetInstance();
//  bind 绑定函数,第一个参数为调用方法,第二个参数是实体,剩下的参数是传递到第一个参数的参数
unique_ptr udp = make_unique(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
#include
#include
#include
#include
#include
#include
#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; // 2
}
}
}
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# "; // 1
std::getline(std::cin, input); // 0
int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
(void)n;
if (input == "QUIT")
{
pthread_cancel(id);
break;
}
}
}
// ./Chat_Client ip port
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]);
// 1. 创建socket
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;
}

感谢各位观看,请大佬们多多支持!!!

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

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

相关文章

浅谈dsu on tree

前言 先学树剖。 讲讲启发式合并,最经典的就是并查集的按秩合并,这里不细讲。 常用的启发式合并就是小集合合并到大集合上,复杂度从 \(O(n^2)\) 优化至 \(O(n \log n)\)。 例题 P3201 [HNOI2009] 梦幻布丁 题目描述…

天河手机网站建设网站建设 讲话

目录 pod启动创建过程 kubelet持续监听的原因 调度概念 调度约束 调度过程 优点 原理 优先级选项 示例 指定调度节点 标签基本操作 获取标签帮助 添加标签&#xff08;Add Labels&#xff09;&#xff1a; 更新标签&#xff08;Update Labels&#xff09; 删除标…

Linux目录下有100百万个文件,如何快速删除

Linux目录下有100百万个文件,如何快速删除Linux目录下有100百万个文件,如何快速删除 利用rsync命令 例:删除/root/files目录下的所有文件ls -l -f /root/files > /tmp/filelist.txt //将目录下的所有文件整理到/…

JavaDay10

Super详解 super注意点: ​ 1.super调用父类的构造方法,必须在构造方法的第一个 ​ 2.super必须只能出现在子类的方法或者构造方法中! ​ 3.super和this不能同时调用构造方法 对比 this: ​ 代表的对象不同: ​ …

29.Linux防火墙管理 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

【转】中国信通院《低代码产业发展研究报告(2025年)》核心解读

【转】中国信通院《低代码产业发展研究报告(2025年)》核心解读中国信通院(CAICT)于2025年6月发布的《低代码产业发展研究报告》是中国低代码行业发展的权威性风向标。这份报告不仅全面梳理了低代码市场的最新发展现…

【C++】内存管理 - 指南

【C++】内存管理 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", &qu…

昇腾多机推理极速上手:10倍简化的 DeepSeek R1 超大规模模型部署

昇腾多机推理太复杂?易出错?试试 GPUStack在昇腾 NPU 上部署超大规模模型,往往面临一个现实难题:目前主流的官方推理引擎 MindIE 的多机分布式推理虽然性能表现尚可,但配置流程异常复杂。从环境准备、配置初始化到…

python开始exe应用程序初级教程

以下是一个关于如何将Python脚本打包成可执行文件(.exe)的初级教程,使用目前最常用的PyInstaller工具。 准备工作 首先需要安装PyInstaller,打开命令提示符(CMD)或终端,运行以下命令: pip install pyinstaller…

中职校园网站建设建议制作网页的思路

cp -rpf #强行递归复制/etc目录到/mist目录中&#xff0c;并保持源目录的权限等信息不变。 有点类似于打patch&#xff0c;不会改变已有的内容。

凡科可以建设多个网站吗上海手机网站建设电话咨询

用Python解析HTML页面 文章目录 用Python解析HTML页面HTML 页面的结构XPath 解析CSS 选择器解析简单的总结 在前面的课程中&#xff0c;我们讲到了使用 request三方库获取网络资源&#xff0c;还介绍了一些前端的基础知识。接下来&#xff0c;我们继续探索如何解析 HTML 代码&…

网站建设选用平台分析极速蜂app拉新加盟

阿里云服务器是阿里云推出的一种云核算产品&#xff0c;它能够帮助企业和个人快速建立、扩展和管理网络服务。可是&#xff0c;有时候在运用阿里云服务器时&#xff0c;或许会遇到无法装置程序的问题。本文将具体介绍如何处理这个问题。 阿里云服务器无法装置程序或许是由多种原…

版权申请网站宾馆酒店网站建设方案

小伙伴们好久不见&#xff0c;今天我们来聊聊中国 AZURE 的日志分析告警。为什么是中国 AZURE&#xff0c;目前中国 AZURE 的 Monitor 服务和运维相关周围服务和 Global 是有所不同的&#xff0c;所以有些功能和设计不能复制和套用全球版 AZURE 的架构。我们先看一下中国 AZURE…

深入解析:cocos 添加背景,帧动画,贴图

深入解析:cocos 添加背景,帧动画,贴图2025-09-27 10:49 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block…

B站油管抖音一键笔记

在最近,我有一个需求就是需要对视频内容进行一个总结,做成一个笔记,但是又不想自己手动去写,于是我找到了一个项目 BiliNote,BiliNote 是一个开源的 AI 视频笔记助手,支持通过哔哩哔哩、YouTube、抖音等视频链接…

网站安装出现dir更改wordpress地址

集成swagger2的时候swagger-ui.html页面的v2/api-docs接口报404 尝试网上说的权限、包版本不一致、资源路径映射问题&#xff0c;发现都没有问题。 单独访问v2/api-docs接口的时候报 Swagger2Controller Unable to find specification for group 查看相关代码&#xff1a; …

成熟网站开发联系电话陕西网

简介&#xff1a;OpenKruise 是针对 Kubernetes 的增强能力套件&#xff0c;聚焦于云原生应用的部署、升级、运维、稳定性防护等领域。 云原生应用自动化管理套件、CNCF Sandbox 项目 -- OpenKruise&#xff0c;近期发布了 v1.0 大版本。 OpenKruise[1] 是针对 Kubernetes 的…

网站设计公司官网如何设计购物网站

Java流程控制语句有三种&#xff1a; 顺序结构、分支结构和循环结构。 顺序结构&#xff1a; 顺序结构语句是Java程序默认的执行流程&#xff0c;按照代码的先后顺序&#xff0c;从上到下依次执行。 原文链接&#xff1a; Java流程控制控制语句 - 红客网络编程与渗透技术 示例…

介绍自己

大家好!我是一个数据科学与大数据技术专业的大三学生。在日常生活中我有着许多的兴趣爱好打羽毛球、听音乐、做手工。技能树与专业规划 当前技术能力 编程基础:稍微掌握Python数据处理(Pandas、NumPy) 数据库技能:…

pycharm更换国内源

1、找到pip.ini2、记事本修改 [global] timeout = 6000 index-url = https://pypi.tuna.tsinghua.edu.cn/simple trusted-host = https://pypi.tuna.tsinghua.edu.cn