什么是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;
}
細節問題:
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.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;
}
細節問題
- 在傳入翻譯函數的時候,需要使用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消息,然后退出進程。
為了方便觀看,我們標準錯誤重定向到管道文件當中,再新開一個終端從管道文件當中讀取,這樣就可以把服務端返回的消息與輸出結果分割開來。注意:管道文件必須讀端和寫端都打開我們才可以看到結果,不然會一直阻塞住。
問題集
- 這個必須在循環里創建
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;
};
}