1.认识接口
listen


- 监测套接字,让套接字随时等待新链接到来。
- 参数1:被监听的套接字
- 参数2:链接队列长度
accept


- 从套接字中获取链接
- 参数1:绑定监听的套接字
- 参数2,3:输出型参数,获取客户端套接字信息,
- 返回值:返回一个文件描述符,该文件描述符是转属于服务端和该链接的套接字文件,服务端和该客户端通信时使用该返回的文件描述符。
connect


- 客户端建立连接的请求
- 参数2,3:向谁发起连接的请求->服务端的套接字结构体。
- 参数1:客户端自己建立的套接字
- 返回值:0表示成功,-1失败。
- 该函数作用:1.bind本地socket地址;2.向server发起请求。
2.TCP简单回显服务器代码
cpp
#include<iostream>
#include<sys/socket.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
#include<string>
#include"Logger.hpp"
#include"InetAddr.hpp"
using namespace NS_LOG_MODULE;
enum
{
SUCCESS=0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR
};
static const int gbacklog=16;
static const uint16_t gport=8080;
class TcpServer
{
public:
TcpServer(uint16_t port = gport):
_port(gport)
{}
~TcpServer(){}
void InitServer()
{
//TCP:面向字节流
_listensockfd = socket(AF_INET,SOCK_STREAM,0);//TCP
if(_listensockfd<0)
{
LOG(LogLevel::FATAL)<<"create socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG)<<"create socket success: "<<_listensockfd;
//2.填充本地套接字信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
//3.bind
int n=bind(_listensockfd,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
LOG(LogLevel::FATAL)<<"socket bind error";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG)<<"bind success";
//4.tcp是面向连接的,tcp服务器要处于监听状态
n=listen(_listensockfd,gbacklog);
if(n<0)
{
LOG(LogLevel::FATAL)<<"listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::DEBUG)<<"listen success";
}
void serviceIO(int sockfd,InetAddr &address)
{
//Tcp 和udp一样是全双工的
LOG(LogLevel::DEBUG)<<"client info is:"<<address.ToString();
while(true)
{
char inbuffer[1024] = {0};
//面向字节流:read write
//读
ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);
if(n>0)//读取成功
{
inbuffer[n]=0;
LOG(LogLevel::INFO)<<address.ToString()<<" say#"<<inbuffer;
}
else if(n==0)//链接断开
{
//断开连接,就是客户端把文件描述符关闭
LOG(LogLevel::INFO)<<"client quit,address: "<<address.ToString();
break;
}
else
{
LOG(LogLevel::ERROR)<<"client read error,address: "<<address.ToString();
break;
}//n<0读取失败
//写
std::string echo_string = "server echo#" + std::string(inbuffer);
write(sockfd,echo_string.c_str(),echo_string.size());
}
}
void Start()
{
while(true)
{
//tcp不能直接读数据,要先获取链接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int sockfd = accept(_listensockfd,(struct sockaddr*)&clientaddr,&len);
if(sockfd <0)
{
LOG(LogLevel::WARNING)<<"accept error";
continue;
}
LOG(LogLevel::DEBUG)<<"accept success: "<<sockfd;
//6.处理新sockfd
//version 0 单线程
InetAddr clientaddress(clientaddr);
serviceIO(sockfd,clientaddress);
close(sockfd);
}
}
private:
uint16_t _port;
//套接字文件描述符
int _listensockfd;
};
cpp
#include"EchoTcpServer.hpp"
#include<memory>
static void Usage(const std::string &proc)
{
std::cout<<"Usage:\n\t";
std::cout<<proc<<" Local_port"<<std::endl;
}
// ./server_tcp port
int main(int argc,char* argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
ENABLE_CONSOLE_LOG_STRATEGY();
uint16_t server_port = std::stoi(argv[1]);
std::unique_ptr<TcpServer> tsvr =std::make_unique<TcpServer>();
tsvr->InitServer();
tsvr->Start();
return 0;
}
cpp
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<cstring>
#include<cstdlib>
#include<string>
#include<unistd.h>
#include"InetAddr.hpp"
static void Usage(const std::string &proc)
{
std::cout<<"Usage:\n\t";
std::cout<<proc<<"server_ip server_port"<<std::endl;
}
// ./client_tcp server_ip server_port
int main(int argc , char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(1);
}
std::string server_ip =argv[1];
uint16_t server_port = std::stoi(argv[2]);
//如果连接失败,重新连接,使用while
//创建TCP套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
std::cerr<<"socket error" <<std::endl;
exit(2);
}
//不能显示bind,会引起端口号冲突,OS会自动bind。
//OS帮助进行随机端口,防止端口冲突,客户端只需要保证唯一性即可。
//TCP面向连接,发起建立连接
InetAddr serveraddress(server_port,server_ip);
int n=connect(sockfd,(struct sockaddr*)serveraddress.GetNetAddress(),serveraddress.Len());
if(n<0)
{
std::cerr<<"connect to" <<serveraddress.ToString()<<"false";
exit(3);
}
std::cerr<<"connect to" <<serveraddress.ToString()<<"success";
//通信
while(true)
{
std::string line;
std::cout<<"please enter# ";
std::getline(std::cin,line);
write(sockfd,line.c_str(),line.size());
char inbuffer[1024];
ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer));
if(n>0)
{
inbuffer[n]=0;
std::cout<<inbuffer<<std::endl;
}
else if(n==0)
{
std::cout<<"read enf of file"<<std::endl;
break;
}
else
{
std::cerr<<"read error"<<std::endl;
break;
}
}
return 0;
}
cpp
#pragma once
#include <iostream>
#include <pthread.h>
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_lock, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_lock);
}
pthread_mutex_t *Ptr()
{
return &_lock;
}
void Unlock()
{
pthread_mutex_unlock(&_lock);
}
~Mutex()
{
pthread_mutex_destroy(&_lock);
}
private:
pthread_mutex_t _lock;
};
class LockGuard // RAII风格代码
{
public:
LockGuard(Mutex &lock):_lockref(lock)
{
_lockref.Lock();
}
~LockGuard()
{
_lockref.Unlock();
}
private:
Mutex &_lockref;
};
cpp
#ifndef __LOGGER_HPP
#define __LOGGER_HPP
#include <iostream>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <ctime>
#include <sys/time.h>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
#include "Mutex.hpp"
namespace NS_LOG_MODULE
{
enum class LogLevel
{
INFO,
WARNING,
ERROR,
FATAL,
DEBUG
};
std::string LogLevel2Message(LogLevel level)
{
switch (level)
{
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARNING";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
case LogLevel::DEBUG:
return "DEBUG";
default:
return "UNKNOWN";
}
}
// 1. 时间戳 2. 日期+时间
std::string GetCurrentTime()
{
struct timeval current_time;
int n = gettimeofday(¤t_time, nullptr);
(void)n;
// current_time.tv_sec; current_time.tv_usec;
struct tm struct_time;
localtime_r(&(current_time.tv_sec), &struct_time); // r: 可重入函数
char timestr[128];
snprintf(timestr, sizeof(timestr), "%04d-%02d-%02d %02d:%02d:%02d.%ld",
struct_time.tm_year + 1900,
struct_time.tm_mon + 1,
struct_time.tm_mday,
struct_time.tm_hour,
struct_time.tm_min,
struct_time.tm_sec,
current_time.tv_usec);
return timestr;
}
// 输出角度 -- 刷新策略
// 1. 显示器打印
// 2. 文件写入
// 策略模式,策略接口
class LogStrategy
{
public:
virtual ~LogStrategy() = default;
virtual void SyncLog(const std::string &message) = 0;
};
// 控制台日志刷新策略, 日志将来要向显示器打印
class ConsoleStrategy : public LogStrategy
{
public:
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::cerr << message << std::endl; // ??
}
~ConsoleStrategy()
{
}
private:
Mutex _mutex;
};
const std::string defaultpath = "./log";
const std::string defaultfilename = "log.txt";
// 文件策略
class FileLogStrategy : public LogStrategy
{
public:
FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
: _logpath(path),
_logfilename(name)
{
LockGuard lockguard(_mutex);
if (std::filesystem::exists(_logpath))
return;
try
{
std::filesystem::create_directories(_logpath);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
}
void SyncLog(const std::string &message) override
{
{
LockGuard lockguard(_mutex);
if (!_logpath.empty() && _logpath.back() != '/')
{
_logpath += "/";
}
std::string targetlog = _logpath + _logfilename; // "./log/log.txt"
std::ofstream out(targetlog, std::ios::app); // 追加方式写入
if (!out.is_open())
{
std::cerr << "open " << targetlog << "failed" << std::endl;
return;
}
out << message << "\n";
out.close();
}
}
~FileLogStrategy()
{
}
private:
std::string _logpath;
std::string _logfilename;
Mutex _mutex;
};
// 交给大家
// const std::string defaultfilename = "log.info";
// const std::string defaultfilename = "log.warning";
// const std::string defaultfilename = "log.fatal";
// const std::string defaultfilename = "log.error";
// const std::string defaultfilename = "log.debug";
// 文件策略&&分日志等级来进行保存
// class FileLogLevelStrategy : public LogStrategy
// {
// public:
// private:
// };
// 日志类:
// 1. 日志的生成
// 2. 根据不同的策略,进行刷新
class Logger
{
// 日志的生成:
// 构建日志字符串
public:
Logger()
{
UseConsoleStrategy();
}
void UseConsoleStrategy()
{
_strategy = std::make_unique<ConsoleStrategy>();
}
void UseFileStrategy()
{
_strategy = std::make_unique<FileLogStrategy>();
}
// 内部类, 标识一条完整的日志信息
// 一条完整的日志信息 = 做半部分固定部分 + 右半部分不固定部分
// LogMessage RAII风格的方式,进行刷新
class LogMessage
{
public:
LogMessage(LogLevel level, std::string &filename, int line, Logger &logger)
: _level(level),
_curr_time(GetCurrentTime()),
_pid(getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
// 先构建出来左半部分
std::stringstream ss;
ss << "[" << _curr_time << "] "
<< "[" << LogLevel2Message(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "] "
<< " - ";
_loginfo = ss.str();
}
template <typename T>
LogMessage &operator<<(const T &info)
{
std::stringstream ss;
ss << info;
_loginfo += ss.str();
return *this; // 返回当前LogMessage对象,方便下次继续进行<<
}
~LogMessage()
{
if (_logger._strategy)
{
_logger._strategy->SyncLog(_loginfo);
}
}
private:
LogLevel _level;
std::string _curr_time;
pid_t _pid;
std::string _filename;
int _line;
std::string _loginfo; // 一条完整的日志信息
// 一个引用,引用外部的Logger类对象
Logger &_logger; // 方便我们后续进行策略式刷新
};
// 这里已经不是内部类了
// 故意采用拷贝LogMessage
LogMessage operator()(LogLevel level, std::string filename, int line)
{
return LogMessage(level, filename, line, *this);
}
~Logger()
{
}
private:
std::unique_ptr<LogStrategy> _strategy; // 刷新策略
};
// 日志对象,全局使用
Logger logger;
#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy();
#define ENABLE_FILE_LOG_STRATEGY() logger.UseFileStrategy();
#define LOG(level) logger(level, __FILE__, __LINE__)
}
#endif
cpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// 对客户端进行先描述
class InetAddr
{
public:
InetAddr(const struct sockaddr_in &address):_address(address), _len(sizeof(address))
{
_ip = inet_ntoa(_address.sin_addr);
_port = ntohs(_address.sin_port);
}
InetAddr(uint16_t port, const std::string &ip = "0.0.0.0"):_ip(ip), _port(port)
{
bzero(&_address, sizeof(_address));
_address.sin_family = AF_INET;
_address.sin_port = htons(_port); // h->n
_address.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. 字符串ip->4字节IP 2. hton
_len = sizeof(_address);
}
bool operator == (const InetAddr &addr)
{
return (this->_ip == addr._ip) && (this->_port == addr._port);
}
std::string ToString()
{
return "[" + _ip + ":" + std::to_string(_port) + "]";
}
InetAddr()
{}
struct sockaddr_in *GetNetAddress()
{
return &_address;
}
socklen_t Len()
{
return _len;
}
~InetAddr()
{}
private:
// net address
struct sockaddr_in _address;
socklen_t _len;
// host address
std::string _ip;
uint16_t _port;
};

- 当前代码的服务端通信是单线程的,所以只有第一个客户端连接时可以通信,第二个客户端来连接时就会被阻塞。

- 只有第一个连接断开时,服务器的主线程才会与第二个客户端进行连接通信。
3.回显服务器多进程版本
cpp
#include<iostream>
#include<sys/socket.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
#include<string>
#include<sys/wait.h>
#include<signal.h>
#include"Logger.hpp"
#include"InetAddr.hpp"
using namespace NS_LOG_MODULE;
enum
{
SUCCESS=0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR,
FORK_ERR
};
static const int gbacklog=16;
static const uint16_t gport=8080;
class TcpServer
{
public:
TcpServer(uint16_t port = gport):
_port(gport)
{}
~TcpServer(){}
void InitServer()
{
//TCP:面向字节流
_listensockfd = socket(AF_INET,SOCK_STREAM,0);//TCP
if(_listensockfd<0)
{
LOG(LogLevel::FATAL)<<"create socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG)<<"create socket success: "<<_listensockfd;
//2.填充本地套接字信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
//3.bind
int n=bind(_listensockfd,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
LOG(LogLevel::FATAL)<<"socket bind error";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG)<<"bind success";
//4.tcp是面向连接的,tcp服务器要处于监听状态
n=listen(_listensockfd,gbacklog);
if(n<0)
{
LOG(LogLevel::FATAL)<<"listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::DEBUG)<<"listen success";
}
void serviceIO(int sockfd,InetAddr &address)
{
//Tcp 和udp一样是全双工的
LOG(LogLevel::DEBUG)<<"client info is:"<<address.ToString();
while(true)
{
char inbuffer[1024] = {0};
//面向字节流:read write
//读
ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);
if(n>0)//读取成功
{
inbuffer[n]=0;
LOG(LogLevel::INFO)<<address.ToString()<<" say#"<<inbuffer;
}
else if(n==0)//链接断开
{
//断开连接,就是客户端把文件描述符关闭
LOG(LogLevel::INFO)<<"client quit,address: "<<address.ToString();
break;
}
else
{
LOG(LogLevel::ERROR)<<"client read error,address: "<<address.ToString();
break;
}//n<0读取失败
//写
std::string echo_string = "server echo#" + std::string(inbuffer);
write(sockfd,echo_string.c_str(),echo_string.size());
}
}
void Start()
{
//忽略子进程的退出信号,告诉OS,父进程不关心子进程的退出状态,OS会自动清理子进程的资源
//不会产生僵尸进程,最佳实践。
// signal(SIGCHLD,SIG_IGN);
while(true)
{
//tcp不能直接读数据,要先获取链接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int sockfd = accept(_listensockfd,(struct sockaddr*)&clientaddr,&len);
if(sockfd <0)
{
LOG(LogLevel::WARNING)<<"accept error";
continue;
}
LOG(LogLevel::DEBUG)<<"accept success: "<<sockfd;
//6.处理新sockfd
//version 0 单线程
// InetAddr clientaddress(clientaddr);
// serviceIO(sockfd,clientaddress);
//version 1 :多进程版本
//获取新连接时,创建子进程
pid_t id =fork();
if(id<0)
{
LOG(LogLevel::FATAL)<<"fork error!";
exit(FORK_ERR);
}
else if(id == 0)
{
//子进程
//子进程不需要listensockfd,所以子进程要关闭
close(_listensockfd);
//解决方法二
//子进程再创建一个子进程,让孙子进程去执行通信任务,
//子进程直接退出,孙子进程变成孤儿进程,被bash接管,
//退出后自动被操作系统回收
if(fork()>0) exit(0);
InetAddr clientaddress(clientaddr);
serviceIO(sockfd,clientaddress);
close(sockfd);//通信结束关闭套接字文件描述符
exit(0);
//子进程终止后会处于僵尸状态,父进程还需要wait,父进程被阻塞
//逻辑又变成串行的(单进程)。
}
else
{
//父进程
//父进程只负责监听建立建立连接,不需要去做通信工作
//文件描述符是有限的且有用的,是一种资源,不用的资源就要尽快释放
close(sockfd);
//父进程也不能非阻塞等待,如果非阻塞,父进程识别一次,没有子进程退出
//,父进程继续执行到accept函数,如果之后没有新连接,子进程退出后还是会一直僵尸状态
//最佳实践:重定义信号处理,将子进程退出信号忽略
//子进程创建新进程后直接退出,不会等太长时间
pid_t rid =waitpid(id,nullptr,0);
(void)rid;
}
close(sockfd);
}
}
private:
uint16_t _port;
//套接字文件描述符
int _listensockfd;
};

4.回显服务器多线程版本

cpp
#include<iostream>
#include<sys/socket.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
#include<string>
#include<sys/wait.h>
#include<signal.h>
#include<pthread.h>
#include<errno.h>
#include"Logger.hpp"
#include"InetAddr.hpp"
using namespace NS_LOG_MODULE;
enum
{
SUCCESS=0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR,
FORK_ERR
};
static const int gbacklog=16;
static const uint16_t gport=8080;
class TcpServer
{
public:
TcpServer(uint16_t port = gport):
_port(gport)
{}
~TcpServer(){}
void InitServer()
{
//TCP:面向字节流
_listensockfd = socket(AF_INET,SOCK_STREAM,0);//TCP
if(_listensockfd<0)
{
LOG(LogLevel::FATAL)<<"create socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG)<<"create socket success: "<<_listensockfd;
//2.填充本地套接字信息
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_ANY;
//3.bind
int n=bind(_listensockfd,(struct sockaddr*)&local,sizeof(local));
if(n<0)
{
LOG(LogLevel::FATAL)<<"socket bind error";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG)<<"bind success";
//4.tcp是面向连接的,tcp服务器要处于监听状态
n=listen(_listensockfd,gbacklog);
if(n<0)
{
LOG(LogLevel::FATAL)<<"listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::DEBUG)<<"listen success";
}
void serviceIO(int sockfd,InetAddr &address)
{
//Tcp 和udp一样是全双工的
LOG(LogLevel::DEBUG)<<"client info is:"<<address.ToString();
while(true)
{
char inbuffer[1024] = {0};
//面向字节流:read write
//读
ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);
if(n>0)//读取成功
{
inbuffer[n]=0;
LOG(LogLevel::INFO)<<address.ToString()<<" say#"<<inbuffer;
}
else if(n==0)//链接断开
{
//断开连接,就是客户端把文件描述符关闭
LOG(LogLevel::INFO)<<"client quit,address: "<<address.ToString();
break;
}
else
{
LOG(LogLevel::ERROR)<<"client read error,address: "<<address.ToString();
perror("info:");
break;
}//n<0读取失败
//写
std::string echo_string = "server echo#" + std::string(inbuffer);
write(sockfd,echo_string.c_str(),echo_string.size());
}
}
class ThreadData
{
public:
ThreadData(TcpServer* ts,int sockfd,InetAddr addr)
:_this(ts),
_sockfd(sockfd),
_addr(addr)
{
}
~ThreadData()
{}
public:
TcpServer* _this;
int _sockfd;
InetAddr _addr;
};
//线程的回调函数返回值void*,参数void*.
//静态的就不会有this指针,但是没有this指针,没办法调用成员函数
//解决:结构体传参,定义内部类
static void* thread_routine(void* args)
{
ThreadData* td = static_cast<ThreadData*>(args);
pthread_detach(pthread_self());
td->_this->serviceIO(td->_sockfd,td->_addr);
delete td;
return nullptr;
}
void Start()
{
//忽略子进程的退出信号,告诉OS,父进程不关心子进程的退出状态,OS会自动清理子进程的资源
//不会产生僵尸进程,最佳实践。
// signal(SIGCHLD,SIG_IGN);
while(true)
{
//tcp不能直接读数据,要先获取链接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int sockfd = accept(_listensockfd,(struct sockaddr*)&clientaddr,&len);
if(sockfd <0)
{
LOG(LogLevel::WARNING)<<"accept error";
continue;
}
LOG(LogLevel::DEBUG)<<"accept success: "<<sockfd;
//6.处理新sockfd
//version 0 单线程
// InetAddr clientaddress(clientaddr);
// serviceIO(sockfd,clientaddress);
//version 1 :多进程版本
//获取新连接时,创建子进程
// pid_t id =fork();
// if(id<0)
// {
// LOG(LogLevel::FATAL)<<"fork error!";
// exit(FORK_ERR);
// }
// else if(id == 0)
// {
// //子进程
// //子进程不需要listensockfd,所以子进程要关闭
// close(_listensockfd);
// //解决方法二
// //子进程再创建一个子进程,让孙子进程去执行通信任务,
// //子进程直接退出,孙子进程变成孤儿进程,被bash接管,
// //退出后自动被操作系统回收
// if(fork()>0) exit(0);
// InetAddr clientaddress(clientaddr);
// serviceIO(sockfd,clientaddress);
// close(sockfd);//通信结束关闭套接字文件描述符
// exit(0);
// //子进程终止后会处于僵尸状态,父进程还需要wait,父进程被阻塞
// //逻辑又变成串行的(单进程)。
// }
// else
// {
// //父进程
// //父进程只负责监听建立建立连接,不需要去做通信工作
// //文件描述符是有限的且有用的,是一种资源,不用的资源就要尽快释放
// close(sockfd);
// //父进程也不能非阻塞等待,如果非阻塞,父进程识别一次,没有子进程退出
// //,父进程继续执行到accept函数,如果之后没有新连接,子进程退出后还是会一直僵尸状态
// //最佳实践:重定义信号处理,将子进程退出信号忽略
// //子进程创建新进程后直接退出,不会等太长时间
// pid_t rid =waitpid(id,nullptr,0);
// (void)rid;
// }
//多进程成本太高,地址空间,页表都需要拷贝
//version2:多线程版本
pthread_t tid;
InetAddr clientaddress(clientaddr);
ThreadData* td=new ThreadData(this,sockfd,clientaddress);
pthread_create(&tid,nullptr,thread_routine,(void*)td);
//线程把自己设置为分离状态,主线程就不用join了
// close(sockfd);
}
}
private:
uint16_t _port;
//套接字文件描述符
int _listensockfd;
};
4.1短服务
cpp
#include <iostream>
#include <sys/socket.h>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include "Logger.hpp"
#include "InetAddr.hpp"
using namespace NS_LOG_MODULE;
enum
{
SUCCESS = 0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR,
FORK_ERR
};
static const int gbacklog = 16;
static const uint16_t gport = 8080;
class TcpServer
{
public:
TcpServer(uint16_t port = gport) : _port(gport)
{
}
~TcpServer() {}
void InitServer()
{
// TCP:面向字节流
_listensockfd = socket(AF_INET, SOCK_STREAM, 0); // TCP
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "create socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG) << "create socket success: " << _listensockfd;
// 2.填充本地套接字信息
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
// 3.bind
int n = bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
LOG(LogLevel::FATAL) << "socket bind error";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG) << "bind success";
// 4.tcp是面向连接的,tcp服务器要处于监听状态
n = listen(_listensockfd, gbacklog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::DEBUG) << "listen success";
}
void serviceIO(int sockfd, InetAddr &address)
{
// Tcp 和udp一样是全双工的
// 长服务,长连接,小型应用
// LOG(LogLevel::DEBUG)<<"client info is:"<<address.ToString();
// while(true)
// {
// char inbuffer[1024] = {0};
// //面向字节流:read write
// //读
// ssize_t n = read(sockfd,inbuffer,sizeof(inbuffer)-1);
// if(n>0)//读取成功
// {
// inbuffer[n]=0;
// LOG(LogLevel::INFO)<<address.ToString()<<" say#"<<inbuffer;
// }
// else if(n==0)//链接断开
// {
// //断开连接,就是客户端把文件描述符关闭
// LOG(LogLevel::INFO)<<"client quit,address: "<<address.ToString();
// break;
// }
// else
// {
// LOG(LogLevel::ERROR)<<"client read error,address: "<<address.ToString();
// perror("info:");
// break;
// }//n<0读取失败
// //写
// std::string echo_string = "server echo#" + std::string(inbuffer);
// write(sockfd,echo_string.c_str(),echo_string.size());
// }
// 短服务
char inbuffer[1024] = {0};
// 面向字节流:read write
// 读
ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
if (n > 0) // 读取成功
{
inbuffer[n] = 0;
LOG(LogLevel::INFO) << address.ToString() << " say#" << inbuffer;
std::string echo_string = "server echo#" + std::string(inbuffer);
write(sockfd,echo_string.c_str(),echo_string.size());
}
else if (n == 0) // 链接断开
{
// 断开连接,就是客户端把文件描述符关闭
LOG(LogLevel::INFO) << "client quit,address: " << address.ToString();
}
else
{
LOG(LogLevel::ERROR) << "client read error,address: " << address.ToString();
perror("info:");
} // n<0读取失败
}
class ThreadData
{
public:
ThreadData(TcpServer *ts, int sockfd, InetAddr addr)
: _this(ts),
_sockfd(sockfd),
_addr(addr)
{
}
~ThreadData()
{
}
public:
TcpServer *_this;
int _sockfd;
InetAddr _addr;
};
// 线程的回调函数返回值void*,参数void*.
// 静态的就不会有this指针,但是没有this指针,没办法调用成员函数
// 解决:结构体传参,定义内部类
static void *thread_routine(void *args)
{
ThreadData *td = static_cast<ThreadData *>(args);
pthread_detach(pthread_self());
td->_this->serviceIO(td->_sockfd, td->_addr);
close(td->_sockfd);
delete td;
return nullptr;
}
void Start()
{
// 忽略子进程的退出信号,告诉OS,父进程不关心子进程的退出状态,OS会自动清理子进程的资源
// 不会产生僵尸进程,最佳实践。
// signal(SIGCHLD,SIG_IGN);
while (true)
{
// tcp不能直接读数据,要先获取链接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int sockfd = accept(_listensockfd, (struct sockaddr *)&clientaddr, &len);
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
LOG(LogLevel::DEBUG) << "accept success: " << sockfd;
// 6.处理新sockfd
// version 0 单线程
// InetAddr clientaddress(clientaddr);
// serviceIO(sockfd,clientaddress);
// version 1 :多进程版本
// 获取新连接时,创建子进程
// pid_t id =fork();
// if(id<0)
// {
// LOG(LogLevel::FATAL)<<"fork error!";
// exit(FORK_ERR);
// }
// else if(id == 0)
// {
// //子进程
// //子进程不需要listensockfd,所以子进程要关闭
// close(_listensockfd);
// //解决方法二
// //子进程再创建一个子进程,让孙子进程去执行通信任务,
// //子进程直接退出,孙子进程变成孤儿进程,被bash接管,
// //退出后自动被操作系统回收
// if(fork()>0) exit(0);
// InetAddr clientaddress(clientaddr);
// serviceIO(sockfd,clientaddress);
// close(sockfd);//通信结束关闭套接字文件描述符
// exit(0);
// //子进程终止后会处于僵尸状态,父进程还需要wait,父进程被阻塞
// //逻辑又变成串行的(单进程)。
// }
// else
// {
// //父进程
// //父进程只负责监听建立建立连接,不需要去做通信工作
// //文件描述符是有限的且有用的,是一种资源,不用的资源就要尽快释放
// close(sockfd);
// //父进程也不能非阻塞等待,如果非阻塞,父进程识别一次,没有子进程退出
// //,父进程继续执行到accept函数,如果之后没有新连接,子进程退出后还是会一直僵尸状态
// //最佳实践:重定义信号处理,将子进程退出信号忽略
// //子进程创建新进程后直接退出,不会等太长时间
// pid_t rid =waitpid(id,nullptr,0);
// (void)rid;
// }
// 多进程成本太高,地址空间,页表都需要拷贝
// version2:多线程版本
pthread_t tid;
InetAddr clientaddress(clientaddr);
ThreadData *td = new ThreadData(this, sockfd, clientaddress);
pthread_create(&tid, nullptr, thread_routine, (void *)td);
// 线程把自己设置为分离状态,主线程就不用join了
// close(sockfd);
}
}
private:
uint16_t _port;
// 套接字文件描述符
int _listensockfd;
};
4.2接入线程池
cpp
#include <iostream>
#include <sys/socket.h>
#include <cstring>
#include<functional>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <errno.h>
#include"ThreadPool.hpp"
#include "Logger.hpp"
#include "InetAddr.hpp"
using namespace NS_LOG_MODULE;
using namespace NS_THREAD_POOL_MODULE;
using task_t = std::function<void()>;
enum
{
SUCCESS = 0,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
USAGE_ERR,
FORK_ERR
};
static const int gbacklog = 16;
static const uint16_t gport = 8080;
class TcpServer
{
public:
TcpServer(uint16_t port = gport) : _port(gport)
{
}
~TcpServer() {}
void InitServer()
{
// TCP:面向字节流
_listensockfd = socket(AF_INET, SOCK_STREAM, 0); // TCP
if (_listensockfd < 0)
{
LOG(LogLevel::FATAL) << "create socket error";
exit(SOCKET_ERR);
}
LOG(LogLevel::DEBUG) << "create socket success: " << _listensockfd;
// 2.填充本地套接字信息
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
// 3.bind
int n = bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));
if (n < 0)
{
LOG(LogLevel::FATAL) << "socket bind error";
exit(BIND_ERR);
}
LOG(LogLevel::DEBUG) << "bind success";
// 4.tcp是面向连接的,tcp服务器要处于监听状态
n = listen(_listensockfd, gbacklog);
if (n < 0)
{
LOG(LogLevel::FATAL) << "listen error";
exit(LISTEN_ERR);
}
LOG(LogLevel::DEBUG) << "listen success";
}
void serviceIO(int sockfd, InetAddr address)
{
// 短服务
char inbuffer[1024] = {0};
// 面向字节流:read write
// 读
ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
if (n > 0) // 读取成功
{
inbuffer[n] = 0;
LOG(LogLevel::INFO) << address.ToString() << " say#" << inbuffer;
std::string echo_string = "server echo#" + std::string(inbuffer);
write(sockfd,echo_string.c_str(),echo_string.size());
}
else if (n == 0) // 链接断开
{
// 断开连接,就是客户端把文件描述符关闭
LOG(LogLevel::INFO) << "client quit,address: " << address.ToString();
}
else
{
LOG(LogLevel::ERROR) << "client read error,address: " << address.ToString();
perror("info:");
} // n<0读取失败
close(sockfd);
}
class ThreadData
{
public:
ThreadData(TcpServer *ts, int sockfd, InetAddr addr)
: _this(ts),
_sockfd(sockfd),
_addr(addr)
{
}
~ThreadData()
{
}
public:
TcpServer *_this;
int _sockfd;
InetAddr _addr;
};
// 线程的回调函数返回值void*,参数void*.
// 静态的就不会有this指针,但是没有this指针,没办法调用成员函数
// 解决:结构体传参,定义内部类
static void *thread_routine(void *args)
{
ThreadData *td = static_cast<ThreadData *>(args);
pthread_detach(pthread_self());
td->_this->serviceIO(td->_sockfd, td->_addr);
close(td->_sockfd);
delete td;
return nullptr;
}
void Start()
{
// 忽略子进程的退出信号,告诉OS,父进程不关心子进程的退出状态,OS会自动清理子进程的资源
// 不会产生僵尸进程,最佳实践。
// signal(SIGCHLD,SIG_IGN);
while (true)
{
// tcp不能直接读数据,要先获取链接
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int sockfd = accept(_listensockfd, (struct sockaddr *)&clientaddr, &len);
if (sockfd < 0)
{
LOG(LogLevel::WARNING) << "accept error";
continue;
}
LOG(LogLevel::DEBUG) << "accept success: " << sockfd;
//version3:线程池
InetAddr clientaddress(clientaddr);
ThreadPool<task_t>::Instance()->Enqueue([this,sockfd,clientaddress]()->void{
this->serviceIO(sockfd,clientaddress);
});
}
}
private:
uint16_t _port;
// 套接字文件描述符
int _listensockfd;
};
线程池代码位置