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 的通信过程,其中只对 UDP 和 TCP 进行了简单的介绍,本篇主要实现的是代码,至于 UDP 和 TCP 的详细讲解将会在之后的文章中给出。 本篇给出的 tcp 和 udp 的代码中的 echo 都是测试连接是…...
【C语言】最详细的单链表(两遍包会!)
🦄个人主页:小米里的大麦-CSDN博客 🎏所属专栏:C语言数据结构_小米里的大麦的博客-CSDN博客 🎁代码托管:黄灿灿/数据结构 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、前言 二、单链表的概念 1. 单链表的特点 2. 单链表的基本…...
QT:VS2019 CMake编译CEF
CEF介绍 CEF作为一个基于Chromium的开源Web浏览器控件,为第三方应用提供了强大的嵌入浏览器支持。其多平台支持、HTML5特性、自定义能力以及多进程架构等特性,使得CEF在浏览器开发、桌面应用、开发工具以及自动化测试等领域得到了广泛应用。 多平台支持…...
day31(8/19)——静态文件共享、playbook
目录 一、ansible模块 script模块 copy模块 使用command模块下载 nfs-utils rpcbind 在被控制的主机上添加static目录,并创建test文件 command模块 service模块 二、playbook 三、playbook编排vsftpd 1、安装 2、卸载 3、启动服务 4、修改配置文件设置不…...
白骑士的C#教学实战项目篇 4.4 游戏开发
系列目录 上一篇:白骑士的C#教学实战项目篇 4.3 Web开发 在这一部分,我们将探索如何使用 Unity 和 C# 开发游戏。游戏开发结合了编程、图形设计和创意,既充满挑战又充满乐趣。通过这一节的学习,您将了解游戏引擎的基础知识&#…...
在Vue工程中开发页面时,发现页面垂直方向出现两个滚动条的处理
在Vue工程中开发页面时,发现页面垂直方向出现两个滚动条 最近在开发页面时,发现页面多了两个滚动条,如图: 原因: 当一个页面的内容高度大于屏幕的高度时就会出现滚动条。一般情况下当一个页面高度大于屏幕高度时&a…...
【C++初阶】:C++入门篇(一)
文章目录 前言一、C命名空间1.1 命名空间的定义1.2 命名空间的使用 二、C的输入和输出2.1 cin和cout的使用 三、缺省参数3.1 缺省参数的分类 四、函数重载4.1 函数重载概念及其条件4.2 C支持函数重载原理 -- 名字修饰 前言 C是在C语言的基础之上,增加了一些面向对象…...
【JAVA CORE_API】Day14 Collection、Iterator、增强for、泛型、List、Set
Collection接口及常用方法 Collection<Object> collection new ArrayList();:实例化ArrayList集合对象; collectionName.add(Object obj);:在集合中增加元素; int sizeName collectionName.size();:获取集合…...
Go更换国内源配置环境变量
背景 要在中国境内下载和使用Go编程语言的包,可以使用国内的Go模块代理来加速下载速度。以下是一些常见的国内Go模块代理源以及如何切换到这些源的方法: 常见国内Go模块代理源 七牛云(Qiniu) https://goproxy.cn 阿里云࿰…...
澎湃认证显实力,浪潮信息存储兼容新篇章
浪潮信息在存储技术兼容性领域取得新突破,其集中式存储HF/AS系列与长擎安全操作系统24强强联合,成功完成澎湃技术认证。此次合作不仅验证了双方产品的无缝对接能力,更体现了浪潮信息在推动全产业链共建共享方面的坚定决心。 浪潮信息澎湃技术…...
Leetcode 3255. Find the Power of K-Size Subarrays II
Leetcode 3255. Find the Power of K-Size Subarrays II 1. 解题思路2. 代码实现 题目链接:3255. Find the Power of K-Size Subarrays II 1. 解题思路 这一题是题目3254的进阶版,其实主要就是增加了算法复杂度。 整体上来说的话思路还是一个分段的思…...
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++的模板简介
文章目录 一、前言二、函数模板(Function Template)三、类模板(Class Template)四、变参模板(Variadic Template)五、模板的递归与元编程六、模板的局限与陷阱七、常用模板的实例八、C20 的概念(…...
树莓派5 笔记25:第一次启动与配置树莓派5_8G
今日继续学习树莓派5 8G:(Raspberry Pi,简称RPi或RasPi) 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 与 python 版本如下: 今日购得了树莓派5_8G版本,性能是同运…...
Melittin 蜂毒肽;GIGAVLKVLT TGLPALISWI KRKRQQ
【Melittin 蜂毒肽 简介】 蜂毒肽(Melittin)是蜜蜂毒液中的主要活性成分,由26个氨基酸组成,具有强碱性,易溶于水,是已知抗炎性最强的物质之一。蜂毒肽具有多种生物学、药理学和毒理学作用,包括…...
day32
更新源 cd /etc/apt/ sudo cp sources.list sources.list.save 将原镜像备份 sudo vim sources.list 将原镜像修改成阿里源/清华源,如所述 阿里源 deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiver…...
【clickhouse】 使用 SQLAlchemy 连接 ClickHouse 数据库的完整指南
我听见有人猜 你是敌人潜伏的内线 和你相知多年 我确信对你的了解 你舍命救我画面 一一在眼前浮现 司空见惯了鲜血 你忘记你本是娇娆的红颜 感觉你我彼此都那么依恋 🎵 许嵩《内线》 ClickHouse 是一款非常高效的开源列式数据库,因…...
按键收集单击,双击和长按
按键收集单击,双击和长按 引言 在我们生活中, 按键是必不可少的, 不同的电器, 有不同的按键, 但是按键总有不够用的时候, 那么给与一个按键赋予不同的功能,就必不可少了. 一个按键可以通过按下的时间长短和频次, 来定义其类型。 一次按键收集, 都是在一个按键收集周…...
进程的异常终止
进程的异常终止 进程收到了某些信号,他杀 进程自己调用abort函数,产生了SIGABRT(6)信号,自杀 进程的最后一个线程收到了"取消"操作,并且做出响应 如果进程是异常结束的,atexit\on_exit它们事先注册的遗言…...
并发编程 | Future是如何优化程序性能
在初识Future一文中介绍了Future的核心方法。本文中接着介绍如何用Future优化我们的程序性能。 在此之前,有必要介绍Future接口的一个实现类FutureTask。 FutureTask介绍 FutureTask继承结构 首先我们看一下FutureTask的继承结构: public class Futur…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...
MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...





