当前位置: 首页 > news >正文

UDP/TCP --- Socket编程

        本篇将使用 Linux 中的系统调用来实现模拟 TCP 和 UDP 的通信过程,其中只对 UDP 和 TCP 进行了简单的介绍,本篇主要实现的是代码,至于 UDP 和 TCP 的详细讲解将会在之后的文章中给出。

        本篇给出的 tcp 和 udp 的代码中的 echo 都是测试连接是否成功的代码,之后的代码都是在 echo 代码的基础上修改实现了不同功能的代码。目录如下:

目录

网络字节序/Socket编程接口

1. socket 常见 API

2. sockaddr 结构

UDP Socket编程

1. echo server

2. Dict server

3. chat_server

TCP Socket编程

1. echo server

2. command server

网络字节序/Socket编程接口

        在内存中的多字节数据相对于内存地址有着大小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有着大小端之分。但是对于不同的主机有的是小端存储有的是大端存储,然而在网络间通信是从一个主机传输到另一个主机,我们应该怎样区分传送过来的数据是小端数据还是大端数据呢?

        所以 TCP/IP 为了区分这两种存储方式,规定了在网络间传输数据必须都按照大端数据流进行传输(小端机器在传输前需要将数据转换成大端数据流)。因此,网络间先发出的数据是低地址,后发出的数据在高地址。

        转换函数如下:

#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong);
// 32 位主机序列转入网络序列
uint16_t htons(uint16_t hostshort);
// 16 位主机序列转入网络序列
uint32_t ntohl(uint32_t netlong);
// 32 位网络序列转入主机序列
uint16_t ntohs(uint16_t netshort);
// 16 位网络序列转入主机序列以上的转换函数:
h (host)代表主机
n (network)代表网络若主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
若主机是大端字节序,这些函数不做转换,将参数原封不动的返回。

1. socket 常见 API

        一下为 socket 编程常用的接口函数,如下:

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

2. sockaddr 结构

        上文中的 socket API 是一层抽象的网络编程接口,适用于各种底层网络协议,如 IPv4、IPv6,但是我们发现其中大部分接口都有一个结构体:struct sockaddr ,这个结构体其实有分为两个不同的结构体,struct sockaddr_in、struct sockaddr_un,前者用于网络间通信,后者用于主机内通信,如下:

        当我们将 struct sockaddr_in、struct sockaddr_un 这两种类型的数据传入接口中,只需要使用指针强转为 struct sockaddr 即可。

UDP Socket编程

        UDP 协议是在传输层中常用的一种通信协议,其主要特点如下:

        1. 传输层协议;

        2. 无连接

        3. 不可靠传输;

        4. 面向数据报;

        以上的不可靠传输我们并不能将其认定为 UDP 协议的缺点,只将其认定为 UDP 协议的一种的特性,虽然是不可靠的传输,但是 UDP 的效率相对 TCP 很高。

        注:本篇的代码量比较多,其中大部分相同的代码都放在了 echo server 中,之后的代码只会给出一些不同的代码文件。

1. echo server

        在这一小节将写一个 UDP 协议的一个测试代码,主要实现的功能为,我们向服务器发送什么信息,服务器就像我们返回什么信息。

        UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"using namespace log_ns;// 客户端在未来一定要知道服务器的ip port
int main(int argc, char* args[]) {if (argc != 3) {LOG(ERROR, "please input the -> ./client、ip and port\n");return 0;}// 获取 ip 和 port 以及 socketuint16_t server_port = std::stoi(args[2]);std::string server_ip = args[1];int local_socket = socket(AF_INET, SOCK_DGRAM, 0);if (local_socket < 0) {LOG(FATAL, "Create socket fail\n");exit(0);}LOG(INFO, "Create socket success\n");// 对于client的端口号,一般不让用户自己设定,而是让client OS随机选择// client需要bind自己的ip和port,但是client不需要显示的bind自己的ip、port// client在首次向服务器发送数据的时候,OS会自动给client bind上它自己的ip、port// 绑定服务器信息struct sockaddr_in server;memset(&server, 0, sizeof(server));socklen_t len = sizeof(server);server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());// 客户端的ip和port不需要指定,自己会给出// 在这里就可以发送消息了while (true) {std::string info;std::cout << "Please input the info: ";std::getline(std::cin, info);if (info.size() > 0) {// 将消息发送出去int num = sendto(local_socket, info.c_str(), info.size(), 0, (struct sockaddr*)&server, len);if (num > 0) {// 收消息struct sockaddr_in temp;char buff[1024];int recvlen = recvfrom(local_socket, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&temp, &len);if (recvlen > 0) {buff[recvlen] = 0;std::cout << buff << std::endl;} else {break;}}} else {break;}}close(local_socket);return 0;
}

        UdpServer.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"// 在这里也可以设计为 main(argc, args)多参数,指定ip和port
int main() {// 将日志内容打印到屏幕上EnableToScreen();// 服务器ip一般指定为0,服务器可以收到来自任意ip的信息(只要求端口对应)UdpServer* usvr = new UdpServer("127.0.0.1", 8899);usvr->Init();usvr->Start();return 0;
}

        UdpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"using namespace log_ns;enum {SOCKET_ERROR = 1,BIND_ERROR
};const int gsockfd = -1;
const uint16_t glocalport = 8888;// 网络中的很多东西不建议直接进行拷贝,所以设计我们的类的时候
// 将其设置为不可拷贝的类
// 一般服务器主要用来进行网络数据读取和写入、IO的
// 我们可以将服务器的IO逻辑和业务逻辑解耦
class UdpServer : public nocopy {
private:std::string ServerEcho(struct sockaddr_in& peer) {// 获取发送方的ip portInetAdrr addr(peer);std::string echo("[");echo += addr.Ip();echo += " ";echo += std::to_string(addr.Port());echo += "]> ";return echo;}public:// 构造函数传入ip和portUdpServer(const std::string& ip, uint16_t port = glocalport) : _sockfd(gsockfd),_localip(ip),_localport(port),_isrunning(false){}void Init() {// 先创建 sockfd 文件,使用如下接口的最后一个参数设置为0,// 会自动推测是哪个协议_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) {LOG(FATAL, "Create sockfd fail\n");exit(SOCKET_ERROR);}LOG(INFO, "Create Socket success\n");// 绑定我们的信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;// 将ip地址设置为0,可以进行任意ip绑定  INADDR_ANY值为0local.sin_addr.s_addr = INADDR_ANY; // 将指定的ip转化为网络序列// local.sin_addr.s_addr = inet_addr(_localip.c_str());local.sin_port = htons(_localport);socklen_t len = sizeof(local);int n = ::bind(_sockfd, (struct sockaddr*)&local, len);if (n < 0) {LOG(FATAL, "Bind socket fail, %s\n", strerror(errno));exit(BIND_ERROR);}LOG(INFO, "Bind Socket success\n");}void Start() {_isrunning = true;// 需要收消息while (_isrunning) {struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);// peer.sin_family = AF_INET;// peer.sin_addr.s_addrchar buff[1024];int n = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&peer, &len);if (n > 0) {buff[n] = 0;std::string echo = ServerEcho(peer);echo += buff;sendto(_sockfd, echo.c_str(), echo.size(), 0, (struct sockaddr*)&peer, len);}}_isrunning = false;}~UdpServer() {if (_sockfd > gsockfd)::close(_sockfd);}
private:int _sockfd;// 对于这个ip变量,也可以直接不要这个参数,直接让ip设置为0,可以接收来自任意ip的信息std::string _localip; uint16_t _localport;bool _isrunning;
};

        nocopy.hpp

#pragma once
#include <iostream>class nocopy {
public:nocopy() {}~nocopy() {}nocopy(const nocopy&) = delete;const nocopy& operator=(const nocopy&) = delete;
};

        Log.hpp -> 日志Log程序(C++)-CSDN博客

#pragma once
#include <iostream>
#include <string>
#include <cstdarg>
#include <cstring>
#include <fstream>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>namespace log_ns {enum { DEBUG = 1, INFO, WARNING, ERROR, FATAL };// 定义日子真正需要记录的信息struct LogMessage {std::string _level;int _id;std::string _filename;int _filenumber;std::string _curtime;std::string _log_message;};#define SCREEN_TYPE 1#define FILE_TYPE   2const std::string defaultlogfile = "./log.txt";pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER;class Log {private:std::string LevelToString(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 "UNKNOWN";}}std::string CurTime() {// 获取当前的时间戳time_t curtime = time(nullptr);// 将当前时间戳转换成结构体struct tm* now = localtime(&curtime);char buff[128];snprintf(buff, sizeof(buff), "%d-%02d-%02d %02d:%02d:%02d", now->tm_year + 1900,now->tm_mon + 1,now->tm_mday,now->tm_hour,now->tm_min,now->tm_sec);return buff;}void Flush(const LogMessage& lg) {// 打印日志的时候可能存在线程安全,使用锁lock住pthread_mutex_lock(&log_lock);switch(_type) {case SCREEN_TYPE:FlushToScreen(lg);break;case FILE_TYPE:FlushToFile(lg);break;}pthread_mutex_unlock(&log_lock);}void FlushToFile(const LogMessage& lg) {std::ofstream out;out.open(_logfile, std::ios::app); // 文件的操作使用追加if (!out.is_open()) return;char buff[2024];snprintf(buff ,sizeof(buff), "[%s][%d][%s][%d][%s] %s",lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._curtime.c_str(),lg._log_message.c_str());            out.write(buff, strlen(buff));out.close();}void FlushToScreen(const LogMessage& lg) {printf("[%s][%d][%s][%d][%s] %s",lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._curtime.c_str(),lg._log_message.c_str());}public:Log(std::string logfile = defaultlogfile): _type(SCREEN_TYPE),_logfile(logfile){}void Enable(int type) {_type = type;}void LoadMessage(std::string filename, int filenumber, int level, const char* format, ...) {LogMessage lg;lg._level = LevelToString(level);lg._filename = filename;lg._filenumber = filenumber;// 获取当前时间lg._curtime = CurTime();// std::cout << lg._curtime << std::endl;lg._id = getpid();// 获取可变参数va_list ap;va_start(ap, format);char buff[2048];vsnprintf(buff, sizeof(buff), format, ap);va_end(ap);lg._log_message = buff;// std::cout << lg._log_message;Flush(lg);}void ClearOurFile() {std::ofstream out;out.open(_logfile);out.close();}~Log() {}private:int _type;std::string _logfile;};Log lg;// LOG 宏
#define LOG(level, format, ...)                                           \do                                                                    \{                                                                     \lg.LoadMessage(__FILE__, __LINE__, level, format, ##__VA_ARGS__); \} while (0)#define EnableToScreen()        \do                          \{                           \lg.Enable(SCREEN_TYPE); \} while (0)#define EnableToFile()        \do                        \{                         \lg.Enable(FILE_TYPE); \} while (0)// 清理文件
#define ClearFile()        \do                     \{                      \lg.ClearOurFile(); \} while (0)
}

        InetAddr.hpp

#pragma once
#include <iostream>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>class InetAdrr {void ToHost(const struct sockaddr_in& addr) {// inet_ntoa 函数不是线程安全的函数,推荐使用 inet_ntop 函数// _ip = inet_ntoa(addr.sin_addr);char ip_buff[32];// 该函数是网络序列转主机序列 :network to processinet_ntop(AF_INET, &addr.sin_addr, ip_buff, sizeof(ip_buff));// 若想要将主机序列转换成网络序列使用函数 :// inet_pton(AF_INET, _ip.c_str(), (void*)&addr.sin_addr.s_addr); _ip = ip_buff;_port = ntohs(addr.sin_port);}
public:InetAdrr(const struct sockaddr_in& addr) : _addr(addr){ToHost(_addr);}std::string Ip() const {return _ip;}bool operator==(const InetAdrr& addr) {return (_port == addr._port && _ip == addr._ip);}struct sockaddr_in Addr() const {return _addr;}std::string AddrString() const {return _ip + ":" + std::to_string(_port);}uint16_t Port() const {return _port;}~InetAdrr() {}
private:uint16_t _port;std::string _ip;struct sockaddr_in _addr;
};

        makefile

.PHONY:all
all:server clientserver:UdpServer.ccg++ -o $@ $^ -std=c++11
client:UdpClient.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f server client

        测试结果:

2. Dict server

        这里实现的是一个字典翻译程序,连接上服务器后只需要输入想要翻译的单词,就可以翻译出来,不过单词库需要我们提前填充

        UdpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"using namespace log_ns;enum {SOCKET_ERROR = 1,BIND_ERROR
};using func_t = std::function<std::string(const std::string&)>;const int gsockfd = -1;
const uint16_t glocalport = 8888;// 网络中的很多东西不建议直接进行拷贝,所以设计我们的类的时候
// 将其设置为不可拷贝的类
// 一般服务器主要用来进行网络数据读取和写入、IO的
// 我们可以将服务器的IO逻辑和业务逻辑解耦
class UdpServer : public nocopy {
private:std::string ServerEcho(struct sockaddr_in& peer) {// 获取发送方的ip portInetAdrr addr(peer);std::string echo("[");echo += addr.Ip();echo += " ";echo += std::to_string(addr.Port());echo += "]> ";return echo;}public:// 构造函数传入ip和portUdpServer(func_t func, const std::string& ip, uint16_t port = glocalport) : _func(func), _sockfd(gsockfd),_localip(ip),_localport(port),_isrunning(false){}void Init() {// 先创建 sockfd 文件,使用如下接口的最后一个参数设置为0,// 会自动推测是哪个协议_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) {LOG(FATAL, "Create sockfd fail\n");exit(SOCKET_ERROR);}LOG(INFO, "Create Socket success\n");// 绑定我们的信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY; // 将ip地址设置为0,可以进行任意ip绑定// local.sin_addr.s_addr = inet_addr(_localip.c_str());local.sin_port = htons(_localport);socklen_t len = sizeof(local);int n = ::bind(_sockfd, (struct sockaddr*)&local, len);if (n < 0) {LOG(FATAL, "Bind socket fail, %s\n", strerror(errno));exit(BIND_ERROR);}LOG(INFO, "Bind Socket success\n");}void Start() {_isrunning = true;// 需要收消息while (_isrunning) {struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);// peer.sin_family = AF_INET;// peer.sin_addr.s_addrchar buff[1024];int n = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&peer, &len);if (n > 0) {buff[n] = 0;std::string echo = ServerEcho(peer);echo += _func(buff);sendto(_sockfd, echo.c_str(), echo.size(), 0, (struct sockaddr*)&peer, len);} }_isrunning = false;}~UdpServer() {if (_sockfd > 0)::close(_sockfd);}
private:int _sockfd;std::string _localip;uint16_t _localport;bool _isrunning;func_t _func;
};

        UdpServer.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"
#include "Dict.hpp"const std::string dict_path = "./dict.txt";int main() {EnableToScreen();// 服务器ip一般指定为0,服务器可以收到来自任意ip的信息(只要求端口对应)Dict dict(dict_path);func_t translate = std::bind(&Dict::Translate, &dict, std::placeholders::_1);UdpServer* usvr = new UdpServer(translate, "127.0.0.1", 8899);usvr->Init();usvr->Start();return 0;
}

        UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"using namespace log_ns;// 客户端在未来一定要知道服务器的ip port
int main(int argc, char* args[]) {if (argc != 3) {LOG(ERROR, "please input the -> ./client、ip and port\n");return 0;}// 获取 ip 和 port 以及 socketuint16_t server_port = std::stoi(args[2]);std::string server_ip = args[1];int local_socket = socket(AF_INET, SOCK_DGRAM, 0);if (local_socket < 0) {LOG(FATAL, "Create socket fail\n");exit(0);}LOG(INFO, "Create socket success\n");// 绑定服务器信息struct sockaddr_in server;memset(&server, 0, sizeof(server));socklen_t len = sizeof(server);server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());// 客户端的ip和port不需要指定,自己会给出// 在这里就可以发送消息了while (true) {std::string info;std::cout << "Please input the info: ";std::getline(std::cin, info);if (info.size() > 0) {// 将消息发送出去int num = sendto(local_socket, info.c_str(), info.size(), 0, (struct sockaddr*)&server, len);if (num > 0) {// 收消息struct sockaddr_in temp;char buff[1024];int recvlen = recvfrom(local_socket, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&temp, &len);if (recvlen > 0) {buff[recvlen] = 0;std::cout << buff << std::endl;}}} else {break;}}close(local_socket);return 0;
}

        Dict.hpp

#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
#include "Log.hpp"using namespace log_ns;const std::string sep = ": ";class Dict {void LoadDict(const std::string& path) {std::ifstream in(path);if (!in.is_open()) {LOG(FATAL, "load the dictionary failed\n");exit(0);}LOG(DEBUG, "open the dictionary success\n");// 将数据加载到map中std::string info;while (std::getline(in, info)) {if (info.empty()) continue;size_t pos = info.find(sep);if (pos == std::string::npos) continue;// 现在将获取出来的字符串分隔开std::string key = info.substr(0, pos);std::string value = info.substr(pos + sep.size());if (key.empty() || value.empty()) continue;// 走到这里就是正常可以加载的数据LOG(DEBUG, "load the info: %s\n", info.c_str());_dict.insert(std::make_pair(key, value));}LOG(DEBUG, "load the dictionary success\n");in.close();}
public:Dict(const std::string& path) : _dict_path(path){LoadDict(_dict_path);}std::string Translate(const std::string& word) {if (word.empty() || !_dict.count(word))return "None";return _dict[word];}~Dict() {}
private:std::string _dict_path;std::unordered_map<std::string, std::string> _dict;
};

        dict.txt 这个文件可以自己填充

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

        测试结果:

3. chat_server

        接下来的代码为一个聊天室的代码,只需要连接到我们的服务器端,就可以发送消息了,只要连接该服务器的客户端都可以收到来自其他客服端发送的消息。(使用多线程的单例模式实现的)

        UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include "Thread.hpp"using namespace log_ns;int GetSockfd() {int local_socket = socket(AF_INET, SOCK_DGRAM, 0);if (local_socket < 0) {LOG(FATAL, "Create socket fail\n");exit(0);}LOG(INFO, "Create socket success\n");return local_socket;
}void SendInfo(int local_socket, uint16_t server_port, const std::string& server_ip, const std::string& name) {struct sockaddr_in server;memset(&server, 0, sizeof(server));socklen_t len = sizeof(server);server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());while (true) {std::string info;std::cout << "Please input the info: ";std::getline(std::cin, info);if (info.size() > 0)// 将消息发送出去int num = sendto(local_socket, info.c_str(), info.size(), 0, (struct sockaddr *)&server, len);elsebreak;}
}void ReceiveInfo(int local_socket, const std::string& name) {while (true) {struct sockaddr_in temp;socklen_t len = sizeof(temp);char buff[1024];int recvlen = recvfrom(local_socket, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&temp, &len);// 加锁的话还需要使用条件变量if (recvlen > 0){buff[recvlen] = 0;std::cout << buff << std::endl;}}
}// 客户端在未来一定要知道服务器的ip port
int main(int argc, char* args[]) {if (argc != 3) {LOG(ERROR, "please input the -> ./client、ip and port\n");return 0;}// 获取 ip 和 port 以及 socketuint16_t server_port = std::stoi(args[2]);std::string server_ip = args[1];int local_socket = GetSockfd();// 现在需要将发送消息和接收消息的函数绑定func_t send_func = std::bind(&SendInfo, local_socket, server_port, server_ip, std::placeholders::_1);func_t recev_func = std::bind(&ReceiveInfo, local_socket, std::placeholders::_1);Thread receive_thread(recev_func, "client receive");Thread send_thread(send_func, "client send");// 绑定服务器信息receive_thread.Start();send_thread.Start();receive_thread.Join();send_thread.Join();close(local_socket);return 0;
}

        UdpServer.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"
#include "Route.hpp"int main() {EnableToScreen();// 服务器ip一般指定为0,服务器可以收到来自任意ip的信息(只要求端口对应)Route route_message;server_task_t forword_message = std::bind(&Route::ForwordMessage, &route_message, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);UdpServer* usvr = new UdpServer(forword_message, "127.0.0.1", 8899);usvr->Init();usvr->Start();return 0;
}

        UdpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"using namespace log_ns;enum {SOCKET_ERROR = 1,BIND_ERROR
};const int gsockfd = -1;
const uint16_t glocalport = 8888;using server_task_t = std::function<void(int, const std::string&, InetAdrr&)>;// 网络中的很多东西不建议直接进行拷贝,所以设计我们的类的时候
// 将其设置为不可拷贝的类
// 一般服务器主要用来进行网络数据读取和写入、IO的
// 我们可以将服务器的IO逻辑和业务逻辑解耦
class UdpServer : public nocopy {
private:std::string ServerEcho(struct sockaddr_in& peer) {// 获取发送方的ip portInetAdrr addr(peer);std::string echo("[");echo += addr.Ip();echo += " ";echo += std::to_string(addr.Port());echo += "]> ";return echo;}public:// 构造函数传入ip和portUdpServer(server_task_t task, const std::string& ip, uint16_t port = glocalport) : _task(task),_sockfd(gsockfd),_localip(ip),_localport(port),_isrunning(false){}void Init() {// 先创建 sockfd 文件,使用如下接口的最后一个参数设置为0,// 会自动推测是哪个协议_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) {LOG(FATAL, "Create sockfd fail\n");exit(SOCKET_ERROR);}LOG(INFO, "Create Socket success\n");// 绑定我们的信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY; // 将ip地址设置为0,可以进行任意ip绑定// local.sin_addr.s_addr = inet_addr(_localip.c_str());local.sin_port = htons(_localport);socklen_t len = sizeof(local);int n = ::bind(_sockfd, (struct sockaddr*)&local, len);if (n < 0) {LOG(FATAL, "Bind socket fail, %s\n", strerror(errno));exit(BIND_ERROR);}LOG(INFO, "Bind Socket success\n");}void Start() {_isrunning = true;// 需要收消息while (_isrunning) {struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);// peer.sin_family = AF_INET;// peer.sin_addr.s_addrchar buff[1024];int n = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&peer, &len);if (n > 0) {InetAdrr addr(peer);buff[n] = 0;std::string messeage = buff;// 将消息转发出去LOG(INFO, "begin to forword\n");_task(_sockfd, messeage, addr);LOG(INFO, "return server\n");}}_isrunning = false;}~UdpServer() {if (_sockfd > 0)::close(_sockfd);}
private:int _sockfd;std::string _localip;uint16_t _localport;bool _isrunning;server_task_t _task;
};

        ThreadPool.hpp

#pragma once
#include <iostream>
#include <queue>
#include <vector>
#include <string>
#include <pthread.h>
#include "Thread.hpp"const int default_thread_num = 5;using namespace log_ns;template <typename T> 
class ThreadPool {
private:void LockQueue() {pthread_mutex_lock(&_mutex);}void UnLockQueue() {pthread_mutex_unlock(&_mutex);}void WakeUpThread() {pthread_cond_signal(&_cond);}void SleepThread() {pthread_cond_wait(&_cond, &_mutex);}bool IsEmptyQueue() {return _task_queue.empty();}void HandlerTask(std::string name) {while (true) {LockQueue();while (IsEmptyQueue() && _isrunning) {// 只有当队列为空以及在运行的状态才会继续向下运行LOG(DEBUG, "%s sleep\n", name.c_str());_sleep_thread_num++;SleepThread();_sleep_thread_num--;LOG(DEBUG, "%s wakeup\n", name.c_str());}// 当队列为空且不运行时自动退出if (IsEmptyQueue() && !_isrunning) {// std::cout << name << " quit..." << std::endl;LOG(DEBUG, "%s quit...\n", name.c_str());UnLockQueue();break;}// 运行到这个位置任务队列中一定有元素,且愿意运行下去T t = _task_queue.front();_task_queue.pop();t();// t 执行任务// std::cout << name << " -> " << t.result() << std::endl;// LOG(DEBUG, "%s -> %s\n", name.c_str(), t.result().c_str());UnLockQueue();}}ThreadPool(int threadnum = default_thread_num): _thread_num(default_thread_num),_sleep_thread_num(0),_isrunning(false){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}ThreadPool(const ThreadPool<T>& tp) = delete;ThreadPool& operator=(const ThreadPool<T>& tp) = delete;void Init() {// 将线程池内中的handler任务绑定this,让其可以传入线程中运行func_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);for (int i = 0; i < _thread_num; i++) {std::string name = "thread-" + std::to_string(i + 1);_threads.emplace_back(func, name);LOG(DEBUG, "%s init\n", name.c_str());}}void Start() {// 将线程池的状态设置为运行状态_isrunning = true;for (auto& thread : _threads)thread.Start();}public:static ThreadPool<T>* GetInstance() {if (_tp == nullptr) {// 创建线程池可能存在线程安全的问题pthread_mutex_lock(&_sig_mutex);if (_tp == nullptr) {_tp = new ThreadPool();_tp->Init();_tp->Start();LOG(INFO, "create thread pool\n");}pthread_mutex_unlock(&_sig_mutex);} else {LOG(INFO, "get thread pool\n");}return _tp;}void Stop() {LockQueue();_isrunning = false;// 唤醒所有线程,让线程退出pthread_cond_broadcast(&_cond);UnLockQueue();LOG(DEBUG, "thread pool stop\n");}void Push(const T& in) {LockQueue();// 只有在运行状态我们才往任务队列中放入任务if (_isrunning) {_task_queue.push(in);if (_sleep_thread_num > 0)WakeUpThread();}UnLockQueue();}~ThreadPool() {for (auto& t : _threads)t.Join();pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}private:int _thread_num;int _sleep_thread_num;std::vector<Thread> _threads;std::queue<T> _task_queue;bool _isrunning;pthread_mutex_t _mutex;pthread_cond_t _cond;static ThreadPool<T>* _tp;static pthread_mutex_t _sig_mutex;
};// 单例(懒汉)模式
template <typename T>
ThreadPool<T>* ThreadPool<T>::_tp = nullptr;template <typename T>
pthread_mutex_t ThreadPool<T>::_sig_mutex = PTHREAD_MUTEX_INITIALIZER;

        Thread.hpp

#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <cstring>
#include <cerrno>
#include "Log.hpp"// using func_t = std::function<void(const std::string& name, pthread_mutex_t* lock)>;
using func_t = std::function<void(const std::string& name)>;
using namespace log_ns;
// typedef void*(*func_t)(void*);const pthread_t ctid = -1;class Thread {
private:void excute() {// std::cout << _name << " begin to run" << std::endl;// LOG(INFO, "%s begin to run\n", _name.c_str());_isrunning = true;_func(_name);_isrunning = false;}static void* ThreadRoutine(void* args) {Thread* self = static_cast<Thread*>(args);self->excute();return nullptr;}
public:Thread(func_t func, const std::string& name) : _func(func),_isrunning(false),_tid(ctid),_name(name){}~Thread() {}void Start() {// 创建之后就开始运行了int n = pthread_create(&_tid, nullptr, ThreadRoutine, (void*)this);if (n != 0) {std::cout << "thread create failed!!!" << std::endl;exit(1);}}void Stop() {// 将线程暂停,使用if (_isrunning == false) return;// std::cout << _name << " stop " << std::endl;int n = ::pthread_cancel(_tid);if (n != 0)  {std::cout << "thread stop failed" << std::endl;}_isrunning = false;}void Join() {// 线程等待,if (_isrunning) return;int n = pthread_join(_tid, nullptr);if (n != 0) {std::cout << "thread wait failed!!!" << strerror(errno) << std::endl;}// std::cout << _name << " join " << std::endl;}std::string Status() {if (_isrunning) return "running";else return "sleep";}
private:pthread_t _tid;func_t _func;bool _isrunning;std::string _name;
};

        Route.hpp

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "LockGuard.hpp"
#include "ThreadPool.hpp"
using namespace log_ns;using route_task_t = std::function<void()>;class Route {
private:void CheckUserinList(const InetAdrr& who) {LockGuard lockguard(&_mutex);for (auto& user : _user_online) {if (user == who) return;}LOG(DEBUG, "%s is not exist, add it now\n", who.AddrString().c_str());_user_online.push_back(who);}void Lineoff(InetAdrr& who) {LockGuard lockguard(&_mutex);auto it = _user_online.begin();while (it != _user_online.end()) {if (*it == who) {_user_online.erase(it);LOG(DEBUG, "%s line off\n", who.AddrString().c_str());return;}}}void ForwordHelper(int sockfd, const std::string& message, InetAdrr& who) {std::string send_message = "[" + who.AddrString() + "]> ";send_message += message;// 现在将messeage转发出去for (auto& user : _user_online) {struct sockaddr_in peer = user.Addr();socklen_t len = sizeof(peer);LOG(INFO, "%s forword to %s\n", send_message.c_str(), user.AddrString().c_str());sendto(sockfd, send_message.c_str(), send_message.size(), 0, (struct sockaddr*)&peer, len);}}public:Route() {pthread_mutex_init(&_mutex, nullptr);}void ForwordMessage(int sockfd, const std::string& message, InetAdrr& who) {// 先检查当前用户列表中是否存在whoCheckUserinList(who);if (message == "Q" || message == "QUIT") {Lineoff(who);}// 开始转发信息// ForwordHelper(sockfd, message, who);// 现在开始绑定我们的函数route_task_t t = std::bind(&Route::ForwordHelper, this, sockfd, message, who);ThreadPool<route_task_t>::GetInstance()->Push(t);}~Route() {pthread_mutex_destroy(&_mutex);}
private:// 用户列表std::vector<InetAdrr> _user_online;pthread_mutex_t _mutex;
};

        LockGuard.hpp

#pragma once
#include <iostream>
#include <pthread.h>class LockGuard {
public:LockGuard(pthread_mutex_t* mtx): _mtx(mtx){pthread_mutex_lock(_mtx);}~LockGuard() {pthread_mutex_unlock(_mtx);}
private:pthread_mutex_t* _mtx;
};

        测试结果:

TCP Socket编程

        Tcp(传输层控制协议) 协议是传输层协议中很常用很重要的一个协议,主要特点如下:

        1. 有连接

        2. 可靠的传输数据

        3. 面向字节流

        在传输层中关于 Tcp 协议的选择和 Udp 协议的选择看主要的应用场景,tcp 可靠但是效率相对较低,udp 不可靠的但是效率相对较高

1. echo server

        这部分的代码功能和 udp 的 echo 的代码功能十分相似,因为该程序主要是为了试探我们写的代码对不对,测试客户端和服务端是否连接上,如下:

        TcpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"using namespace log_ns;enum {SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR
};const int glistensockfd = -1;
const int gblcklog = 8;using tcp_task_t = std::function<void()>;class TcpServer {
private:void Service(int sockfd, InetAdrr& who) {while (true) {// 开始读和写char buff[1024];int n = read(sockfd, buff, sizeof(buff) - 1);if (n > 0) {buff[n] = 0;LOG(DEBUG, "%s send a message: %s\n", who.AddrString().c_str(), buff);std::string echo = "[" + who.AddrString() + "]> ";echo += buff;write(sockfd, echo.c_str(), echo.size());} else if (n == 0) {LOG(INFO, "client %s quit\n", who.AddrString().c_str());break;} else {LOG(FATAL, "read error\n");break;}}close(sockfd);}public:TcpServer(uint16_t port): _port(port),_listensocked(glistensockfd),_isrunning(false) {}void Init() {// 先获取listensocked_listensocked = socket(AF_INET, SOCK_STREAM, 0);if (_listensocked < 0) {LOG(FATAL, "create listensockfd fail\n");exit(SOCKET_ERROR);}LOG(INFO, "get listensockfd success, listensockfd: %d\n", _listensocked);// 现在开始绑定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 bind_n = bind(_listensocked, (struct sockaddr*)&local, sizeof(local));if (bind_n < 0) {LOG(FATAL, "bind listensockfd fail\n");exit(BIND_ERROR);}LOG(INFO, "bind success\n");int n = listen(_listensocked, gblcklog);if (n < 0) {LOG(FATAL, "listen socket fail\n");exit(LISTEN_ERROR);}LOG(INFO, "listen sucess\n");}// 创建一个内部类struct ThreadData {int _sockfd;InetAdrr _addr;TcpServer* _tcp_point;ThreadData(const int sockfd, const InetAdrr& addr, TcpServer* tcp): _sockfd(sockfd),_addr(addr),_tcp_point(tcp){}};static void* runServer(void* args) {// 将线程分离,防止线程pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);td->_tcp_point->Service(td->_sockfd, td->_addr);delete td;return nullptr;}void Start() {_isrunning = true;while (_isrunning) {// 现在开始收消息和发消息struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensocked, (struct sockaddr*)&peer, &len);InetAdrr addr(peer);if (sockfd < 0) {LOG(ERROR, "%s get sockfd fail, the reason is %s\n", addr.AddrString().c_str(), strerror(errno));continue;}LOG(INFO, "get sockfd success, sockfd: %d\n", sockfd);// Service(sockfd, addr);// 串行运行长服务并不能满足多个用户访问服务器,需要使用并行运行才能满足// 1. 多进程 2、多线程 3、线程池// // 1. 多进程版本// pid_t id = fork();// if (id == 0) {//     // 让子进程关闭listensocked文件描述符//     close(_listensocked);//     // 创建孙子进程,直接让子进程退出,孙子进程会被bash接管//     // 这样长服务就会被孙子进程运行,孙子进程退出直接退出//     // 子进程也不会阻塞,可以让父进程继续等下去//     // 当然最好的方法是使用信号 signal(SIGCHLD, SIG_IGN) 操作//     if (fork() > 0) exit(0);//     Service(sockfd, addr);//     exit(0);// }// // 让父进程关闭sockfd文件描述符,防止文件描述符太多导致文件描述符泄露// close(sockfd);// pid_t n = waitpid(id, nullptr, 0);// if (n > 0) {//     LOG(INFO, "wait child process success\n");// }// // 2. 多线程// pthread_t tid;// ThreadData* data = new ThreadData(sockfd, addr, this);// pthread_create(&tid, nullptr, runServer, (void*)data);// 3. 线程池tcp_task_t task = std::bind(&TcpServer::Service, this, sockfd, addr);ThreadPool<tcp_task_t>::GetInstance()->Push(task);}_isrunning = false;}~TcpServer() {if (_listensocked > 0) close(_listensocked);}
private:uint16_t _port;int _listensocked;bool _isrunning;
};

        TcpServer.cc

#include "TcpServer.hpp"int main() {TcpServer* tcvr = new TcpServer(8888);tcvr->Init();tcvr->Start();return 0;
}

        TcpClient.cc

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"using namespace log_ns;int GetSockfd() {int local_socket = socket(AF_INET, SOCK_STREAM, 0);if (local_socket < 0) {LOG(FATAL, "Create socket fail\n");exit(0);}LOG(INFO, "Create socket success\n");return local_socket;
}int main(int argc, char* args[]) {if (argc != 3) {LOG(ERROR, "please input the -> ./client、ip and port\n");return 0;}// 获取 ip 和 port 以及 socketuint16_t server_port = std::stoi(args[2]);std::string server_ip = args[1];// 绑定int local_socket = GetSockfd();struct sockaddr_in server;memset(&server, 0, sizeof(server));socklen_t len = sizeof(server);server.sin_family = AF_INET;server.sin_port = htons(server_port);// server.sin_addr.s_addr = inet_addr(server_ip.c_str());inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);int n = connect(local_socket, (struct sockaddr*)&server, sizeof(server));if (n < 0) {LOG(FATAL, "get sockfd fail\n");exit(1);}while (true) {std::string info;std::cout << "Please enter > ";std::getline(std::cin, info);// 现在将数据写入int n = write(local_socket, info.c_str(), info.size());if (n > 0) {char buff[1024];int readlen = read(local_socket, buff, sizeof(buff) - 1);if (readlen > 0) {buff[readlen] = 0;std::cout << buff << std::endl;}} else {LOG(INFO, "client quit\n");break;}}close(local_socket);return 0;
}

        其余的代码文件和 udp 的一样,测试如下:

2. command server

        该代码是在 echo 代码基础上改编的代码,主要实现的功能为:将我们输入的命令执行,相当于一个小型的 shell 程序,如下:

        TcpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace log_ns;enum {SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERROR
};const int glistensockfd = -1;
const int gblcklog = 8;using tcp_task_t = std::function<void(int, InetAdrr&)>;class TcpServer {
private:void Service(int sockfd, InetAdrr& who) {while (true) {// 开始读和写char buff[1024];int n = read(sockfd, buff, sizeof(buff) - 1);if (n > 0) {buff[n] = 0;LOG(DEBUG, "%s send a message: %s\n", who.AddrString().c_str(), buff);std::string echo = "[" + who.AddrString() + "]> ";echo += buff;write(sockfd, echo.c_str(), echo.size());} else if (n == 0) {LOG(INFO, "client %s quit\n", who.AddrString().c_str());break;} else {LOG(FATAL, "read error\n");break;}}close(sockfd);}public:TcpServer(tcp_task_t task, uint16_t port): _task(task),_port(port),_listensocked(glistensockfd),_isrunning(false) {}void Init() {// 先获取listensocked_listensocked = socket(AF_INET, SOCK_STREAM, 0);if (_listensocked < 0) {LOG(FATAL, "create listensockfd fail\n");exit(SOCKET_ERROR);}LOG(INFO, "get listensockfd success, listensockfd: %d\n", _listensocked);// 现在开始绑定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 bind_n = bind(_listensocked, (struct sockaddr*)&local, sizeof(local));if (bind_n < 0) {LOG(FATAL, "bind listensockfd fail\n");exit(BIND_ERROR);}LOG(INFO, "bind success\n");int n = listen(_listensocked, gblcklog);if (n < 0) {LOG(FATAL, "listen socket fail\n");exit(LISTEN_ERROR);}LOG(INFO, "listen sucess\n");}// 创建一个内部类struct ThreadData {int _sockfd;InetAdrr _addr;TcpServer* _tcp_point;ThreadData(const int sockfd, const InetAdrr& addr, TcpServer* tcp): _sockfd(sockfd),_addr(addr),_tcp_point(tcp){}};static void* runServer(void* args) {// 将线程分离,就不用阻塞的join线程pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);// LOG(INFO, "the sockfd: %d\n", td->_sockfd);td->_tcp_point->_task(td->_sockfd, td->_addr);close(td->_sockfd);delete td;return nullptr;}void Start() {_isrunning = true;while (_isrunning) {// 现在开始收消息和发消息struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensocked, (struct sockaddr*)&peer, &len);InetAdrr addr(peer);if (sockfd < 0) {LOG(ERROR, "%s get sockfd fail, the reason is %s\n", addr.AddrString().c_str(), strerror(errno));continue;}LOG(INFO, "get sockfd success, sockfd: %d\n", sockfd);// 2. 多线程pthread_t tid;ThreadData* data = new ThreadData(sockfd, addr, this);pthread_create(&tid, nullptr, runServer, (void*)data);}_isrunning = false;}~TcpServer() {if (_listensocked > 0) close(_listensocked);}
private:uint16_t _port;int _listensocked;bool _isrunning;tcp_task_t _task;
};

        TcpServer.cc

#include "TcpServer.hpp"
#include "Command.hpp"int main() {Command cmd;tcp_task_t task = std::bind(&Command::CommandHandler, &cmd, std::placeholders::_1, std::placeholders::_2);TcpServer* tcvr = new TcpServer(task, 8888);tcvr->Init();tcvr->Start();return 0;
}

        TcpClient.cc

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"using namespace log_ns;int GetSockfd() {int local_socket = socket(AF_INET, SOCK_STREAM, 0);if (local_socket < 0) {LOG(FATAL, "Create socket fail\n");exit(0);}LOG(INFO, "Create socket success\n");return local_socket;
}int main(int argc, char* args[]) {if (argc != 3) {LOG(ERROR, "please input the -> ./client、ip and port\n");return 0;}// 获取 ip 和 port 以及 socketuint16_t server_port = std::stoi(args[2]);std::string server_ip = args[1];// 绑定int local_socket = GetSockfd();struct sockaddr_in server;memset(&server, 0, sizeof(server));socklen_t len = sizeof(server);server.sin_family = AF_INET;server.sin_port = htons(server_port);// server.sin_addr.s_addr = inet_addr(server_ip.c_str());inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);int n = connect(local_socket, (struct sockaddr*)&server, sizeof(server));if (n < 0) {LOG(FATAL, "get sockfd fail\n");exit(1);}while (true) {std::string info;std::cout << "Please enter > ";std::getline(std::cin, info);// 现在将数据写入int n = write(local_socket, info.c_str(), info.size());if (n > 0) {char buff[1024];int readlen = read(local_socket, buff, sizeof(buff) - 1);if (readlen > 0) {buff[readlen] = 0;std::cout << buff << std::endl;}} else {LOG(INFO, "client quit\n");break;}}close(local_socket);return 0;
}

        Command.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include "InetAddr.hpp"
#include "Log.hpp"using namespace log_ns;class Command {std::string Excute(const std::string& cmd) {FILE* fp = popen(cmd.c_str(), "r");if (fp) {std::string result;char info[1024];while (fgets(info, sizeof(info), fp)) {result += info;}pclose(fp);return result.empty() ? "success" : result;} else {LOG(WARNING, "open the fp fail\n");return "None";}}public:Command() {}void CommandHandler(int sockfd, InetAdrr& who) {// 接收消息,然后返回消息    while (true) {// 开始读和写char buff[1024];// LOG(INFO, "the sockfd: %d\n", sockfd);int n = recv(sockfd, buff, sizeof(buff) - 1, 0);if (n > 0) {buff[n] = 0;LOG(DEBUG, "%s send a message: %s\n", who.AddrString().c_str(), buff);std::string result = Excute(buff);send(sockfd, result.c_str(), result.size(), 0);} else if (n == 0) {LOG(INFO, "client %s quit\n", who.AddrString().c_str());break;} else {LOG(FATAL, "read error, %s\n", strerror(errno));break;}}}~Command() {}
};

        测试如下:

相关文章:

UDP/TCP --- Socket编程

本篇将使用 Linux 中的系统调用来实现模拟 TCP 和 UDP 的通信过程&#xff0c;其中只对 UDP 和 TCP 进行了简单的介绍&#xff0c;本篇主要实现的是代码&#xff0c;至于 UDP 和 TCP 的详细讲解将会在之后的文章中给出。 本篇给出的 tcp 和 udp 的代码中的 echo 都是测试连接是…...

【C语言】最详细的单链表(两遍包会!)

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C语言数据结构_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:黄灿灿/数据结构 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、前言 二、单链表的概念 1. 单链表的特点 2. 单链表的基本…...

QT:VS2019 CMake编译CEF

CEF介绍 CEF作为一个基于Chromium的开源Web浏览器控件&#xff0c;为第三方应用提供了强大的嵌入浏览器支持。其多平台支持、HTML5特性、自定义能力以及多进程架构等特性&#xff0c;使得CEF在浏览器开发、桌面应用、开发工具以及自动化测试等领域得到了广泛应用。 多平台支持…...

day31(8/19)——静态文件共享、playbook

目录 一、ansible模块 script模块 copy模块 使用command模块下载 nfs-utils rpcbind 在被控制的主机上添加static目录&#xff0c;并创建test文件 command模块 service模块 二、playbook 三、playbook编排vsftpd 1、安装 2、卸载 3、启动服务 4、修改配置文件设置不…...

白骑士的C#教学实战项目篇 4.4 游戏开发

系列目录 上一篇&#xff1a;白骑士的C#教学实战项目篇 4.3 Web开发 在这一部分&#xff0c;我们将探索如何使用 Unity 和 C# 开发游戏。游戏开发结合了编程、图形设计和创意&#xff0c;既充满挑战又充满乐趣。通过这一节的学习&#xff0c;您将了解游戏引擎的基础知识&#…...

在Vue工程中开发页面时,发现页面垂直方向出现两个滚动条的处理

在Vue工程中开发页面时&#xff0c;发现页面垂直方向出现两个滚动条 最近在开发页面时&#xff0c;发现页面多了两个滚动条&#xff0c;如图&#xff1a; 原因&#xff1a; 当一个页面的内容高度大于屏幕的高度时就会出现滚动条。一般情况下当一个页面高度大于屏幕高度时&a…...

【C++初阶】:C++入门篇(一)

文章目录 前言一、C命名空间1.1 命名空间的定义1.2 命名空间的使用 二、C的输入和输出2.1 cin和cout的使用 三、缺省参数3.1 缺省参数的分类 四、函数重载4.1 函数重载概念及其条件4.2 C支持函数重载原理 -- 名字修饰 前言 C是在C语言的基础之上&#xff0c;增加了一些面向对象…...

【JAVA CORE_API】Day14 Collection、Iterator、增强for、泛型、List、Set

Collection接口及常用方法 Collection<Object> collection new ArrayList();&#xff1a;实例化ArrayList集合对象&#xff1b; collectionName.add(Object obj);&#xff1a;在集合中增加元素&#xff1b; int sizeName collectionName.size();&#xff1a;获取集合…...

Go更换国内源配置环境变量

背景 要在中国境内下载和使用Go编程语言的包&#xff0c;可以使用国内的Go模块代理来加速下载速度。以下是一些常见的国内Go模块代理源以及如何切换到这些源的方法&#xff1a; 常见国内Go模块代理源 七牛云&#xff08;Qiniu&#xff09; https://goproxy.cn 阿里云&#xff0…...

澎湃认证显实力,浪潮信息存储兼容新篇章

浪潮信息在存储技术兼容性领域取得新突破&#xff0c;其集中式存储HF/AS系列与长擎安全操作系统24强强联合&#xff0c;成功完成澎湃技术认证。此次合作不仅验证了双方产品的无缝对接能力&#xff0c;更体现了浪潮信息在推动全产业链共建共享方面的坚定决心。 浪潮信息澎湃技术…...

Leetcode 3255. Find the Power of K-Size Subarrays II

Leetcode 3255. Find the Power of K-Size Subarrays II 1. 解题思路2. 代码实现 题目链接&#xff1a;3255. Find the Power of K-Size Subarrays II 1. 解题思路 这一题是题目3254的进阶版&#xff0c;其实主要就是增加了算法复杂度。 整体上来说的话思路还是一个分段的思…...

Kotlin学习02-变量、常量、整数、浮点数、操作符、元组、包、导入

变量、常量、整数、浮点数、操作符、元组、包、导入 Book.kt package com.wujialiang.packclass Book {var title: String "Hello" }val PI 3.14; val E 2.178;Main.kt //引入包 //import com.wujialiang.pack.Book; import com.wujialiang.pack.*; //重命名导…...

C++的模板简介

文章目录 一、前言二、函数模板&#xff08;Function Template&#xff09;三、类模板&#xff08;Class Template&#xff09;四、变参模板&#xff08;Variadic Template&#xff09;五、模板的递归与元编程六、模板的局限与陷阱七、常用模板的实例八、C20 的概念&#xff08…...

树莓派5 笔记25:第一次启动与配置树莓派5_8G

今日继续学习树莓派5 8G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 与 python 版本如下&#xff1a; 今日购得了树莓派5_8G版本&#xff0c;性能是同运…...

Melittin 蜂毒肽;GIGAVLKVLT TGLPALISWI KRKRQQ

【Melittin 蜂毒肽 简介】 蜂毒肽&#xff08;Melittin&#xff09;是蜜蜂毒液中的主要活性成分&#xff0c;由26个氨基酸组成&#xff0c;具有强碱性&#xff0c;易溶于水&#xff0c;是已知抗炎性最强的物质之一。蜂毒肽具有多种生物学、药理学和毒理学作用&#xff0c;包括…...

day32

更新源 cd /etc/apt/ sudo cp sources.list sources.list.save 将原镜像备份 sudo vim sources.list 将原镜像修改成阿里源/清华源&#xff0c;如所述 阿里源 deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiver…...

【clickhouse】 使用 SQLAlchemy 连接 ClickHouse 数据库的完整指南

我听见有人猜 你是敌人潜伏的内线 和你相知多年 我确信对你的了解 你舍命救我画面 一一在眼前浮现 司空见惯了鲜血 你忘记你本是娇娆的红颜 感觉你我彼此都那么依恋 &#x1f3b5; 许嵩《内线》 ClickHouse 是一款非常高效的开源列式数据库&#xff0c;因…...

按键收集单击,双击和长按

按键收集单击,双击和长按 引言 在我们生活中, 按键是必不可少的, 不同的电器, 有不同的按键, 但是按键总有不够用的时候, 那么给与一个按键赋予不同的功能,就必不可少了. 一个按键可以通过按下的时间长短和频次, 来定义其类型。 一次按键收集&#xff0c; 都是在一个按键收集周…...

进程的异常终止

进程的异常终止 进程收到了某些信号&#xff0c;他杀 进程自己调用abort函数&#xff0c;产生了SIGABRT(6)信号&#xff0c;自杀 进程的最后一个线程收到了"取消"操作&#xff0c;并且做出响应 如果进程是异常结束的&#xff0c;atexit\on_exit它们事先注册的遗言…...

并发编程 | Future是如何优化程序性能

在初识Future一文中介绍了Future的核心方法。本文中接着介绍如何用Future优化我们的程序性能。 在此之前&#xff0c;有必要介绍Future接口的一个实现类FutureTask。 FutureTask介绍 FutureTask继承结构 首先我们看一下FutureTask的继承结构&#xff1a; public class Futur…...

Oracle笔记

一、 如何解决 sqlplus 无法使用退格键和方向键 .bashrc 中添加如下内容&#xff0c;解决 退格键 stty erase ^h 安装 rlwap 后&#xff0c;执行如下命令可解决 方向键 rlwap sqlplus 二、 都有哪些备份数据到工具 三、 谈谈 你对 oracle 中实例和数据库的理解 数据库是一…...

LVS+Keepalived 双机热备

LVSKeepalived 双机热备 Keepalived案例分析Keepalived工具介绍Keepalived工具介绍一、功能特点 一、理解Keepalived实现原理实验报告资源列表一、安装keepalived以及ipvsadm Keepalived案例分析 企业应用中&#xff0c;单台服务器承担应用存在单点故障的危险单点故障一旦发生…...

Web Image scr图片从后端API获取基本实现

因系统开发中需求&#xff0c;会有页面显示图片直接从后端获取后显示&#xff0c;代码如下&#xff1a; 后端&#xff1a; /*** 获取图片流* param response* param fileName*/RequestMapping(value"getImgStream",method RequestMethod.GET)public void getImgStr…...

2024音频剪辑指南:探索四大高效工具!

音频剪辑不仅仅是技术活&#xff0c;更是一种艺术创作&#xff0c;它能够让声音更加生动、更具感染力。今天&#xff0c;我们就来探索几款优秀的音频剪辑工具。 福昕音频剪辑 链接&#xff1a;www.pdf365.cn/foxit-clip/ 福昕音频剪辑是一款界面简洁、操作直观的音频编辑软件…...

“CSS”第一步——WEB开发系列13

CSS (Cascading Style Sheets&#xff0c;层叠样式表&#xff09;&#xff0c;是一种用来为结构化文档&#xff08;如 HTML 文档或 XML 应用&#xff09;添加样式&#xff08;字体、间距和颜色等&#xff09;的计算机语言&#xff0c;CSS 文件扩展名为 .css。 一、什么是 CSS&a…...

IEEE802网络协议和标准

IEEE802网络协议和标准 802委员会IEEE 802介绍现有标准 IEEE 802.3介绍物理媒介类型MAC子层与LLC子层主要内容通讯标准POE供电标准802.3af、802.3at、802.3btIEEE802.3af的工作过程&#xff1a;IEEE802.3af主要供电参数&#xff1a;IEEE802.3af的分级参数&#xff1a;为什么会有…...

vulnhub靶机 DC-9(渗透测试详解)

一、靶机信息收集 1、靶机下载 https://download.vulnhub.com/dc/DC-9.zip 2、靶机IP扫描 3、探测靶机主机、端口、服务版本信息 4、靶机目录扫描 二、web渗透测试 1、访问靶机IP 查看页面功能点&#xff0c;发现一个搜索框和登录框 2、测试一下是否存在sql注入 查看当前数…...

javaweb的新能源充电系统pf

TOC springboot339javaweb的新能源充电系统pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域…...

如何在桌面同时展示多个窗口

一、实现2分屏显示 win箭头 二、实现3分屏显示 1. 在实现2分屏显示的基础上&#xff0c;再次点击箭头图标&#xff0c;这次选择屏幕的上方或下方。 2. 点击后&#xff0c;第三个窗口将会出现在你选择的区域。现在&#xff0c;你可以在三个窗口之间自由切换&#xff0c;提高工…...

The Sandbox 游戏制作教程(第 5 部分):创建基于分类的系统

欢迎回到我们的系列&#xff0c;我们将记录 The Sandbox Game Maker 的 “On-Equip”&#xff08;装备&#xff09;功能的多种用途。 如果你刚加入 The Sandbox&#xff0c;装备功能是 “可收集组件”&#xff08;Collectable Component&#xff09;中的一个多功能工具&#x…...

HTML浏览器缓存(Browser Cache)

介绍&#xff1a; 浏览器缓存是Web缓存中最直接、最常见的一种形式。当浏览器首次请求某个资源时&#xff0c;如果服务器响应中包含了缓存控制指令&#xff08;如Cache-Control、Expires等&#xff09;&#xff0c;浏览器就会将这些资源存储在本地缓存中。后续请求相同资源时&a…...

短剧APP系统,推动短剧市场发展

近年来&#xff0c;短剧作为一直火爆的新兴行业&#xff0c;凭借着剧情进奏、爽、时长短等优势&#xff0c;深受大众欢迎&#xff0c;成为了大众碎片化时间的解压神器。 目前&#xff0c;随着短剧市场的快速发展&#xff0c;各个类型的短剧层出不穷&#xff0c;也推动了短剧AP…...

嵌入式 | 嵌入式 Linux 系统使用摄像头

点击上方"蓝字"关注我们 01、引言 >>> 在嵌入式 Linux 系统使用摄像头 俗话说“眼见为实”,这或许是为什么近年来摄像头在嵌入式系统上快速增长的原因。它们被用于不同的场景,如: 远程监控:典型的例子是闭路电视,监控人员在监视环境(或许你所在的大楼…...

C 开源库之cJSON

cJSON简介 CJSON库是一个用于解析和生成JSON数据的C语言库。 它提供了一组函数&#xff0c;使得在C语言中操作JSON数据变得简单而高效。 您可以使用CJSON库来解析从服务器返回的JSON数据&#xff0c;或者将C语言数据结构转换为JSON格式以进行传输。 cJSON 使用 官网地址&…...

ROW_NUMBER(), RANK(), DENSE_RANK() SQL排序函数图文详解

ROW_NUMBER(), RANK(), DENSE_RANK() ROW_NUMBER(): 为结果集中的每一行分配唯一的连续编号。即使有重复的值&#xff0c;ROW_NUMBER() 也会为它们分配不同的序号。 SELECT column_name, ROW_NUMBER() OVER (ORDER BY column_name) AS row_num FROM table_name;2. RANK(): 对结…...

Spring IoCDI(下)—DI的尾声

我们之前学习了控制反转IoC&#xff0c;接下来就开始学习依赖注入DI的细节。 依赖注入是一个过程&#xff0c;是指IoC容器在创建Bean时&#xff0c;去提供运行时所依赖的资源&#xff0c;而资源指的就是对象。我们使用 Autowired 注解&#xff0c;完成依赖注入的操作。简单来说…...

仕考网:考外省公务员可以调回本地吗?

一般情况下&#xff0c;公务员岗位是固定不可随意更换的&#xff0c;因为每个职位都对应特定的职责和要求。一旦考到外地的岗位&#xff0c;想要调回本地几乎是不可能的。因为这样的操作可能导致职位空缺&#xff0c;进而需要通过公共招聘流程来填补&#xff0c;而不是简单地从…...

《工厂模式在软件开发中的深度剖析与应用》

工厂模式 在软件开发的领域中&#xff0c;设计模式充当着解决常见问题的高效且可复用的策略角色。其中&#xff0c;工厂模式作为创建对象的重要设计模式&#xff0c;具有不可小觑的应用价值。接下来&#xff0c;我们将深入探讨简单工厂模式、工厂方法模式和抽象工厂模式。 一…...

双向通信之Websocket

介绍 Websocket是一种在单个TCP连接上进行全双工通信的协议。与传统的HTTP协议不同&#xff0c;websocket允许客户端与服务器之间的双向通信&#xff0c;可以在同一条连接上进行多次消息的快速传递。我之前在做一个线上刷题网站的时候&#xff0c;需要设计一个社区讨论模块&am…...

git学习使用碰到的问题1

本来在B站上看到的关于stash的使用时视频末尾讲到git stash drop 编号 会删除暂存记录 确实也是这么回事&#xff0c;但是末尾说到git stash pop 编号时up主说在恢复工作进度的时候我们可以直接删除掉这个工作记录可以直接使用 git stash pop stash{0} 使用完以后却出现了如上图…...

JavaScript初级——Math

一、Math 和其他的对象不同&#xff0c;它不是一个构造函数。它属于一个工具类&#xff0c;不用创建对象&#xff0c;里边封装了数学运算相关的属性和方法。 比如&#xff1a; Math.PI 表示圆周率。 二、Math.abs&#xff08;&#xff09; —— 可以用来计算一个数的绝对值。 三…...

ffmpeg的基础命令

文章目录 ffmpeg/ffplay/ffprobe区别ffmpeg 的作用ffplay的作用ffprobe的作用 ffmpeg使用概述功能概述转码过程简单使用FFMPEG -i常用的 -i例子 ff***工具之间共享的选项ffmpeg主要选项ffmpeg提取音视频数据ffmpeg命令修改原有的视频格式ffmpeg命令裁剪和合并视频拼接视频的方式…...

二建机电工程实务试题内附答案

1.下列有色金属材料中&#xff0c;不属于铜合金的是()。 A.紫铜 B.青铜 C.黄铜 D.白铜【答案】A 2.用于完成介质间热量交换的换热设备是()。 A.分离器 B.反应器 C.冷凝器 D.分解锅【答案】C 3.工程测量的核心是()。 A.测量精度 B.设计要求 C.减少误差累积 D.检核【答案】D 4.吊…...

Redis的热key以及Big(大)key是什么?如何解决Redis的热key以及Big(大)key问题?

一、先讲讲什么是redis的热key问题 在Redis中&#xff0c;我们把访问频率高的Key&#xff0c;称为热Key。比如突然有几十万的请求去访问redis中某个特定的Key&#xff0c;那么这样会造成redis服务器短时间流量过于集中&#xff0c;很可能导致redis的服务器宕机。那么接下来对这…...

django学习入门系列之第九点《MySQL命令介绍一》

文章目录 MySQL命令数据库的管理&#xff08;文件夹&#xff09;查看现在已有的数据库&#xff08;文件夹&#xff09;创建数据库&#xff08;文件夹&#xff09;删除数据库&#xff08;文件夹&#xff09;进入数据库&#xff08;文件夹&#xff09;查看文件夹下所有的数据表&a…...

Mysql面试一

目录 一、事务的四大特性&#xff08;ACID&#xff09;&#xff1a; 脏读 不可重复读 幻读 隔离性与隔离级别 数据库的三大范式 第一范式。确保数据表中的每个字段都是不可分割的最小单位&#xff0c;即原子性。这意味着表中的每一列都应代表一个独立的数据单元&#xff…...

模型优化之剪枝

文章目录 什么是神经网络剪枝剪枝的好处不同粒度的剪枝剪枝的分类非结构化剪枝结构化剪枝 哪些层的参数更容易被剪掉剪枝效果 什么是神经网络剪枝 神经网络剪枝 在训练期间删除连接密集张量将变得稀疏&#xff08;用零填充&#xff09;可以通过结构化块&#xff08; n m nm nm&…...

JVM的组成

JVM 运行在操作系统之上 java二进制字节码文件的运行环境 JVM的组成部分 java代码在编写完成后编译成字节码文件通过类加载器 来到运行数据区,主要作用是加载字节码到内存 包含 方法区/元空间 堆 程序计数器,虚拟机栈,本地方法栈等等 随后来到执行引擎,主要作用是翻译字…...

快速上手 iOS Protocol Buffer

快速上手 iOS Protocol Buffer | 来自缤纷多彩的灰 本文主要介绍在 iOS 开发中如何快速上手使用 Protobuf。更多关于 Protobuf 的介绍和相关的功能 api&#xff0c;读者可自行查阅官网。 Protocol Buffer&#xff08;简称 Protobuf&#xff09;是一种由Google开发的语言中立、…...

每天一个数据分析题(四百八十)- 线性回归建模

关于线性回归建模&#xff0c;线性回归模型假设说法不正确的是&#xff1f; A. 因变量和自变量要有因果关系 B. 残差均值为0 C. 残差服从正态分布 D. 自变量不存在共线性 数据分析认证考试介绍&#xff1a;点击进入 题目来源于CDA模拟题库 点击此处获取答案 数据分析专…...