文章目录
- 翻译功能
- 命令行解析
- 网络聊天室
- UDP之Windows与Linux
翻译功能
我们写的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 1024char 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;
}