进程与端口号
一个进程可对应多个端口号
一个端口号只能有一个进程
为什么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文件必须读写段同时都打开才有效
板书笔记
![](https://i-blog.csdnimg.cn/direct/1ce7f5c0092b468b92811ea16820d82b.png)
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); // 时间戳,获取可读性较强的时间信息5
char buffer[1024];
// bug
snprintf(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.txt
std::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; // 进程pid
std::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]);
// 创建socket
int 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;
}