设计网站怎么收费,冷色调网站,网站分类目录,设计开发计划书一#xff1a;预备工作
我们先把编程当中需要用到且重要的函数给认识一下
创建socket文件描述符函数 #include sys/types.h /* See NOTES */
#include sys/socket.hint socket(int domain, int type, int protocol);domain#xff1a;协议家族预备工作我们先把编程当中需要用到且重要的函数给认识一下创建socket文件描述符函数#include sys/types.h /* See NOTES */ #include sys/socket.h int socket(int domain, int type, int protocol);domain协议家族常用的有AF_INET (IPv4)AF_INET6(IPv6)。在编程当中我们使用IPv4协议。type套接字类型常用的有SOCK_STREAM(面向连接的字节流基于 TCP可靠、按序、双向) SOCK_DGRAM(无连接的数据报基于 UDP不可靠、有边界)由于这节课是UDP编程所以函数当中我们使用SOCK_DGRAM。protocol具体协议:表示套接字使用的具体协议通常设置为0表示根据domain和type自动选择函数绑定端口号函数#include sys/types.h /* See NOTES */ #include sys/socket.h int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockfd套接字描述符由刚刚的socket函数返回。addr:指向通用地址结构体的指针实际需要传入具体协议族的地址结构其核心作用是绑定ip和端口号。addrlen表示通用结构体的大小。补充通用结构struct sockaddr以及常用的地址结构体通用结构体struct sockaddr#include sys/socket.h struct sockaddr { sa_family_t sa_family; // 地址族 (Address Family) char sa_data[14]; // 地址数据 (可变长度实际不足14字节用0填充) };IPv4地址结构struct sockaddr_in { sa_family_t sin_family; // 地址族: AF_INET in_port_t sin_port; // 端口号 (网络字节序) struct in_addr sin_addr; // IP地址 (网络字节序) char sin_zero[8]; // 填充字段 }; struct in_addr { in_addr_t s_addr; // 32位IPv4地址 };Unix域地址本地通信struct sockaddr_un { sa_family_t sun_family; // 地址族: AF_UNIX/AF_LOCAL char sun_path[108]; // 路径名 };在这里我们看到不同的地址结构体类型可以用一个通用结构体struct sockaddr类型来接收是不是觉得比较熟悉这里不就是C当中继承与多态的特性吗struct sockaddr就相当于是基类而其他地址结构体都是struct sockaddr的子类子类可以被父类的指针类型指向也可以以父类类型作为函数参数接收子类的传值。网络进程发消息函数#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发送信息的字节数flags控制发送行为的标志位可以组合使用按位或通常设置为0代表阻塞发送知道数据发送或出错dest_addr用来传递目的网络进程的ip和端口号addrlenstruct sockaddr通用结构体的大小返回值当发送失败时返回-1发送成功时返回发送信息的字节数网络进程收消息函数#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缓冲区的长度单位是字节flags接收标志也可以组合使用通常设置为0代表阻塞接收直到有数据或接收失败。src_addr用来获取发送信息进程的ip和端口号addrlenstruct sockaddr通用结构体的大小返回值当发送失败时返回-1发送成功时返回接受信息的字节数那么接下来就开始正式的编程了。二一个简单的聊天室制作将网络地址进行封装inet.hpp#pragma once #include iostream #includestring #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h class InetAddr { public: InetAddr(struct sockaddr_in addr) : _addr(addr) { _port ntohs(_addr.sin_port); _ip inet_ntoa(_addr.sin_addr); } bool operator(const InetAddr addr) { return addr._ip _ip addr._port _port; } std::string StringAddr() { return _ipstd::to_string(_port); } std::string Ip() { return _ip; } uint16_t Port() { return _port; } const struct sockaddr_in Addr() { return _addr; } private: struct sockaddr_in _addr; uint16_t _port; std::string _ip; };在这里有两个函数较为重要1.ntohs#include arpa/inet.h uint16_t ntohs(uint16_t netshort);这里ntohs函数的作用是将端口号从网络字节序大端转换成主机字节序。2.inet_ntoa#include arpa/inet.h char *inet_ntoa(struct in_addr in);这里inet_ntoa的作用是将网络字节序的IPv4地址转换成点分十进制的字符串方便打印输出例如0x7F000001→127.0.0.1对用户进行管理Route.hpp在这里实现了对聊天室用户的增加删除查找。通过STL中vector进行管理#pragma once #include iostream #include vector #include Log.hpp #include Inet.hpp using namespace LogModle; class Route { bool IsExist(InetAddr peer) { for (auto user : _inline_users) { if (user peer) return true; } return false; } public: Route() { } void RegisterUser(InetAddrpeer) { LOG(LogLevel::DEBUG)注册用户peer.Ip() peer.Port(); _inline_users.emplace_back(peer); } void DeleteUser(InetAddrpeer) { std::vectorInetAddr::iterator it; for(it_inline_users.begin();it!_inline_users.end();it) { if(*itpeer) { _inline_users.erase(it); break; } } } void MessageRoute(int sockfd, const std::string message, InetAddr peer) { if(!IsExist(peer)) { RegisterUser(peer); } std::string send_messagepeer.StringAddr() #message; for(auto user:_inline_users) { sendto(sockfd,send_message.c_str(),send_message.length(),0,(const sockaddr*)user,sizeof(user)); } if(message QUIT) { LOG(LogLevel::INFO)删除了一个用户; DeleteUser(peer); } } private: std::vectorInetAddr _inline_users; };这个文件基本上没什么难度都是正常增加删除的逻辑进行查找也是对vector进行遍历。服务端窗口的实现UdpServer.hpp服务端的作用本质是1.收到其中某一个用户的消息2.将消息发送到全部的在线用户这步我们通过回调函数实现因为要遍历在线用户不在server当中编写这个函数让外部传入这个函数我们再执行。其中func_t声明的函数就是用于转发或数据处理的回调函数。在该文件中还有一个重点那就是server端口的ip通过INADDR_ANY初始化这样初始化的好处是client不论通过哪种ip本地公网server都可以接收到发来的消息不用再在服务端手动传入。#pragma once #include Log.hpp #include Route.hpp #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include string #include strings.h #include functional using namespace LogModle; const static uint16_t defaultport 8888; const static int defaultfd -1; const static int defaultsize 1024; using func_t std::functionvoid(int sockfd, const std::string message, InetAddr peer); class UdpServer { public: UdpServer(func_t fun, uint16_t port defaultport) : //_ip(ip), _fun(fun), _port(port), _sockfd(defaultfd) { } void Init() { _sockfd socket(AF_INET, SOCK_DGRAM, 0); if (_sockfd 0) { LOG(LogLevel::FATAL) socket error; exit(1); } LOG(LogLevel::INFO) socket success; // 绑定套接字和ip struct sockaddr_in local; bzero(local, sizeof(local)); local.sin_port htons(_port); local.sin_family AF_INET; // 1.把点分十进制ip转成四字节ip 2.4字节转换成网络序列 local.sin_addr.s_addr INADDR_ANY; // local.sin_addr.s_addr inet_addr(_ip.c_str()); // 给服务器配置端口号 int n ::bind(_sockfd, (sockaddr *)local, sizeof(local)); if (n ! 0) { LOG(LogLevel::FATAL) bind error; exit(2); } LOG(LogLevel::INFO)sock success sockfd:_sockfd; } void Start() { char buffer[defaultsize]; for (;;) { struct sockaddr_in peer; socklen_t len sizeof(struct sockaddr_in); // 这里是要收消息 ssize_t n recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)peer, len); if (n 0) { InetAddr addr(peer); buffer[n] 0; // 对消息进行特定的处理 _fun(_sockfd, buffer, addr); } } } ~UdpServer() { } private: // std::string _ip;// 点分十进制的ip uint16_t _port; // 端口号 int _sockfd; // UDP套接字 func_t _fun; // 回调函数 };UdpServer.cc注意r-MessageRoute(sockfd,message,addr);就是我们当时在server没有实现的回调函数他这里通过仿函数的形式来对服务端进行初始化。stoi其作用是将字符串转换为整数。#include string int stoi(const std::string str, size_t* pos 0, int base 10); int stoi(const std::wstring str, size_t* pos 0, int base 10);#include UdpServer.hpp #include Route.hpp int main(int argc, char *argv[]) { ENABLE_CONSOLE_LOG_STRATERY; if (argc ! 2) { std::cerr Usage: argv[0] port std::endl; return 1; } uint16_t port std::stoi(argv[1]); // Route route; std::unique_ptrRoute r std::make_uniqueRoute(); std::unique_ptrUdpServer usvr std::make_uniqueUdpServer([r](int sockfd,const std::string message,InetAddraddr) { r-MessageRoute(sockfd,message,addr); },port); usvr-Init(); usvr-Start(); return 0; }用户端窗口实现UdpClient.cc注意客户端不用显示地进行bind让操作系统来分配端口号。其中收消息和发消息通过线程来进行维护后续我就将线程锁日志的代码全部显示出来#include iostream #include sys/types.h #include sys/socket.h #include netinet/in.h #include strings.h #include arpa/inet.h #include memory.h #include unistd.h #include cstdio #include Thread.hpp #include Inet.hpp #include Log.hpp using namespace threadModlue; using namespace LogModle; struct ThreadData { ThreadData(int sockfd, struct sockaddr_in serveraddr) : _sockfd(sockfd), _serveraddr(serveraddr) { } ~ThreadData() {} InetAddr _serveraddr; int _sockfd; }; void RecverRoutine(ThreadData td) { char buffer[1024]; // 这里是收消息函数 while (true) { struct sockaddr_in peer; socklen_t len sizeof(peer); int n recvfrom(td._sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)peer, len); if (n 0) { buffer[n] 0; std::cerr buffer std::endl; } else { break; } } } void SendRoutine(ThreadData td) { while (true) { std::string sendbuffer; std::cout Please Enter #; getline(std::cin, sendbuffer); auto server td._serveraddr; ssize_t n sendto(td._sockfd, sendbuffer.c_str(), sendbuffer.size(), 0, (const sockaddr *)server, sizeof(server)); if (n 0) { std::cout send error std::endl; } } } int main(int argc, char *argv[]) { ENABLE_CONSOLE_LOG_STRATERY; if (argc ! 3) { std::cerr Usage: argv[0] ip port std::endl; return 1; } // 创建socket int sock socket(AF_INET, SOCK_DGRAM, 0); if (sock 0) { std::cerr socket error: strerror(errno) std::endl; return 2; } std::cout socket create success std::endl; // client不需要显示地进行bind让os自己bind用随机的端口号 // 填充struct sockaddr_in server struct sockaddr_in server; socklen_t len sizeof(server); bzero(server, sizeof(server)); std::string ip argv[1]; uint16_t port std::stoi(argv[2]); // server.sin_port port; server.sin_port htons(port); server.sin_addr.s_addr inet_addr(ip.c_str()); server.sin_family AF_INET; ThreadData td(sock, server); std::string recvsername recvser; ThreadThreadData recvser(recvsername, RecverRoutine, td); std::string sendername sender; ThreadThreadData sender(sendername, SendRoutine, td); recvser.Start(); sender.Start(); recvser.join(); sender.join(); close(sock); return 0; }当我们两个端口实现完成后会发现当Client使用公网ip链接Server时Client发出的消息Server收不到这个时候就要查看你的云服务器是否开放了端口。打开云服务器找到安全组。点击添加规则将端口范围给开放就能够进行通信了。当然还有一件细节就是当你通信过后过了一小段时间又不能通信了这是因为udp基于无连接的通信,也就是你发送过一次消息之后,就不会在保持任何联系了,那么从你的公网到你的另外一个机器上,一路要经过路由器,这些路由器会保存你的请求ip 请求端口,和源ip和源端口,到net表,但是这个表有一段时间没有通信的话,他就会给你刷新掉,那么再次去请求的时候,找不到映射关系了,就无法通信了。Log.hpp#pragma once #include iostream #include unistd.h #include sstream #include fstream #include string #include ctime #include chrono #include iomanip #include filesystem //c17 #include Mutex.hpp namespace LogModle { using namespace Mutex; const std::string defaultpath ./log; const std::string defaultname log.txt; enum class LogLevel { DEBUG, INFO, WARNING, ERROR, FATAL }; std::string LevelToString(LogLevel level) { switch (level) { 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 UNKNOWN; } } std::string GetCurTimeAndToString(const std::string formant %Y-%m-%d %H:%M:%S) { auto now std::chrono::system_clock::now(); std::time_t time std::chrono::system_clock::to_time_t(now); std::tm *local_time std::localtime(time); std::stringstream ss; ss std::put_time(local_time, formant.c_str()); return ss.str(); } class LogStrategy { public: virtual ~LogStrategy() default; virtual void SyncLog(const std::string message) 0; }; // 向显示器打印 class ConsoleLogStrategy : public LogStrategy { public: void SyncLog(const std::string message) { LockGuard lock(_mutex); std::cout message std::endl; } private: MyMutex _mutex; }; // 向文件打印 class FileLogStrategy : public LogStrategy { public: FileLogStrategy(const std::string path defaultpath, const std::string name defaultname) : _log_path(path), _log_name(name) { LockGuard lock(_mutex); //???????为什么加锁 访问了临界资源 if (std::filesystem::exists(_log_path)) return; try { std::filesystem::create_directories(_log_path); } catch (const std::exception e) { std::cerr e.what() \n; } } void SyncLog(const std::string message) override { LockGuard lock(_mutex); std::string log _log_path (_log_path.back() / ? : /) _log_name; std::ofstream fout(log.c_str(), std::ios::app); // 追加信息 if(!fout.is_open()) { return ; } fout message std::endl; fout.close(); } private: MyMutex _mutex; std::string _log_path; std::string _log_name; }; class Logger { public: Logger() { UseConsoleLogStrategy(); // 默认使用显示器策略 } ~Logger() { } void UseConsoleLogStrategy() { _Strategy std::make_uniqueConsoleLogStrategy(); } void UseFileLogStrategy() { _Strategy std::make_uniqueFileLogStrategy(); } class LogMassage { public: LogMassage(LogLevel level, std::string name, int line, Logger logger) : _name(name), _level(level), _line(line), _logger(logger) { _cur_time GetCurTimeAndToString(); _pid getpid(); std::stringstream ss; ss [ _cur_time ] [ LevelToString(_level) ] [ _pid ] [ _name ] [ _line ] - ; _info ss.str(); } template typename T LogMassage operator(const T data) { std::stringstream ss; ss data; _info ss.str(); return *this; } ~LogMassage() { if(_logger._Strategy) { _logger._Strategy-SyncLog(_info); } } private: std::string _cur_time; LogLevel _level; // 等级 pid_t _pid; std::string _name; int _line; // 行号 std::string _info; Logger _logger; // 方便刷新日志 }; LogMassage operator()(LogLevel level, std::string name, int line) // 刷新日志 { return LogMassage(level, name, line, *this); } private: std::unique_ptrLogStrategy _Strategy; }; Logger logger; #define LOG(type) logger(type,__FILE__,__LINE__) #define ENABLE_CONSOLE_LOG_STRATERY logger.UseConsoleLogStrategy() #define ENABLE_FILE_LOG_STRATERY logger.UseFileLogStrategy() }Mutex.hpp#pragma once #includemutex #includepthread.h namespace Mutex { class MyMutex { public: MyMutex() { int n pthread_mutex_init(lock,nullptr); } pthread_mutex_t* GetMutexOriginal() { return lock; } void Lock() { int n pthread_mutex_lock(lock); (void)n; } void UnLock() { int n pthread_mutex_unlock(lock); (void)n; } private: pthread_mutex_t lock; }; class LockGuard { public: LockGuard(MyMutex lock) :_lock(lock) { _lock.Lock(); } ~LockGuard() { _lock.UnLock(); } private: MyMutex _lock; }; }Thread.hpp#ifndef _THREAD_H_ #define _THREAD_H_ #include iostream #include pthread.h #include string #include cstdio #include cstring #include functional namespace threadModlue { static uint32_t number 1; template class T class Thread { private: using func_t std::functionvoid(T); void EnableDetach() { std::cout 线程被分离 std::endl; _isdetach true; } void EnanleRunning() { _isrunning true; } static void *Routine(void *args) { ThreadT *self static_castThreadT *(args); if (self-_isdetach) self-Detach(); self-EnanleRunning(); self-_func(self-_data); return args; } public: Thread(const std::string name,func_t func, T data) : _tid(0), _isdetach(false), _isrunning(false), _res(nullptr), _func(func), _data(data), _name(name) { //_name thread- std::to_string(number); } void Detach() { if (_isdetach) return; if (_isrunning) pthread_detach(_tid); EnableDetach(); } bool Start() { int n pthread_create(_tid, nullptr, Routine, this); if (n ! 0) { std::cerr create thread error strerror(n) std::endl; return false; } return true; } bool Stop() { if (_isrunning) { int n pthread_cancel(_tid); if (n ! 0) { std::cerr Stop thread error strerror(n) std::endl; return false; } else { _isrunning false; std::cout _name stop std::endl; } } return true; } bool join() { if (_isdetach) { std::cout 线程已分离join失败 std::endl; return false; } int n pthread_join(_tid, _res); if (n ! 0) { std::cerr join thread error strerror(n) std::endl; return false; } else { std::cout join seccuss! std::endl; } return true; } ~Thread() { } private: pthread_t _tid; std::string _name; bool _isdetach; bool _isrunning; void *_res; func_t _func; T _data; }; } #endif