【Linux】TCP编程
实验:通过TCP通信—在客户端输入要执行的指令,接收执行结果,另服务端接收指令并执行,向客户端发送执行结果
//主函数
#include<iostream>
#include<string>
#include"log.hpp"
#include"command_excute.hpp"
#include"tcp_serve.hpp"void Usage(std::string name)
{std::cout<<"usage:\n\t"<<name <<" local_port\n"<<std::endl;
}int main(int argc,char* argv[]){if(argc !=2){Usage(argv[0]);return 1;}EnableScrean();//开启屏幕日志uint16_t port =std::stoi(argv[1]);//端口号Command cmd("./safe.txt");//创建Command对象func_t excute_t = bind(&Command::excute,&cmd,std::placeholders::_1);//将excute函数绑定到cmd对象,并指定一个占位符std::unique_ptr<tcp_serve> t_ptr = std::make_unique<tcp_serve>(port,excute_t);//创建智能指针指向tcp_serve类,并构造类对象t_ptr->init_serve();t_ptr->loop();return 0;}
//客户端
#include <functional>
#include <iostream>
#include <string.h>
#include <memory>
#include <cstdint>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "InetAddr.hpp"void Usage(std::string name)
{std::cout << "usage:\n\t" << name << " serveip serveport\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string server_ip = argv[1];uint16_t server_prot = std::stoi(argv[2]);// 创建套接字int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;exit(2);}// 设置本地信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_prot);server.sin_addr.s_addr = inet_addr(server_ip.c_str());// 连接套接字sockfdint n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){std::cerr << "connect error" << std::endl;exit(3);}while (true){std::cout << "please enter#:" << std::endl;std::string outstring;std::getline(std::cin, outstring);ssize_t s = send(sockfd, outstring.c_str(), outstring.size(), 0); // 发送信息if (s > 0){// 发送成功char buffer[1024];ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // 接受信息if (n > 0){buffer[n] = 0;std::cout << buffer << std::endl;}else{break;}}else{break;}}close(sockfd);return 0;
}
//服务端
#pragma once
#include <functional>
#include <iostream>
#include <string.h>
#include <memory>
#include <cstdint>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "InetAddr.hpp"
// 错误码
enum
{SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR,USAGE_ERROR
};static const int gbacklog = 16;
static const int defaultsock = -1;
using func_t = std::function<std::string(const std::string &)>; // 回调函数处理server接收的信息class tcp_serve;class thread_data
{
public:thread_data(int fd, InetAddr clientaddr, tcp_serve *s): _sockfd(fd), _clientaddr(clientaddr), _self(s){}public:InetAddr _clientaddr;int _sockfd;tcp_serve *_self;
};
class tcp_serve
{
public:tcp_serve(uint16_t port, func_t func): _port(port), _func(func), _isruning(false), _listensock(defaultsock){}void init_serve(){// 1. 创建流式套接字_listensock = ::socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){LOG(FATAL, "socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG, "socket create seccess ,sockfd is : %d\n", _listensock);// 2. bind绑定本地协议地址,套接字将用于通信的本地 IP 地址和端口号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;int n = ::bind(_listensock, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind error\n");exit(BIND_ERROR);}LOG(DEBUG, "bind success, sockfd is :%d\n", _listensock);n = ::listen(_listensock, gbacklog); // 将一个未连接的套接字转换为一个被动套接字,即监听套接字,它可以接受其他套接字的连接请求if (n < 0){LOG(FATAL, "listen error\n");exit(LISTEN_ERROR);}LOG(DEBUG, "listen success, sockfd is : %d\n", _listensock);}void service(int sockfd, InetAddr client){LOG(DEBUG, "get new link,info : %s: %d:%d\n", client.Ip(), client.Port(), sockfd);std::string clientaddr = "[" + client.Ip() + " : " + std::to_string(client.Port()) + "]#";while (true) // 死循环一直接收信息,处理信息,发送信息{char buffer[1024];ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // 接收来自套接字sockfd中的信息if (n > 0){buffer[n] == 0;std::cout << clientaddr << buffer << std::endl;std::string ret = _func(buffer); // 通过回调函数处理接收的信息send(sockfd, ret.c_str(), ret.size(), 0); // 发送处理接受信息的结果给sockfd}else if (n == 0){LOG(INFO, "%s quit\n", clientaddr.c_str());break;}else{LOG(ERROR, "read error \n", clientaddr.c_str());break;}}::close(sockfd);}static void *handler_sock(void *args){std::cout << "2" << std::endl;// pthread_detach(pthread_self());// thread_data *ptr = static_cast<thread_data *>(args);// pthread_detach(pthread_self());thread_data *td = static_cast<thread_data *>(args);td->_self->service(td->_sockfd, td->_clientaddr);delete td;return nullptr;}void loop(){_isruning = true;while (_isruning){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = ::accept(_listensock, (struct sockaddr *)&peer, &len);// 用于从监听套接字接受一个连接请求// 阻塞等待直到有一个连接请求到达监听套接_listensock// 当连接请求到达时,accept会创建一个新的套接字处理这个连接,并返回新套接字的文件描述符。// 新套接字用于后续与客户端的通信if (sockfd < 0){LOG(WARNING, "accept error\n");continue;}pthread_t t;std::cout << "1" << std::endl;thread_data *td = new thread_data(sockfd, InetAddr(peer), this); // 创建指针td指向thread_data对象pthread_create(&t, nullptr, handler_sock, td); // 创建一个线程执行handler_sock,并将td'作为参数传递}_isruning = false;}private:uint16_t _port;int _listensock;bool _isruning;func_t _func;
};
//执行指令封装
#include <iostream>
#include <set>
#include <cstdio>
#include <fstream>
#include "log.hpp"
const static std::string sign = " ";
class Command
{
private:void load_safe_command(const std::string &set) // 将安全指令集从磁盘中装入内存中{std::ifstream in(set); // 打开我文件setif (!in.is_open()){LOG(FATAL, "open file error");return;}std::string line;while (std::getline(in, line)){LOG(FATAL, "load command [%s] success!\n", line.c_str());_safe_cmd.insert(line);}in.close(); // 关闭文件}public:Command(const std::string cond_path): _cond_path(cond_path){load_safe_command(_cond_path);}std::string get_head(const std::string &cmd) // ls -a -l 获取指令头部{if (cmd.empty())return std::string();auto index = cmd.find(sign);if (index == std::string::npos)return cmd;elsereturn cmd.substr(0, index);}bool check_safe(const std::string &cmd) // 检查指令是否安全{std::string head = get_head(cmd); // 获取指令头部,如:ls -l -a 指令中 lsif (head.empty())return false;auto iter = _safe_cmd.find(head); // 在安全指令集中查找指令头部if (iter != _safe_cmd.end()){return true;}return false;}std::string excute(const std::string &cmd) // 回调函数,cmd :server端接收的指令,{std::string result;if (check_safe(cmd)) // 检查指令是否安全,即是否在安全指令集中{FILE *fp = popen(cmd.c_str(), "r"); // popen()函数执行cmd指令,创建一个管道,通过这个管道可以读取命令的输出,// 执行完后,可以使用fread,fgets从fp指定的管道中读取数据,最后不用这个管道了,用pclose(fp)关闭if (fp == nullptr){return "failed";}char buffer[1024];while (fgets(buffer, sizeof(buffer), fp) != NULL) // 读取执行结果,写入result中{result += buffer;}pclose(fp);}else{result = "坏人!\n";}return result;}private:std::set<std::string> _safe_cmd; // 安全指令集std::string _cond_path; // 安全指令存放的路径
};
//ip port 信息封装
#pragma once
#include <iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
class InetAddr
{
public:InetAddr(const sockaddr_in& addr):_addr(addr){get_address(&_ip,&_port);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}bool operator==(const InetAddr& addr){if(_ip == addr._ip&&_port == addr._port){return true;}elsereturn false;}struct sockaddr_in addr(){return _addr;}~InetAddr(){}
private:void get_address(std::string* ip,uint16_t* port){*ip = inet_ntoa(_addr.sin_addr);*port = ntohs(_addr.sin_port);}struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};
//日志封装
#pragma once
#include <iostream>
#include <stdarg.h>
#include <fstream>
#include<sys/types.h>
#include<unistd.h>
#include "LockGuard.hpp"
const static char *logname = "log.txt";//日志文件
bool g_save = false;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
enum level//日志的等级
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};
void save_file(const std::string &logname, std::string &massage)//保存日志到文件中
{std::ofstream infile("logname", std::ios::app);if (!infile.is_open()){return;}infile << massage << std::endl;infile.close();
}
// 获取日志等级
std::string get_level_string(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 "None";}
}
// 获取时间字符串
std::string get_time_string()
{time_t cur_time = time(nullptr);if (cur_time == (time_t)-1){printf("Failed to get the current time.\n");return "None";}struct tm *formate_time = localtime(&cur_time);if (formate_time == nullptr){return "None";}char buffer[1024];snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d",formate_time->tm_year + 1900,formate_time->tm_mon + 1,formate_time->tm_mday,formate_time->tm_hour,formate_time->tm_min,formate_time->tm_sec);return buffer;
}
// 日志信息
void Log_inf(std::string filename, int line, bool is_save, int level, const char *format, ...)
{std::string levelstr = get_level_string(level);std::string time = get_time_string();pid_t selfid = getpid();char buffer[1024];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);std::string massage = "[" + time + "]" + "[" + levelstr + "]" + "[" + std::to_string(selfid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "]" + buffer;LockGuard lockguard(mutex); // RAIIif (is_save){// 保存到文件中save_file(logname, massage);}else{ //向屏幕中打印std::cout << massage;}
}// 定义宏
#define LOG(level, format, ...) \do \{ \Log_inf(__FILE__, __LINE__, g_save, level, format, ##__VA_ARGS__); \} while (0)
#define Enablefile() \do \{ \g_save = true; \} while (0)
#define EnableScrean() \do \{ \g_save = false; \} while (0)
//互斥锁
#pragma once
#include<iostream>
#include<pthread.h>
class LockGuard //互斥量RAII
{public:LockGuard(pthread_mutex_t& mutex):_mutex(mutex){pthread_mutex_init(&_mutex,nullptr);pthread_mutex_lock(&_mutex);}~LockGuard(){pthread_mutex_unlock(&_mutex);pthread_mutex_destroy(&_mutex);}private:pthread_mutex_t& _mutex;
};
(完)