Socket 编程 UDP
V1 版本 - echo server
简单的回显服务器和客户端代码,本地回环,要求c,s砸啊哎同一台机器,本地通信,client发送的数据不会被推送到网络,而是在OS内部绕一圈交给对应的服务器,一般用于网络代码的测试
Udpclient.cc
#include <iostream>
#include<sys/socket.h>
#include<string>
#include<string.h>
// 网络四剑客
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// ./udpclient server_ip server
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << "ip port" << std::endl;return 1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);//1.创建套接字int sockfd=socket(AF_INET,SOCK_DGRAM,0);if(socket<0){std::cerr<<"创建套接字失败"<<std::endl;return 2;}//2.绑定,操作系统在首次发消息自动绑定,无需用户bind,OS知道IP,端口号采用随机端口号,为了避免用户端口冲突//2.5填写服务端信息struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_port=htons(server_port);server.sin_addr.s_addr=inet_addr(server_ip.c_str());//3.发消息while(true){std::string input;std::cout<<"请输入#";std::getline(std::cin,input);int n = sendto(sockfd,input.c_str(),input.size(),0,(struct sockaddr*)&server,sizeof(server));(void)n;char buffer[1024];struct sockaddr_in peer;socklen_t len=sizeof(peer);int m= recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(m>0){buffer[m]=0;std::cout<<buffer<<std::endl;}}return 0;
}
Udpserver.cc
#include <iostream>
#include "UdpServer.hpp"
#include <memory>
using namespace std;// ./udpserver ip port
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << "ip port" << std::endl;return 1;}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);Enable_Console_Log_Strategy();std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(ip, port);usvr->Init();usvr->Start();return 0;
}
Udpserver.hpp
#pragma once
#include <iostream>
#include "Log.hpp"
#include "Mutex.hpp"
#include <string>
#include <string.h>
// 网络四剑客
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace LogModule;
class UdpServer
{
public:UdpServer(const std::string &ip, uint16_t port, int sockfd = -1): _sockfd(sockfd), _ip(ip), _port(port) {}// 初始化void Init(){// 1.创建套接字 IPV4网络 + 面向数据包 =UDP_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(Loglevel::FATAL) << "创建套接字失败!_sockfd:" << _sockfd;exit(1);}LOG(Loglevel::INIF) << "创建套接字成功!";// 2.绑定socket信息,ip和端口,ip// 2.1填充sockaddr_in结构体struct sockaddr_in local;local.sin_family = AF_INET; // 网络通信local.sin_port = htons(_port); // 转化网络序列local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 设置结构体内的addr ip转成4字节的网络序列//服务端IP和端口不能轻易改变,所以需要显示绑定int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); // 将本地的套接字设置回内核if (n < 0){LOG(Loglevel::FATAL) << "绑定失败";exit(2);}LOG(Loglevel::INIF) << "绑定成功,_sockfd:" << _sockfd;}// 启动服务器void Start(){_isrunning = true;while(_isrunning){char buffer[1024];//缓冲区struct sockaddr_in peer; //基类socklen_t len=sizeof(peer);//recvfrom 返回实际独到的大小,读取失败返回-1//1.不断收消息 创建的sockfd 缓冲区 缓冲区大小 阻塞式IO对方不发消息一直阻塞,如同scanf 传进的sockaddr结构体大小,返回实际独到的结构体大小 ssize_t s= recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(s>0){buffer[s]=0;LOG(Loglevel::DEBUG)<<"buffer:"<<buffer;//2.发消息std::string echp_string="server say@";echp_string+=buffer; // 发给谁 sendto(_sockfd,echp_string.c_str(),echp_string.size(),0,(struct sockaddr*)&peer,len);}}}~UdpServer() {}private:int _sockfd; // 套接字描述符uint16_t _port; // 端口号std::string _ip; // ipbool _isrunning;
};
其他辅助文件
锁
Mutex.hpp
#pragma once
#include <pthread.h>
#include <iostream>
namespace MutexModule
{ class Mutex{public:Mutex(){pthread_mutex_init(&_mutex, nullptr);}void Lock(){int n = pthread_mutex_lock(&_mutex);(void)n;}void Unlock(){int n = pthread_mutex_unlock(&_mutex);(void)n;}~Mutex(){pthread_mutex_destroy(&_mutex);}pthread_mutex_t *get(){return &_mutex;}private:pthread_mutex_t _mutex;};class LockGuard{public:LockGuard(Mutex &mutex):_mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex &_mutex;};
}
日志
Log.hpp
#ifndef __LOG_HPP__
#define __LOG_HPP__#include <iostream>
#include <string>
#include "Mutex.hpp"
#include <filesystem>
#include <fstream>
#include <memory>
#include <unistd.h>
#include <sstream>
#include<ctime>namespace LogModule
{const std::string sep = "\r\n";using namespace MutexModule ;// 2.刷新策略class LogStrategy{public:~LogStrategy() = default;virtual void SyncLog(const std::string &message) = 0;};// 显示器刷新日志的策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy() {}~ConsoleLogStrategy() {}void SyncLog(const std::string &message) override{LockGuard lockguard(_mutex);std::cout << message << sep;}private:Mutex _mutex;};// 缺省文件路径以及文件本身const std::string defaultpath = "./log";const std::string defaultfile = "my.log";// 文件刷新日志的策略class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile): _path(path), _file(file){LockGuard lockguard(_mutex);if (std::filesystem::exists(_path)) // 判断路径是否存在{return;}try{std::filesystem::create_directories(_path);}catch (const std::filesystem::filesystem_error &e){std::cerr << e.what() << '\n';}}void SyncLog(const std::string &message) override{LockGuard lockguard(_mutex);std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;std::ofstream out(filename, std::ios::app); // 追加写入if (!out.is_open()){return;}out << message << sep;out.close();}~FileLogStrategy() {}private:Mutex _mutex;std::string _path; // 日志文件的路径std::string _file; // 要打印的日志文件};// 形成日志等级enum class Loglevel{DEBUG,INIF,WARNING,ERROR,FATAL};std::string Level2Str(Loglevel level){switch (level){case Loglevel::DEBUG:return "DEBUG";case Loglevel::INIF:return "INIF";case Loglevel::WARNING:return "WARNING";case Loglevel::ERROR:return "ERROR";case Loglevel::FATAL:return "FATAL";default:return "UNKNOWN";}}std::string GetTimeStamp(){time_t cuur =time(nullptr);struct tm curr_tm;localtime_r(&cuur,&curr_tm);char buffer[128];snprintf(buffer,sizeof(buffer),"%4d-%02d-%02d %02d:%02d:%02d",curr_tm.tm_year+1900,curr_tm.tm_mon+1,curr_tm.tm_mday,curr_tm.tm_hour,curr_tm.tm_min,curr_tm.tm_sec);return buffer;}class Logger{public:Logger(){EnableConsoleLogStrategy();}// 选择某种策略// 1.文件void EnableFileLogStrategy(){_ffush_strategy = std::make_unique<FileLogStrategy>();}// 显示器void EnableConsoleLogStrategy(){_ffush_strategy = std::make_unique<ConsoleLogStrategy>();}// 表示的是未来的一条日志class LogMessage{public:LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger): _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger){// 合并左半部分std::stringstream ss;ss << "[" << _curr_time << "] "<< "[" << Level2Str(_level) << "] "<< "[" << _pid << "] "<< "[" << _src_name << "] "<< "[" << _line_number << "] "<< "- ";_loginfo = ss.str();}template <typename T>LogMessage &operator<<(const T &info){// 右半部分,可变std::stringstream ss;ss << info;_loginfo += ss.str();return *this;}~LogMessage(){if (_logger._ffush_strategy){_logger._ffush_strategy->SyncLog(_loginfo);}}private:std::string _curr_time; // 日志时间Loglevel _level; // 日志状态pid_t _pid; // 进程pidstd::string _src_name; // 文件名称int _line_number; // 对应的行号std::string _loginfo; // 合并之后的一条完整信息Logger &_logger;};LogMessage operator()(Loglevel level, std::string src_name, int line_number){return LogMessage(level, src_name, line_number, *this);}~Logger() {}private:std::unique_ptr<LogStrategy> _ffush_strategy;};//全局日志对象Logger logger;//使用宏,简化用户操作,获取文件名和行号// __FILE__ 一个宏,替换完成后目标文件的文件名// __LINE__ 一个宏,替换完成后目标文件对应的行号#define LOG(level) logger(level,__FILE__,__LINE__) #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()}#endif
内网IP和本地环回都可以
内网IP和本地地址不能互通此处不做演示
1.bind公网IP --不行 公网IP没有配置在机器里公网IP无法直接bind,云服务器是内部处理过的
2.bind 127.0.0.1 ||bind 内网IP --可以
3.server bind 内网 127.0.0.1访问 --不行
4.server 127.0.0.1 client 内网访问 --不行
如果我们显示的进行绑定,client未来访问的时候,就必须使用server端bind的地址,否则找不到
例如我绑定了127.0.0.1 8080 但是我用内网xxx 8080去访问找不到没有这个程序
服务器手动分配为零,允许接受任何报文,自动分配任意IP地址,否则指定IP只能对应的IP+地址才能访问,
Udpserver.hpp
#pragma once
#include <iostream>
#include "Log.hpp"
#include "Mutex.hpp"
#include <string>
#include <string.h>
// 网络四剑客
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace LogModule;
class UdpServer
{
public:UdpServer(const std::string &ip, uint16_t port, int sockfd = -1): _sockfd(sockfd), _ip(ip), _port(port) {}// 初始化void Init(){// 1.创建套接字 IPV4网络 + 面向数据包 =UDP_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(Loglevel::FATAL) << "创建套接字失败!_sockfd:" << _sockfd;exit(1);}LOG(Loglevel::INIF) << "创建套接字成功!";// 2.绑定socket信息,ip和端口,ip// 2.1填充sockaddr_in结构体struct sockaddr_in local;local.sin_family = AF_INET; // 网络通信local.sin_port = htons(_port); // 转化网络序列//手动分配local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 设置结构体内的addr ip转成4字节的网络序列//自动分配local.sin_addr.s_addr=INADDR_ANY;//服务端IP和端口不能轻易改变,所以需要显示绑定int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); // 将本地的套接字设置回内核if (n < 0){LOG(Loglevel::FATAL) << "绑定失败";exit(2);}LOG(Loglevel::INIF) << "绑定成功,_sockfd:" << _sockfd;}// 启动服务器void Start(){_isrunning = true;while(_isrunning){char buffer[1024];//缓冲区struct sockaddr_in peer; //基类socklen_t len=sizeof(peer);//recvfrom 返回实际独到的大小,读取失败返回-1//1.不断收消息 创建的sockfd 缓冲区 缓冲区大小 阻塞式IO对方不发消息一直阻塞,如同scanf 传进的sockaddr结构体大小,返回实际独到的结构体大小 ssize_t s= recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(s>0){buffer[s]=0;LOG(Loglevel::DEBUG)<<"buffer:"<<buffer;//2.发消息std::string echp_string="server say@";echp_string+=buffer; // 发给谁 sendto(_sockfd,echp_string.c_str(),echp_string.size(),0,(struct sockaddr*)&peer,len);}}}~UdpServer() {}private:int _sockfd; // 套接字描述符uint16_t _port; // 端口号std::string _ip; // ipbool _isrunning;
};
netstat -naup
内网公网,本地都可以随意访问,甚至另一台主机也能访问