📟作者主页:慢热的陕西人
🌴专栏链接:Linux
📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言
本博客主要内容实现了一个tcp的网络通信的示例代码,并且详细阐述了从中遇到的一些新概念
我们实现一个例子来感受tcp和udp的区别和相同之处
TCP:
- 创建socket:
socket(AF_INET, SOCK_STREAM, 0)
- 绑定socket:
bind()
- 开始监听:
listen()
- 接受新的连接:
accept()
- 发送和接收数据:
send(), recv()
- 关闭socket:
close()
UDP:
- 创建socket:
socket(AF_INET, SOCK_DGRAM, 0)
- 绑定socket:
bind()
- 发送和接收数据:
sendto(), recvfrom()
- 关闭socket:
close()
我们发现udp相比较tcp减少了两步监听和建立连接两步:这也对应了tcp和udp的特性!
这个版本只是简单的实现整个过程。
我们实现简单案例的源码:
//tcpServer.hpp
#pragma once
#include"err.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ns_server
{
static const uint16_t defaultport = 8081;
static const int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port = defaultport):func_(func), port_(port), quit_(true)
{
}
void Initserver()
{
//1.创建socket文件
listen_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if(listen_socket_ < 0)
{
std::cerr<< "create socket error" << std::endl;
exit(SOCKET_ERR);
}
//2.创建对应的socket结构体,并bind
struct sockaddr_in local;
memset(&local, 0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listen_socket_, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr<< "bind socket error" << std::endl;
exit(BIND_ERR);
}
//3.监听
if(listen(listen_socket_, backlog) < 0)
{
std::cerr<< "listen error" << std::endl;
exit(LISTEN_ERR);
}
}
void start()
{
quit_ = false;
while(true)
{
//4.获取连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
std::cout << "我正在等待连接" << std::endl;
int sock = accept(listen_socket_, (struct sockaddr*)&client, &len);
if(sock < 0)
{
std::cerr << "accept error" << std::endl;
continue;
}
//5.获取新连接成功,开始进行业务处理
std::cout << "获取新连接成功" << sock << "from" << listen_socket_ << std::endl;
service(sock);
}
}
void service(int sock)
{
char buffer[1024];
while(true)
{
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if(s > 0)
{
buffer[s] = 0;
//执行回调函数
std::string res = func_(buffer);
std::cout << res << std::endl;
write(sock, res.c_str(), res.size());
}
else if(s == 0)
{
//表示对应的远端关闭了通信
close(sock);
std::cout << "quit" << std::endl;
break;
}
else // s < 0
{
close(sock);
std::cerr << "read error" << std::endl;
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t port_;
int listen_socket_;
bool quit_; //表示当前服务器是否运行
func_t func_;
};
}
//tcpServer.cc
#include"tcpServer.hpp"
using namespace std;
using namespace ns_server;
static void usage(string proc)
{
std::cout << "Usage:\n\t" << proc <<"port\n"<< std::endl;
}
std::string echo (const std::string& message)
{
return message;
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
usage(argv[1]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr<TcpServer> tsvr(new TcpServer(echo, port));
tsvr->Initserver();
tsvr->start();
return 0;
}
//tcpClient.cc
#include"err.hpp"
#include
#include
#include
#include
#include
#include
#include
using namespace std;
static void Usage(string proc)
{
cout << "Usage:\n\t" << proc << "serverip serverport "<< "port\n" << endl;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
//1.create sock
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
std::cerr << "socket error:" << strerror(errno) << std::endl;
exit(SOCKET_ERR);
}
//2.connect
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_port = htons(serverport);
server.sin_family = AF_INET;
inet_aton(serverip.c_str(), &(server.sin_addr));
int cnt = 5;
while(connect(sock, (struct sockaddr*)& server, sizeof(server)) != 0)
{
sleep(1);
cout << "正在尝试重连, 重连次数还有" << cnt-- << "次" << endl;
if(cnt <= 0) break;
}
if(cnt <= 0)
{
//连接失败
std::cerr << "connect error" << endl;
exit(CONNECT_ERR);
}
char buffer[1024];
while(true)
{
string line;
cout << "Enter>>>";
getline(cin, line);
write(sock, line.c_str(), line.size());
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if(s > 0)
{
buffer[s] = 0;
cout << "srever echo>>>" << buffer << endl;
}
else if(s == 0)
{
cerr << "server quit" << endl;
break;
}
else
{
cerr <<"read error: " << strerror(errno) << endl;
break;
}
}
close(sock);
return 0;
}
实现一个多进程版本,因为我们的上一个版本,连接的时候会阻塞在那里,非常的不好。
我们让子进程帮我们去执行对应service服务:
细节:
//tcpServer.hpp
#pragma once
#include"err.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ns_server
{
// signal(SIGCHLD, SIG_IGN); // 我最推荐的
static const uint16_t defaultport = 8081;
static const int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port = defaultport):func_(func), port_(port), quit_(true)
{
}
void Initserver()
{
//1.创建socket文件
listen_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if(listen_socket_ < 0)
{
std::cerr<< "create socket error" << std::endl;
exit(SOCKET_ERR);
}
//2.创建对应的socket结构体,并bind
struct sockaddr_in local;
memset(&local, 0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listen_socket_, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr<< "bind socket error" << std::endl;
exit(BIND_ERR);
}
//3.监听
if(listen(listen_socket_, backlog) < 0)
{
std::cerr<< "listen error" << std::endl;
exit(LISTEN_ERR);
}
}
void start()
{
quit_ = false;
while(true)
{
//4.获取连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
std::cout << "我正在等待连接" << std::endl;
int sock = accept(listen_socket_, (struct sockaddr*)&client, &len);
if(sock < 0)
{
std::cerr << "accept error" << std::endl;
continue;
}
std::string clientip = inet_ntoa(client.sin_addr);
uint16_t clientport = ntohs(client.sin_port);
//5.获取新连接成功,开始进行业务处理
std::cout << "获取新连接成功" << sock << "from" << listen_socket_ << std::endl;
//v1
//service(sock, clientip, clientport);
//v2:多线程版本
pid_t id = fork();
if(id < 0) //父进程
{
//这里因为子进程继承去了父进程的文件描述符表之后,我们这里的sock就没必要了
//所以我们直接关闭,也可以提高文件描述符的利用率
close(sock);
continue;
}
else if(id == 0) //child, 父进程的fd, 会被child继承吗?会。 父子会用同一张文件描述符表吗?不会,子进程拷贝继承父进程的fd table;
{
//建议关闭掉不需要的fd
close(listen_socket_);
if(fork() > 0) exit(0);
//这里直接在创建一个子进程,也就是说子进程创建了一个进程,然后自己推出掉
//那么这时候,就形成了孤儿进程,那么孤儿进程是会被操作系统领养的,而不是留给父进程去等待了
//或者我们也可以直接忽略等待子进程,signal(SIGCHLD, SIG_IGN); // 我最推荐的
service(sock, clientip, clientport);
exit(0);
}
close(sock);
}
}
void service(int sock, std::string ip, uint16_t port)
{
std::string who = ip + "-" + std::to_string(port);
char buffer[1024];
while(true)
{
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if(s > 0)
{
buffer[s] = 0;
//执行回调函数
std::string res = func_(buffer);
std::cout << who << ">>" << res << std::endl;
write(sock, res.c_str(), res.size());
}
else if(s == 0)
{
//表示对应的远端关闭了通信
close(sock);
std::cout << "quit" << std::endl;
break;
}
else // s < 0
{
close(sock);
std::cerr << "read error" << std::endl;
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t port_;
int listen_socket_;
bool quit_; //表示当前服务器是否运行
func_t func_;
};
}
采用多线程的思路。
#pragma once
#include"err.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace ns_server
{
// signal(SIGCHLD, SIG_IGN); // 我最推荐的
static const uint16_t defaultport = 8081;
static const int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;
class TcpServer;
class ThreadData
{
public:
ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer* ts):
sock(fd),
clientip(ip),
clientport(port),
current(ts)
{}
public:
int sock;
std::string clientip;
uint16_t clientport;
TcpServer* current;
};
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port = defaultport):func_(func), port_(port), quit_(true)
{
}
void Initserver()
{
//1.创建socket文件
listen_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if(listen_socket_ < 0)
{
std::cerr<< "create socket error" << std::endl;
exit(SOCKET_ERR);
}
//2.创建对应的socket结构体,并bind
struct sockaddr_in local;
memset(&local, 0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listen_socket_, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr<< "bind socket error" << std::endl;
exit(BIND_ERR);
}
//3.监听
if(listen(listen_socket_, backlog) < 0)
{
std::cerr<< "listen error" << std::endl;
exit(LISTEN_ERR);
}
}
void start()
{
quit_ = false;
while(true)
{
//4.获取连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
std::cout << "我正在等待连接" << std::endl;
int sock = accept(listen_socket_, (struct sockaddr*)&client, &len);
if(sock < 0)
{
std::cerr << "accept error" << std::endl;
continue;
}
std::string clientip = inet_ntoa(client.sin_addr);
uint16_t clientport = ntohs(client.sin_port);
//5.获取新连接成功,开始进行业务处理
std::cout << "获取新连接成功" << sock << "from" << listen_socket_ << std::endl;
//v3多线程
pthread_t tid;
ThreadData* td = new ThreadData(sock, clientip, clientport, this);
pthread_create(&tid, nullptr, threadRoutine, td);
}
}
static void* threadRoutine(void* args)
{
//分离线程,防止主进程阻塞的情况
pthread_detach(pthread_self());
ThreadData* td = static_cast<ThreadData*>(args);
td->current->service(td->sock, td->clientip, td->clientport);
delete td;
return nullptr;
}
void service(int sock, std::string ip, uint16_t port)
{
std::string who = ip + "-" + std::to_string(port);
char buffer[1024];
while(true)
{
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if(s > 0)
{
buffer[s] = 0;
//执行回调函数
std::string res = func_(buffer);
std::cout << who << ">>" << res << std::endl;
write(sock, res.c_str(), res.size());
}
else if(s == 0)
{
//表示对应的远端关闭了通信
close(sock);
std::cout << "quit" << std::endl;
break;
}
else // s < 0
{
close(sock);
std::cerr << "read error" << std::endl;
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t port_;
int listen_socket_;
bool quit_; //表示当前服务器是否运行
func_t func_;
};
}
使用线程池的思路实现+单例模式的实现
思路:我们引入之前实现的线程池,Task类,Thread类和LockGuard类;
首先我们构造一个Task类的实例对象,传入我们需要的参数(也就是service函数需要的参数);
细节:在传入的过程中我们需要绑定一个参数就是this指针。
之后将Task的实例放入到线程池中,让线程去执行即可。
但是因为我们的service函数内部是死循环,所以我们只有一个有限的线程来提供有限的服务,换句话说就是当我们的进程数等于当前的用户访问量时,那么之后进来的用户就会连接失败或者,服务不会被响应,所以我们可以将每一次的响应变成不是死循环,而是每次相应完成就退出。
执行效果:
代码:
//tcp_server.hpp
#pragma once
using namespace std;
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"Thread.hpp"
#include"LockGuard.hpp"
#include"err.hpp"
#include"Task.hpp"
#include"ThreadPool.hpp"
namespace ns_server
{
// signal(SIGCHLD, SIG_IGN); // 我最推荐的
static const uint16_t defaultport = 8081;
static const int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;
class TcpServer;
class ThreadData
{
public:
ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer* ts):
sock(fd),
clientip(ip),
clientport(port),
current(ts)
{}
public:
int sock;
std::string clientip;
uint16_t clientport;
TcpServer* current;
};
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port = defaultport):func_(func), port_(port), quit_(true)
{
}
void Initserver()
{
//1.创建socket文件
listen_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if(listen_socket_ < 0)
{
std::cerr<< "create socket error" << std::endl;
exit(SOCKET_ERR);
}
//2.创建对应的socket结构体,并bind
struct sockaddr_in local;
memset(&local, 0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listen_socket_, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr<< "bind socket error" << std::endl;
exit(BIND_ERR);
}
//3.监听
if(listen(listen_socket_, backlog) < 0)
{
std::cerr<< "listen error" << std::endl;
exit(LISTEN_ERR);
}
}
void start()
{
quit_ = false;
while(true)
{
//4.获取连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
std::cout << "我正在等待连接" << std::endl;
int sock = accept(listen_socket_, (struct sockaddr*)&client, &len);
if(sock < 0)
{
std::cerr << "accept error" << std::endl;
continue;
}
std::string clientip = inet_ntoa(client.sin_addr);
uint16_t clientport = ntohs(client.sin_port);
//5.获取新连接成功,开始进行业务处理
std::cout << "获取新连接成功" << sock << "from" << listen_socket_ << std::endl;
//v4线程池
Task t(sock, clientip, clientport, std::bind(&TcpServer::service, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3));
ThreadPool<Task>::Getinstance()->TaskPush(t);
}
}
static void* threadRoutine(void* args)
{
//分离线程,防止主进程阻塞的情况
pthread_detach(pthread_self());
ThreadData* td = static_cast<ThreadData*>(args);
td->current->service(td->sock, td->clientip, td->clientport);
delete td;
return nullptr;
}
void service(int sock, std::string ip, uint16_t port)
{
std::string who = ip + "-" + std::to_string(port);
char buffer[1024];
while(true)
{
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if(s > 0)
{
buffer[s] = 0;
//执行回调函数
std::string res = func_(buffer);
std::cout << who << ">>" << res << std::endl;
write(sock, res.c_str(), res.size());
}
else if(s == 0)
{
//表示对应的远端关闭了通信
close(sock);
std::cout << "quit" << std::endl;
break;
}
else // s < 0
{
close(sock);
std::cerr << "read error" << std::endl;
break;
}
}
}
~TcpServer()
{
}
private:
uint16_t port_;
int listen_socket_;
bool quit_; //表示当前服务器是否运行
func_t func_;
};
}
改进:为了服务更多的用户
#pragma once
using namespace std;
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"Thread.hpp"
#include"LockGuard.hpp"
#include"err.hpp"
#include"Task.hpp"
#include"ThreadPool.hpp"
namespace ns_server
{
// signal(SIGCHLD, SIG_IGN); // 我最推荐的
static const uint16_t defaultport = 8081;
static const int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;
class TcpServer;
class ThreadData
{
public:
ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer* ts):
sock(fd),
clientip(ip),
clientport(port),
current(ts)
{}
public:
int sock;
std::string clientip;
uint16_t clientport;
TcpServer* current;
};
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port = defaultport):func_(func), port_(port), quit_(true)
{
}
void Initserver()
{
//1.创建socket文件
listen_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if(listen_socket_ < 0)
{
std::cerr<< "create socket error" << std::endl;
exit(SOCKET_ERR);
}
//2.创建对应的socket结构体,并bind
struct sockaddr_in local;
memset(&local, 0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listen_socket_, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cerr<< "bind socket error" << std::endl;
exit(BIND_ERR);
}
//3.监听
if(listen(listen_socket_, backlog) < 0)
{
std::cerr<< "listen error" << std::endl;
exit(LISTEN_ERR);
}
}
void start()
{
quit_ = false;
while(true)
{
//4.获取连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
std::cout << "我正在等待连接" << std::endl;
int sock = accept(listen_socket_, (struct sockaddr*)&client, &len);
if(sock < 0)
{
std::cerr << "accept error" << std::endl;
continue;
}
std::string clientip = inet_ntoa(client.sin_addr);
uint16_t clientport = ntohs(client.sin_port);
//5.获取新连接成功,开始进行业务处理
std::cout << "获取新连接成功" << sock << "from" << listen_socket_ << std::endl;
//v4线程池
Task t(sock, clientip, clientport, std::bind(&TcpServer::service, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3));
ThreadPool<Task>::Getinstance()->TaskPush(t);
}
}
static void* threadRoutine(void* args)
{
//分离线程,防止主进程阻塞的情况
pthread_detach(pthread_self());
ThreadData* td = static_cast<ThreadData*>(args);
td->current->service(td->sock, td->clientip, td->clientport);
delete td;
return nullptr;
}
void service(int sock, std::string ip, uint16_t port)
{
std::string who = ip + "-" + std::to_string(port);
char buffer[1024];
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
// 执行回调函数
std::string res = func_(buffer);
std::cout << who << ">>" << res << std::endl;
write(sock, res.c_str(), res.size());
}
else if (s == 0)
{
// 表示对应的远端关闭了通信
std::cout << "quit" << std::endl;
}
else // s < 0
{
std::cerr << "read error" << std::endl;
}
close(sock);
}
~TcpServer()
{
}
private:
uint16_t port_;
int listen_socket_;
bool quit_; //表示当前服务器是否运行
func_t func_;
};
}
加入一个简单的日志信息,我们尝试用这个函数去实现输出我们的错误信息或者其他信息。
//log.hpp
#pragma once
#include
#include
#include
#include
#include
enum
{
Debug = 0,
Info,
Warning,
Error,
Fatal,
Uknown
};
std::string LogLevel(int level)
{
switch(level)
{
case Debug:
return "Debug";
case Info:
return "Info";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "Uknown";
}
}
std::string GetTime()
{
time_t curr = time(nullptr);
struct tm* tmp = localtime(&curr);
char buffer[128];
snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon + 1,
tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
return buffer;
}
// 日志格式: 日志等级 时间 pid 消息体
void LogMessage(int level, const char* format, ...)
{
//1.等级
//2.时间
//3.格式化
//4.打印
char Logleft[1024];
std::string level_string = LogLevel(level);
std::string cur_time = GetTime();
snprintf(Logleft, sizeof Logleft, "[%s][%s][%d] ", level_string.c_str(), cur_time.c_str(), getpid());
char Logright[1024];
va_list p;
va_start(p, format);
vsnprintf(Logright, sizeof(Logright), format, p);
va_end(p);
printf("%s%s\n", Logleft, Logright);
实例代码:
//server.hpp
#pragma once
using namespace std;
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"log.hpp"
#include"Thread.hpp"
#include"LockGuard.hpp"
#include"err.hpp"
#include"Task.hpp"
#include"ThreadPool.hpp"
namespace ns_server
{
// signal(SIGCHLD, SIG_IGN); // 我最推荐的
static const uint16_t defaultport = 8081;
static const int backlog = 32;
using func_t = std::function<std::string(const std::string&)>;
class TcpServer;
class ThreadData
{
public:
ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer* ts):
sock(fd),
clientip(ip),
clientport(port),
current(ts)
{}
public:
int sock;
std::string clientip;
uint16_t clientport;
TcpServer* current;
};
class TcpServer
{
public:
TcpServer(func_t func, uint16_t port = defaultport):func_(func), port_(port), quit_(true)
{
}
void Initserver()
{
//1.创建socket文件
listen_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if(listen_socket_ < 0)
{
// std::cerr<< "create socket error" << std::endl;
LogMessage(Error, "create socket error");
exit(SOCKET_ERR);
}
//2.创建对应的socket结构体,并bind
struct sockaddr_in local;
memset(&local, 0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listen_socket_, (struct sockaddr*)&local, sizeof(local)) < 0)
{
//std::cerr<< "bind socket error" << std::endl;
LogMessage(Error, "bind socket error");
exit(BIND_ERR);
}
//3.监听
if(listen(listen_socket_, backlog) < 0)
{
//std::cerr<< "listen error" << std::endl;
LogMessage(Error, "listen error");
exit(LISTEN_ERR);
}
}
void start()
{
quit_ = false;
while(true)
{
//4.获取连接
struct sockaddr_in client;
socklen_t len = sizeof(client);
// std::cout << "我正在等待连接" << std::endl;
LogMessage(Info, "我正在等待连接");
int sock = accept(listen_socket_, (struct sockaddr*)&client, &len);
if(sock < 0)
{
// std::cerr << "accept error" << std::endl;
LogMessage(Error, "accept error");
continue;
}
std::string clientip = inet_ntoa(client.sin_addr);
uint16_t clientport = ntohs(client.sin_port);
//v4线程池
Task t(sock, clientip, clientport, std::bind(&TcpServer::service, this, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3));
ThreadPool<Task>::Getinstance()->TaskPush(t);
}
}
static void* threadRoutine(void* args)
{
//分离线程,防止主进程阻塞的情况
pthread_detach(pthread_self());
ThreadData* td = static_cast<ThreadData*>(args);
td->current->service(td->sock, td->clientip, td->clientport);
delete td;
return nullptr;
}
void service(int sock, std::string ip, uint16_t port)
{
std::string who = ip + "-" + std::to_string(port);
char buffer[1024];
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
// 执行回调函数
std::string res = func_(buffer);
// std::cout << who << ">>" << res << std::endl;
LogMessage(Info, "%s >>> %s", who.c_str(), res.c_str());
write(sock, res.c_str(), res.size());
}
else if (s == 0)
{
// 表示对应的远端关闭了通信
// std::cout << "quit" << std::endl;
LogMessage(Info, "quit");
}
else // s < 0
{
// std::cerr << "read error" << std::endl;
LogMessage(Error, "read error");
}
close(sock);
}
~TcpServer()
{
}
private:
uint16_t port_;
int listen_socket_;
bool quit_; //表示当前服务器是否运行
func_t func_;
};
}
当我们在Linux命令行中输入ps -axj
的时候
[mi@lavm-5wklnbmaja tcp]$ ps -axj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
0 1 1 1 ? -1 Ss 0 74:01 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
0 2 0 0 ? -1 S 0 0:13 [kthreadd]
2 3 0 0 ? -1 S 0 1:08 [ksoftirqd/0]
2 5 0 0 ? -1 S< 0 0:00 [kworker/0:0H]
2 7 0 0 ? -1 S 0 0:38 [migration/0]
那么表头的这些信息都是什么含义呢?
PPID(Parent Process ID): 父进程的进程ID。它指示了启动当前进程的进程的ID。
PID(Process ID): 当前进程的进程ID。它是唯一标识当前正在运行的进程的数字。
PGID(Process Group ID): 进程组的ID。进程组是一个或多个进程的集合,它们共享一个组ID,并且通常由作业控制系统使用。
SID(Session ID): 会话的ID。会话是一组相关进程的集合,通常由一个用户登录到系统时创建。所有进程都属于一个会话,并且每个会话都有一个唯一的会话ID。
TTY(Controlling Terminal): 控制终端的名称。对于与终端相关的进程,此字段将显示其所属的终端设备的名称。
其实它也是一个文件,所以Linux下一切皆文件,我们也可以直接向该文件中写入来实现一些神奇的功能。
TPGID(Foreground Process Group ID): 前台进程组的ID。在前台运行的进程组将接收终端输入。
STAT(Process State): 进程的状态。常见的状态包括:
R
:运行(running)S
:睡眠(sleeping)D
:不可中断的睡眠(uninterruptible sleep)T
:停止(stopped)Z
:僵尸(zombie)<
:高优先级N
:低优先级L
:有些页面被锁入内存UID(User ID): 进程所有者的用户ID。它标识了启动进程的用户。
TIME(CPU Time): 进程占用CPU的总时间。它包括用户态和内核态的时间。
COMMAND(Command): 启动进程的命令行。这是启动进程时使用的完整命令行。
我们再详细了解一下会话ID -> SID
Session ID(会话ID,SID)是一个与Linux系统中会话(session)相关联的唯一标识符。在Linux中,会话是一组相关联的进程的集合,通常由一个用户登录到系统时创建。会话ID被用来标识和管理这些进程组。
- 创建会话: 当用户登录到系统时,通常会创建一个新的会话。这个会话会包含用户的登录Shell以及与之关联的其他进程。会话的ID由内核自动分配。
- 进程组和会话: 在Linux中,进程可以被分组成进程组(process group),而进程组则可以被组合成会话。每个进程组都有一个唯一的ID(PGID),而会话则有一个唯一的会话ID(SID)。
- setsid系统调用:
setsid
是一个系统调用,用于创建一个新的会话并将进程设置为会话的领头进程。当进程调用setsid
时,它会脱离当前的会话,创建一个新的会话,并成为该会话的领头进程(session leader)。- Shell会话: 用户登录到系统时通常会打开一个Shell会话。这个Shell会话将会有一个对应的会话ID(SID),而该会话的所有进程都会属于这个会话。
- 控制终端: 会话可以关联一个控制终端(controlling terminal)。控制终端是用户与系统交互的接口,例如终端或SSH会话。与会话相关的进程通常会在控制终端上接收输入和发送输出。
- 终端控制: 控制终端可以控制会话中的前台进程组(foreground process group),这些进程组将接收终端输入。会话中的其他进程组通常在后台运行。
jobs命令:
jobs
:简单地列出当前shell会话中的所有后台任务。jobs -l
:以长格式列出后台任务,包括任务号(job number)、进程号(PID)、状态以及命令。jobs -p
:仅打印后台任务的进程号(PID)。jobs -r
:仅列出正在运行中的后台任务。jobs -s
:仅列出已停止的后台任务。jobs %n
:列出指定作业号(job number)为n的后台任务。fg %n
:将指定作业号(job number)为n的后台任务调回前台运行。bg %n
:将指定作业号(job number)为n的后台任务在后台继续运行。在这些选项中,
%n
表示作业号,可以通过jobs
命令或在任务启动时由shell分配的作业号来指定。
setsid
不仅是一个命令行工具,还是一个系统调用(system call)。在C语言中,您可以使用setsid
系统接口来执行与命令行setsid
相同的功能。
setsid
系统调用的原型如下:#include
pid_t setsid(void);
- 1
- 2
- 3
这个系统调用会创建一个新的会话(session)并将调用进程设置为该会话的领头进程(session leader)。如果调用进程已经是一个会话的领头进程,
setsid
调用会失败并返回-1。否则,它会返回一个新会话的会话ID。一般情况下,
setsid
系统调用是在创建守护进程**(daemon)**时使用的。当创建守护进程时,为了脱离终端会话的控制,并确保即使终端关闭它也能继续运行,可以使用setsid
来创建一个新的会话。以下是一个简单的示例,演示了如何在C语言中使用
setsid
系统调用来创建一个守护进程:#include
#include #include int main() { pid_t pid = fork(); if (pid < 0) { // fork失败 perror("fork"); exit(EXIT_FAILURE); } if (pid > 0) { // 父进程退出 exit(EXIT_SUCCESS); } // 子进程 umask(0); // 设置文件权限掩码 chdir("/"); // 切换工作目录 close(STDIN_FILENO); // 关闭标准输入 close(STDOUT_FILENO); // 关闭标准输出 close(STDERR_FILENO); // 关闭标准错误输出 // 创建新会话 if (setsid() < 0) { perror("setsid"); exit(EXIT_FAILURE); } // 此时,该进程已经是一个守护进程,可以在后台持续运行 return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
这段代码创建了一个守护进程,通过调用
setsid
来创建一个新会话,并将该进程设置为会话的领头进程。
在Linux环境下实现守护进程(Daemon)的过程通常涉及以下步骤和细节:
- 创建子进程: 首先,通过调用
fork
函数创建一个子进程。父进程退出,而子进程继续执行后续步骤。- 创建新会话: 在子进程中,调用
setsid
函数创建一个新的会话,并使其成为该会话的领头进程。这个步骤将使守护进程摆脱终端的控制,即使终端关闭,守护进程也能继续运行。- 更改工作目录: 为了防止守护进程占用任何挂载的文件系统,通常将工作目录切换到根目录。这可以通过调用
chdir("/")
函数来实现。- 关闭文件描述符: 为了避免守护进程与终端会话关联,并且不受任何终端输入输出的影响,需要关闭标准输入、标准输出和标准错误输出。这可以通过调用
close(STDIN_FILENO)
、close(STDOUT_FILENO)
和close(STDERR_FILENO)
函数来实现。- 重定向标准输入输出: 可选地,您可以将标准输入、标准输出和标准错误输出重定向到/dev/null或其他适当的文件描述符,以防止守护进程产生不必要的输出或错误。例如,可以使用
open
和dup2
函数来实现这一点。- 处理信号: 守护进程通常需要处理一些信号,例如
SIGTERM
(终止信号)和SIGINT
(中断信号),以便在关闭守护进程时进行清理操作。可以使用signal
函数或sigaction
函数来注册信号处理函数。- 记录日志: 守护进程通常会记录其活动和状态,以便进行故障排除和监视。您可以使用系统日志(syslog)或自己的日志文件来记录守护进程的日志。
- 其他细节: 在实现守护进程时,还应考虑一些其他细节,如创建锁文件以防止多个守护进程同时运行,设置适当的文件权限,以及在启动时检查守护进程是否已经在运行。
//daemon.hpp #pragma once #include
#include #include #include #include #include #include #include"log.hpp" void Daedmon() { //1.忽略信号 signal(SIGPIPE, SIG_IGN); signal(SIGCHLD, SIG_IGN); //2.让自己不成为组长 if(fork() > 0) exit(0); //3.新建会话让自己成为新会话的新进程 pid_t ret = setsid(); if(ret < 0) { LogMessage(Error, "setsid error ,error code: %d - %s", errno, strerror(errno)); } //4.(可选)可以更改守护进程的工作进程 //5.处理后续的对于文件描述符012的问题 int fd = open("/dev/null", O_RDWR); if(fd < 0) { LogMessage(Error, "open /dev/null error ,error code: %d - %s", errno, strerror(errno)); } dup2(fd, 0); dup2(fd, 1); dup2(fd, 2); close(fd); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
至此我们已经可以实现我们的server在后台执行,并且当我们关闭xshell会话的时候,也并不会引起服务的关闭,一遍我们随时去访问。
到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正