该文承接文章 简单的UDP网络程序
对于客户端和服务端的基本源码参考上文,该文对服务器润色一下,并且实现几个基本的业务服务逻辑
目录
demo1
第一个功能:字典翻译
初始化字典
测试代码:打印
字符串分割
客户端修改
成品效果
字典热加载
signal
demo2
远端命令行解析
popen
远端操作服务器执行命令
对客户端稍加修改
添加一个函数
效果
小知识
字典&&远端操作服务器执行命令代码
udpServer.cc
udpServer.hpp
udpClient.cc
dict.txt
demo3
聊天室
效果一:没上线
效果二:上线了
聊天室源码
精简
onlineUser.hpp
udpClient.cc
udpClient.hpp
udpServer.cc
udpServer.hpp
demo1
第一个功能:字典翻译
这个用于对发送过来的数据进行简单的翻译,采取文件读取的方式
先创建一个文件,注意在当前目录下建立一个 dict.txt文件放入一些待翻译的字符组,例如下面
apple:苹果
banana:香蕉
hello:你好
world:世界
初始化字典
获取数据
unordered_map<string, string> dict; // 一个字典
const std::string dictTxt = "./dict.txt";
static void initDict()
{
ifstream in(dictTxt, std::ios::binary); // 二进制形式打开一个文件
if (!in.is_open())
{
std::cerr << "open file" << dictTxt << "error" << endl;
exit(OPEN_ERR); //已经添加至错误联合体中
}
string line;
while (getline(in, line))
{
cout << line << endl; //-- 测试初始化是否成功
}
in.close();
}
小知识:getline的返回值虽然不是bool类型的但是它会被隐式类型转换,所以我们直接放到while里面充当判断是没有问题的
利用getline获得数据
测试代码:打印
字符串分割
输出结果
static bool cutString(const string &target, string *s1, string *s2, const string &sep) //把sep暴露出来可以让外部进行传递
{
//apple:苹果
auto pos = target.find(sep);
if(pos == string::npos) return false;
*s1 = target.substr(0, pos);
*s2 = target.substr(pos + sep.size()); //默认到最后一个
return true;
}
客户端修改
需要拥有把数据接受并且处理的功能
成品效果
字典热加载
实现当捕捉到2号信号时候,重新加载字典里面的数据,用以翻译
signal
demo2
远端命令行解析
实现一个对于简单的命令,通过客户端发送消息给服务端,操作服务端命令
认识接口
popen
远端操作服务器执行命令
对客户端稍加修改
添加一个函数
直接写一个回调函数,给下面回调使用,体现了解耦的方便性
udpServer.cc 添加函数
// demo2
void execCommand(int sockfd, string clientip, uint16_t clientport, string cmd)
{
// 1.cmd
// 2.如果必要,可能需要fork, exec*
if (cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("rmdir") != string::npos)
{
cerr << clientip << ":" << clientport << " 正在做一个非法的操作: " << cmd << endl;
return;
}
string response;
FILE *fp = popen(cmd.c_str(), "r");
if(fp == nullptr) response = cmd + " exec failed";
char line[1024];
while(fgets(line, sizeof(line), fp))
{
response += line;
}
pclose(fp);
//开始返回 -- 同上没有变化
struct sockaddr_in client;
bzero(&client, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str()); // 字符串转网络(点分十进制 转 数字序列)
sendto(sockfd, response.c_str(), response.size(), 0, (const sockaddr *)&client, sizeof(client));
}
效果
注意:这里是几个简单的Xshell指令,只能分析一些基本的指令,是一个原理性的代码
小知识
字典&&远端操作服务器执行命令代码
udpServer.cc
#include "udpServer.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>
#include <stdio.h>
using namespace std;
using namespace Server;
static void Usage(string proc)
{
cout << "Usage:\n\t" << proc << " local_port\n\n"; // 命令提示符
}
unordered_map<string, string> dict; // 一个字典
const std::string dictTxt = "./dict.txt";
static bool cutString(const string &target, string *s1, string *s2, const string &sep) // 把sep暴露出来可以让外部进行传递
{
// apple:苹果
auto pos = target.find(sep);
if (pos == string::npos)
return false;
*s1 = target.substr(0, pos);
*s2 = target.substr(pos + sep.size()); // 默认到最后一个
return true;
}
static void initDict()
{
ifstream in(dictTxt, std::ios::binary); // 二进制形式打开一个文件
if (!in.is_open())
{
std::cerr << "open file" << dictTxt << "error" << endl;
exit(OPEN_ERR); // 已经添加至错误联合体中
}
string line;
std::string key, value;
while (getline(in, line))
{
// cout << line << endl; -- 测试初始化是否成功
if (cutString(line, &key, &value, ":"))
{
dict.insert(make_pair(key, value));
}
}
in.close();
cout << "load dict succss" << endl;
}
void reload(int signal)
{
(void)signal;
initDict();
}
static void debugPrint()
{
for (auto &dt : dict)
{
cout << dt.first << " # " << dt.second << endl;
}
}
// demo1
void handlerMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
// 这里就可以对message进行特定的业务处理,而不关心message怎么来的 --- 这就是server通信和业务逻辑解耦!
// 幼儿园版的业务逻辑
string response_message;
auto iter = dict.find(message);
if (iter == dict.end())
response_message = "unknown";
else
response_message = iter->second;
// 开始返回
struct sockaddr_in client;
bzero(&client, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str()); // 字符串转网络(点分十进制 转 数字序列)
sendto(sockfd, response_message.c_str(), response_message.size(), 0, (const sockaddr *)&client, sizeof(client));
}
// demo2
void execCommand(int sockfd, string clientip, uint16_t clientport, string cmd)
{
// 1.cmd
// 2.如果必要,可能需要fork, exec*
if (cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("rmdir") != string::npos)
{
cerr << clientip << ":" << clientport << " 正在做一个非法的操作: " << cmd << endl;
return;
}
string response;
FILE *fp = popen(cmd.c_str(), "r");
if(fp == nullptr) response = cmd + " exec failed";
char line[1024];
while(fgets(line, sizeof(line), fp))
{
response += line;
}
pclose(fp);
//开始返回 -- 同上没有变化
struct sockaddr_in client;
bzero(&client, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str()); // 字符串转网络(点分十进制 转 数字序列)
sendto(sockfd, response.c_str(), response.size(), 0, (const sockaddr *)&client, sizeof(client));
}
int main(int argc, char *argv[])
{
if (argc != 2) // 这里我们只想要传递两个参数,所以当argc不是3的时候就直接报错退出就行了,注意文件名运行的那个指令也会算进去所以argc +1
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]); // 使用atoi强转,因为argv里面放置的都是字符串,类型需要转换
// string ip = argv[1];
//signal(2, reload); // 信号捕捉 CTRL + C (2号信号)
//initDict(); // 字典初始化
//debugPrint(); 字典打印测试
//std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port));
std::unique_ptr<udpServer> usvr(new udpServer(execCommand, port));
usvr->initServer();
usvr->start();
return 0;
}
udpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
namespace Server
{
using namespace std;
static const string defaultIp = "0.0.0.0"; // 直接使用这个缺省值,代表监听机器上的所有ip端口
static const int gnum = 1024;
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
OPEN_ERR
}; // 1.命令行输入错误 2.创建套接字错误 3.绑定端口号错误
typedef function<void(int, string, uint16_t, string)> func_t;
class udpServer
{
public:
udpServer(const func_t &cd, const uint16_t &port, const string &ip = defaultIp)
: _callback(cd), _port(port), _ip(ip), _sockfd(-1)
{
}
void initServer() // 初始化
{
// 1.创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd == -1)
{
cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
exit(SOCKET_ERR); // 创建套接字失败直接终止进程
}
cout << "udpServer success: "
<< " : " << _sockfd << endl;
// 2.绑定套接字(port, ip)
// 未来服务器要明确的port, 不能随意改变 -- 变了别人就找不到了
struct sockaddr_in local; // 这里是定义了一个变量,在栈上,而且是用户层,还没有bind之前都是没有产生联系
bzero(&local, sizeof(local)); // 先填 0 再修正
// 注意这下面几个名字是拼接出来的,就是那个##拼接而来的
local.sin_family = AF_INET; // 这里设置与套接字的AF_INET设置意义是不一样的,socket是创建一个网络通信的套接字,在这里是填充一个sockaddr_in的结构体用来网络通信
local.sin_port = htons(_port); // 你如果给别人发信息,你的port和ip要不要发送给对方? 答案是要的
local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. string->unit32_t 2. htonl(); -> inet_addr
// local.sin_addr.s_addr = htonl(INADDR_ANY); //可以主机转网络,不够也可以不处理,直接赋值也行
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n == -1)
{
cerr << "bin error: " << errno << " : " << strerror(errno) << endl;
exit(BIND_ERR);
}
// UDP Server 的预备工作完成
}
void start() // 启动
{
// 服务器的本质其实就是一个死循环
char buffer[gnum]; // 定义一个数组来充当缓冲区
for (;;)
{
// 读取数据
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 必须设置成这个结构体的大小,当作为输入时,告诉recvfrom的长度的多少
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
// 关系两件事情
// 1.数据是什么? 2. 谁发的?
if (s > 0)
{
buffer[s] = 0;
// 因为是从网络上读取的,所以一定要转,可以使用接口
// inet_ntoa 将一个网络字节序的IP地址(也就是结构体in_addr类型变量)转化为点分十进制的IP地址(字符串)
string clientip = inet_ntoa(peer.sin_addr); // 1.网络序列 2.整数 -> 点分十进制的ip
uint16_t clientport = ntohs(peer.sin_port);
string message = buffer;
cout << clientip << "[" << clientport << "]# " << message << endl;
//我们只把数据读上来就完了吗? 我们要对数据进行处理 -- 所以我们用回调函数的方式来解决
_callback(_sockfd, clientip, clientport, message);
}
}
}
~udpServer() // 析构
{
}
private:
uint16_t _port;
// 实际上,一款网络服务器,不建议指明一个IP,因为一个服务器可以能有多个ip,万一用户使用其他的ip地址来访问该端口号(这里是8080,就收不到了),这也是我们为什么使用0.0.0.0的IP缺省值
string _ip;
int _sockfd;
func_t _callback; // 回调函数,用以处理数据
};
}
udpClient.cc
#include "udpClient.hpp"
#include <memory>
using namespace Client;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " server_ip server_port\n\n"; //命令提示符
}
// ./udpClient server_ip server_port 第一个是运行方式 要知道要发送的服务端的ip地址 和 端口号
int main(int argc, char *argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
unique_ptr<udpClient> ucli(new udpClient(serverip, serverport));
ucli->initClient();
ucli->run();
return 0;
}
udpClient.hpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
namespace Client
{
using namespace std;
class udpClient
{
public:
udpClient(const string &serverip, const uint16_t &serverport)
: _serverip(serverip), _serverprot(serverport), _sockfd(-1), _quit(false)
{
}
void initClient()
{
// 1. 创建socket
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd == -1)
{
cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
exit(1); // 创建套接字失败直接终止进程
}
cout << "socket success: "
<< " : " << _sockfd << endl; // 成功打印出来
// 2. client要不要bind(必须要!)因为bind就是和系统、网络产生联系. client要不要显示的bind -> 需不需要程序员bind? 名字不重要,重要的是唯一性的,和服务端是不一样的
// 就像宿舍号是几不重要,有就行了。一个端口号只能被一个客户端绑定,就像是服务端是明星,客户端是民众,民众名字不重要
// 服务端是具体的一家公司,比如抖音是字节的,就像一个手机有很多app比如抖音,快手这样的客户端,不能让它们固定bind端口号,万一其他公司也用了用一个端口就冲突其他不来了
// 写服务器的是一家公司,写client是无数家公司 -- 有OS自动形成端口进行bind!不需要自己操作,包括ip地址也不需要,OS自己会处理,当然也可以自己写
// 那么OS在什么时候,如何bind
}
void run()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
server.sin_port = htons(_serverprot);
string messages;
char cmdline[1024];
while (!_quit)
{
cout << "[xxx@我的远程机器 project42]# "; // ls -a -l
//cin >> messages; 读取不到空格
fgets(cmdline, sizeof(cmdline), stdin);
messages = cmdline;
sendto(_sockfd, messages.c_str(), messages.size(), 0, (struct sockaddr *)&server, sizeof(server));
char buffer[1024];
struct sockaddr_in temp;
socklen_t temp_len = sizeof(temp);
size_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &temp_len);
if(n >= 0) buffer[n] = 0; //把这里先当作字符串进行处理,这里是C式风格的
cout << "\n" << buffer << endl;
}
}
~udpClient()
{
}
private:
int _sockfd;
string _serverip;
uint16_t _serverprot;
bool _quit;
};
}
dict.txt
apple:苹果
banana:香蕉
hello:你好
world:世界
success:除了成功,别无选择
nihao:你好
demo3
聊天室
效果一:没上线
没有上线在大厅是看不到的
效果二:上线了
上线就可以看到了
聊天室源码
精简
onlineUser.hpp
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
class User
{
public:
User(const string &ip, const uint16_t &port) : _ip(ip), _port(port)
{
}
~User()
{
}
string ip() { return _ip; }
uint16_t port() { return _port; }
private:
string _ip;
uint16_t _port;
};
class OnlineUser
{
public:
OnlineUser()
{
}
~OnlineUser()
{
}
void addUser(const string &ip, const uint16_t &port)
{
string id = ip + "-" + to_string(port);
users.insert(make_pair(id, User(ip, port)));
}
void delUser(const string &ip, const uint16_t &port)
{
string id = ip + "-" + to_string(port);
users.erase(id);
}
bool isOnline(const string &ip, const uint16_t &port)
{
string id = ip + "-" + to_string(port);
return users.find(id) == users.end() ? false : true;
}
void broadcastMessage(int sockfd, const string &ip, const uint16_t &port, const string &messages)
{
for (auto &user : users)
{
struct sockaddr_in client;
bzero(&client, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(user.second.port());
client.sin_addr.s_addr = inet_addr(user.second.ip().c_str());
string s = ip + "-" + to_string(port) + "# "; //把用户名用ip加端口号的形式显示出来,谁发的拿谁的
s += messages;
sendto(sockfd, s.c_str(), s.size(), 0, (const sockaddr *)&client, sizeof(client));
}
}
private:
unordered_map<string, User> users;
};
udpClient.cc
#include "udpClient.hpp"
#include <memory>
using namespace Client;
static void Usage(string proc)
{
cerr << "\nUsage:\n\t" << proc << " server_ip server_port\n\n"; //命令提示符 demo3 为了测试
}
// ./udpClient server_ip server_port 第一个是运行方式 要知道要发送的服务端的ip地址 和 端口号
int main(int argc, char *argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
unique_ptr<udpClient> ucli(new udpClient(serverip, serverport));
ucli->initClient();
ucli->run();
return 0;
}
udpClient.hpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
namespace Client
{
using namespace std;
class udpClient
{
public:
udpClient(const string &serverip, const uint16_t &serverport)
: _serverip(serverip), _serverprot(serverport), _sockfd(-1), _quit(false)
{
}
void initClient()
{
// 1. 创建socket
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd == -1)
{
cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
exit(1); // 创建套接字失败直接终止进程
}
cout << "socket success: "
<< " : " << _sockfd << endl; // 成功打印出来
}
// 类内线程必须使用静态方法
static void *readMessage(void *args)
{
// 新线程只负责读消息
int sockfd = *(static_cast<int *>(args));
pthread_detach(pthread_self()); // 分离
while (true) //死循环一直读
{
char buffer[1024];
struct sockaddr_in temp;
socklen_t temp_len = sizeof(temp);
size_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&temp, &temp_len);
if (n >= 0)
buffer[n] = 0; // 把这里先当作字符串进行处理,这里是C式风格的
cout << buffer << endl; // demo3
}
return nullptr;
}
void run()
{
// 主线程只负责发消息
pthread_create(&_reader, nullptr, readMessage, (void *)&_sockfd);
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
server.sin_port = htons(_serverprot);
string messages;
char cmdline[1024];
while (!_quit)
{
//cerr << "# "; // demo3 因为使用了管道的重定向,1号被占用,所以使用2号cerr打印提示符
fprintf(stderr, "Enter# ");
fflush(stderr); //刷新
fgets(cmdline, sizeof(cmdline), stdin);
cmdline[strlen(cmdline)-1] = 0; //把反斜杠n处理掉
messages = cmdline;
sendto(_sockfd, messages.c_str(), messages.size(), 0, (struct sockaddr *)&server, sizeof(server));
}
}
~udpClient()
{
}
private:
int _sockfd;
string _serverip;
uint16_t _serverprot;
bool _quit;
pthread_t _reader;
pthread_t _writer;
};
}
udpServer.cc
#include "udpServer.hpp"
#include "onlineUser.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>
#include <stdio.h>
using namespace std;
using namespace Server;
static void Usage(string proc)
{
cout << "Usage:\n\t" << proc << " local_port\n\n"; // 命令提示符
}
OnlineUser onlineuser; // 先定义一个全局的对象
// demo3
void routeMessage(int sockfd, string clientip, uint16_t clientport, string messages)
{
if (messages == "online")onlineuser.addUser(clientip, clientport);
if (messages == "offline")onlineuser.delUser(clientip, clientport);
if (onlineuser.isOnline(clientip, clientport))
{
// 消息的路由
onlineuser.broadcastMessage(sockfd, clientip, clientport, messages);
}
else
{
// 还没有上线,单独给该用户发送
struct sockaddr_in client;
bzero(&client, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(clientport);
client.sin_addr.s_addr = inet_addr(clientip.c_str()); // 字符串转网络(点分十进制 转 数字序列)
string response = "你还没有上线,请先上线,运行: online";
sendto(sockfd, response.c_str(), response.size(), 0, (const sockaddr *)&client, sizeof(client));
}
}
int main(int argc, char *argv[])
{
if (argc != 2) // 这里我们只想要传递两个参数,所以当argc不是3的时候就直接报错退出就行了,注意文件名运行的那个指令也会算进去所以argc +1
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]); // 使用atoi强转,因为argv里面放置的都是字符串,类型需要转换
std::unique_ptr<udpServer> usvr(new udpServer(routeMessage, port)); // 在线聊天室
usvr->initServer();
usvr->start();
return 0;
}
udpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
namespace Server
{
using namespace std;
static const string defaultIp = "0.0.0.0"; // 直接使用这个缺省值,代表监听机器上的所有ip端口
static const int gnum = 1024;
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
OPEN_ERR
}; // 1.命令行输入错误 2.创建套接字错误 3.绑定端口号错误
typedef function<void(int, string, uint16_t, string)> func_t;
class udpServer
{
public:
udpServer(const func_t &cd, const uint16_t &port, const string &ip = defaultIp)
: _callback(cd), _port(port), _ip(ip), _sockfd(-1)
{
}
void initServer() // 初始化
{
// 1.创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd == -1)
{
cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
exit(SOCKET_ERR); // 创建套接字失败直接终止进程
}
cout << "udpServer success: "
<< " : " << _sockfd << endl;
// 2.绑定套接字(port, ip)
// 未来服务器要明确的port, 不能随意改变 -- 变了别人就找不到了
struct sockaddr_in local; // 这里是定义了一个变量,在栈上,而且是用户层,还没有bind之前都是没有产生联系
bzero(&local, sizeof(local)); // 先填 0 再修正
// 注意这下面几个名字是拼接出来的,就是那个##拼接而来的
local.sin_family = AF_INET; // 这里设置与套接字的AF_INET设置意义是不一样的,socket是创建一个网络通信的套接字,在这里是填充一个sockaddr_in的结构体用来网络通信
local.sin_port = htons(_port); // 你如果给别人发信息,你的port和ip要不要发送给对方? 答案是要的
local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1. string->unit32_t 2. htonl(); -> inet_addr
// local.sin_addr.s_addr = htonl(INADDR_ANY); //可以主机转网络,不够也可以不处理,直接赋值也行
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n == -1)
{
cerr << "bin error: " << errno << " : " << strerror(errno) << endl;
exit(BIND_ERR);
}
// UDP Server 的预备工作完成
}
void start() // 启动
{
// 服务器的本质其实就是一个死循环
char buffer[gnum]; // 定义一个数组来充当缓冲区
for (;;)
{
// 读取数据
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 必须设置成这个结构体的大小,当作为输入时,告诉recvfrom的长度的多少
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
// 关系两件事情
// 1.数据是什么? 2. 谁发的?
if (s > 0)
{
buffer[s] = 0;
// 因为是从网络上读取的,所以一定要转,可以使用接口
// inet_ntoa 将一个网络字节序的IP地址(也就是结构体in_addr类型变量)转化为点分十进制的IP地址(字符串)
string clientip = inet_ntoa(peer.sin_addr); // 1.网络序列 2.整数 -> 点分十进制的ip
uint16_t clientport = ntohs(peer.sin_port);
string message = buffer;
cout << clientip << "[" << clientport << "]# " << message << endl;
//我们只把数据读上来就完了吗? 我们要对数据进行处理 -- 所以我们用回调函数的方式来解决
_callback(_sockfd, clientip, clientport, message);
}
}
}
~udpServer() // 析构
{
}
private:
uint16_t _port;
// 实际上,一款网络服务器,不建议指明一个IP,因为一个服务器可以能有多个ip,万一用户使用其他的ip地址来访问该端口号(这里是8080,就收不到了),这也是我们为什么使用0.0.0.0的IP缺省值
string _ip;
int _sockfd;
func_t _callback; // 回调函数,用以处理数据
};
}
下篇预告:基于windows环境在Visual Studio 2022下与Linux云服务器进行通信交流