需要云服务器等云产品来学习Linux的同学可以移步/–>腾讯云<–/官网,轻量型云服务器低至112元/年,优惠多多。(联系我有折扣哦)
前言:在上一篇文中,我们使用了udp协议进行socket编程,实现了简单的客户端和服务端通信,那么实际上,这里可以添加一些业务代码,下面是几个例子
1. 翻译功能(网络翻译)
1.1 代码编写
在之前的代码基础上,我们需要修改的只有服务端业务逻辑部分和客户端run的部分
完整代码如下:
/*udpServer.cc*/
#include "udpServer.hpp"
#include <fstream>
#include <memory>
#include <string>
#include <map>
using namespace Server;
// demo1:网络翻译
/**
* 业务逻辑:
* 1. 在服务器启动之前加载数据文件(对应的翻译),然后启动服务端
* 2. 服务端接收客户端发来的原文,在数据文件中找到对应的翻译内容
* 3. 将翻译内容发回给客户端
* 4. 服务端提供热更新数据文件
*/
// 配置文件:可以提供一个转门的配置文件存放,这里为了方便,就直接写死
const std::string filename = "dict.txt";
const std::string filepath = "./";
std::map<std::string, std::string> dict;
static bool cutString(const std::string &line, std::string *s1, std::string *s2, const std::string &sep)
{
auto pos = line.find(sep);
if (pos == std::string::npos)
return false;
*s1 = line.substr(0, pos);
*s2 = line.substr(pos + sep.size());
return true;
}
static void initDict()
{
std::string file = filepath + filename;
std::ifstream in(file.c_str(), std::ios::binary); // 二进制读取的方式打开文件
if (!in.is_open())
{
std::cerr << "open file error " << errno << " : " << strerror(errno) << std::endl;
exit(1);
}
std::string line;
std::string key, value;
while (getline(in, line))
{
if (cutString(line, &key, &value, ":"))
{
dict.insert(std::make_pair(key, value));
}
}
in.close();
std::cout << "load dict success" << std::endl;
}
void handleMessage(int sockfd, std::string clientIp, uint16_t clientPort, std::string message)
{
// 在这里做一些服务端需要完成的任务
std::string response_message;
auto it = dict.find(message);
if(it != dict.end())
response_message = it->second;
else
response_message = "can not find this word";
// 将结果返回给客户端
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, (struct sockaddr *)&client, sizeof(client));
}
// 调用的指令 :./udpServer port
int main(int argc, char *argv[])
{
// 解析指令
if (argc != 2)
{
Usage();
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
// 加载配置文件
initDict();
// 创建对象,进行通信
std::unique_ptr<udpServer> usvr(new udpServer(handleMessage, port));
usvr->initServer(); // 初始化服务进程
usvr->start(); // 开始监听
return 0;
}
/*udpServer.hpp*/
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <string.h>
#include <cerrno>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
namespace Server
{
// using func_t = std::function<void(std::string, uint16_t, std::string)>;
typedef std::function<void(int, std::string, uint16_t, std::string)> func_t;
static void Usage()
{
std::cout << "\nUsage:\n\t./udpServer local_port\n\n";
}
enum // 枚举出错类型
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR
};
const static std::string defaultIP = "0.0.0.0";
const int gnum = 1024; // 处理任务的缓冲区大小
class udpServer
{
public:
udpServer(const func_t &cb, const uint16_t &port, const std::string &ip = defaultIP)
:_callback(cb), _port(port), _ip(ip) {}
// 这里初始化要做的事情有两件: 1. 创建sockfd 2.bind端口号和ip
void initServer()
{
_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 参数1:这里使用AF_INET表示使用IPv4进行网络通信;参数2:我们这里使用UDP策略;参数3:这里使用0表示默认
if (_sockfd == -1) // 差错处理
{
std::cerr << "socket error " << errno << strerror(errno) << std::endl;
exit(SOCKET_ERR);
}
// std::cout << "socket sucess : " << _sockfd << std::endl;
// 在当前函数的栈帧上创建一个local对象,设置相关属性,然后将相关属性bind到系统内核中
struct sockaddr_in local; // 这里struct sockaddr_in类型需要头文件arpa/inet.h
bzero(&local, sizeof(local)); // 在填充数据之前首先将对象内部元素清空,这里使用bzero
local.sin_family = AF_INET; // 设定协议家族
local.sin_port = htons(_port); // 设置端口号,这里端口号需要首先转换成网络端口号
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 设置ip,这里的ip是string类型,但是实际在传输的时候使用的是整型,所以需要转换,这里使用inet_addr
local.sin_addr.s_addr = INADDR_ANY;
// inet_addr的作用有两个: 1.string -> uint32_t; 2. htonl()
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); // 将local设置到内核中,即bind
if (n == -1)
{
std::cerr << "bind error " << errno << strerror(errno) << std::endl;
exit(BIND_ERR);
}
// 至此初始化的操作完成
}
void start() // 让服务器开始跑起来
{
// 服务器的本质是一个死循环,在循环内部处理收到的任务
char buffer[gnum];
while (true)
{
// 1. 读取数据
struct sockaddr_in peer; // 定义一个变量用于接收数据
socklen_t len = sizeof(peer);
ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
// a. 数据是什么 b. 谁发的
if (n > 0)
{
buffer[n] = 0;
std::string clientIp = inet_ntoa(peer.sin_addr); // 转换网络字节序, 点分十进制
uint16_t clientPort = ntohs(peer.sin_port);
std::string message = buffer;
std::cout << clientIp << "[" << clientPort << "]# " << message << std::endl;
// 2. 处理任务
_callback(_sockfd, clientIp, clientPort, message);
}
}
}
private:
// 成员变量分析:作为一个服务端进程,我们首先需要一个端口号port和一个本地ip
// 还需要有一个文件描述符sockfd,用于进行通信(网络通信是基于文件的,所以使用的都是文件的一套内容,包括fd)
int _sockfd; // socket文件描述符
std::string _ip; // 本地ip
uint16_t _port; // 服务进程端口号
func_t _callback;
};
}
/*udpClient.hpp*/
#pragma once
#include <iostream>
#include <string>
#include <string.h>
#include <cerrno>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
namespace Client
{
enum // 枚举出错类型
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
};
class udpClient
{
public:
udpClient(const std::string &serverIp, const uint16_t &serverPort)
: _serverIp(serverIp), _serverPort(serverPort), _sockfd(-1), _quit(false) {}
void initClient()
{
// 1.创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd == -1)
{
std::cerr << "socket error: " << errno << " : " << strerror(errno) << std::endl;
exit(SOCKET_ERR);
}
std::cout << "socket success: " << _sockfd << std::endl;
}
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(_serverPort);
std::string message;
#define NUM 1024
char buffer[NUM];
while(!_quit)
{
std::cout << "Please Enter# ";
std::cin >> message;
// 1. 向服务端发送
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
// 2. 接收服务端返回的结果
struct sockaddr_in peer; // 用于接收发送端的相关信息
socklen_t len = sizeof peer;
ssize_t n = recvfrom(_sockfd, buffer, sizeof buffer, 0, (struct sockaddr *)&peer, &len);
if(n >= 0) buffer[n] = 0;
std::cout << "服务器翻译结果# " << buffer << std::endl;
}
}
private:
int _sockfd; // 套接字
std::string _serverIp; // 服务端IP
uint16_t _serverPort; // 服务端端口号
bool _quit; // 客户端退出标志
};
} // namespace Client
/*udpCilent.cc*/
#include "udpClient.hpp"
#include <string>
#include <memory>
using namespace Client;
static void Usage(std::string proc)
{
std::cout << "\nUsage:\n\t" << proc << " server_ip server_port\n\n";
}
// ./udpClient server_ip server_port
int main(int argc, char *argv[])
{
// 解析指令
if (argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverIp = argv[1];
uint16_t serverPort = atoi(argv[2]);
std::unique_ptr<udpClient> ucli(new udpClient(serverIp, serverPort));
ucli->initClient();
ucli->run();
return 0;
}
1.2 测试
2. 命令行解析(远程shell)
实际上命令行解析需要做的事情就是客户端发送命令行数据,然后服务器接收,进行解析处理,然后将结果再发送回客户端
/* udpServer.hpp */
#pragma once
#include <string>
#include <iostream>
#include <functional>
#include <cassert>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
namespace Server
{
using func_t = std::function<void(int, std::string, uint16_t, std::string)>;
class udpServer
{
public:
udpServer(func_t cp, uint16_t &port)
: _callback(cp), _port(port), _sockfd(-1) {}
void initServer()
{
// 1. 创建socket
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
std::cerr << "socket error" << std::endl;
exit(1);
}
// 2. bind
struct sockaddr_in local;
bzero(&local, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
int n = bind(_sockfd, (sockaddr *)&local, sizeof local);
assert(n == 0);
(void)n;
}
void start()
{
#define NUM 1024
char buffer[NUM];
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof peer;
int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if(n > 0)
{
buffer[n] = 0;
std::string clientIP = inet_ntoa(peer.sin_addr);
uint16_t clientPort = ntohs(peer.sin_port);
std::cout << clientIP << "[" << clientPort << "]" << buffer << std::endl;
_callback(_sockfd, clientIP, clientPort, buffer);
}
}
}
private:
int _sockfd;
uint16_t _port;
func_t _callback;
};
}
/* udpServer.cc */
#include "udpServer.hpp"
#include <iostream>
#include <memory>
using namespace Server;
static void remoteShell(int sockfd, std::string clientIp, uint16_t clientPort, std::string cmd)
{
// cmd : ls -a -l
std::string response;
FILE* fp = popen(cmd.c_str(), "r");
if(fp == nullptr) response = cmd + "exec fail";
char line[1024];
while(fgets(line, sizeof line, fp))
{
response += line;
}
pclose(fp);
// 开始返回
struct sockaddr_in client;
client.sin_family = AF_INET;
client.sin_addr.s_addr = inet_addr(clientIp.c_str());
client.sin_port = htons(clientPort);
sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&client, sizeof client);
}
static void Usage(std::string proc)
{
std::cout << "\n\tUsage: " << proc << " local_port\n";
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t port = atoi(argv[1]);
std::unique_ptr<udpServer> usvr(new udpServer(remoteShell, port));
usvr->initServer();
usvr->start();
return 0;
}
/* udpClient.hpp */
#pragma once
#include <string>
#include <iostream>
#include <functional>
#include <cassert>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
namespace Client
{
class udpClient
{
public:
udpClient(const std::string &serverIP, const uint16_t &serverPort)
: _serverIP(serverIP), _serverPort(serverPort), _sockfd(-1)
{
}
void initClient()
{
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
std::cerr << "create socket error" << std::endl;
exit(1);
}
// OS在发起通信的时候自动bind
}
void run()
{
struct sockaddr_in server;
bzero(&server, sizeof server);
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverIP.c_str());
server.sin_port = htons(_serverPort);
char buffer[1024];
std::string message;
while(true)
{
std::cout << "Enter# ";
std::getline(std::cin, message);
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof server);
struct sockaddr_in peer;
bzero(&peer, sizeof peer);
socklen_t len = sizeof peer;
int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)& peer, &len);
if(n > 0) buffer[n] = 0;
std::cout << "服务器运行结果# \n" << buffer << std::endl;
buffer[0] = 0;//清空上次运行结果
}
}
~udpClient()
{
}
private:
int _sockfd;
std::string _serverIP;
uint16_t _serverPort;
};
}
/* udpClient.cc */
#include "udpClient.hpp"
#include <memory>
using namespace Client;
static void Usage(std::string proc)
{
std::cout << "\n\tUsage: " << proc << " local_ip local_port\n";
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string IP = argv[1];
uint16_t Port = atoi(argv[2]);
std::unique_ptr<udpClient> uclt(new udpClient(IP, Port));
uclt->initClient();
uclt->run();
return 0;
}
3. 网络聊天室
对于网络聊天室,实现逻辑就是:首先需要在线用户的管理,当任意一个在线用户发送消息的时候,把消息广播给所有在线用户
/* User.hpp */
#pragma once
#include <string>
#include <map>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class User
{
public:
User(const std::string &ip, const uint16_t &port)
: _ip(ip), _port(port)
{
}
std::string ip() { return _ip; }
uint16_t port() { return _port; }
private:
std::string _ip;
uint16_t _port;
};
class OnlineUser
{
public:
void addUser(const std::string &ip, const uint16_t &port)
{
std::string id = ip + "-" + std::to_string(port);
_users.insert(std::make_pair(id, User(ip, port)));
}
void delUser(const std::string &ip, const uint16_t &port)
{
std::string id = ip + "-" + std::to_string(port);
_users.erase(id);
}
bool isOnline(const std::string &ip, const uint16_t &port)
{
std::string id = ip + "-" + std::to_string(port);
return _users.find(id) == _users.end() ? false : true;
}
void boardcastMessage(int sockfd, const std::string &ip, const uint16_t &port, std::string &massage) // 给当前在线的所有用户都发送消息(广播以实现群聊天)
{
for(auto &user : _users)
{
struct sockaddr_in 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());
std::string s = "[" + ip + "-" + std::to_string(port) + "]# ";
s += massage;
sendto(sockfd, s.c_str(), s.size(), 0, (struct sockaddr*)&client, sizeof client);
}
}
private:
std::map<std::string, User> _users;
};
/* udpServer.hpp */
#pragma once
#include <string>
#include <iostream>
#include <functional>
#include <cassert>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
namespace Server
{
using func_t = std::function<void(int, std::string, uint16_t, std::string)>;
class udpServer
{
public:
udpServer(func_t cp, uint16_t &port)
: _callback(cp), _port(port), _sockfd(-1) {}
void initServer()
{
// 1. 创建socket
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
std::cerr << "socket error" << std::endl;
exit(1);
}
// 2. bind
struct sockaddr_in local;
bzero(&local, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
int n = bind(_sockfd, (sockaddr *)&local, sizeof local);
assert(n == 0);
(void)n;
}
void start()
{
#define NUM 1024
char buffer[NUM];
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof peer;
int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if(n > 0)
{
buffer[n] = 0;
std::string clientIP = inet_ntoa(peer.sin_addr);
uint16_t clientPort = ntohs(peer.sin_port);
std::cout << clientIP << "[" << clientPort << "]" << buffer << std::endl;
_callback(_sockfd, clientIP, clientPort, buffer);
}
}
}
private:
int _sockfd;
uint16_t _port;
func_t _callback;
};
}
/* udpServer.cc */
#include "udpServer.hpp"
#include "User.hpp"
#include <iostream>
#include <memory>
using namespace Server;
OnlineUser onlineUser;
static void remoteMessage(int sockfd, std::string clientIp, uint16_t clientPort, std::string message)
{
// 在进入聊天室之前需要先输入online
if(message == "online") onlineUser.addUser(clientIp, clientPort);
if(message == "offline") onlineUser.delUser(clientIp, clientPort); // 退出聊天室
if(onlineUser.isOnline(clientIp, clientPort))
{
//在线,进行消息的路由
onlineUser.boardcastMessage(sockfd, clientIp, clientPort, message);
}
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());
std::string response = "你还没上线,请发送online上线";
sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&client, sizeof(client));
}
}
static void Usage(std::string proc)
{
std::cout << "\n\tUsage: " << proc << " local_port\n";
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t port = atoi(argv[1]);
std::unique_ptr<udpServer> usvr(new udpServer(remoteMessage, port));
usvr->initServer();
usvr->start();
return 0;
}
/* udpClient.hpp */
#pragma once
#include <string>
#include <iostream>
#include <functional>
#include <cassert>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
namespace Client
{
class udpClient
{
public:
udpClient(const std::string &serverIP, const uint16_t &serverPort)
: _serverIP(serverIP), _serverPort(serverPort), _sockfd(-1)
{
}
void initClient()
{
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
std::cerr << "create socket error" << std::endl;
exit(1);
}
// OS在发起通信的时候自动bind
}
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;
std::cout << buffer << std::endl;
}
return nullptr;
}
void run()
{
pthread_create(&_reader, nullptr, readMessage, &_sockfd);
struct sockaddr_in server;
bzero(&server, sizeof server);
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverIP.c_str());
server.sin_port = htons(_serverPort);
char buffer[1024];
std::string message;
while(true)
{
std::cout << "Enter# ";
std::getline(std::cin, message);
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof server);
}
}
~udpClient()
{
}
private:
int _sockfd;
std::string _serverIP;
uint16_t _serverPort;
pthread_t _reader;
};
}
/* udpClient.cc */
#include "udpClient.hpp"
#include <memory>
using namespace Client;
static void Usage(std::string proc)
{
std::cout << "\n\tUsage: " << proc << " local_ip local_port\n";
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string IP = argv[1];
uint16_t Port = atoi(argv[2]);
std::unique_ptr<udpClient> uclt(new udpClient(IP, Port));
uclt->initClient();
uclt->run();
return 0;
}
4. Windows客户端和Linux服务端
/* Window端代码,编译环境 Windows11, Visual Studio 2022 */
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)
#include <iostream>
#include <string>
#include <cstring>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib") // 表示需要链接库 ws2_32.lib
using namespace std;
// 写死端口号和ip
uint16_t serverport = 8080;
string serverip = "8.134.152.121";
int main()
{
// 创建WSAData对象并初始化
WSAData wsd;
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
cout << "WSAStartup Error = " << WSAGetLastError() << endl;
return 0;
}
else
{
cout << "WSAStartup Success" << endl;
}
// 创建socket
SOCKET csock = socket(AF_INET, SOCK_DGRAM, 0);
if (csock == SOCKET_ERROR)
{
cout << "socket Error = " << WSAGetLastError() << endl;
return 1;
}
else
{
cout << "socket success" << endl;
}
// 创建sockaddr_in对象进行通信
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
#define NUM 1024
char inbuffer[NUM];
string line;
while (true)
{
cout << "Please Enter#";
getline(cin, line);
int n = sendto(csock, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server));
if (n < 0)
{
cerr << "sendto error!" << endl;
break;
}
struct sockaddr_in peer;
int peerlen = sizeof(peer);
inbuffer[0] = 0;
n = recvfrom(csock, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&peer, &peerlen);
if (n > 0)
{
inbuffer[n] = 0;
cout << "server 返回的消息是#" << inbuffer << endl;
}
else break;
}
// 回收相关资源
closesocket(csock);
WSACleanup();
return 0;
}
/* Linux端,编译环境 centOS7 g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)*/
/* User.hpp */
#pragma once
#include <string>
#include <map>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class User
{
public:
User(const std::string &ip, const uint16_t &port)
: _ip(ip), _port(port)
{
}
std::string ip() { return _ip; }
uint16_t port() { return _port; }
private:
std::string _ip;
uint16_t _port;
};
class OnlineUser
{
public:
void addUser(const std::string &ip, const uint16_t &port)
{
std::string id = ip + "-" + std::to_string(port);
_users.insert(std::make_pair(id, User(ip, port)));
}
void delUser(const std::string &ip, const uint16_t &port)
{
std::string id = ip + "-" + std::to_string(port);
_users.erase(id);
}
bool isOnline(const std::string &ip, const uint16_t &port)
{
std::string id = ip + "-" + std::to_string(port);
return _users.find(id) == _users.end() ? false : true;
}
void boardcastMessage(int sockfd, const std::string &ip, const uint16_t &port, std::string &massage) // 给当前在线的所有用户都发送消息(广播以实现群聊天)
{
for(auto &user : _users)
{
struct sockaddr_in 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());
std::string s = "[" + ip + "-" + std::to_string(port) + "]# ";
s += massage;
sendto(sockfd, s.c_str(), s.size(), 0, (struct sockaddr*)&client, sizeof client);
}
}
private:
std::map<std::string, User> _users;
};
/* udpServer.hpp */
#pragma once
#include <string>
#include <iostream>
#include <functional>
#include <cassert>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
namespace Server
{
using func_t = std::function<void(int, std::string, uint16_t, std::string)>;
class udpServer
{
public:
udpServer(func_t cp, uint16_t &port)
: _callback(cp), _port(port), _sockfd(-1) {}
void initServer()
{
// 1. 创建socket
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
std::cerr << "socket error" << std::endl;
exit(1);
}
// 2. bind
struct sockaddr_in local;
bzero(&local, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
int n = bind(_sockfd, (sockaddr *)&local, sizeof local);
assert(n == 0);
(void)n;
}
void start()
{
#define NUM 1024
char buffer[NUM];
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof peer;
int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if(n > 0)
{
buffer[n] = 0;
std::string clientIP = inet_ntoa(peer.sin_addr);
uint16_t clientPort = ntohs(peer.sin_port);
std::cout << clientIP << "[" << clientPort << "]" << buffer << std::endl;
_callback(_sockfd, clientIP, clientPort, buffer);
}
}
}
private:
int _sockfd;
uint16_t _port;
func_t _callback;
};
}
/* udpServer.cc */
#include "udpServer.hpp"
#include "User.hpp"
#include <iostream>
#include <memory>
using namespace Server;
OnlineUser onlineUser;
static void remoteMessage(int sockfd, std::string clientIp, uint16_t clientPort, std::string message)
{
// 在进入聊天室之前需要先输入online
if(message == "online") onlineUser.addUser(clientIp, clientPort);
if(message == "offline") onlineUser.delUser(clientIp, clientPort); // 退出聊天室
if(onlineUser.isOnline(clientIp, clientPort))
{
//在线,进行消息的路由
onlineUser.boardcastMessage(sockfd, clientIp, clientPort, message);
}
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());
std::string response = "你还没上线,请发送online上线";
sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&client, sizeof(client));
}
}
static void Usage(std::string proc)
{
std::cout << "\n\tUsage: " << proc << " local_port\n";
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t port = atoi(argv[1]);
std::unique_ptr<udpServer> usvr(new udpServer(remoteMessage, port));
usvr->initServer();
usvr->start();
return 0;
}
本节完…