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.pswp.cn/bicheng/74681.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/74681.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/74681.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

汽車行業可信數據空間研究探索

近期&#xff0c;相關老師在新能源汽車國家大數據聯盟微課堂發表了題為“汽車行業可信數據空間研究探索”的演講&#xff0c;主要包括可信數據空間的概念內涵、汽車行業可信數據空間的發展現狀、數據流通場景和技術需求研究、汽車行業可信數據空間的場景建設建議四個方面展開。…

圓弧插補相關算法匯總(C++和ST源代碼)

運動控制需要了解相關的插補概念,在閱讀本篇博客之前需要了解相關的準備知識,常用鏈接如下: SMART PLC直線插補詳解-CSDN博客文章瀏覽閱讀2.1k次,點贊2次,收藏4次。本文介紹了SMART PLC中軸組對象的概念,詳細講解了直線插補的原理和指令使用,包括SMART PLC從V2.7版本開…

Entity Framework框架

深入理解C#中的Entity Framework框架&#xff1a;從理論到實踐 在C#開發中&#xff0c;與數據庫交互是幾乎所有應用程序的核心需求之一。Entity Framework (EF) 作為微軟官方推出的ORM框架&#xff0c;極大地簡化了數據庫操作。本文將帶您深入理解EF框架的核心概念&#xff0c…

C++11QT復習 (五)

文章目錄 **Day6-2 成員訪問運算符重載&#xff08;2025.03.25&#xff09;****1. 復習****2. 成員訪問運算符重載****2.1 箭頭運算符 (->) 重載****(1) 語法** **2.2 解引用運算符 (*) 重載****(1) 語法** **3. 代碼分析****3.1 代碼結構****3.2 代碼解析****(1) Data 類**…

簡歷含金量的描述和注意事項!

背景 最近&#xff0c;在公司負責后端相關面試&#xff0c;簡歷看了不下 50 份&#xff0c;面試 10&#xff0c;純手碼 2000 多字&#xff0c;說說我對簡歷的看法&#xff0c;希望給大家一點啟發。 教育經歷 在眾多求職面試中&#xff0c;我發現多數求職者容易忽視教育背景的…

cellnet框架概述

cellnet框架是一個?高性能、組件化、多協議支持?的開源服務器網絡庫&#xff0c;專注于游戲服務器、分布式的多進程通信等場景的開發。 一、核心特性 ?支持多個主流協議&#xff0c;包括TCP、UDP、HTTP、WebSocket。并且抽象底層協議差異&#xff0c;統一網絡連接管理?。 …

【加密社】如何創建自己的幣圈工具站

需要準備的工作 1.域名 2.服務器 周末的時候主要弄了快訊這方面的代碼 我這里用的是星球日報的api&#xff0c;也可以訂閱他們的rss&#xff0c;這部分在github上是開源的 https://github.com/ODAILY 我這里用的是WordPressonenav主題&#xff0c;然后用小工具在主頁展示&am…

Docker學習筆記(十一)宿主機無法鏈接宿主機問題處理

故障排查優先級排序 服務狀態 → 2. 端口監聽 → 3. 防火墻 → 4. 權限配置 → 5. 網絡路由 &#xff08;按此順序可覆蓋95%的常見問題?15&#xff09; mysql鏡像啟動命令&#xff1a; docker run -p 3306:3306 --restartalways --name mysqlv8 -e MYSQL_ROOT_PASSWORDCd…

力扣:回溯算法

組合I class Solution {List<List<Integer>> result new ArrayList(); // 所有結果集List<Integer> list new ArrayList(); // 當前結果集public List<List<Integer>> combine(int n, int k) {dfs(n, k, 1);return result;}public void dfs(i…

華為HCIE鴻蒙應用開發認證靠譜嗎?

在萬物互聯時代&#xff0c;智能終端設備的多樣性與協同需求催生了操作系統的革新。華為HarmonyOS&#xff08;鴻蒙系統&#xff09;憑借其分布式架構與全場景能力&#xff0c;正成為打破設備邊界、重塑用戶體驗的核心技術底座。HCIE鴻蒙應用開發認證作為華為認證體系的頂級資質…

23種設計模式-原型(Prototype)設計模式

原型設計模式 &#x1f6a9;什么是原型設計模式&#xff1f;&#x1f6a9;原型設計模式的特點&#x1f6a9;原型設計模式的結構&#x1f6a9;原型設計模式的優缺點&#x1f6a9;原型設計模式的Java實現&#x1f6a9;代碼總結&#x1f6a9;總結 &#x1f6a9;什么是原型設計模式…

Oracle-rman restore遭遇RMAN-03002與ORA-19563

文章目錄 在原DB上檢查是否有重復的文件名&#xff1a;查看rman恢復的日志修正重名部分重新執行rman恢復結論&#xff1a; 在 RMAN 恢復過程中&#xff0c;遇到RMAN-03002連同ORA-19563:錯誤。 操作是將 Oracle 10.0.5的數據庫備份從 RMAN備份恢復到另一臺測試主機的同一個目錄…

運維網絡排查工具介紹與使用

作為一名運維工程師&#xff0c;日常工作中最令人頭疼的莫過于各種網絡故障。在過去一年半的運維生涯中&#xff0c;我積累了豐富的網絡故障排查經驗&#xff0c;今天就來和大家分享一下如何運用抓包工具&#xff08;Wireshark、tcpdump&#xff09;和網絡排查工具&#xff08;…

解決vscode終端和本地終端python版本不一致的問題

&#x1f33f; 問題描述 本地終端&#xff1a; vscode終端&#xff1a; 別被這個給騙了&#xff0c;繼續往下看&#xff1a; 難怪我導入一些包的時候老提示找不到&#xff0c;在本地終端就不會這樣&#xff0c;于是我嚴重懷疑vscode中的python版本和終端不一樣&#xff0c…

Sublime全局搜索快捷鍵Ctrl+Shift+F不能使用解決

問題描述&#xff1a; 在安裝好Sublime后&#xff0c;我們使用快捷鍵進行全局搜索&#xff0c;發現沒有反應&#xff0c;但是中文輸入變成了繁體。 解決方案&#xff1a; 如截圖&#xff0c;在關閉簡繁切換的快捷鍵或者換成其他的就行

海康HTTP監聽報警事件數據

http監聽接收報警事件數據 海康獲取設備報警事件數據兩種方式&#xff1a; 1、sdk 布防監聽報警事件數據,服務端布防。&#xff08;前面文章有示例&#xff09; 2、http監聽接收報警事件數據&#xff0c;設備直接推送。 http監聽接收報警事件數據&#xff0c;服務端可以使用n…

Python----計算機視覺處理(Opencv:圖像邊緣檢測:非極大值抑制,雙閾值篩選)

一、 高斯濾波 邊緣檢測本身屬于銳化操作&#xff0c;對噪點比較敏感&#xff0c;所以需要進行平滑處理。這里使用的是一個5*5的高斯 核對圖像進行消除噪聲。 二、計算圖像的梯度和方向 三、非極大值抑制 在得到每個邊緣的方向之后&#xff0c;其實把它們連起來邊緣檢測就算完了…

Maven工具學習使用(四)——倉庫

倉庫分類 對于Mavne來說,倉庫只分為兩類:本地倉庫和遠程倉庫。當Maven根據坐標查詢尋找構件的時候,它首先會查看本地倉庫,如果本地倉庫存在此構件,則直接使用;如果本地倉庫不存在此構件,或者需要查看是否有更新的構件版本,Maven就會去遠程倉庫查找,發現需要的構件之后…

Axure PR 9.0(發音:Ack-sure)原型圖工具入門教程:鏈接交互

文章目錄 引言Axure? RP 9I Axure RP9入門介紹元件庫對兩個元件進行連接頁面:導航視圖、概要母版交互II 鏈接交互從A頁面跳轉到B頁面返回之前的頁面see also引言 【 產品原型圖】核心價值和實際應用場景:可視化需求,統一團隊理解 https://blog.csdn.net/z929118967/articl…

docker遠程debug

1. 修改 Java 啟動命令 在 Docker 容器中啟動 Java 程序時&#xff0c;需要添加 JVM 調試參數&#xff0c;jdk8以上版本 java -agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005 -jar your-app.jar jdk8及以下版本&#xff1a; java -Xdebug -Xrunjdwp:tra…