简单的UDP网络程序:多人群聊系统

本章重点

能够实现一个简单的udp客户端/服务器;

1.创建套接字

我们把服务器封装成一个类,当我们定义出一个服务器对象后需要马上初始化服务器,而初始化服务器需要做的第一件事就是创建套接字。

⭐参数说明:

  • domain:创建套接字的域或者叫做协议家族,也就是创建套接字的类型。该参数就相当于struct sockaddr结构的前16个位。如果是本地通信就设置为AF_UNIX,如果是网络通信就设置为AF_INET(IPv4)或 AF_INET6(IPv6)。
  • type:创建套接字时所需的服务类型。其中最常见的服务类型是SOCK_STREAMSOCK_DGRAM,如果是基于UDP的网络通信,我们采用的就是SOCK_DGRAM,叫做用户数据报服务,如果是基于TCP的网络通信,我们采用的就是SOCK_STREAM,叫做流式套接字,提供的是流式服务。
  • protocol:创建套接字的协议类别。你可以指明为TCPUDP,但该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。

这里我们使用我们之前写的Log.hpp文件来方便观察输出信息。

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);// printf("%s", logtxt); // 暂时打印printLog(level, logtxt);}private:int printMethod;std::string path;
};

当我们在进行初始化服务器创建套接字时,就是调用socket函数创建套接字,创建套接字时我们需要填入的协议家族就是AF_INET,因为我们要进行的是网络通信,而我们需要的服务类型就是SOCK_DGRAM,因为我们现在编写的UDP服务器是面向数据报的,而第三个参数之间设置为0即可。

enum
{SOCKET_ERR = 1
};class UdpServer
{
public:UdpServer(){}void Init(){// 1.创建udp socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 创建套接字失败{log.operator()(Fatal, "sockfd create error: %d", _sockfd);exit(SOCKET_ERR);}log(Info, "socket create success, sockfd: %d", _sockfd); // 3}~UdpServer() {}private:int _sockfd;    // 网络文件描述符
};

我们来运行一下:

2.绑定端口号

⭐注意:编写的是UDP协议的服务器

现在套接字已经创建成功了,但作为一款服务器来讲,如果只是把套接字创建好了,那我们也只是在系统层面上打开了一个通道,用户并不知道将来并不知道是要将数据传给哪个服务器,此时客户端服务器还没有与服务端服务器关联起来,所以我们就要提前讲服务器的ip地址,端口号和套接字绑定起来。

⭐参数说明:

  • sockfd:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。
  • addr:网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:传入的addr结构体的长度。

⭐返回值说明:

  • 绑定成功返回0,绑定失败返回-1,同时错误码会被设置。

套接字创建完毕后我们就需要进行绑定了,但在绑定之前我们需要先定义一个struct sockaddr_in结构,将对应的网络属性信息填充到该结构当中。由于该结构体当中还有部分选填字段,因此我们最好在填充之前对该结构体变量里面的内容进行清空。

然后再将协议家族、端口号、IP地址等信息填充到该结构体变量当中。需要注意的是,在发送到网络之前需要将端口号设置为网络序列,由于端口号是16位的,因此我们需要使用前面说到的htons函数将端口号转为网络序列。

此外,由于网络当中传输的是整数IP,我们如何快速的将字符串IP和整数IP快速转化呢?

那么上面的这个需要我们自己来实现嘛?那网络用起来也太繁琐了吧!不要紧,操作系统为我们提供了方法:inet_addr,能将字符串分隔的地址转成网络序列的的四字节整数,我们需要调用inet_addr函数将字符串IP转换成整数IP,然后再将转换后的整数IP进行设置。

这里有一点细节需要注意:

因此我们这里需要使用in_add的成员s_addr才能将我们的类型进行很好的匹配。

我们的struct sockaddr_in local在哪呢?它在进程地址空间中用户的栈上面,也就是在用户区,我们给结构体填入所有的内容都是在用户区填入的,但是socket套接字是系统调用,在内核区,也就是说此时我们并没有和内核中的套接字相关联,所以我们此时就要绑定bind.

由于bind函数提供的是通用参数类型,因此在传入结构体地址时还需要将struct sockaddr_in*强转为struct sockaddr*类型后再进行传入。

class UdpServer
{
public:UdpServer(uint16_t port = defaultport, const string &ip = defaultip): _port(port), _ip(ip){}void Init(){// 1.创建udp socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 创建套接字失败{log.operator()(Fatal, "sockfd create error: %d", _sockfd);exit(-1);}log(Info, "socket create success, sockfd: %d", _sockfd); // 3// 2.绑定端口号struct sockaddr_in local;bzero(&local, sizeof(local)); // 清空结构体local.sin_family = AF_INET;local.sin_port = htons(_port);//需要保证我的端口号是网络字节序列(大端),因为该端口号是要给对方发送的//1.string -> uint_32_t//2.必须是网络序列的local.sin_addr.s_addr = inet_addr(_ip.c_str());int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));if(n < 0) //绑定失败{log(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}log(Info,"bind create success"); }void Run() {}~UdpServer() {}private:int _sockfd;    // 网络文件描述符string _ip;     // 服务器的ipuint16_t _port; // 服务器进程的端口号
};

我们来运行一下:

3.服务器运行并接收处理请求

接下里我们就要让我们的服务器跑起来,作为一款服务器,它应该是24小时始终运行的。服务器实际上就是在周而复始的为我们提供某种服务,服务器之所以称为服务器,是因为服务器运行起来后就永远不会退出,因此服务器实际执行的是一个死循环代码。由于UDP服务器是不面向连接的,因此只要UDP服务器启动后,就可以直接读取客户端发来的数据,怎么读取呢?UDP服务器不是面向字节流的所以不能用read,它是面向数据报的,所以要使用recvfrom

⭐参数说明:

  • sockfd:对应操作的文件描述符。表示从该文件描述符索引的文件当中读取数据。
  • buf:读取数据的存放位置。
  • len:期望读取数据的字节数。
  • flags:读取的方式。一般设置为0,表示阻塞读取。
  • src_addr:用户端网络相关的属性信息,包括协议家族、IP地址、端口号等,输出型参数。
  • addrlen:调用时传入期望读取的src_addr结构体的长度,返回时代表实际读取到的src_addr结构体的长度,这是一个输入输出型参数。

⭐返回值说明:

  • 读取成功返回实际读取到的字节数,读取失败返回-1,同时错误码会被设置。

⭐注意:

  • 由于UDP是不面向连接的,因此我们除了获取到数据以外还需要获取到用户端网络相关的属性信息,包括IP地址和端口号等。
  • 在调用recvfrom读取数据时,必须将addrlen设置为你要读取的结构体对应的大小。
  • 由于recvfrom函数提供的参数也是struct sockaddr*类型的,因此我们在传入结构体地址时需要将struct sockaddr_in*类型进行强转。

4.服务器发送请求结果

发送数据的函数叫做sendto,该函数的函数原型如下:

⭐参数说明:

  • sockfd:对应操作的文件描述符。表示将数据写入该文件描述符索引的文件当中。
  • buf:待写入数据的存放位置。
  • len:期望写入数据的字节数。
  • flags:写入的方式。一般设置为0,表示阻塞写入。
  • dest_addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等,输入型参数。
  • addrlen:传入dest_addr结构体的长度,输入型参数。

⭐返回值说明:

  • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。

⭐注意:

  • 由于UDP不是面向连接的,因此除了传入待发送的数据以外还需要指明对端网络相关的信息,包括IP地址和端口号等。
  • 由于sendto函数提供的参数也是struct sockaddr*类型的,因此我们在传入结构体地址时需要将struct sockaddr_in*类型进行强转。
class UdpServer
{
public:UdpServer(uint16_t port = defaultport, const string &ip = defaultip): _port(port), _ip(ip){}void Init(){// 1.创建udp socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 创建套接字失败{log.operator()(Fatal, "sockfd create error: %d", _sockfd);exit(-1);}log(Info, "socket create success, sockfd: %d", _sockfd); // 3// 2.绑定端口号struct sockaddr_in local;bzero(&local, sizeof(local)); // 清空结构体local.sin_family = AF_INET;local.sin_port = htons(_port);//需要保证我的端口号是网络字节序列(大端),因为该端口号是要给对方发送的//1.string -> uint_32_t//2.必须是网络序列的local.sin_addr.s_addr = inet_addr(_ip.c_str());int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));if(n < 0) //绑定失败{log(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}log(Info,"bind create success"); }void Run() {// 服务器一直在运行_isrunning = true;char inbuffer[1024];while(_isrunning){// 获取用户端的ip,端口号,用户发送的请求struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if(n < 0){log(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}inbuffer[n] = '\0'; //当作字符串来看// 数据的处理string info = inbuffer;string echo_string = "sever echo#" + info;// 数据发送给用户sendto(_sockfd, echo_string.c_str(), echo_string.size(),0,(const struct sockaddr*)&client, len);}}~UdpServer() {}private:int _sockfd;    // 网络文件描述符string _ip;     // 服务器的ipuint16_t _port; // 服务器进程的端口号bool _isrunning; // 服务器是否在运行
};

此时程序运行起来了,但是服务器到底有没有启动呢?我们可以通过netstat指令查看

netstat -nlup 是一个在类Unix系统(如Linux和macOS)中常用的命令,用于查看网络连接状态。这个命令结合了几个选项来提供特定的输出信息。下面是对每个选项的解释:

  • -n:表示以数字形式显示IP地址和端口号,而不是尝试将其解析为主机名和服务名称。
  • -l:显示正在监听的套接字。
  • -u:显示UDP协议的连接信息。
  • -p:显示与每个连接或监听端口关联的进程ID和进程名称。

并且此时能看到服务器的ip是0.0.0.0,端口号是8080

我们现在使用的是轻量级服务器,我们可以尝试一下绑定我们的远端服务器的ip,看看此时有什么效果。

此时绑定失败了,为什么呢?云服务器禁止直接绑定公网ip,云服务的ip地址可能有多个,如果你指绑定一个,其他的ip就收不到请求。怎么解决呢?将绑定操作中的IP地址设为0,代表“任意IP地址”。这意味着相应的网络服务将会监听并接受来自该主机上任何IP地址的所有网络接口上的连接请求。bind(IP:0):凡是发给我这台主机的数据,我们都要根据端口号向上交付,这种方式叫做任意地址绑定,所以我们刚刚绑定的ip就可以这样写啦!

从此以后,凡是发给我这台主机的数据,可以忽略ip地址,只需要使用端口号向上交付。然后我们在恢复之前的ip地址的形式,随后我们刚刚给我们端口号设置的是8080,现在我们设置成80,结果咋样呢?

提示没有权限,好,我们提权sudo

此时的端口号通过提权就能绑定成功,但是为什么刚刚8080的端口号就不需要提权呢?[0,1023]:系统内定的端口号, 一般都要有固定的应用层协议使用,http: 80 https: 443 mysq: 3606...,期望我们绑定的端口号都在1024以上。所以我们的端口号该怎么处理呢?使用我们的命令行参数决定绑定哪一个端口号。

void Usage(string proc)
{cout << "\n\tUsage: " << proc << " port[1024+]" << endl;
}// ./udpserver port
int main(int argc, char* argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = stoi(argv[1]);unique_ptr<UdpServer> svr(new UdpServer(port));svr->Init();svr->Run();return 0;
}

运行结果:

那我们总得看效果吧,光让服务器跑起来没啥用处啊,接下里我们就来写一个客户端。

5.编写客户端

1.本地网络通信

⭐细节问题:

直接来看代码:

void Usage(string proc)
{cout << "\n\tUsage: " << proc << " serverip serverport" << endl;
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}string serverip = argv[1];uint16_t serverport = stoi(argv[2]);// 我怎么知道服务器是谁呀 - 命令行参数来解决struct sockaddr_in server;bzero(&server, sizeof(server)); // 清空结构体server.sin_family = AF_INET;server.sin_port = htons(serverport); // 需要保证我的端口号是网络字节序列(大端),因为该端口号是要给对方发送的// 1.string -> uint_32_t// 2.必须是网络序列的server.sin_addr.s_addr = inet_addr(serverip.c_str());socklen_t len = sizeof(server);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) // 创建套接字失败{cout << "sockfd create error";exit(1);}log(Info, "socket create success, sockfd: %d", sockfd); // 3// 客户端也需要有ip和端口号,这样服务器才能找到用户,返回用户的请求// client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!// 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!// 如果用户自行绑定,有可能绑定同一个端口号,导致应用无法运行// 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!// 未来是用户给服务器,一旦绑定,用户的端口号是先发给服务端,此时服务器就知道是谁了!// 但是server的port需要唯一确定,用户要访问服务器,随机变化导致第一天还可以,后面无法运行// 系统什么时候给我bind呢?首次发送数据的时候string message;char buffer[1024];while (true){cout << "Please Enter: ";getline(cin, message);// 发送信息给服务端sendto(sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&server, len);// 接收服务端的信息// recvform输出型参数struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if(s > 0){buffer[s] = '\0';cout << buffer << endl;}}close(sockfd);return 0;
}

⭐注意:我们要传入云服务器的私有ip,不是我们的公网ip哟!后面解释!!!

我们来看看运行结果:

此时你只要拥有了客户端的这个代码,ip和端口号,那么你就可以给我这台机器随便发消息,服务器都能收到!!!

但是现在我们不想在服务器处理用户发过来的数据,我们在用户来处理数据,这份做法本质是让代码进行分层,方便维护。

std::string Handler(const std::string &str)
{std::string res = "Server get a message: ";res += str;std::cout << res << std::endl;return res;
}

同时这里用户还可以随意设置自己想要的结果,所以我们现在就可以写一点好玩的。我们可以把传入的字符串当作指令来处理。

popen 是一个在 C 语言中使用的函数,用于通过创建一个管道(pipe)来启动一个子进程,并执行一个shell命令。这个函数允许父进程与子进程之间进行输入/输出通信。popen 函数的主要特点和用途包括:

  1. 创建管道:它首先创建一个管道,这是一个半双工的通信机制,允许数据在两个进程间单向流动。

  2. 启动子进程:接着,通过 fork() 系统调用创建一个子进程。子进程继承了父进程的管道描述符。

  3. 执行命令:在子进程中,使用 execl() 或相似的函数来执行一个shell命令。这使得父进程能够间接地执行系统命令或外部程序。

  4. 返回文件指针popen 函数返回一个 FILE * 类型的文件指针。如果命令执行成功,这个文件指针可以用作 fread()fwrite()fgets() 等标准I/O函数的参数,从而读取子进程的输出的数据。

  5. 关闭管道:当完成通信后,应该使用 pclose() 函数来关闭管道并等待子进程结束。pclose() 也会返回子进程的退出状态。

std::string ExcuteCommand(const std::string &cmd)
{// popen创建一个管道并来启动一个子进程执行shell命令// 随后通过管道将执行的命令给父进程// 父进程可以通过打开文件来看执行结果FILE *fp = popen(cmd.c_str(), "r");if(nullptr == fp){perror("popen");return "error";}// 拿执行结果std::string result;char buffer[4096];while(true){char *ok = fgets(buffer, sizeof(buffer), fp);if(ok == nullptr) break;result += buffer;}pclose(fp);return result;
}

此时我们就可以根据字符串执行相应的指令了,但是我们还是要检测一下指令的输入,万一客户端来个删库的命令哪咋办,所以我们要处理一下,保证指令是一个安全的指令。

bool SafeCheck(const string &cmd)
{vector<string> key_word = {"rm","mv","cp","kill","sudo","unlink","uninstall","yum","top","while"};for (auto e : key_word){auto pos = cmd.find(e);if (pos != string::npos){return false;}}return true;
}std::string ExcuteCommand(const std::string &cmd)
{if(!SafeCheck(cmd)) //安全检查return "Bad man";// popen创建一个管道并来启动一个子进程执行shell命令// 随后通过管道将执行的命令给父进程// 父进程可以通过打开文件来看执行结果FILE *fp = popen(cmd.c_str(), "r");if (nullptr == fp){perror("popen");return "error";}std::string result;char buffer[4096];while (true){char *ok = fgets(buffer, sizeof(buffer), fp);if (ok == nullptr)break;result += buffer;}pclose(fp);return result;
}

之前都是使用的云服务器的ip地址,现在我们学习一个新的ip地址,127.0.0.1是一个特殊的IP地址,用于回环测试。它也被称为本地主机或回环地址。当一个设备向这个IP地址发送数据时,数据并不会离开该设备,而是在设备自身的网络堆栈中进行循环。这通常用于在不涉及外部网络的情况下,在本地机器上测试网络应用程序和服务。现在我们在构造的时候传入127.0.0.1的ip地址。

⭐127.0.0.1:本地环回地址,通常用它来进行cs的测试

其实上面的用户端就类似于我们的xshell,我们每次登录的时候都需要ip地址去连接远端服务器,我们在xshell里面输入的字符串,服务器会接收到并处理好返回给我们。

完整代码展示:

makefile:

.PHONY:all
all:udpserver udpclient
udpserver:main.cppg++ -o $@ $^ -std=c++11
udpclient:udpClient.cppg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f udpserver udpclient

udpClient.cpp:

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;void Usage(string proc)
{cout << "\n\tUsage: " << proc << " serverip serverport" << endl;
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}string serverip = argv[1];uint16_t serverport = stoi(argv[2]);// 我怎么知道服务器是谁呀 - 命令行参数来解决struct sockaddr_in server;bzero(&server, sizeof(server)); // 清空结构体server.sin_family = AF_INET;server.sin_port = htons(serverport); // 需要保证我的端口号是网络字节序列(大端),因为该端口号是要给对方发送的// 1.string -> uint_32_t// 2.必须是网络序列的server.sin_addr.s_addr = inet_addr(serverip.c_str());socklen_t len = sizeof(server);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) // 创建套接字失败{cout << "sockfd create error";exit(1);}// 客户端也需要有ip和端口号,这样服务器才能找到用户,返回用户的请求// client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!// 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!// 如果用户自行绑定,有可能绑定同一个端口号,导致应用无法运行// 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!// 未来是用户给服务器,一旦绑定,用户的端口号是先发给服务端,此时服务器就知道是谁了!// 但是server的port需要唯一确定,用户要访问服务器,随机变化导致第一天还可以,后面无法运行// 系统什么时候给我bind呢?首次发送数据的时候string message;char buffer[1024];while (true){cout << "Please Enter: ";getline(cin, message);// 发送信息给服务端sendto(sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&server, len);// 接收服务端的信息// recvform输出型参数struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if(s > 0){buffer[s] = '\0';cout << buffer << endl;}}close(sockfd);return 0;
}

udpServer.hpp:

#pragma once#include <iostream>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include <unistd.h>
#include <sys/types.h>
#include <cstdlib>
#include <functional>using namespace std;using func_t = function<string(const string&)>;
// 类似于
// typedef function<string(const string&)> func_t;Log lg;enum
{SOCKET_ERR = 1,BIND_ERR = 2
};uint16_t defaultport = 8080;
string defaultip = "0.0.0.0";class UdpServer
{
public:UdpServer(uint16_t port = defaultport, const string &ip = defaultip): _port(port), _ip(ip), _sockfd(0),_isrunning(false){}void Init(){// 1.创建udp socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 创建套接字失败{lg.operator()(Fatal, "sockfd create error: %d", _sockfd);exit(SOCKET_ERR);}lg(Info, "socket create success, sockfd: %d", _sockfd); // 3// 2.绑定端口号struct sockaddr_in local;bzero(&local, sizeof(local)); // 清空结构体local.sin_family = AF_INET;local.sin_port = htons(_port);//需要保证我的端口号是网络字节序列(大端),因为该端口号是要给对方发送的//1.string -> uint_32_t//2.必须是网络序列的local.sin_addr.s_addr = inet_addr(_ip.c_str());//local.sin_addr.s_addr = INADDR_ANY; // 任意ip地址int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));if(n < 0) //绑定失败{lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}lg(Info,"bind create success"); }void Run(func_t func) {// 服务器一直在运行_isrunning = true;char inbuffer[1024];while(_isrunning){// 获取用户端的ip,端口号,用户发送的请求struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd, inbuffer, 1023, 0, (struct sockaddr*)&client, &len);if(n < 0){lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}inbuffer[n] = '\0'; //当作字符串来看// 数据的处理string info = inbuffer;string echo_string = func(info);// cout << echo_string << endl;// 数据发送给用户sendto(_sockfd, echo_string.c_str(), echo_string.size(),0,(const struct sockaddr*)&client, len);}}~UdpServer() {if(_sockfd > 0)close(_sockfd);}private:int _sockfd;    // 网络文件描述符string _ip;     // 服务器的ip  任意地址绑定uint16_t _port; // 服务器进程的端口号bool _isrunning; // 服务器是否在运行
};

main.cpp:

#include "udpServer.hpp"
#include <memory>
#include <cstdio>
#include <vector>// "120.78.126.148" 点分十进制字符串风格的IP地址
// 4个字节ip地址,但是用户不关心void Usage(string proc)
{cout << "\n\tUsage: " << proc << " port[1024+]" << endl;
}std::string Handler(const std::string &str)
{std::string res = "Server get a message: ";res += str;std::cout << res << std::endl;return res;
}bool SafeCheck(const string &cmd)
{vector<string> key_word = {"rm","mv","cp","kill","sudo","unlink","uninstall","yum","top","while"};for (auto e : key_word){auto pos = cmd.find(e);if (pos != string::npos){return false;}}return true;
}std::string ExcuteCommand(const std::string &cmd)
{if (!SafeCheck(cmd))// 安全检查return "Bad man";// popen创建一个管道并来启动一个子进程执行shell命令// 随后通过管道将执行的命令给父进程// 父进程可以通过打开文件来看执行结果FILE *fp = popen(cmd.c_str(), "r");if (nullptr == fp){perror("popen");return "error";}std::string result;char buffer[4096];while (true){char *ok = fgets(buffer, sizeof(buffer), fp);if (ok == nullptr)break;result += buffer;}pclose(fp);return result;
}// ./udpserver port
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(0);}uint16_t port = stoi(argv[1]);unique_ptr<UdpServer> svr(new UdpServer(port));svr->Init();svr->Run(Handler);return 0;
}

2.跨平台网络通信

linux的套接字接口和windows的套接字接口一样吗?虽然操作系统是不同的,但是它们都遵守网络标准,底层的网络协议栈是相同的,所以它们的套接字接口都是一样的,所以两个不同的平台也可以进行网络通信,那咱们来试试!!!

#pragma warning(disable:4996) //inet_addr,不安全,直接禁掉#include <iostream>
#include <winsock2.h>
#include <Windows.h>
#include <string>#pragma comment(lib,"ws2_32.lib")using namespace std;#define IP ""172.17.40.254""
#define PORT 8080int main(int argc, char* argv[])
{//初始化网络环境WSADATA wsa;if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0){cout << "WSAStartup failed" << endl;return -1;}// 申明一个网络地址信息的结构体,保存服务器的地址信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(PORT);server.sin_addr.s_addr = inet_addr(IP);//建立一个udp的socketSOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);if (sockClient < 0){cout << "create socket failed" << endl;return -1;}string message;char buffer[1024];while (true){cout << "Please Enter: ";getline(cin, message);// 发送信息给服务端sendto(sockClient, message.c_str(), (int)message.size(), 0, (const struct sockaddr*)&server, sizeof(server));// 接收服务端的信息// recvform输出型参数struct sockaddr_in temp;int len = sizeof(temp);int s = recvfrom(sockClient, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if (s > 0){buffer[s] = '\0';cout << buffer << endl;}}//关闭sockClientclosesocket(sockClient);//清理网络环境WSACleanup();system("pause");return 0;
}

那咱们来运行一下哈

3.简易的群聊系统

首先我们就需要获取到用户的端口号和ip地址,我们可以在服务器哪里进行获取。

此时就成功获取了用户那端的ip和端口号,此时我只有一个主机,服务端和客户端都在同一台主机上,所以ip地址都一样,这在群聊中相当于自己发信息给自己。

作为服务器,服务器不仅收到了用户发送的信息,还接收了到了用户的ip地址,因此我们就可以通过ip地址来标识用户,所以我们可以维护一个登录列表,看看当前有多少用户登录了服务器,直接写代码。

#pragma once#include <iostream>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include <unistd.h>
#include <sys/types.h>
#include <cstdlib>
#include <functional>
#include <unordered_map>using namespace std;using func_t = function<string(const string &, const string &, uint16_t &)>;
// 类似于
// typedef function<string(const string&)> func_t;Log lg;enum
{SOCKET_ERR = 1,BIND_ERR = 2
};uint16_t defaultport = 8080;
string defaultip = "0.0.0.0";class UdpServer
{
public:UdpServer(uint16_t port = defaultport, const string &ip = defaultip): _port(port), _ip(ip), _sockfd(0), _isrunning(false){}void Init(){// 1.创建udp socket// udp 的socket是全双工的,允许被同时读写的_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0) // 创建套接字失败{lg.operator()(Fatal, "sockfd create error: %d", _sockfd);exit(SOCKET_ERR);}lg(Info, "socket create success, sockfd: %d", _sockfd); // 3// 2.绑定端口号struct sockaddr_in local;bzero(&local, sizeof(local)); // 清空结构体local.sin_family = AF_INET;local.sin_port = htons(_port); // 需要保证我的端口号是网络字节序列(大端),因为该端口号是要给对方发送的// 1.string -> uint_32_t// 2.必须是网络序列的local.sin_addr.s_addr = inet_addr(_ip.c_str());// local.sin_addr.s_addr = INADDR_ANY; // 任意ip地址int n = bind(_sockfd, (const struct sockaddr *)&local, sizeof(local));if (n < 0) // 绑定失败{lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}lg(Info, "bind create success");}void CheckUser(const struct sockaddr_in &client, const string& clientip, uint16_t& clientport){auto iter = _onlineuser.find(clientip); // 找ipif (iter == _onlineuser.end()){// 添加用户 - 入群_onlineuser.insert({clientip, client});cout << "[" << clientip << ":" << clientport << "] add to online user" << endl;}else{return;}}void Broadcast(const string& info, const string& clientip, uint16_t& clientport){for(const auto& user: _onlineuser){// 数据的处理std::string message = "[";message += clientip;message += ":";message += std::to_string(clientport);message += "]# ";message += info;socklen_t len = sizeof(user.second);sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)(&user.second), len);}}void Run(){// 服务器一直在运行_isrunning = true;char inbuffer[1024];while (_isrunning){// 获取用户端的ip,端口号,用户发送的请求struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd, inbuffer, 1023, 0, (struct sockaddr *)&client, &len);if (n < 0){lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}inbuffer[n] = '\0'; // 当作字符串来看// 拿到用户端的端口号和ip地址uint16_t clientport = ntohs(client.sin_port);string clientip = inet_ntoa(client.sin_addr);// 判断是否是一个新用户CheckUser(client, clientip, clientport);// 数据发送给所有用户string info = inbuffer;Broadcast(info, clientip, clientport);}}~UdpServer(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;     // 网络文件描述符string _ip;      // 服务器的ip  任意地址绑定uint16_t _port;  // 服务器进程的端口号bool _isrunning; // 服务器是否在运行unordered_map<string, struct sockaddr_in> _onlineuser;
};

我们来看看运行结果:

但是我们的客户端是一个单进程,向服务器发信息和从服务器收到信息都在同一个进程,并且我们是先发送信息的,所以就有一点尴尬,我们任意一个用户只有发一个信息才能收到其他用户发出的信息,如果它不发信息,getline便会阻塞住,代码就不能继续向后执行,尽管此时别的服务器给我发送了信息,但是我们的代码还在getline那里阻塞者呢,我们还没执行到从服务器接收信息的代码,所以此时不能收到信息,但是群聊的时候,我们不发消息也能收到其他人的信息呀!此时我们就需要多线程来解决,向服务器发信息和从服务器收到信息使用多线程,让它俩互不干扰。

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>using namespace std;struct ThreadData
{struct sockaddr_in server;int sockfd;
};void Usage(string proc)
{cout << "\n\tUsage: " << proc << " serverip serverport" << endl;
}void *recv_message(void *args)
{ThreadData *td = static_cast<ThreadData *>(args);char buffer[1024];while (true){// 接收服务端的信息// recvform输出型参数struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = '\0';cout << buffer << endl;}}
}void *send_message(void *args)
{ThreadData *td = static_cast<ThreadData *>(args);string message;socklen_t len = sizeof(td->server);while (true){cout << "Please Enter: ";getline(cin, message);// 发送信息给服务端sendto(td->sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&td->server, len);}
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}string serverip = argv[1];uint16_t serverport = stoi(argv[2]);struct ThreadData td;// 我怎么知道服务器是谁呀 - 命令行参数来解决bzero(&td.server, sizeof(td.server)); // 清空结构体td.server.sin_family = AF_INET;td.server.sin_port = htons(serverport); // 需要保证我的端口号是网络字节序列(大端),因为该端口号是要给对方发送的// 1.string -> uint_32_t// 2.必须是网络序列的td.server.sin_addr.s_addr = inet_addr(serverip.c_str());td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd < 0) // 创建套接字失败{cout << "sockfd create error";exit(1);}// 客户端也需要有ip和端口号,这样服务器才能找到用户,返回用户的请求// client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!// 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!// 如果用户自行绑定,有可能绑定同一个端口号,导致应用无法运行// 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!// 未来是用户给服务器,一旦绑定,用户的端口号是先发给服务端,此时服务器就知道是谁了!// 但是server的port需要唯一确定,用户要访问服务器,随机变化导致第一天还可以,后面无法运行// 系统什么时候给我bind呢?首次发送数据的时候pthread_t recvr, sender;pthread_create(&recvr, nullptr, recv_message, &td);pthread_create(&sender, nullptr, send_message, &td);pthread_join(recvr, nullptr);pthread_join(sender, nullptr);close(td.sockfd);return 0;
}

运行一下:

但是此时我们的输入和输出都在一个窗口,看着比较混乱,我们可以多开几个终端来让它们分开,linux下一些皆文件,我们的终端也是文件。

此时我们可以发现我们这个终端其实就是dev/pts目录下的0号文件,所以我们可以借助这个文件向终端输入内容,那我们怎么通过代码来执行呢?

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string terminal = "/dev/pts/0";
using namespace std;
int main()
{int fd = open(terminal.c_str(), O_WRONLY);if (fd < 0){std::cerr << "open terminal error" << std::endl;exit(1);}//cout << fd << endl;dup2(fd, 1); // 重定向标准输出到/dev/pts/0printf("hello world\n");close(fd);return 0;
}

运行一下:

紧接着我们立马把它应用到客户端,让客户端的输入和输出在两个终端。

Treminal.hpp

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string terminal = "/dev/pts/0";
using namespace std;int OpenTerminal()
{int fd = open(terminal.c_str(), O_WRONLY);if (fd < 0){std::cerr << "open terminal error" << std::endl;exit(1);}//cout << fd << endl;// 由于线程的文件描述符是共享的,所以我换一个dup2(fd, 2); // 重定向标准错误到/dev/pts/0return 0;
}

udpClient.cpp

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "Terminal.hpp"using namespace std;struct ThreadData
{struct sockaddr_in server;int sockfd;
};void Usage(string proc)
{cout << "\n\tUsage: " << proc << " serverip serverport" << endl;
}void *recv_message(void *args)
{OpenTerminal();ThreadData *td = static_cast<ThreadData *>(args);char buffer[1024];while (true){// 接收服务端的信息// recvform输出型参数struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = '\0';cerr << buffer << endl; //使用标准错误// 此时我们的标准错误就已经重定向到终端}}
}void *send_message(void *args)
{ThreadData *td = static_cast<ThreadData *>(args);string message;socklen_t len = sizeof(td->server);while (true){cout << "Please Enter: ";getline(cin, message);// 发送信息给服务端sendto(td->sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&td->server, len);}
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}string serverip = argv[1];uint16_t serverport = stoi(argv[2]);struct ThreadData td;// 我怎么知道服务器是谁呀 - 命令行参数来解决bzero(&td.server, sizeof(td.server)); // 清空结构体td.server.sin_family = AF_INET;td.server.sin_port = htons(serverport); // 需要保证我的端口号是网络字节序列(大端),因为该端口号是要给对方发送的// 1.string -> uint_32_t// 2.必须是网络序列的td.server.sin_addr.s_addr = inet_addr(serverip.c_str());td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd < 0) // 创建套接字失败{cout << "sockfd create error";exit(1);}// 客户端也需要有ip和端口号,这样服务器才能找到用户,返回用户的请求// client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!// 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!// 如果用户自行绑定,有可能绑定同一个端口号,导致应用无法运行// 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!// 未来是用户给服务器,一旦绑定,用户的端口号是先发给服务端,此时服务器就知道是谁了!// 但是server的port需要唯一确定,用户要访问服务器,随机变化导致第一天还可以,后面无法运行// 系统什么时候给我bind呢?首次发送数据的时候pthread_t recvr, sender;pthread_create(&recvr, nullptr, recv_message, &td);pthread_create(&sender, nullptr, send_message, &td);pthread_join(recvr, nullptr);pthread_join(sender, nullptr);close(td.sockfd);return 0;
}

运行结果:

此时就完成了简单的群聊系统。还有一种简单的方法,我们上面的用户发出信息是使用的标准输出,而收到信息是标准错误,两个文件描述符是不同的,所以我们可以直接将用户端输入ip和端口号的地方标准错误重定向到我们的终端下即可。

./udpclient 172.17.40.254 8080 2 > /dev/pts/0

现在我们再来优化一下,每个用户在一旦访问我们的服务器的时候,我们可以设置一条欢迎语。

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "Terminal.hpp"
#include <string.h>using namespace std;struct ThreadData
{struct sockaddr_in server;int sockfd;std::string serverip;
};void Usage(string proc)
{cout << "\n\tUsage: " << proc << " serverip serverport" << endl;
}void *recv_message(void *args)
{OpenTerminal();ThreadData *td = static_cast<ThreadData *>(args);char buffer[1024];while (true){memset(buffer, 0, sizeof(buffer));// 接收服务端的信息// recvform输出型参数struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = '\0';cerr << buffer << endl; // 使用标准错误// 此时我们的标准错误就已经重定向到终端}}
}void *send_message(void *args)
{ThreadData *td = static_cast<ThreadData *>(args);string message;socklen_t len = sizeof(td->server);// 发送欢迎语std::string welcome = td->serverip;welcome += " comming...";sendto(td->sockfd, welcome.c_str(), welcome.size(), 0, (struct sockaddr *)&(td->server), len);while (true){cout << "Please Enter: ";getline(cin, message);// 发送信息给服务端sendto(td->sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&td->server, len);}
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}string serverip = argv[1];uint16_t serverport = stoi(argv[2]);struct ThreadData td;// 我怎么知道服务器是谁呀 - 命令行参数来解决bzero(&td.server, sizeof(td.server)); // 清空结构体td.server.sin_family = AF_INET;td.server.sin_port = htons(serverport); // 需要保证我的端口号是网络字节序列(大端),因为该端口号是要给对方发送的// 1.string -> uint_32_t// 2.必须是网络序列的td.server.sin_addr.s_addr = inet_addr(serverip.c_str());td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd < 0) // 创建套接字失败{cout << "sockfd create error";exit(1);}td.serverip = serverip;// 客户端也需要有ip和端口号,这样服务器才能找到用户,返回用户的请求// client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!// 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!// 如果用户自行绑定,有可能绑定同一个端口号,导致应用无法运行// 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!// 未来是用户给服务器,一旦绑定,用户的端口号是先发给服务端,此时服务器就知道是谁了!// 但是server的port需要唯一确定,用户要访问服务器,随机变化导致第一天还可以,后面无法运行// 系统什么时候给我bind呢?首次发送数据的时候pthread_t recvr, sender;pthread_create(&recvr, nullptr, recv_message, &td);pthread_create(&sender, nullptr, send_message, &td);pthread_join(recvr, nullptr);pthread_join(sender, nullptr);close(td.sockfd);return 0;
}

其实这个谁谁conmming,就相当于谁谁已经加入群聊啦!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/15791.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx代理配置(专业版)

写在前面提醒&#xff1a;使用代理&#xff0c;如果可以&#xff0c;请尽量支持双协议&#xff0c;http、https均要支持哈。 注意&#xff1a;监控系统只是运行代码&#xff0c;是否支持https&#xff0c;需要运维同学在你们的服务器上配置https证书&#xff0c;配置好证书&…

在 CentOS 上安装 PostgreSQL 的全面指南

PostgreSQL 是一种功能强大的开源关系型数据库管理系统&#xff0c;广泛应用于各种领域。它提供了诸如事务处理、并发控制和数据完整性等高级功能&#xff0c;因此深受开发者和企业的欢迎。本指南将逐步引导您在 CentOS 上安装 PostgreSQL&#xff0c;以便您充分利用其众多优势…

决定了,将ChatGPTer开源!主打一个大模型人人可用。

一个快速上手且极易部署的类ChatGPT开源应用&#xff0c;可接入 OPENAI API 或 通义千问API 开源地址&#xff1a; https://github.com/isnl/EsChat 大声(偷偷)告诉你&#xff1a;通义千问有免费API额度可白嫖&#xff01;&#xff01;&#xff01; 版本特性 OPENAI 和 通义千…

利用英特尔 Gaudi 2 和至强 CPU 构建经济高效的企业级 RAG 应用

检索增强生成 (Retrieval Augmented Generation&#xff0c;RAG) 可将存储在外部数据库中的新鲜领域知识纳入大语言模型以增强其文本生成能力。其提供了一种将公司数据与训练期间语言模型学到的知识分开的方式&#xff0c;有助于我们在性能、准确性及安全隐私之间进行有效折衷。…

任推邦:实力强劲的APP推广拉新平台,号称不扣量

任推邦简介 任推邦是国内数一数二的项目分发平台&#xff0c;也是一个不扣量的项目APP推广拉新平台&#xff0c;隶属于聚名科技集团股份有限公司。聚名科技成立时间在2012年&#xff0c;是安徽省老牌互联网企业&#xff0c;历经11年的飞速发展&#xff0c;聚名科技成功布局打造…

小程序的这些知识你知道吗?

一:导航传参 无论是编程式还是声明式导肮传参都是在url?keyvalue&key1value1,无论是否是tabbar页面. 对于回退页面,没办法传参. 这个参数是,跳转到页面的时候,跳转到另一个页面,这个页面就是刚开始执行,等数据执行之后,触发onload,传递的参数放在内存中,跳转是内部底层触…

云端力量:利用移动云服务器高效部署Spring Boot Web应用

文章目录 一、移动云介绍二、移动云产品选择三、体验云主机ECS四、使用移动云服务器部署SpringBoot Web应用4.1移动云ECS安装JDK4.2移动云ECS安装MySQL4.3移动云ECS数据库插入数据4.4移动云ECS部署Spring Boot Web应用 总结 一、移动云介绍 移动云是中国移动基于自研的先进技术…

Linux中常见的基本指令(上)

目录 一、ls指令 1. ls 2. ls -l 3. ls -a 4.ls -F 二、qwd指令 三、cd指令 1. cd .. 2. cd / / / 3. cd ../ / / 4. cd ~ 5. cd - 五、mkdir指令 六、rmdir指令和rm指令 一、ls指令 语法 &#xff1a; ls [ 选项 ][ 目录或文件 ] 。 功能 &#xff1a;对于目录…

桶排序和基数排序

前言&#xff1a; 这篇文章&#xff0c;我们就来了解一些鲜为人知的排序&#xff0c;桶排序和基数排序。 桶排序&#xff1a; 桶排序的思想&#xff1a; 桶排序的思想就是把待排序的数尽量均匀地放到各个桶中&#xff0c;再对各个桶进行局部的排序&#xff0c;最后再按序将各…

AI Agent: Agent框架+7个实例

何谓Agent Agent 作为一种新兴的人工智能技术&#xff0c;正在受到越来越多的关注。要说清楚什么是 Agent&#xff0c;先得看看人工智能的本质是什么。 人工智能这个名称来自它试图通过计算机程序或机器来模拟、扩展和增强人类智能的 一些方面。在这个定义中&#xff0c;“人…

C# WPF入门学习(四)—— 按钮控件

上期介绍了WPF的实现架构和原理&#xff0c;之后我们开始来使用WPF来学习各种控件。 一、尝试插入一个按钮&#xff08;方法一&#xff09; 1. VS2019 在界面中&#xff0c;点击工具栏中的视图&#xff0c;在下拉菜单中选择工具箱。 至于编译器中的视图怎么舒服怎么来布置&am…

服务器硬件全攻略:从入门到精通,全面解析服务器性能与稳定性!

服务器是计算机网络中提供特定服务的计算机系统&#xff0c;其硬件配置和性能直接影响到整个网络系统的运行效率和稳定性。作为一个资深的技术人员&#xff0c;本文将全面详细地介绍服务器硬件基础知识&#xff0c;包括介绍、命令或语法、主要作用以及使用方法等。 一、介绍 服…

Linux基础(七):Linux 系统上的库文件生成与使用

学过C语言我们知道&#xff0c;C语言有标准库和自定义库&#xff0c;这些方便了我们的实际开发&#xff0c;提供了已经实现好的函数接口&#xff0c;我们使用的时候&#xff0c;只需要引入头文件即可&#xff0c;那具体的实现过程又是怎么样的呢&#xff1f;我们又该如何实现我…

Mysql中的慢查询

Mysql慢查询的一些sql命令 慢查询的默认事件为10秒 #注意&#xff1a;慢查询一般是在调试阶段开启的&#xff0c;在开发阶段中一般不会开启&#xff0c;会对效率产生延误 #查询慢查询是否开启 show variables like %general%; #慢查询时间设置 show variables like long_query…

查找专利渠道

官方渠道 常规检索 (cnipa.gov.cn)https://pss-system.cponline.cnipa.gov.cn/conventionalSearch 佰腾网 佰腾网 - 查专利就上佰腾网_佰腾全球专利搜索平台_商标查询平台_企业工商信息查询平台 (baiten.cn)https://www.baiten.cn/

NLP(19)--大模型发展(3)

前言 仅记录学习过程&#xff0c;有问题欢迎讨论 大模型训练相关知识&#xff1a; 问题&#xff1a; 数据集过大&#xff0c;快速训练模型过大&#xff0c;gpu跑不完 方案&#xff1a; 数据并行训练&#xff1a; 复制数据&#xff08;batch_size&#xff09;到多个gpu&…

[杂项]优化AMD显卡对DX9游戏(天谕)的支持

目录 关键词平台说明背景RDNA 1、2、3 架构的显卡支持游戏一、 优化方法1.1 下载 二、 举个栗子&#xff08;以《天谕》为例&#xff09;2.1 下载微星 afterburner 软件 查看游戏内信息&#xff08;可跳过&#xff09;2.2 查看D3D9 帧数2.3 关闭游戏&#xff0c;替换 dll 文件2…

精品PPT | MES设计与实践,业务+架构+实施(免费下载))

【1】关注本公众号&#xff0c;转发当前文章到微信朋友圈 【2】私信发送 MES设计与实践 【3】获取本方案PDF下载链接&#xff0c;直接下载即可。 如需下载本方案PPT/WORD原格式&#xff0c;请加入微信扫描以下方案驿站知识星球&#xff0c;获取上万份PPT/WORD解决方案&#x…

【探索数据结构】线性表之单链表

&#x1f389;&#x1f389;&#x1f389;欢迎莅临我的博客空间&#xff0c;我是池央&#xff0c;一个对C和数据结构怀有无限热忱的探索者。&#x1f64c; &#x1f338;&#x1f338;&#x1f338;这里是我分享C/C编程、数据结构应用的乐园✨ &#x1f388;&#x1f388;&…

Autodl服务器中Faster-rcnn(jwyang)复现(一)

前言 在做实验时需要用到faster-rcnn做对比,本节首先完成代码复现,用的数据集是VOC2007~ 项目地址:https://github.com/jwyang/faster-rcnn.pytorch/tree/pytorch-1.0 复现环境:autodl服务器+python3.6+cuda11.3+Ubuntu20.04+Pytorch1.10.0 目录 一、环境配置二、编译cud…