翻译功能
我们写的UDP服务端并不是只接收到数据就完了,还需要进行处理任务。
我们可以在服务端udpServer.hpp中设置一个回调函数 _callback,具体的逻辑通过udpServer.cc中由外部进行传入
代码如下所示:
void start()
{
// 服务器的本质其实就是一个死循环
char buffer[gnum];
for(;;)
{
// 读取数据
struct sockaddr_in peer;
socklen_t len = sizeof(peer); //必填
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
if(s > 0)
{
buffer[s] = 0;
string clientip = inet_ntoa(peer.sin_addr);
uint16_t clientport = ntohs(peer.sin_port);
string message = buffer;
cout << clientip <<"[" << clientport << "]# " << message << endl;//把收到的消息打印出来
_callback(_sockfd, clientip, clientport, message);
}
}
}
翻译功能:客户端输入一个单词,然后发送给服务端,然后去接收服务端翻译之后的结果。
首先提供一个字典dict:把鞋有英语和汉语对应的文件dicTxt加载进我们的unordered_map
词典,此时的unordered_map就保存了字典的内容:
dict.txt:外部文件可由自己填写补充,这里只是给个样例测试代码而已
apple:苹果
world:世界
hello:你好
goodman:你是一个好人
const std::string dictTxt="./dict.txt";//文件
unordered_map<string, string> dict;//字典
下面,初始化我们的字典:打开文件,把文件中的每一行切分成key和value,也就是英文和中文,然后把结果插入到dict中,也就是把结果放进dict中,代码如下:
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;
}
static void initDict()
{
ifstream in(dictTxt, std::ios::binary);
if(!in.is_open())
{
cerr << "open file " << dictTxt << " error" << endl;
exit(OPEN_ERR);
}
string line;
std::string key, value;
while(getline(in, line))//读取文件
{
if(cutString(line, &key, &value, ":"))
{
dict.insert(make_pair(key, value));
}
}
in.close();
cout << "load dict success" << endl;
}
在udpServer.cc中通过函数handlerMessage处理数据,然后在把处理的结果反馈给客户端:
void handlerMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
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, (struct sockaddr*)&client, sizeof(client));
}
客户端udpClient.hpp输入消息cin并发送sendto给服务端,然后服务端udpServer.hpp调用回调函数对消息进行翻译,翻译完成后把最终的结果在传送sendto给客户端,客户端udpClient.hpp在接收recvfrom翻译之后的结果,最终把翻译结果打印出来即可:
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 cmdline[1024];
while (!_quit)
{
cout<<"Please Enter#";
cin>>message;
sendto(_sockfd,message.c_str(),message.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;
cout<<"服务器的翻译结果# "<<buffer<<endl;
}
}
下面,进行测试运行结果:
//udpServer.cc
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
initDict();
std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port));
}
//udpClient.cc
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;
}
先启动服务端:
在启动客户端输入信息,给服务端传送消息,服务端收到消息打印出来,并将翻译完成的结果返回给客户端,客户端再把翻译后的结果打印出来,这就是上面所说的整个过程:
命令行解析
借用popen接口:(功能相当于pipe+fork,exec*)
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
command
:传递进来的字符串,比如 ls -a -l
;type
:以什么方式打开文件(r/w/a),我们通过一个函数execComand进行调用即可,只需要在udpServer.cc文件中修改传入的函数即可实现该功能:
void execCommand(int sockfd, string clientip, uint16_t clientport, string cmd)
{
//1. cmd解析,ls -a -l
//先禁止一下非法操作,防止有人搞破坏
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, (struct sockaddr*)&client, sizeof(client));
}
测试运行:
//udpServer.cc
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
std::unique_ptr<udpServer> usvr(new udpServer(execCommand, port));
}
网络聊天室
我们需要去管理用户,而对于每个用户我们用IP和port来标识唯一性,那么多的用户我们可以用哈希表来进行统一管理:
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());
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;
};
在回调函数中,如果收到的消息是online,就把用户添加进哈希表。如果是offline,就从哈希表中删除
if (message == "online") onlineuser.addUser(clientip, clientport);
if (message == "offline") onlineuser.delUser(clientip, clientport);
OnlineUser onlineuser;
void routeMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
if (message == "online") onlineuser.addUser(clientip, clientport);
if (message == "offline") onlineuser.delUser(clientip, clientport);
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 = "你还没有上线,请先上线,运行: online";
sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&client, sizeof(client));
}
}
多线程:客户端udpClient.hpp不能立即收到消息打印出来,为了解决这个问题我们可以使用多线程,一个线程专门接收消息,一个线程专门发送消息:让主线程负责发送消息,子线程负责接收消息:
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;
cout << buffer << 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 cmdline[1024];
while (!_quit)
{
fprintf(stderr, "Enter# ");
fflush(stderr);
fgets(cmdline, sizeof(cmdline), stdin);
cmdline[strlen(cmdline)-1] = 0;
message = cmdline;
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
}
}
UDP之Windows与Linux
UDP的实现可以在不同的平台上进行交互的,在这里我们以Linux充当服务端,windows充当客户端,进行连通
windows端代码:
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)
#include <iostream>
#include <string>
#include <cstring>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
uint16_t serverport = 8080;
string serverip = "8.134.152.121";
int main()
{
WSAData wsd;
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
cout << "WSAStartup Error = " << WSAGetLastError() << endl;
return 0;
}
else
{
cout << "WSAStartup Success" << endl;
}
SOCKET csock = socket(AF_INET, SOCK_DGRAM, 0);
if (csock == SOCKET_ERROR)
{
cout << "socket Error = " << WSAGetLastError() << endl;
return 1;
}
else
{
cout << "socket success" << endl;
}
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端代码:
pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cerrno>
#include <cstring>
#include <functional>
#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";
static const int gnum = 1024;
enum {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,OPEN_ERR};
typedef function<void (int,string,uint16_t,string)> func_t;
class udpServer
{
public:
udpServer(const func_t&cb,const uint16_t&port,const string&ip = defaultIp)
:_callback(cb),_port(port),_ip(ip),_sockfd(-1)
{}
void initServer()
{
_sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(_sockfd == -1)
{
cerr<<"sdocket error: "<<errno<<" : "<<strerror(errno)<<endl;
exit(SOCKET_ERR);
}
cout<<"socket success: "<<" : "<<_sockfd<<endl;
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip.c_str());
int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
if(n == -1)
{
cerr<<"bind errpr: "<<errno<<" : "<<strerror(errno)<<endl;
exit(BIND_ERR);
}
}
void start()
{
char buffer[gnum];
for(;;)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(s>0)
{
buffer[s] = 0;
string clientip = inet_ntoa(peer.sin_addr);
uint16_t clientport = ntohs(peer.sin_port);
string message = buffer;
cout<<clientip<<"["<<clientport<<"]#"<< message<<endl;
_callback(_sockfd,clientip,clientport,message);
}
}
}
~udpServer()
{
}
private:
int _sockfd;
uint16_t _port;
string _ip;
func_t _callback;
};
}
//udpServer.cc
#include <iostream>
#include <memory>
#include "udpServer.hpp"
using namespace std;
using namespace Server;
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<" locla_port\n\n";
}
void handlerMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
string response_message = message;
response_message+=" [server echo]";
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));
}
int main(int argc,char*argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage,port));
usvr->initServer();
usvr->start();
return 0;
}