? 个人主页:Zfox_
? 系列专栏:Linux
目录
一:? UDP 网络编程 ? 接口讲解? V1 版本 - echo server? V2 版本 - DictServer? V3 版本 - 简单聊天室 二:? 观察者模式 三:? 补充参考内容 ? 地址转换函数? 关于 inet_ntoa 四:? 补充网络命令 ? Ping 命令? netstat? pidof 五:? 共勉
一:? UDP 网络编程
? 接口讲解
socket
#include <sys/types.h>#include <sys/socket.h>// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)int socket(int domain, int type, int protocol);domain: 域 / 协议家族AF_INET IPv4 Internet protocolsAF_INET6 IPv6 Internet protocolstype: 报文类型 SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length). SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported.protocol: 传输层类型默认为0On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropriately.
bind
#include <sys/types.h>#include <sys/socket.h>// 绑定端口号 (TCP/UDP, 服务器)int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);On success, zero is returned. On error, -1 is returned, and errno is set appropriately.// 2. 填充网络信息,并bind绑定// 2.1 没有把socket信息设置进入内核struct sockaddr_in local;bzero(&local, sizeof(local)); // string.hlocal.sin_family = AF_INET;local.sin_port = ::htons(_port); // 要被发送给对方,既要发送到网络中! 主机序列转换为网络序列 大小端转换 网络中都是大端 #include <arpa/inet.h>local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. string ip -> 4bytes 2. network order #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h>local.sin_addr.s_addr = INADDR_ANY;// 2.1 bind 这里设置进入内核int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen());
? 我们之前在调用socket的时候,明明已经填充了一次 AF_INET
, 为什么这里还需要一次呢?
创建套接字的时候填充的 AF_INET 是给操作系统文件系统里的网络文件接口,告诉我们的操作系统我们要创建一个网络的套接字。
这里则是用来填充 sockaddr_in 网络信息,只有套接字的结构和这里的结构一样,操作系统才能绑定成功。
? 必带四件套
#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>
recvfrom
#include <sys/types.h>#include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);sockfd: 指定的套接字buf: 存放收到的消息内容缓冲区len: 期望收取多少个字节flag: 收方式,默认为0,阻塞收输出型参数:sockaddr *src_addr: 谁发来的消息(ip + port)远端addrlen:These calls return the number of bytes received, or -1 if an error occurred. In the event of an error, errno is set to indicate the error.
sendto
#include <sys/types.h>#include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);sockfd: 指定的套接字buf: 发送的字符串len: flag: 收方式,默认为0,阻塞发输出型参数:sockaddr *dest_addr: 目标主机(ip + port)远端addrlen:
? V1 版本 - echo server
简单的回显服务器和客户端代码
备注: 代码中会用到 地址转换函数 . 参考接下来的章节.
UdpServer.hpp
#ifndef __UDP_SERVER_HPP__#define __UDP_SERVER_HPP__#include <iostream>#include <string>#include <memory>#include <functional>#include <cstring>#include <cerrno>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include "InetAddr.hpp"#include "Log.hpp"#include "Common.hpp"using namespace LogModule;const static int gsockfd = -1;// const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机const static uint16_t gdefaultport = 8080;using func_t = std::function<std::string(const std::string&)>;class UdpServer{public: UdpServer(func_t func, uint16_t port = gdefaultport) :_sockfd(gsockfd) ,_addr(port) ,_isrunning(false) ,_func(func) { } // 都是套路 void InitServer() { // 1. 创建socket _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地? if (_sockfd < 0) { LOG(LogLevel::FATAL) << "socket: " << strerror(errno); Die(SOCKET_ERR); } LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd; // 2. 填充网络信息,并bind绑定 // 2.1 没有把socket信息设置进入内核 // struct sockaddr_in local; // bzero(&local, sizeof(local)); // local.sin_family = AF_INET; // local.sin_port = ::htons(_port); // 要被发送给对方,既要发送到网络中! 主机序列转换为网络序列 大小端转换 网络中都是大端 // // local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. strinf ip -> 4bytes 2. network order // local.sin_addr.s_addr = INADDR_ANY; // 2.1 bind 这里设置进入内核 int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen()); if(n < 0) { LOG(LogLevel::FATAL) << "bind: " << strerror(errno); Die(BIND_ERR); } LOG(LogLevel::INFO) << "bind success"; } void Start() { _isrunning = true; while(true) { char inbuffer[1024]; struct sockaddr_in peer; socklen_t len = sizeof(peer); // 必须设置 ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer - 1), 0, CONV(&peer), &len); if(n > 0) { inbuffer[n] = 0; std::string result = _func(inbuffer); // 回调 出去还会回来 ::sendto(_sockfd, result.c_str(), result.size(), 0, CONV(&peer), sizeof(peer)); } } _isrunning = false; } ~UdpServer() { if(_sockfd > gsockfd) ::close(_sockfd); }private: int _sockfd; InetAddr _addr; // uint16_t _port; // 服务器未来的端口号 // std::string _ip; // 服务器所对应的IP bool _isrunning; // 服务器运行状态 // 业务 回调方法 func_t _func;};#endif
InetAddr.hpp
#pragma once#include <string>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include "Common.hpp"class InetAddr{private: void PortNettoHost() { _port = ::ntohs(_net_addr.sin_port); } void IpNettoHost() { char ipbuffer[64]; const char *ip = ::inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer)); (void)ip; }public: InetAddr() { } InetAddr(const struct sockaddr_in &addr) : _net_addr(addr) { PortNettoHost(); IpNettoHost(); } InetAddr(uint16_t port) : _port(port), _ip("") { _net_addr.sin_family = AF_INET; _net_addr.sin_port = htons(_port); _net_addr.sin_addr.s_addr = INADDR_ANY; } struct sockaddr *NetAddr() { return CONV(&_net_addr); } socklen_t NetAddrLen() { return sizeof(_net_addr); } std::string Ip() { return _ip; } uint16_t Port() { return _port; } ~InetAddr() { }private: struct sockaddr_in _net_addr; std::string _ip; uint16_t _port;};
Comm.hpp
#pragma once#include <iostream>#define Die(code) \ do \ { \ exit(code); \ } while (0)#define CONV(v) (struct sockaddr *)(v)enum{ USAGE_ERR = 1, SOCKET_ERR, BIND_ERR};
Log.hpp 前面的博客已经有了, 这里就不再复制粘贴了云服务器不允许直接 bind 公有 IP, 我们也不推荐编写服务器的时候, bind 明确的 IP, 推荐直接写成 INADDR_ANY
C++/* Address to accept any incoming messages. */#define INADDR_ANY ((in_addr_t) 0x00000000)
在网络编程中, 当一个进程需要绑定一个网络端口以进行通信时, 可以使用 INADDR_ANY
作为 IP 地址参数。 这样做意味着该端口可以接受来自任何 IP 地址的连接请求, 无论是本地主机还是远程主机。 例如, 如果服务器有多个网卡(每个网卡上有不同的 IP 地址) , 使用 INADDR_ANY
可以省去确定数据是从服务器上具体哪个 网卡/IP 地址上面获取的。
UdpServerMain.cc
#include "UdpServer.hpp"// ./server_udp localportint main(int argc, char *argv[]){ if (argc != 2) { std::cerr << "Usage: " << argv[0] << " localport" << std::endl; Die(USAGE_ERR); } // std::string ip = argv[1]; uint16_t port = std::stoi(argv[1]); LogModule::ENABLE_CONSOLE_LOG(); std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port); svr_uptr->InitServer(); svr_uptr->Start(); return 0;}
UdpClientMain.cc
#include "UdpClient.hpp"#include "Common.hpp"#include <iostream>#include <cstdlib>#include <string>#include <cstring>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>// CS// ./client_udp serverip serverportint main(int argc, char *argv[]){ if (argc != 3) { std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl; Die(USAGE_ERR); } std::string serverip = argv[1]; uint16_t serverport = std::stoi(argv[2]); // 1. 创建 socket int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { std::cerr << "socket error" << std::endl; Die(SOCKET_ERR); } // 1.1 填充server信息 struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = ::htons(serverport); server.sin_addr.s_addr = ::inet_addr(serverip.c_str()); // 2. clientdone while (true) { std::cout << "Please Enter# "; std::string message; std::getline(std::cin, message); int n = ::sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server)); (void)n; struct sockaddr_in temp; socklen_t len = sizeof(temp); char buffer[1024]; n = ::recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&temp), &len); if(n > 0) { buffer[n] = 0; std::cout << buffer << std::endl; } } return 0;}
? client 端要不要显示 bind 的问题 client 不需要bind吗? socket <-> socketclient 必须也有自己的ip和端口! 客户端不需要自己显示的调用bind!! 而是客户端首次sendto信息的时候,由OS自动进行bind1. 如何理解client自动随机bind端口号 一个端口号,只能被一个进程bind2. 如何理解server要显示的bind? 服务器的端口号,必须稳定!必须是众所周知且不能轻易改变的!
? V2 版本 - DictServer
实现一个简单的英译汉的网络字典
dict.txt
apple: 苹果banana: 香蕉cat: 猫dog: 狗book: 书pen: 笔happy: 快乐的sad: 悲伤的run: 跑jump: 跳teacher: 老师...
Dictionary.hpp
#pragma once#include <iostream>#include <string>#include <fstream>#include <unordered_map>#include "Log.hpp"#include "Common.hpp"const std::string gpath = "./";const std::string gdictname = "dict.txt";const std::string gsep = ": ";using namespace LockModule;class Dictionary{private: bool LoadDictionary() { std::string file = _path + _filename; std::ifstream in(file.c_str()); if(!in.is_open()) { LOG(LogLevel::ERROR) << "open file " << file << " error"; return false; } // happy: 快乐的 std::string line; while(std::getline(in, line)) { std::string key; std::string value; if(SplitString(line, &key, &value, gsep)) { _dictionary.insert(std::make_pair(key, value)); } } in.close(); return true; }public: Dictionary(const std::string &path = gpath, const std::string &filename = gdictname) :_path(path) ,_filename(filename) { LoadDictionary(); } std::string Translate(const std::string &word) { auto iter = _dictionary.find(word); if(iter == _dictionary.end()) return "None"; return iter->second; } ~Dictionary() {}private: std::unordered_map<std::string, std::string> _dictionary; std::string _path; std::string _filename;};
UdpServer.hpp
#ifndef __UDP_SERVER_HPP__#define __UDP_SERVER_HPP__#include <iostream>#include <string>#include <memory>#include <functional>#include <cstring>#include <cerrno>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include "InetAddr.hpp"#include "Log.hpp"#include "Common.hpp"using namespace LogModule;const static int gsockfd = -1;// const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机const static uint16_t gdefaultport = 8080;using func_t = std::function<std::string(const std::string&)>;class UdpServer{public: UdpServer(func_t func, uint16_t port = gdefaultport) :_sockfd(gsockfd) ,_addr(port) ,_isrunning(false) ,_func(func) { } // 都是套路 void InitServer() { // 1. 创建socket _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地? if (_sockfd < 0) { LOG(LogLevel::FATAL) << "socket: " << strerror(errno); Die(SOCKET_ERR); } LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd; // 2. 填充网络信息,并bind绑定 // 2.1 没有把socket信息设置进入内核 // struct sockaddr_in local; // bzero(&local, sizeof(local)); // local.sin_family = AF_INET; // local.sin_port = ::htons(_port); // 要被发送给对方,既要发送到网络中! 主机序列转换为网络序列 大小端转换 网络中都是大端 // // local.sin_addr.s_addr = ::inet_addr(_ip.c_str()); // 1. strinf ip -> 4bytes 2. network order // local.sin_addr.s_addr = INADDR_ANY; // 2.1 bind 这里设置进入内核 int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen()); if(n < 0) { LOG(LogLevel::FATAL) << "bind: " << strerror(errno); Die(BIND_ERR); } LOG(LogLevel::INFO) << "bind success"; } void Start() { _isrunning = true; while(true) { char inbuffer[1024]; struct sockaddr_in peer; socklen_t len = sizeof(peer); // 必须设置 ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer - 1), 0, CONV(&peer), &len); if(n > 0) { inbuffer[n] = 0; std::string result = _func(inbuffer); // 回调 出去还会回来 ::sendto(_sockfd, result.c_str(), result.size(), 0, CONV(&peer), sizeof(peer)); } } _isrunning = false; } ~UdpServer() { if(_sockfd > gsockfd) ::close(_sockfd); }private: int _sockfd; InetAddr _addr; // uint16_t _port; // 服务器未来的端口号 // std::string _ip; // 服务器所对应的IP bool _isrunning; // 服务器运行状态 // 业务 回调方法 func_t _func;};#endif
UdpServerMain.cc
#include "UdpServer.hpp"#include "Dictionary.hpp"// ./server_udp localportint main(int argc, char *argv[]){ if (argc != 2) { std::cerr << "Usage: " << argv[0] << " localport" << std::endl; Die(USAGE_ERR); } // std::string ip = argv[1]; uint16_t port = std::stoi(argv[1]); LogModule::ENABLE_CONSOLE_LOG(); std::shared_ptr<Dictionary> dict_sptr = std::make_shared<Dictionary>(); std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>([&dict_sptr](const std::string &word) { return dict_sptr->Translate(word); }, port); // func_t f = std::bind(&Dictionary::Translate, dict_sptr.get(), std::placeholders::_1); // std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(f, port); svr_uptr->InitServer(); svr_uptr->Start(); return 0;}
UdpClientMain.cc
#include "UdpClient.hpp"#include "Common.hpp"#include <iostream>#include <cstdlib>#include <string>#include <cstring>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>// CS// ./client_udp serverip serverportint main(int argc, char *argv[]){ if (argc != 3) { std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl; Die(USAGE_ERR); } std::string serverip = argv[1]; uint16_t serverport = std::stoi(argv[2]); // 1. 创建 socket int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { std::cerr << "socket error" << std::endl; Die(SOCKET_ERR); } // 1.1 填充server信息 struct sockaddr_in server; memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = ::htons(serverport); server.sin_addr.s_addr = ::inet_addr(serverip.c_str()); // 2. clientdone while (true) { std::cout << "Please Enter# "; std::string message; std::getline(std::cin, message); // client 不需要bind吗? socket <-> socket // client 必须也有自己的ip和端口! 客户端不需要自己显示的调用bind!! // 而是客户端首次sendto信息的时候,由OS自动进行bind // 1. 如何理解client自动随机bind端口号 一个端口号,只能被一个进程bind // 2. 如何理解server要显示的bind? 服务器的端口号,必须稳定!必须是众所周知且不能轻易改变的! int n = ::sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server)); (void)n; struct sockaddr_in temp; socklen_t len = sizeof(temp); char buffer[1024]; n = ::recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&temp), &len); if(n > 0) { buffer[n] = 0; std::cout << buffer << std::endl; } } return 0;}
? V3 版本 - 简单聊天室
UdpServer.hpp
#ifndef __UDP_SERVER_HPP__#define __UDP_SERVER_HPP__#include <iostream>#include <string>#include <memory>#include <functional>#include <cstring>#include <cerrno>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <functional>#include "InetAddr.hpp"#include "Log.hpp"#include "Common.hpp"#include "ThreadPool.hpp"using namespace LogModule;using namespace ThreadPoolModule;const static int gsockfd = -1;// const static std::string gdefaultip = "127.0.0.1"; // 表示本地主机const static uint16_t gdefaultport = 8080;using adduser_t = std::function<void(InetAddr &id)>;using remove_t = std::function<void(InetAddr &id)>;using task_t = std::function<void()>;using route_t = std::function<void(int sockfd, const std::string &message)>;// 编程技巧 继承nocopy的类就都不会被拷贝了class nocopy{public: nocopy() {} nocopy(const nocopy &) = delete; const nocopy &operator=(const nocopy &) = delete; ~nocopy() {}};class UdpServer : public nocopy{public: UdpServer(uint16_t port = gdefaultport) : _sockfd(gsockfd), _addr(port), _isrunning(false) { } // 都是套路 void InitServer() { // 1. 创建socket _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); // IP?PORT?网络?本地? if (_sockfd < 0) { LOG(LogLevel::FATAL) << "socket: " << strerror(errno); Die(SOCKET_ERR); } LOG(LogLevel::INFO) << "socket success, sockfd is : " << _sockfd; int n = ::bind(_sockfd, _addr.NetAddr(), _addr.NetAddrLen()); if (n < 0) { LOG(LogLevel::FATAL) << "bind: " << strerror(errno); Die(BIND_ERR); } LOG(LogLevel::INFO) << "bind success"; } void RegisterService(adduser_t adduser, route_t route, remove_t removeuser) { _adduser = adduser; _route = route; _removeuser = removeuser; } void Start() { _isrunning = true; while (true) { char inbuffer[4096]; struct sockaddr_in peer; socklen_t len = sizeof(peer); // 必须设置 ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &len); if (n > 0) { InetAddr cli(peer); inbuffer[n] = 0; std::string message; if (strcmp(inbuffer, "QUIT") == 0) { // 移除观察者 _removeuser(cli); message = cli.Addr() + "# " + "我走了, 你们聊!"; } else { // 2. 新增用户 _adduser(cli); message = cli.Addr() + "# " + inbuffer; } // task_t task = [this, message]() // { // this->_route(this->_sockfd, message); // }; // 3. 构建转发任务,推送给线程池,让线程池进行转发 线程池是无参数的所以要绑定 task_t task = std::bind(UdpServer::_route, _sockfd, message); ThreadPool<task_t>::getInstance()->Equeue(task); // std::string echo_string = "echo# "; // echo_string += inbuffer; // ::sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, CONV(&peer), sizeof(peer)); } } _isrunning = false; } ~UdpServer() { if (_sockfd > gsockfd) ::close(_sockfd); }private: int _sockfd; InetAddr _addr; bool _isrunning; // 服务器运行状态 // 新增用户 adduser_t _adduser; // 移除用户 remove_t _removeuser; // 数据转发 route_t _route;};#endif
UdpClientMain.cc
#include "UdpClient.hpp"#include "Common.hpp"#include <iostream>#include <cstdlib>#include <string>#include <cstring>#include <unistd.h>#include <pthread.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <signal.h>int sockfd = -1;struct sockaddr_in server;void ClientQuit(int signo){ (void)signo; const std::string quit = "QUIT"; int n = ::sendto(sockfd, quit.c_str(), quit.size(), 0, CONV(&server), sizeof(server)); (void)n; exit(0);}void *Recver(void *args){ (void)args; while (true) { struct sockaddr_in temp; socklen_t len = sizeof(temp); char buffer[1024]; int n = ::recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, CONV(&temp), &len); if (n > 0) { buffer[n] = 0; std::cerr << buffer << std::endl; } }}// CS// ./client_udp serverip serverportint main(int argc, char *argv[]){ if (argc != 3) { std::cerr << "Usage: " << argv[0] << " serverip serverport" << std::endl; Die(USAGE_ERR); } std::string serverip = argv[1]; uint16_t serverport = std::stoi(argv[2]); // 1. 创建 socket sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { std::cerr << "socket error" << std::endl; Die(SOCKET_ERR); } signal(2, ClientQuit); // 1.1 填充server信息 memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; server.sin_port = ::htons(serverport); server.sin_addr.s_addr = ::inet_addr(serverip.c_str()); pthread_t tid; ::pthread_create(&tid, nullptr, Recver, nullptr); // 1.2 启动的时候 给服务器推送消息即可 const std::string online = " ... 来了哈!"; int n = ::sendto(sockfd, online.c_str(), online.size(), 0, CONV(&server), sizeof(server)); // 2. clientdone while (true) { fprintf(stdout, "Please Enter# "); fflush(stdout); std::string message; std::getline(std::cin, message); int n = ::sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server)); (void)n; } return 0;}
User.hpp
#pragma once#include <iostream>#include <string>#include <list>#include <memory>#include <sys/types.h>#include <sys/socket.h>#include "InetAddr.hpp"#include "Log.hpp"#include "Mutex.hpp"using namespace LogModule;using namespace LockModule;class UserInterface{public: virtual ~UserInterface() = default; virtual void SendTo(int sockfd, const std::string &message) = 0; virtual bool operator==(const InetAddr &u) = 0; virtual std::string Id() = 0;};class User : public UserInterface{public: User(const InetAddr &id) : _id(id) { } virtual void SendTo(int sockfd, const std::string &message) override { LOG(LogLevel::DEBUG) << "send message to " << _id.Addr() << " info:" << message; int n = ::sendto(sockfd, message.c_str(), message.size(), 0, _id.NetAddr(), _id.NetAddrLen()); (void)n; } virtual bool operator==(const InetAddr &u) override { return _id == u; } virtual std::string Id() override { return _id.Addr(); } ~User() { }private: InetAddr _id;};// 对用户消息进行路由// UserManager 把所有用户先管理起来!// 观察者模式 : observerclass UserManager{public: UserManager() { } void AddUser(InetAddr &id) { LockGuard lockguard(_mutex); for (auto &user : _online_user) { if (*user == id) // 父类的指针调用 必须重写 父类必须也重载 继承的话也得用子类的指针访问 { LOG(LogLevel::INFO) << id.Addr() << " 用户已经存在"; return; } } LOG(LogLevel::INFO) << " 新增该用户: " << id.Addr(); _online_user.push_back(std::make_shared<User>(id)); PrintUser(); } void DelUser(InetAddr &id) { auto pos = std::remove_if(_online_user.begin(), _online_user.end(), [&id](std::shared_ptr<UserInterface> &user){ return *user == id; }); _online_user.erase(pos, _online_user.end()); PrintUser(); } // 通知观察者 void Router(int sockfd, const std::string &message) { LockGuard lockguard(_mutex); for(auto &user : _online_user) { user->SendTo(sockfd, message); } } void PrintUser() { for(auto &user : _online_user) { LOG(LogLevel::DEBUG) << "在线用户-> " << user->Id(); } } ~UserManager() { }private: std::list<std::shared_ptr<UserInterface>> _online_user; Mutex _mutex;};
UDP 协议支持全双工, 一个 sockfd, 既可以读取, 又可以写入, 对于客户端和服务端同样如此
多线程客户端, 同时读取和写入
测试的时候, 使用管道进行演示
二:? 观察者模式
观察者模式(发布订阅模式)是一种行为型设计模式,用于定义对象之间的一种一对多的依赖关系,使得一个对象状态发生变化时,所有依赖它的对象都会收到通知并自动更新。
它的目的就是将观察者和被观察者代码解耦,使得一个对象或者说事件的变更,让不同观察者可以有不同的处理,非常灵活,扩展性很强,是事件驱动编程的基础。
? 观察者模式的特点:
松耦合
: 观察者和被观察者之间是松耦合的,便于扩展和维护。动态订阅
: 可以动态添加或移除观察者,灵活性高。单向通信
: 被观察者通知观察者,观察者不能反向修改被观察者的状态。 ? 一般用在什么场景?
事件驱动系统:在用户操作界面中,通过监听事件(如按钮点击)触发响应。系统间通信:系统中某个模块发生变化时,需要通知多个依赖模块。分布式系统:数据更新时通知所有订阅者,例如推送通知、实时数据同步。? 典型场景:
GUI 事件处理系统(如按钮点击、窗口关闭事件)。数据模型与视图同步更新(如MVC架构中的数据绑定)。·股票价格更新通知订阅者。在本篇博客中每一个user就是一个观察者,一旦收到了消息,就通知所有注册过的观察者。
三:? 补充参考内容
? 地址转换函数
本节只介绍基于 IPv4 的 socket 网络编程,sockaddr_in 中的成员 struct in_addr.sin_addr 表示 32 位 的 IP 地址
但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示 和 in_addr 表示之间转换;
in_addr
的函数:in_addr
转字符串的函数:? 其中 inet_pton
和 inet_ntop
不仅可以转换 IPv4 的 in_addr, 还可以转换 IPv6 的 in6_addr, 因此函数接口是 void *addrptr。
? 关于 inet_ntoa
inet_ntoa 这个函数返回了一个 char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存 ip 的结果. 那么是否需要调用者手动释放呢?
man 手册上说, inet_ntoa 函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放.
那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:
? 运行结果如下:
因为 inet_ntoa 把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果.
inet_ntop
, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题; ? 多线程调用 inet_ntoa
代码示例如下:(大家自行去测试)
#include <stdio.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <pthread.h>void* Func1(void* p) {struct sockaddr_in* addr = (struct sockaddr_in*)p;while (1) {char* ptr = inet_ntoa(addr->sin_addr);printf("addr1: %s\n", ptr);}return NULL;} void* Func2(void* p) {struct sockaddr_in* addr = (struct sockaddr_in*)p;while (1) {char* ptr = inet_ntoa(addr->sin_addr);printf("addr2: %s\n", ptr);}return NULL;}int main() {pthread_t tid1 = 0;struct sockaddr_in addr1;struct sockaddr_in addr2;addr1.sin_addr.s_addr = 0;addr2.sin_addr.s_addr = 0xffffffff;pthread_create(&tid1, NULL, Func1, &addr1);pthread_t tid2 = 0;pthread_create(&tid2, NULL, Func2, &addr2);pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;}
四:? 补充网络命令
? Ping 命令
Ping (Packet Internet Groper),因特网包探索器,用于测试网络连接量的程序。Ping 发送一个ICMP;回声请求消息给目的地并报告是否收到所希望的 ICMP echo (ICMP回声应答)。它是用来检查网络是否通畅或者网络连接速度的命令。
? Ping 的原理:? 向指定的网络地址发送一定长度的数据包,按照约定,若指定网络地址存在的话,会返回同样大小的数据包,当然,若在特定时间内没有返回,就是“超时”,会被认为指定的网络地址不存在。
? netstat
✨ netstat
是一个用来查看网络状态的重要工具.
? 语法: netstat [选项]
? 功能: 查看网络状态
✨ 常用选项:
n 拒绝显示别名, 能显示数字的全部转化成数字l 仅列出有在 Listen (监听) 的服務状态p 显示建立相关链接的程序名t (tcp) 仅显示 tcp 相关选项u (udp) 仅显示 udp 相关选项a (all) 显示所有选项, 默认不显示 LISTEN 相关// 每个 1s 执行一次 netstat -nltp$ watch -n 1 netstat -nltp
? pidof
✨ 在查看服务器的进程 id 时非常方便.
? 语法: pidof [进程名]
? 功能: 通过进程名, 查看进程 id
$ ps axj | head -1 && ps ajx | grep tcp_serverPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND2958169 2958285 2958285 2958169 pts/2 2958285 S+ 1002 0:00 ./tcp_server 8888 $ pidof tcp_server2958285
五:? 共勉
以上就是我对 【【Linux】Socket编程-UDP构建自己的C++服务器
的理解,想要完整代码可以私信博主噢!觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~?