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地址。
- 注意: 这里要注意我们服务端需要进行绑定操作,一般服务器都会有一个固定的端口号。而对于客户端而言,在01023个端口号被一些公司厂家等占用,例如HTTP端口为80等等。而客户端我们用户选择的范围就是102465536。但是用户并不知道自己的电脑上究竟哪些端口被占用了,如果用户使用了一个正在被使用的端口号,那么另外一个进程就使用不了了,所以为了避免这个问题,客户端我们无需进行绑定操作,操作系统自动帮我们绑定一个端口号。
接受消息
与 发送消息
//接受消息
#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.10
std::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;
}
细节问题:
socklen_t len
这个参数一定要初始化!!!
- 无论是
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.10
std::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;
}
细节问题
- 在传入翻译函数的时候,需要使用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.10
if(::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消息,然后退出进程。
为了方便观看,我们标准错误重定向到管道文件当中,再新开一个终端从管道文件当中读取,这样就可以把服务端返回的消息与输出结果分割开来。注意:管道文件必须读端和写端都打开我们才可以看到结果,不然会一直阻塞住。
问题集
- 这个必须在循环里创建
sockaddr_in peer; // 客户端Recv的结构体
socklen_t len = sizeof(peer);
//这个必须在循环里创建,udp每一个都有独立的数据报,如果是全局的,那么len大小会错误,会导致输出不能正常显示。
- 一定要初始化!!! ->socklen_t len = sizeof(peer);
- 传值传递
// 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;
};
}