进程与端口号
一个进程可对应多个端口号
一个端口号只能有一个进程
为什么client要OS自动bind端口号?
OS确定client端口号可避免不同client重复端口号,且client端口号不追求稳定性。
为什么sever端口号要手动设置?
sever端口号必须稳定,众所周知且不能轻易改变
netstat -naup
查看sever
云服务器禁止用户bind公网ip
虚拟机可以bind任何ip
sever不需要bind具体ip,这样只会限制sever接收信息范围到一个ip上。应用0(INADDR_ANY)表示任意ip
tips
子进程可以继承文件描述符
oldfd > newfd 命令行中重定向
注意,命令行中执行此操作,会打开newfd(清空),用>>避免清空
将两个fd重定向同一个新文件时,为了防止>打开时清空文件,应写为
1>newfile 2>&1
fifo文件必须读写段同时都打开才有效
板书笔记
code
link:code/lesson34/1. EchoServer · whb-helloworld/112 - 码云 - 开源中国
本文只提供CharServer代码, EchoServe与DictServer代码可在上链接中查看
ChatServer
Common.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
};
InetAddr.hpp
#pragma once #include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"class InetAddr
{
private:void PortNet2Host(){_port = ::ntohs(_net_addr.sin_port);}void IpNet2Host(){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){PortNet2Host();IpNet2Host();}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;
};
Log.hpp
#pragma once#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem> //C++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"// namespace LogModule
// {using namespace LockModule;// 获取一下当前系统的时间std::string CurrentTime(){time_t time_stamp = ::time(nullptr);struct tm curr;localtime_r(&time_stamp, &curr); // 时间戳,获取可读性较强的时间信息5char buffer[1024];// bugsnprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",curr.tm_year + 1900,curr.tm_mon + 1,curr.tm_mday,curr.tm_hour,curr.tm_min,curr.tm_sec);return buffer;}// 构成: 1. 构建日志字符串 2. 刷新落盘(screen, file)// 1. 日志文件的默认路径和文件名const std::string defaultlogpath = "./log/";const std::string defaultlogname = "log.txt";// 2. 日志等级enum class LogLevel{DEBUG = 1,INFO,WARNING,ERROR,FATAL};std::string Level2String(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 "None";}}// 3. 刷新策略.class LogStrategy{public:virtual ~LogStrategy() = default;virtual void SyncLog(const std::string &message) = 0;};// 3.1 控制台策略class ConsoleLogStrategy : public LogStrategy{public:ConsoleLogStrategy(){}~ConsoleLogStrategy(){}void SyncLog(const std::string &message){LockGuard lockguard(_lock);std::cout << message << std::endl;}private:Mutex _lock;};// 3.2 文件级(磁盘)策略class FileLogStrategy : public LogStrategy{public:FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname): _logpath(logpath),_logname(logname){// 确认_logpath是存在的.LockGuard lockguard(_lock);if (std::filesystem::exists(_logpath)){return;}try{std::filesystem::create_directories(_logpath);}catch (std::filesystem::filesystem_error &e){std::cerr << e.what() << "\n";}}~FileLogStrategy(){}void SyncLog(const std::string &message){LockGuard lockguard(_lock);std::string log = _logpath + _logname; // ./log/log.txtstd::ofstream out(log, std::ios::app); // 日志写入,一定是追加if (!out.is_open()){return;}out << message << "\n";out.close();}private:std::string _logpath;std::string _logname;// 锁Mutex _lock;};// 日志类: 构建日志字符串, 根据策略,进行刷新class Logger{public:Logger(){// 默认采用ConsoleLogStrategy策略_strategy = std::make_shared<ConsoleLogStrategy>();}void EnableConsoleLog(){_strategy = std::make_shared<ConsoleLogStrategy>();}void EnableFileLog(){_strategy = std::make_shared<FileLogStrategy>();}~Logger() {}// 一条完整的信息: [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)class LogMessage{public:LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger): _currtime(CurrentTime()),_level(level),_pid(::getpid()),_filename(filename),_line(line),_logger(logger){std::stringstream ssbuffer;ssbuffer << "[" << _currtime << "] "<< "[" << Level2String(_level) << "] "<< "[" << _pid << "] "<< "[" << _filename << "] "<< "[" << _line << "] - ";_loginfo = ssbuffer.str();}template <typename 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 _currtime; // 当前日志的时间LogLevel _level; // 日志等级pid_t _pid; // 进程pidstd::string _filename; // 源文件名称int _line; // 日志所在的行号Logger &_logger; // 负责根据不同的策略进行刷新std::string _loginfo; // 一条完整的日志记录};// 就是要拷贝,故意的拷贝LogMessage operator()(LogLevel level, const std::string &filename, int line){return LogMessage(level, filename, line, *this);}private:std::shared_ptr<LogStrategy> _strategy; // 日志刷新的策略方案};Logger logger;#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
// }
UdpClient.hpp
#pragma once
UdpClientMain.cc
#include "UdpClient.hpp"
#include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int 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]);// 创建socketint sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr<<"socker error"<<std::endl;Die(SOCKET_ERR);}// 填充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());while(true){std::cout<<"Please Enter_# ";std::string message;std::getline(std::cin, message);printf("get line success:\n");int n = ::sendto(sockfd, message.c_str(), message.size(), 0, CONV(&server), sizeof(server));printf("sento success\n");// void(n);struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];printf("client waiting recvfrom\n");n = ::recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, CONV(&temp), &len);// recvfrom会更新倒数两个参数, 所以传递指针printf("client recvfrom success\n");if(n > 0){buffer[n] = 0;std::cout<<"server:" <<buffer << std::endl;}else {printf("没有收到server echo\n");}}return 0;
}
UdpServer.hpp
#ifndef __UDP_SERVER_HPP__
#define __UDP_SERVER_HPP__
#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <strings.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 uint16_t gdefaultport = 8080;class UdpServer
{
public:UdpServer(uint16_t port = gdefaultport):_sockfd(gsockfd),_addr(port),_isrunning(false){}void InitSever(){_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd < 0){LOG(LogLevel::FATAL)<<"socker: "<<strerror(errno);Die(SOCKET_ERR);}LOG(LogLevel::INFO)<<"socker success, sockfd is:"<<_sockfd;// 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;// peer中存储的永远是client信息,sever信息已经bind到内核(_addr中也有)socklen_t len = sizeof(peer);printf("server waiting recvfrom\n");ssize_t n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) -1, 0, CONV(&peer), &len);printf("server waiting recvfrom success\n");printf("recvfrom over\n");if(n > 0){InetAddr cli(peer);inbuffer[n] = 0;std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + "#" + inbuffer;LOG(LogLevel::DEBUG)<<clientinfo;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;
};#endif
UdpServerMain.cc
#include "UdpServer.hpp"int main(int argc, char* argv[])
{if(argc != 2){std::cerr<<"Usage:"<<argv[0]<<"lockport"<<std::endl;Die(USAGE_ERR);}uint16_t port = std::stoi(argv[1]);ENABLE_CONSOLE_LOG();std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);svr_uptr->InitServer();svr_uptr->Start();return 0;
}