UDP套接字编程(代码)

什么是socket套接字编程?

通过Ip地址 + 端口号这种方式定位一台主机,这样的方式我们就叫做socket套接字。

Udp Socket

接口介绍

这些案列我们使用的接口基本都是一样的,所以在这里我先把接口介绍完,具体的细节后面在说明。

创建socket

#include <sys/socket.h>
int socket(int domain, int type, int protocol); //创建套接字
domain: AF_INET //- 代表网络通信
type:   SOCK_DGRAM  //udp通信(Supports datagrams (connectionless, unreliable messages of a fixed maximum length))                          
protocol: 这个我们默认给 0 就可以,前面的已经可以表示我们是网络udp通信了。返回值:如果成功返回文件描述符(整数),失败则是-1//所以我们网络通信在Linux当中也是文件读写操作

绑定套接字 bind

网络字节序:

  • 在我们计算机当中,大于一个字节的数据就要区分如何存储了,所以我们计算机当中有大端存储和小端存储。

大端存储:高权值位的存储在低地址处,低权值位的存储在高地址处。

小端存储:高权值位的存储在高地址处,低权值位的存储在低地址处。

而在网络当中也是一样的,网络字节序为大端字节序,一般在网络通信当中,无论是大端机器还是小端机器都要转换一下(代码的可移植性)。

而转换操作C语言也为我们提供了库函数,如下:

//这里我们主要是为了转换端口号,端口号为无符号16字节,所以我们只关注前两个
#include <arpa/inet.h> //所需头文件uint16_t htons(uint16_t hostshort); //主机序列转为网络序列
uint16_t ntohs(uint16_t netshort);  //网络序列转为主机序列
//uint32_t htonl(uint32_t hostlong);
// uint32_t ntohl(uint32_t netlong);in_addr_t inet_addr(const char *cp); //把字符串转为四字节的网络字节序
// typedef uint32_t in_addr_t; 32位无符号整形
///
//把网络字节序转为 192.168.132.10 这样的字符串
//char Ip[1024];
//::inet_ntop(AF_INET,&(sockaddr_in.sin_addr),Ip,sizeof(Ip) 用法
const char *inet_ntop(int af, const void *restrict src,
char dst[restrict.size], socklen_t size);
int af                      : 需要转换的数据类型 (AF_INET) 
const void *restrict src    : sockaddr_in 结构体中的sin_addr (无符号整数)
char dst[restrict.size]     : 存放结果的字符数组
socklen_t size              : 存放结果的数组大小
///
#define AF_INET   PF_INET
#define PF_INET   2 /* IP protocol family.  */                    
AF_INET本质是一个整数
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen); //绑定套接字
/*
这里的const struct sockaddr *addr,虽然参数是这个,但是在网络基础我们也说了,这个是数据结构是为了
统一接口而设定的,真正我们要用的是 struct sockaddr_in 这个结构体。所以最后我们需要强壮一下。
*/
在绑定套接字的时候,我们需要创建struct sockaddr_in 这个结构体,同时需要自己初始化一些属性。
#include <netinet/in.h> //所需头文件
struct sockaddr_in {
sa_family_t     sin_family;     /* AF_INET */
in_port_t       sin_port;       /* Port number */
struct in_addr  sin_addr;       /* IPv4 address */
};
如上,我们真正所需要设置的就是上面三个值,其实这个结构体中还有一个成员(如下),不过这个我们一般不管。
/* Pad to size of `struct sockaddr'.  */
// unsigned char sin_zero[sizeof (struct sockaddr)
// - __SOCKADDR_COMMON_SIZE
// - sizeof (in_port_t)
// - sizeof (struct in_addr)];
//
那么下面三个参数如何设置呢?
struct sockaddr_in {
sa_family_t     sin_family;     /* AF_INET */
in_port_t       sin_port;       /* Port number */
struct in_addr  sin_addr;       /* IPv4 address */
};
//struct in_addr  sin_addr 内容解析
struct in_addr
{
in_addr_t s_addr;//32位无符号整型 其实就是一个32为无符号整形
// typedef uint32_t in_addr_t;
};struct sockaddr_in local; //名字以local为例子
local.sin_family = AF_INET ; //设置网络通信
local.sin_port = ::htons(port) //把端口转为网络字节序local.sin_addr.s_addr = INADDR_ANY; //32位无符号整型
#define INADDR_ANY    ((in_addr_t) 0x00000000) //这个表示我们这个服务端可以接受任意IP地址的数据
//一般我们服务器的来源IP都设置位 000...,而不是指定一个IP地址。
  • 注意: 这里要注意我们服务端需要进行绑定操作,一般服务器都会有一个固定的端口号。而对于客户端而言,在0~1023个端口号被一些公司厂家等占用,例如HTTP端口为80等等。而客户端我们用户选择的范围就是1024~65536。但是用户并不知道自己的电脑上究竟哪些端口被占用了,如果用户使用了一个正在被使用的端口号,那么另外一个进程就使用不了了,所以为了避免这个问题,客户端我们无需进行绑定操作,操作系统自动帮我们绑定一个端口号。

接受消息发送消息

//接受消息
#include <sys/socket.h> //头文件ssize_t recvfrom(int sockfd, void buf[restrict.len], size_t len,
int flags,
struct sockaddr *_Nullable restrict src_addr,socklen_t *_Nullable restrict addrlen);$ sockfd                   :创建socket的返回值(文件描述符)
$ void buf[restrict.len]   :接受数据的数组
$ size_t len               :接受数据的大小(数组大小)-1   
$ int flags                :0 , 阻塞等待
$ struct sockaddr *_Nullable restrict src_addr
- 自己创建一个sockaddr_in结构体,用来接受消息发送端的IP地址和端口信息。
$ socklen_t *_Nullable restrict addrlen);
自己创建的sockaddr_in结构体大小 /////发送消息
#include <sys/socket.h> //头文件ssize_t sendto(int sockfd, const void buf[.len], size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);$ sockfd                   :创建socket的返回值(文件描述符)
$ void buf[restrict.len]   :发送数据的数组
$ size_t len               :发送数据的大小(数组大小)-1   
$ int flags                :0 
$ struct sockaddr *_Nullable restrict src_addr
包含目的主机的IP地址和端口信息。(struct sockaddr_in) 上方自己创建的结构体
$ socklen_t *_Nullable restrict addrlen (sizoef(struct sockaddr_in));

Echo Server

代码目的:实现客户端向服务端发送信息,服务端给我们返回消息。

# Udp_server.hpp
#pragma once 
#include <iostream>
#include <cstdint>
#include <sys/socket.h>
#include <netinet/in.h>
#include "Commn.hpp"
#include "Log.hpp"
#include <string>
#include <arpa/inet.h>
using namespace LogModule;
const uint16_t gport = 8888;
class Udp_server
{
public:
Udp_server()
:_port(gport)
{}
void Init()
{_socketfd = ::socket(AF_INET,SOCK_DGRAM,0); //获取文件描述符if (_socketfd < 0) {Die(SOCKET_ERROR);}LOG(LogLevel::DEBUG) << "socket create success, fd is " << _socketfd;//进行绑定sockaddr_in local;local.sin_family = AF_INET; //本质也是无符号16位整数local.sin_port = ::htons(_port); //主机序列转为网络序列local.sin_addr.s_addr = INADDR_ANY; //s2位无符号整型socklen_t len = sizeof(local);int n = ::bind(_socketfd,CONV(&local),len);if (n < 0){Die(BIND_ERROR);}LOG(LogLevel::DEBUG) << "bind create success";
}
void Start()
{char buffer[1024];sockaddr_in peer; //对端消息socklen_t len = sizeof(peer); //一定要初始化!!!!while(true){int n = ::recvfrom(_socketfd,buffer,sizeof(buffer)-1,0,CONV(&peer),&len);if (n > 0){buffer[n] = 0;std::string client = "echo server says# ";client += buffer;char Ip[64];::inet_ntop(AF_INET,&(peer.sin_addr),Ip,64); //把网络ip地址转为点分十进制 192.132.168.10std::string ip = Ip;std::string port = std::to_string(ntohs(_port));LOG(LogLevel::INFO) << ip << ":" << port << "says# " << buffer;::sendto(_socketfd,client.c_str(),client.size(),0,CONV(&peer),len);}}
}
~Udp_server()
{}
private:
int _socketfd;
uint16_t _port;
};
//Udp_serverMain.cc
#include "udp_server.hpp"#include <memory>
int main(int argc, char* argv[])
{if (argc != 2){std::cout << "Please use for ./server_udp + port!!" << std::endl;Die(USE_ERROR);}std::shared_ptr<Udp_server> server = std::make_shared<Udp_server>(); //使用智能指针创建对象server->Init(); //初始化套接字server->Start();//开始通信return 0;
}
//Udp_clientMain.cc
#include "udp_client.hpp"
#include "Commn.hpp"
#include "Log.hpp"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace LogModule;
#include <limits>int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Please use for ./client_udp + ip + port" << std::endl;Die(USE_ERROR);}int socketfd = ::socket(AF_INET6, SOCK_DGRAM, 0);// char = argv[1];std::string ip = argv[1];uint16_t port = atoi(argv[2]);if (socketfd < 0){Die(SOCKET_ERROR);}LOG(LogLevel::DEBUG) << "socket create success, fd is " << socketfd;sockaddr_in server;server.sin_family = AF_INET;server.sin_addr.s_addr = ::inet_addr(ip.c_str()); server.sin_port = ::htons(port);socklen_t len = sizeof(server);std::string message;while(true){std::cout << "Please Enter# ";getline(std::cin,message);::sendto(socketfd,message.c_str(),message.size(),0,CONV(&server),len);   sockaddr_in peer;socklen_t Len = sizeof(peer); //一定要初始化char inbuffer[1024];int n = ::recvfrom(socketfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&Len);if (n > 0){inbuffer[n] = 0;LOG(LogLevel::INFO) << inbuffer;}}return 0;
}

细节问题:

  1. socklen_t len这个参数一定要初始化!!!

  1. 无论是 recvfrom接受网络的消息,还是向网络中消息sendto,如果我们想要使用接受的数据需要把网络中的数据,转化为本机存储序列。向网络中发送数据也需要转化为网络字节序列。

通信效果图:

服务端我们启动的方式为: ./server_udp + 端口号

客户端启动方式:./client_udp + 访问的IP地址 + 对应的端口号

简单版英汉字典

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

如上图是一份简单字典,我们将实现用户输入单词,服务端返回单词意思的功能。

实现代码

//Dictionary.hpp
#pragma once 
#include <unordered_map>
#include <string>
#include <fstream>
#include <iostream>
class Dictionary
{
public:
static std::unordered_map<std::string,std::string> _dict;
static Dictionary*_instance;
static Dictionary* Getinstace()
{   if (_instance==nullptr){_instance = new Dictionary();return _instance;}return _instance;
}
~Dictionary()
{if(_instance){delete _instance;_instance = nullptr;}
}
std::string Translate(std::string& word)
{if (_dict.find(word) == _dict.end()) return "NONE";return _dict[word];
}
void Debug()
{for (auto& x : _dict){std::cout << x.first << " : " << x.second << std::endl;}
}
private:
Dictionary() 
{// std::cout << "开始加载字典" << std::endl;LoadDictionary();
}
Dictionary(const Dictionary& dic) = delete;
Dictionary& operator =(const Dictionary& dic) = delete;void LoadDictionary()
{std::ifstream input("Dict.txt");if (!input.is_open()) {std::cout << "无法打开文件" << std::endl;return;}std::string line;while(std::getline(input,line)){//apple: 苹果 - 数据格式size_t pos = line.find(':');std::string fruit = line.substr(0,pos);std::string content = line.substr(pos+2);_dict[fruit] = content;}
}
};
Dictionary* Dictionary::_instance = nullptr;
std::unordered_map<std::string,std::string> Dictionary::_dict;
//Udp_server.hpp
#pragma once 
#include <iostream>
#include <cstdint>
#include <sys/socket.h>
#include <netinet/in.h>
#include "Commn.hpp"
#include "Log.hpp"
#include <string>
#include <arpa/inet.h>
#include <functional>
using namespace LogModule;
const uint16_t gport = 8888;
using func_t = std::function<std::string(std::string&)>;class Udp_server
{
public:
Udp_server(func_t func,uint16_t port=gport) //把翻译函数传进来
:_port(port),
_translate(func)
{}
void Init()
{_socketfd = ::socket(AF_INET,SOCK_DGRAM,0); //获取文件描述符if (_socketfd < 0) {Die(SOCKET_ERROR);}LOG(LogLevel::DEBUG) << "socket create success, fd is " << _socketfd;//进行绑定sockaddr_in local;local.sin_family = AF_INET; //本质也是无符号16位整数local.sin_port = ::htons(_port); //主机序列转为网络序列local.sin_addr.s_addr = INADDR_ANY; //s2位无符号整型socklen_t len = sizeof(local);int n = ::bind(_socketfd,CONV(&local),len);if (n < 0){Die(BIND_ERROR);}LOG(LogLevel::DEBUG) << "bind create success";
}
void Start()
{char buffer[1024];sockaddr_in peer; //对端消息socklen_t len = sizeof(peer); //一定要初始化!!!!while(true){int n = ::recvfrom(_socketfd,buffer,sizeof(buffer)-1,0,CONV(&peer),&len);if (n > 0){buffer[n] = 0;std::string word = buffer;std::string client = _translate(word); //存储翻译的结果//转换char Ip[64];::inet_ntop(AF_INET,&(peer.sin_addr),Ip,64); //把网络ip地址转为点分十进制 192.132.168.10std::string ip = Ip; //等到客户端IP地址std::string port = std::to_string(ntohs(_port)); //等到客户端端口号LOG(LogLevel::INFO) << ip << ":" << port << "says# " << buffer;//发送给客户端::sendto(_socketfd,client.c_str(),client.size(),0,CONV(&peer),len);}}
}
~Udp_server()
{}
private:
int _socketfd;
uint16_t _port;
func_t _translate;
};
//Udp_serverMain.cc
#include "udp_server.hpp"
#include "Dicitonary.hpp"#include <memory>
int main(int argc, char* argv[])
{// std::string t;// Dictionary::Getinstace()->Debug();// while(true)// {//     std::cin >> t;//     std::cout << "查询: " << t << " ";//     std::cout << " 结果为: " << Dictionary::Getinstace()->Translate(t) << std::endl;// }if (argc != 2){std::cout << "Please use for ./server_udp + port!!" << std::endl;Die(USE_ERROR);}auto dict = Dictionary::Getinstace(); //获取字典单例//把翻译函数注册进去std::shared_ptr<Udp_server> server = std::make_shared<Udp_server>([&dict](std::string&word){ return dict->Translate(word);});server->Init();server->Start();return 0;
}
//Udp_clientMain.cc
#include "udp_client.hpp"
#include "Commn.hpp"
#include "Log.hpp"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace LogModule;
#include <limits>int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Please use for ./client_udp + ip + port" << std::endl;Die(USE_ERROR);}int socketfd = ::socket(AF_INET6, SOCK_DGRAM, 0);// char = argv[1];std::string ip = argv[1];uint16_t port = atoi(argv[2]);if (socketfd < 0){Die(SOCKET_ERROR);}LOG(LogLevel::DEBUG) << "socket create success, fd is " << socketfd;sockaddr_in server;server.sin_family = AF_INET;server.sin_addr.s_addr = ::inet_addr(ip.c_str()); server.sin_port = ::htons(port);socklen_t len = sizeof(server);std::string message;while(true){std::cout << "Please Enter# ";getline(std::cin,message);// std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');::sendto(socketfd,message.c_str(),message.size(),0,CONV(&server),len);   sockaddr_in peer;socklen_t Len = sizeof(peer); //一定要初始化char inbuffer[1024];int n = ::recvfrom(socketfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&Len);if (n > 0){inbuffer[n] = 0;LOG(LogLevel::INFO) << inbuffer;}}return 0;
}

细节问题

  1. 在传入翻译函数的时候,需要使用lambda表达式,我们需要捕获单例对象的地址,然后才可以在lambda表达式中使用。

通信效果图

对于字典当中没有的数据直接返回NONE。

简单聊天室

实现思路

通过前面两个案例我们可以发现,在我们**服务端绑定Ip和端口是一样的,**同时服务端接受消息的时候都需要解析网络中的IP和端口号。那么我们可以把这两部进行封装。

所以我们可以创建 InetAddr.hpp

#pragma once 
#include <cstdint>
#include <string>
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Commn.hpp"
#include <cstring>
class InetAddr
{
public:
InetAddr(sockaddr_in& addr) //传入sockaddr_in默认把网络字节序解析得到IP字符串与主机序列的端口号
:_net_addr(addr)
{Net2HostIp();Net2HostPort();
}
void Net2HostIp()
{char Ip[1024];memset(Ip,0,sizeof(Ip));//::inet_ntop(AF_INET,&(_net_addr.sin_addr),Ip,sizeof(Ip)); //把网络ip地址转为点分十进制 192.132.168.10if(::inet_ntop(AF_INET,&(_net_addr.sin_addr),Ip,sizeof(Ip)) == nullptr){perror("inet_ntop failed");_ip = "InvalidIP";}else{_ip = Ip;}
}void Net2HostPort()
{_port = ::ntohs(_net_addr.sin_port);
}InetAddr(uint16_t port) //如果只是传入端口号,则是初始化服务端bindIP地址 + 端口号。
:_port(port)
{_net_addr.sin_family = AF_INET;_net_addr.sin_port = ::htons(_port);_net_addr.sin_addr.s_addr = INADDR_ANY; //IP地址默认接受任意来源的IP
}sockaddr* NetAddr()
{return (sockaddr*)(&_net_addr);
}socklen_t Len() { return sizeof(_net_addr);}
std::string IP() {return _ip;}
uint16_t Port() { return _port;}std::string netaddr() { //返回Ip地址和端口号的字符串形式。std::string Ip = _ip;std::string pp = std::to_string(_port);std::string ans = Ip +":" + pp;return ans;
}
private:
uint16_t _port;
std::string _ip = "";
sockaddr_in _net_addr;
};

那么如果管理每一个客户端呢?

//User.hpp
#pragma once 
#include <iostream>
#include <string>
#include "InetAddr.hpp"
#include "Log.hpp"
#include <vector>
#include <memory>
#include <list>
#include "Mutex.hpp"
#include <algorithm>using namespace MpthreayMutex;
using namespace LogModule;
class UserStrategy //用户接口类
{
public:
UserStrategy() = default;
virtual ~UserStrategy() {}
virtual void SendMessage(int socketfd, std::string message) = 0; //抽象类
virtual std::string Info() = 0; 
virtual bool operator==(InetAddr& addr) = 0;
};class User : public UserStrategy
{  
public:
User(InetAddr& addr) //使用我们自己的InetAddr初始化
:_net_addr(addr)
{}void SendMessage(int socketfd, std::string message)
{::sendto(socketfd,message.c_str(),message.size(),0,_net_addr.NetAddr(),_net_addr.Len());
}
std::string Info()
{return _net_addr.netaddr();
}
virtual bool operator==(InetAddr& addr) {return _net_addr.IP()==addr.IP() && _net_addr.Port() == addr.Port();}
~User(){}
private:
InetAddr _net_addr;
};class UserManage
{
public:
UserManage()
{}void AddUser(InetAddr& in)
{LockGround lock(_mtx); //要加锁,我们链表被多线程使用是一份临界资源。for (auto user_ptr:_user_set){if (*user_ptr== in) {std::cout << in.netaddr() << "已经存在" << std::endl;return;}}_user_set.emplace_back(std::make_shared<User>(in)); //指针指向的是子类LOG(LogLevel::DEBUG) << _user_set.back()->Info() << "添加成功";PrintUser(); //打印所有的在线用户
}void Route(int fd,std::string message)
{LockGround lock(_mtx);for (auto _ptr:_user_set) {_ptr->SendMessage(fd,message);}
}void Delete(InetAddr& out)
{LockGround lock(_mtx);//remove_if 把目标值放到迭代器的末尾,然后返回这个迭代器auto pos = std::remove_if(_user_set.begin(),_user_set.end(),[&](std::shared_ptr<UserStrategy>& id){return *id==out;});_user_set.erase(pos,_user_set.end()); //一个迭代器区间范围PrintUser();
}
void PrintUser()
{for (auto _ptr : _user_set){LOG(LogLevel::INFO) <<  "在线用户" << _ptr->Info();}
}
~UserManage(){}
private:
//使用接口类构造,User子类创建元素,父类指针,指针指向的是子类,形成多态。
std::list<std::shared_ptr<UserStrategy>> _user_set; //使用链表管理所有的用户
Mutex _mtx;
};
//Udp_server.hpp
#pragma once 
#include <iostream>
#include <cstdint>
#include <sys/socket.h>
#include <netinet/in.h>
#include "Commn.hpp"
#include "Log.hpp"
#include <string>
#include <arpa/inet.h>
#include <functional>
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include <cstring>using namespace LogModule;
const uint16_t gport = 8888;
using func_t = std::function<std::string(std::string&)>;
using adduser_t = std::function<void(InetAddr&)>;
using deluser_t = std::function<void(InetAddr&)>;
using route_t = std::function<void(int fd,std::string& message)>;
using task_t = std::function<void()>;
using namespace MyThreadPool;class Udp_server
{
public:
Udp_server(uint16_t port=gport)
:_port(port)
{}
void Init()
{_socketfd = ::socket(AF_INET,SOCK_DGRAM,0); //获取文件描述符if (_socketfd < 0) {Die(SOCKET_ERROR);}LOG(LogLevel::DEBUG) << "socket create success, fd is " << _socketfd;//进行绑定InetAddr local(_port); //只需要传入一个端口号即可。// local.sin_family = AF_INET; //本质也是无符号16位整数// local.sin_port = ::htons(_port); //主机序列转为网络序列// local.sin_addr.s_addr = INADDR_ANY; //s2位无符号整型// socklen_t len = sizeof(local);int n = ::bind(_socketfd,local.NetAddr(),local.Len());if (n < 0){Die(BIND_ERROR);}LOG(LogLevel::DEBUG) << "bind create success";
}void Register(adduser_t adduser,route_t route,deluser_t deluser)
{_adduser = adduser;_route = route;_deluser = deluser;
}void Start()
{char buffer[1024];sockaddr_in peer; //对端消息socklen_t len = sizeof(peer); //一定要初始化!!!!while(true){int n = ::recvfrom(_socketfd,buffer,sizeof(buffer)-1,0,CONV(&peer),&len);if (n > 0){buffer[n] = 0;InetAddr Addr(peer);std::string message;if (strcmp(buffer,"QUIT") == 0){_deluser(Addr);message = "我走了,你们聊!";}else {_adduser(Addr);message = Addr.netaddr() + " #" + buffer;}LOG(LogLevel::INFO) << message;task_t t = std::bind(Udp_server::_route,_socketfd,message); //也可以直接绑定// task_t t = [=]() mutable{ //这里要传值传递,传引用传递的话可能会出现乱码!!!!!!!!!!!!!!!!!!!!!!!!//     _route(_socketfd, message);// };threadpool<task_t>::Getinstance()->Equeue(t);// std::string client = "echo server says# ";// client += buffer;// //转换// // char Ip[64];// // ::inet_ntop(AF_INET,&(peer.sin_addr),Ip,64); //把网络ip地址转为点分十进制 192.132.168.10// // std::string ip = Ip;// // std::string port = std::to_string(ntohs(_port));// LOG(LogLevel::INFO) << Addr.IP() << ":" << Addr.Port() << "says# " << buffer;// ::sendto(_socketfd,client.c_str(),client.size(),0,CONV(&peer),len);}}}~Udp_server(){}
private:int _socketfd;uint16_t _port;adduser_t _adduser;route_t _route;deluser_t _deluser;
};
//Udp_serverMain.cc
#include "udp_server.hpp"
#include <memory>
#include "User.hpp"int main(int argc, char* argv[])
{if (argc != 2){std::cout << "Please use for ./server_udp + port!!" << std::endl;Die(USE_ERROR);}std::shared_ptr<UserManage> um = std::make_shared<UserManage>();std::shared_ptr<Udp_server> server = std::make_shared<Udp_server>();server->Init();//为了降低程序的耦合度,把用户管理的方式注册进去,而不是当作服务端对象的参数。//这样以后有什么新的方法,直接注册进去就可以。server->Register([&um](InetAddr& in){um->AddUser(in);}, [&um](int fd,std::string message){um->Route(fd,message);},[&um](InetAddr& out){um->Delete(out);});server->Start();return 0;
}
//Udp_clientMain.cc
#include "udp_client.hpp"
#include "Commn.hpp"
#include "Log.hpp"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace LogModule;
#include <limits>
#include <signal.h>
int socketfd = -1;
sockaddr_in server;void ClientQuit(int signo)
{(void)signo;std::string quit = "QUIT";::sendto(socketfd,quit.c_str(),quit.size(),0,CONV(&server),sizeof(server)); //退出的时候发送QUIT   exit(0);
}void* Receive(void* args)
{while(true){//出现的问题 !!!sockaddr_in peer; //这个必须在循环里创建,udp每一个都有独立的数据报,如果是全局的,那么len大小会错误,会导致输出不能正常显示。socklen_t len = sizeof(peer);char inbuffer[1024];int n = ::recvfrom(socketfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&len);if (n > 0){inbuffer[n] = 0;std::cerr << inbuffer << std::endl;}}return nullptr;
}int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Please use for ./client_udp + ip + port" << std::endl;Die(USE_ERROR);}signal(2,ClientQuit); //处理用户退出socketfd = ::socket(AF_INET6, SOCK_DGRAM, 0);// char = argv[1];std::string ip = argv[1];uint16_t port = atoi(argv[2]);if (socketfd < 0){Die(SOCKET_ERROR);}LOG(LogLevel::DEBUG) << "socket create success, fd is " << socketfd;//手动写server.sin_family = AF_INET;server.sin_addr.s_addr = ::inet_addr(ip.c_str()); server.sin_port = ::htons(port);socklen_t len = sizeof(server);std::string message;pthread_t tid;int ret = pthread_create(&tid,nullptr,Receive,nullptr); //创建线程来发送消息//如何让用户一进来就可以看到服务器当中发送的消息呢?//向服务器发送消息,服务器轮询发送给所有的用户std::string online = " ...来了哈!";::sendto(socketfd,online.c_str(),online.size(),0,CONV(&server),len);while(true){std::cout << "Please Enter# ";getline(std::cin,message);// std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');::sendto(socketfd,message.c_str(),message.size(),0,CONV(&server),len);   }return 0;
}

通信效果图

当我们按下 ctrl + c,的时候自动发送QUIT消息,然后退出进程。

为了方便观看,我们标准错误重定向到管道文件当中,再新开一个终端从管道文件当中读取,这样就可以把服务端返回的消息与输出结果分割开来。注意:管道文件必须读端和写端都打开我们才可以看到结果,不然会一直阻塞住。

问题集

  1. 这个必须在循环里创建

sockaddr_in peer; // 客户端Recv的结构体

socklen_t len = sizeof(peer);

//这个必须在循环里创建,udp每一个都有独立的数据报,如果是全局的,那么len大小会错误,会导致输出不能正常显示。

  1. 一定要初始化!!! ->socklen_t len = sizeof(peer);
  2. 传值传递

// task_t t = = mutable{ //这里要传值传递,传引用传递的话可能会出现乱码!! 位置 -> 服务端创建任务

// _route(_socketfd, message);

// };

其他所需hpp文件

//Log.hpp
#pragma once 
#include <iostream>
#include <memory>
#include <unistd.h>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <time.h>
#include <sstream>namespace LogModule 
{using namespace MpthreayMutex;const std::string defaultlogpath = "./log/";    //log日志所保存的路径const std::string defaultlogname = "log.txt";  //log文件名//日志等级enum class LogLevel{DEBUG = 1,  //调式INFO,       //正常WARNING,    //警告ERROR,      //错误FATAL       //致命的
};//获取时间信息std::string GerCurrTime(){time_t timp = time(nullptr);struct tm t;localtime_r(&timp,&t);char buffer[1024];snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d", t.tm_year+1900,t.tm_mon+1,t.tm_wday+1,t.tm_hour,t.tm_min,t.tm_sec);return buffer;}std::string loglevelToString(LogLevel loglevel){switch(loglevel){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 "NONE";                                                                            }}//1.策略模式class Stragety{public:virtual ~Stragety() = default; //多态的话先把析构处理了virtual void Synclog(const std::string& message) = 0;   //基类提供抽象类,有子类提供实现。1. 不能实列化对象 2. 但是可以创建指针
};//默认策略模式 - 向显示器输出日志信息class ConsoleStragety : public Stragety{public://override: 如果没有不是虚函数重写就报错void Synclog(const std::string& message) override {LockGround lock(_mtx); // 多线程的话日志输出也是临界资源std::cout << message << std::endl; }~ConsoleStragety(){}private:Mutex _mtx; 
};class FileLogStragety : public Stragety{public:FileLogStragety(const std::string& logpath = defaultlogpath,const std::string logname = defaultlogname):_logpath(logpath),_logname(logname){LockGround lock(_mtx);//检查对应的目录以及文件是否存在if (std::filesystem::exists(_logpath)) //如果存在的话直接返回就可以{return;}else {//创建文件夹try {std::filesystem::create_directories(_logpath);}catch(const std::filesystem::filesystem_error & e){std::cout << e.what() << std::endl;}}}void Synclog(const std::string& message) override{LockGround lock(_mtx);std::string filename = _logpath + _logname;std::ofstream  out(filename.c_str(),std::ios::app); //如果不存在创建,存在追加if (!out.is_open()) return;out << message << std::endl;out.close();}~FileLogStragety(){}private:std::string _logpath;std::string _logname;Mutex _mtx; };  //具体的日志类class Logger{public:Logger(){_strategy = std::make_shared<ConsoleStragety>();}void EnableConsoleStragety(){_strategy = std::make_shared<ConsoleStragety>();}void EnableFilesystemStragety(){_strategy = std::make_shared<FileLogStragety>();}//为什么要实现这一个内部类呢?class LogMessage{public:LogMessage(LogLevel level,int line,const std::string& filename,Logger& logger):_level(level),_line(line),_filename(filename),_logger(logger){//初始化_loginfo //1. 时间:std::stringstream ss;ss  << "[" << GerCurrTime() << "] "<< "[" << loglevelToString(_level) << "] "<< "[" << getpid() << "] "<< "[" << filename << "] "<< "[" << _line << "] " << " - ";_loginfo += ss.str();}//因为不确定传入什么类型 ,调用方式: LOG(DUBUG) << "hello world" << 3 << 2.2 << 1;template<class T>LogMessage& operator<<(const T& info){std::stringstream ss;ss << info;_loginfo += ss.str();return *this; //返回调用方便下一调用//注意这里需要引用返回,不然的话会创建两次}~LogMessage(){//更新日志信息本质是输出?if (_logger._strategy)  //调用析构之后以特定方式输出日志{_logger._strategy->Synclog(_loginfo);}}private:    std::string _loginfo; //总的信息LogLevel _level;int _line;std::string _filename;Logger& _logger;};LogMessage operator()(LogLevel level,int line,const std::string& filename){//这里需要注意理论上应该创建两个LogMessage的,但是编译器直接给优化掉了,避免了不必要的拷贝return LogMessage(level,line,filename,*this);}~Logger(){}private:std::shared_ptr<Stragety> _strategy;};Logger logger;//定义调用的宏。,返回的是匿名对象,生命周期在当前行,然后调用LogMessage的析构#define LOG(type) logger(type,__LINE__,__FILE__) //定义转换策略模式的宏#define ENABLE_CONSOLE_LOG_STRATEGY() logger.EnableConsoleStragety()#define ENABLE_FILESYSTEM_LOG_STRATEGY() logger.EnableFilesystemStragety()
}
//Mutex.hpp
#pragma once
#include <pthread.h>
namespace MpthreayMutex
{class Mutex{public:Mutex(){int n = pthread_mutex_init(&_mtx,nullptr);(void)n;}Mutex(const Mutex& _mtx) = delete;Mutex& operator=(const Mutex& _mtx) = delete;~Mutex(){int n = pthread_mutex_destroy(&_mtx);(void)n;}pthread_mutex_t* LockPtr(){return &_mtx;}void Lock(){pthread_mutex_lock(&_mtx);}void Unlock(){pthread_mutex_unlock(&_mtx);}private:pthread_mutex_t _mtx;
};class LockGround{public:LockGround(Mutex& mtx) :_mtx(mtx) {_mtx.Lock();            }~LockGround(){_mtx.Unlock();}private:Mutex& _mtx;
};
}
//Cond.hpp
#pragma once
#include "Mutex.hpp"
#include <pthread.h>namespace MyCond
{using namespace MpthreayMutex;class Cond{public:Cond(){int n = pthread_cond_init(&_cond, nullptr);(void)n;}void wait(Mutex& mtx){int n = pthread_cond_wait(&_cond, mtx.LockPtr());(void)n;}void Notify() // 通知{int n = pthread_cond_signal(&_cond);(void)n;}void NotifyAll(){int n = pthread_cond_broadcast(&_cond);(void)n;}pthread_cond_t *Adrr(){return &_cond;}~Cond(){int n = pthread_cond_destroy(&_cond);(void)n;}private:pthread_cond_t _cond;
};
}

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

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

相关文章

C# 调用 VITS,推理模型 将文字转wav音频net8.0 跨平台

一、系统环境 操作系统&#xff1a;win10&#xff0c;win11 运行环境&#xff1a;dotnet8 工具:命令行&#xff0c;powershell 开源库:sherpa-onnx 二、工具和源码下载 开源库:https://k2-fsa.github.io/sherpa/onnx/index.html 运行环境下载 https://dotnet.microsoft.c…

【AI学习笔记】Coze平台实现将Excel文档批量导入数据库全过程

背景前摇&原视频教程&#xff1a; 最近看到很多同学都在用Coze平台操作数据&#xff0c;我也想了解一下工作流的搭建和数据处理过程&#xff0c;但是一下子又看不懂太复杂的逻辑&#xff0c;于是上B站搜索相关的基础教程。 Coze官方教程&#xff1a; 之前有看过Coze平台…

Certd自动化申请和部署SSL证书并配置https

服务器使用的华为云&#xff0c;之前SSL证书通过配置Cloudflare的DNS实现的&#xff0c;最近华为云备案提示需修改解析至境内华为云IP&#xff0c;若解析境外IP&#xff0c;域名无需备案&#xff0c;需注销或取消接入备案信息&#xff0c;改为使用Certd自搭建证书管理工具&…

AI基础01-文本数据采集

本篇文章是学习文本数据的采集&#xff0c;作为人工智能训练师或者数据分析师有时需要先获取数据&#xff0c;然后进行数据清洗、数据标注。很明显数据采集是后续步骤的基础。 1&#xff09;数据采集定义 数据采集&#xff1a;data acquisition&#xff0c;DAQ 又称为数据获取…

生活电子常识-deepseek-r1本地化部署+ui界面搭建

前言 deepseek-r1 14b模型&#xff0c;32b模型部署在本地电脑上也能实现非常好的性能。 因此有兴趣研究了下如何在本地部署。 同时最新流行mauns工作流&#xff0c;他们提供一句话实现网页端任意应用的能力。实际上&#xff0c;你也可以用本地的模型来实现离线的ai工作流功能。…

vue3+ts中 .vue文件引入报错:找不到模块或其相应的类型声明

新创建的vue3项目在vscode打开出现报错&#xff1a;找不到模块或其相应的类型声明 解决&#xff1a;在env.d.ts文件添加配置&#xff1a; declare module *.vue {import type { DefineComponent } from vue// eslint-disable-next-line typescript-eslint/no-explicit-any, …

Ubuntu 22.04 二进制安装单节点 MySQL

Ubuntu 22.04 二进制安装 MySQL LTS&#xff08;长期支持版&#xff09;完整教程 MySQL LTS 版本选择&#xff1a; 目前 MySQL 8.4.4 是长期支持&#xff08;LTS&#xff09;版本&#xff0c;持续更新并保持稳定。 下载版本&#xff1a; 你也可以在 MySQL 官方网站确认最新稳…

安装和管理最新的Python3环境(以Mac为例)

背景&#xff1a; 随着大模型技术的快速发展&#xff0c;各种基于AI的测试技术也层出不穷&#xff0c;有些场景需要在较高版本的Python3环境下实现&#xff0c;否则可能会出现兼容性问题。另外考虑自己对于Python3的各个版本环境的管理和使用其实一直都不是特别的清楚&#xf…

【计算机网络】网络简介

文章目录 1. 局域网与广域网1.1 局域网1.2 广域网 2. 路由器和交换机3. 五元组3.1 IP和端口3.2 协议3.3 协议分层 4. OSI七层网络协议5. TCP/IP五层模型5.1 TCP/IP模型介绍5.2 网络设备所在分层 6. 封装与分用6.1 数据包的称谓6.2 封装6.3 分用 1. 局域网与广域网 1.1 局域网 …

【云馨AI-大模型】自动化部署Dify 1.1.2,无需科学上网,Linux环境轻松实现,附Docker离线安装等

Dify介绍 官网&#xff1a;https://dify.ai/zh生成式 AI 应用创新引擎开源的 LLM 应用开发平台。提供从 Agent 构建到 AI workflow 编排、RAG 检索、模型管理等能力&#xff0c;轻松构建和运营生成式 AI 原生应用。 Dify安装脚本 目录创建 mkdir -p /data/yunxinai &&a…

人工智能和量子时代的网络安全

在不断发展的网络安全领域&#xff0c;人工智能和量子技术正在迅速改变游戏规则。它们的潜力有望极大地改变政府和组织保护、防御和发展系统以应对不断发展的网络威胁的方式。 人工智能 (AI) 在检测和缓解网络威胁方面表现出了巨大的潜力。人工智能算法可以快速分析大量数据、…

前端样式库推广——TailwindCss

官方网址&#xff1a; https://tailwindcss.com/docs/installation/using-vite 中文官方文档&#xff1a;https://www.tailwindcss.cn/ github地址&#xff1a;tailwindcss 正在使用tailwindcss的网站&#xff1a;https://tailwindcss.com/showcase 一看github&#xff0c;竟然…

《基于深度学习的指纹识别智能门禁系统》开题报告

个人主页&#xff1a;大数据蟒行探索者 1研究背景 1.1开发目的和意义 指纹识别作为生物特征识别领域的一项重要技术&#xff0c;在安全认证、犯罪侦查和个人身份验证等方面具有广泛应用前景。随着深度学习技术的迅猛发展&#xff0c;基于深度学习的指纹识别系统成为了当前研究…

WSL Linux 子系统download

WSL各Linux 子系统下载 WSL Linux 最新下载 微软应用商店 | Microsoft StoreWSL Linux 历史版下载复制应用商店Linux地址到转换下载地址https://store.rg-adguard.net/ Version百度网盘离线下载OracleLinux提取

Java替换jar包中class文件

在更新java应用版本的运维工作中&#xff0c;由于一些原因&#xff0c;开发没办法给到完整的jar包&#xff0c;这个时候&#xff0c;就可以只将修改后的某个Java类的class文件替换掉原来iar包中的class文件&#xff0c;重新启动服务即可&#xff1a; 1、将jar包和将要替换的cl…

23种设计模式-创建型模式-抽象工厂

文章目录 简介场景问题1. 风格一致性失控2. 对象创建硬编码3. 产品族管理失效 解决总结 简介 抽象工厂是一种创建型设计模式&#xff0c;可以生成相关对象系列&#xff0c;而无需指定它们的具体类。 场景 假设你正在写一个家具店模拟器。 你的代码这些类组成&#xff1a; 相…

修改服务器windows远程桌面默认端口号

修改服务器windows远程桌面默认端口号 在Windows服务器上修改远程桌面协议&#xff08;RDP&#xff09;的默认端口&#xff08;3389&#xff09;可以增强服务器的安全性&#xff0c;减少被恶意扫描和攻击的风险。以下是修改远程端口的详细步骤&#xff1a; 按 Win R 打开运行…

【MySQL】 基本查询(上)

欢迎拜访&#xff1a;-CSDN博客 本篇主题&#xff1a;【MySQL】 基本查询(上) 发布时间&#xff1a;2025.2.14 隶属专栏&#xff1a;MySQL CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; 目录 Create 基本知识…

Vue3(自定义指令directive详解)

文章目录 前言一、自定义指令的生命周期钩子二、自定义指令的创建与注册使用三、扩展 简化形式​总结 前言 在Vue3中&#xff0c;自定义指令是一种强大的工具&#xff0c;允许开发者扩展和增强HTML元素的功能。以下是对Vue3中自定义指令的详细解析&#xff1a; 一、自定义指令…

进制转换(R转十)(1290. 二进制转换十进制、1292. 十六进制转十进制、1291. 八进制转十进制、1405. 小丽找潜在的素数)

题单地址&#xff1a;题单中心-东方博宜OJ 这里以二进制转十进制为例&#xff08;按位加权求和法&#xff09; 1290. 二进制转换十进制 问题描述 请将一个 25 位以内的 2 进制正整数转换为 1010 进制&#xff01; 输入 一个 25 位以内的二进制正整数。 输出 该数对应的…