实现英汉互译
思路
我们在客户端发英文,服务端做翻译工作,让翻译好的中文再次发给我们的客户端,然后打印出来。
服务端代码
翻译的操作
创建一个txt文件里面包含英汉互译的数据
dict.txt
banana:香蕉
apple:苹果
pig:猪
beef:牛肉
hello:你好
对txt中的数据进行操作
分割函数
将英汉通过冒号分开。
// 分割函数
static bool cutString(const string &target, string *s1, string *s2, const string &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;
}
将文件数据插入map里面
// 按行将文件里面的数据给插入到map里面
static void initDict()
{
dict.clear();
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 success" << endl;
}
重新加载文件
通过捕捉2号(ctrl c)信号来进行重新加载文件。
void reload(int signo)
{
(void)signo;
initDict();
}
// ./udpClient server_ip server_port
int main(int argc, char *argv[])
{
signal(2, reload); // 通过发2号信号来使dict.txt中的数据进行更新
}
网络通信的操作
将翻译后的数据发送给客户端
void handlerMessage(int sockfd, string message, uint16_t clientport, string clientip)
{
// 就可以对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());
// 在服务端收到客户端数据的时候我们获取到了客户端的端口号和ip,因此回调到了这个函数中,
// 此时我们就可以使用客户端的端口号和ip来给客户端发送翻译后的信息
sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr *)&client, sizeof(client));
}
客户端代码
创建socket
void initClient()
{
// 1. 创建socket
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd == -1)
{
cerr << "socket error: " << errno << " : " << strerror(errno) << endl;
exit(2);
}
cout << "socket success: " << _sockfd << endl;
// 2. client要不要bind[不需要的] , client要不要显示的bind,需不需要程序员自己bind? 不需要
// 写服务器的一家公司,写客户端是无数家公司 -- 因此让OS自动形成端口进行bind! -- 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(_serverport);
string message;
char buffer[1024];
while (!_quit)
{
// fprintf(stderr, "Please Enter# ");
// fflush(stderr);
// fgets(buffer, sizeof(buffer), stdin);
cout << "Please Enter# ";
cin >> message;
// buffer[strlen(buffer) - 1] = 0;
// message = buffer;
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
char recv_buffer[1024];
// 接受翻译后的信息
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
//
ssize_t s = recvfrom(_sockfd, recv_buffer, sizeof(recv_buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
// 读取数据
buffer[s] = 0;
}
cout << "服务器翻译成# " << recv_buffer << endl;
// recv_buffer[0] = 0;
memset(recv_buffer, 0, sizeof(buffer)); // 清空缓冲区
}
}
成果展示
程序替换
思路
主要使用popen接口同时实现管道+创建子进程+程序替换的功能。将我们客户端输入的命令信息发送给服务端。服务端将该命令经过popen接口让它执行命令运行出来的结果放在一个文件中,然后在将文件中的内容读取出来发送给客户端。
popen接口
#include <stdio.h>
FILE *popen(const char *command,const char *type); // 相当于pipe+fork+exec*
int pclose(FILE *stream);
参数
const char *command
未来要执行的命令字符串:
ls -a -l 等 可以将这些命令执行的结果返回到一个文件当中
const char *type
对文件的操作方式"r" "w" "a" 等
返回值
返回 nullptr 说明执行失败了
服务端代码
void execCommand(int sockfd, string cmd, uint16_t clientport, string clientip)
{
// 1. com解析,ls -a -l
// 2. 如果必要,可能需要fork,exec*
if (cmd.find("rm") != string::npos || cmd.find("mv") != string::npos || cmd.find("remdir") != 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());
// 在服务端收到客户端数据的时候我们获取到了客户端的端口号和ip,因此回调到了这个函数中,
// 此时我们就可以使用客户端的端口号和ip来给客户端发送翻译后的信息
sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&client, sizeof(client));
}
成果展示
多线程聊天室
思路
通过在客户端的角度创建多线程,一个线程进行发消息,一个线程进行读消息。这样我们就能让多个线程(用户)一起聊天。
让用户的id(ip + "-" + port)作为key值,User作为value值。来形成一个个unordered_map类型的容器。如果用户上线了就将该对象添加到unordered_map容器里面,下线就从unordered_map容器删除。
通过输入"online"来将该用户添加到unordered_map容器里面,意味上线。
通过输入"offline"来将该用户从unordered_map容器里面删除,意味下线。
我们服务端收到客户端发来的数据的时候通过isOnline函数来判断该用户是否在unordered_map容器里面,如果在就将该信息发送给客户端并且客户端接受后打印出来,如果不在直接打印"未上线"。
用户信息代码代码 onlineUser.hpp
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.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 &message)
{
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());
// 将用户id也加入
string s = ip + "-" + to_string(port) + "# ";
s += message;
sendto(sockfd, s.c_str(), s.size(), 0, (struct sockaddr *)&client, sizeof(client));
}
}
private:
unordered_map<string, User> users;
};
服务端代码
// demo 3
void routeMessage(int sockfd, string message, uint16_t clientport, string clientip)
{
// 判断是否上线
// 上线就将该用户添加到onlineuser
if (message == "online")
onlineuser.addUser(clientip, clientport);
// 下线就将该用户在onlineuser中删除
if ((message == "offline"))
onlineuser.delUser(clientip, clientport);
// 如果以下if为真 那么说明用户已经上线,因此需要将用户发的信息进行路由
if (onlineuser.isOnline(clientip, clientport))
{
// 消息的路由
onlineuser.broadcastMessage(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());
string response_message = "你还没有上线,请先上线,运行: online";
sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr *)&client, sizeof(client));
}
客户端代码(多线程)
static void *readMessage(void *args)
{
int sockfd = *(static_cast<int *>(args));
pthread_detach(pthread_self());
while (true)
{
char buffer_recv[1024];
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(sockfd, buffer_recv, sizeof(buffer_recv) - 1, 0, (struct sockaddr *)&peer, &len);
if (s >= 0)
buffer_recv[s] = 0;
cout << buffer_recv << endl;
}
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(_serverport);
string message;
char buffer[1024];
while (!_quit)
{
fprintf(stderr, "Please Enter# ");
fflush(stderr);
fgets(buffer, sizeof(buffer), stdin);
buffer[strlen(buffer) - 1] = 0;
message = buffer;
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
}