一、引入
在上一章【網(wǎng)絡編程】demo版UDP網(wǎng)絡服務器實現(xiàn)實現(xiàn)了客戶端和服務端之間的數(shù)據(jù)的發(fā)送與接收,上一章我們是直接讓服務端把接收到的數(shù)據(jù)打印出來。
但是服務端并不是只接收到數(shù)據(jù)就完了,它還要處理任務。
所以我們可以在服務端設置一個回調(diào)函數(shù):
typedef std::function<void(std::string, uint16_t, std::string)> func_t;
用來處理接收到的信息。
二、翻譯軟件實現(xiàn)
2.1 加載字典
要實現(xiàn)客戶端發(fā)送一個單詞,接收到服務端翻譯的結(jié)果。
那么首先需要把英語和漢語加載進unordered_map
詞典中。
static std::unordered_map<std::string, std::string> Dict;
從文件中把數(shù)據(jù)加載進詞典中:
const std::string path = "./Dict.txt";
static std::unordered_map<std::string, std::string> Dict;
// 分割字符串
static bool cutstring(std::string s, std::string *key, std::string *val)
{
size_t pos = s.find(":");
if(pos == std::string::npos)
{
return false;
}
*key = s.substr(0, pos);
*val = s.substr(pos + 1);
return true;
}
static void InitDict()
{
std::ifstream ifs(path);
if(!ifs.is_open())// 打開失敗
{
std::cout << "ifstream open fail" << std::endl;
exit(1);
}
std::string sline;
std::string key, val;
while(getline(ifs, sline))// 按行讀取
{
if(cutstring(sline, &key, &val))
{
Dict.insert({key, val});
}
else
{
std::cout << "cutstring fail" << std::endl;
exit(1);
}
}
ifs.close();
std::cout << "Dict Load Success\n";
}
2.2 處理數(shù)據(jù)并傳遞給用戶端
接下來處理數(shù)據(jù)并且要把處理的結(jié)果反饋給用戶端。
處理數(shù)據(jù)比較簡單:
std::string res;
auto it = Dict.find(msg);
if(it == Dict.end())
{
res = "not find";
}
else
{
res = it->second;
}
接下來要把res
發(fā)送給客戶端,經(jīng)過前面的學習sendto
需要構建一個結(jié)構體表示要傳給誰。
構建完結(jié)構體使用sendto
的時候發(fā)現(xiàn)要有文件描述符,所以調(diào)用回調(diào)函數(shù)的時候要把_sockfd
傳遞進去。
// 回調(diào)方法
void handler(int _sockfd, std::string ip, uint16_t port, std::string msg)
{
std::string res;
auto it = Dict.find(msg);
if(it == Dict.end())
{
res = "not find";
}
else
{
res = it->second;
}
struct sockaddr_in si;
bzero(&si, sizeof si);
si.sin_family = AF_INET;
si.sin_port = htons(port);
si.sin_addr.s_addr = inet_addr(ip.c_str());
sendto(_sockfd, res.c_str(), res.size(), 0, (struct sockaddr*)&si, sizeof si);
}
2.3 客戶端獲取結(jié)果
// 獲取結(jié)果
char buf[1024];
sockaddr_in tmp;
socklen_t tmplen = sizeof tmp;
size_t n = recvfrom(_sockfd, buf, sizeof buf - 1, 0, (struct sockaddr*)&tmp, &tmplen);
if(n > 0) buf[n] = 0;
std::cout << "翻譯結(jié)果:" << buf << "\n";
2.4 結(jié)果
客戶端:
服務端:
2.5 執(zhí)行命名功能
現(xiàn)在我們可以做一個小小的改動,不讓服務端做翻譯了,而是處理任務,例如我們發(fā)送pwd,他要返回服務端此時的路徑。
這里其他的邏輯都不用變,只需要修改回調(diào)函數(shù)即可。
介紹一個命令行解析接口:popen
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
先回憶一下之前手寫的建議shell【linux】進程控制詳述,我們首先要命令行分割,把每個選項都提取出來,然后fork創(chuàng)建子進程再程序替換。
而這個函數(shù)包含了上述的所有功能,并且還多了一個創(chuàng)建管道的功能。
參數(shù)介紹:
command
:傳遞進來的字符串,比如ls -a -l
type
:以什么方式打開文件(r/w/a)
比方說現(xiàn)在以r只讀方式打開,就直接從管道中提取出結(jié)果。
void execcommand(int _sockfd, std::string ip, uint16_t port, std::string msg)
{
std::string res;
FILE* fp = popen(msg.c_str(), "r");
if(fp == nullptr) res = msg + " execute fail";
else
{
char line[1024];
while(fgets(line, sizeof line, fp))
{
res += line;
}
}
pclose(fp);
struct sockaddr_in si;
bzero(&si, sizeof si);
si.sin_family = AF_INET;
si.sin_port = htons(port);
si.sin_addr.s_addr = inet_addr(ip.c_str());
sendto(_sockfd, res.c_str(), res.size(), 0, (struct sockaddr*)&si, sizeof si);
}
客戶端:
服務端:
三、網(wǎng)絡聊天室實現(xiàn)
聊天室是什么遠離呢?我們發(fā)送消息會進過服務端的轉(zhuǎn)發(fā),讓每個在線的客戶端都能看到發(fā)送的消息,這樣就實現(xiàn)了群聊。
每個客戶端發(fā)送一個online
表示上線了,服務端就會把所有的數(shù)據(jù)都發(fā)送給上線的客戶端。
所以首先我們先要把所有的用戶管理起來。
3.1 管理用戶
對于每個用戶我們用IP和port來標識唯一性。
class User
{
public:
User(const std::string& ip, const uint16_t& port)
: _ip(ip)
, _port(port)
{}
public:
std::string _ip;
uint16_t _port;
};
對于每個用戶我們可以用哈希表來管理:
class OnlineUsers
{
public:
void adduser(const std::string& ip, const uint16_t& port)
{
std::string id = ip + "@" + std::to_string(port);
users.insert({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();
}
private:
std::unordered_map<std::string, User> users;
};
在回調(diào)函數(shù)中,如果收到的消息是online,就把用戶添加進哈希表。如果是offline,就從哈希表中刪除。
if(msg == "online") onlinemap.adduser(ip, port);
if(msg == "offline") onlinemap.deluser(ip, port);
3.2 發(fā)送消息
我們要發(fā)送消息其實就是給哈希表中所有的用戶都sendto
消息。在這之前要先判斷是否在線:
void groupmsg(int _sockfd, std::string ip, uint16_t port, std::string msg)
{
if(msg == "online") onlinemap.adduser(ip, port);
if(!onlinemap.isonline(ip, port))// 不在線
{
struct sockaddr_in si;
bzero(&si, sizeof si);
si.sin_family = AF_INET;
si.sin_port = htons(port);
si.sin_addr.s_addr = inet_addr(ip.c_str());
std::string res = "你還沒有上線,請先上線";
sendto(_sockfd, res.c_str(), res.size(), 0, (struct sockaddr*)&si, sizeof si);
return;
}
// 廣播消息
}
現(xiàn)在要廣播消息,那么就在OnlineUsers
類中增加一個廣播消息的成員函數(shù)。
它的參數(shù)要包含_sockfd, msg
,還要有是誰發(fā)送的(ip
, port
)。
void broadmsg(int _sockfd, const std::string& msg, std::string ip, uint16_t port)
{
for(auto& e : users)
{
struct sockaddr_in si;
bzero(&si, sizeof si);
si.sin_family = AF_INET;
si.sin_port = htons(e.second._port);
si.sin_addr.s_addr = inet_addr(e.second._ip.c_str());
std::string res = ip + "@" + std::to_string(port) + "# ";
res += msg;
sendto(_sockfd, res.c_str(), res.size(), 0, (struct sockaddr*)&si, sizeof si);
}
}
3.3 多線程處理
因為客戶端不能立即收到消息打印出來(阻塞停留在接收消息),為了解決這個問題我們可以使用多線程,一個線程專門接收消息,一個線程專門發(fā)送消息。
那么我們可以讓主線程負責發(fā)送消息,子線程負責接收消息。文章來源:http://www.zghlxwxcb.cn/news/detail-448893.html
static void* getmsg(void *args)
{
pthread_detach(pthread_self());
int sockfd = *(static_cast<int*>(args));
while(1)
{
char buf[1024];
sockaddr_in tmp;
socklen_t tmplen = sizeof tmp;
size_t n = recvfrom(sockfd, buf, sizeof buf - 1, 0, (struct sockaddr*)&tmp, &tmplen);
if(n > 0) buf[n] = 0;
std::cout << buf << "\n";
}
}
void start()
{
pthread_create(&_get, nullptr, getmsg, (void*)&_sockfd);
struct sockaddr_in si;
bzero(&si, sizeof(si));
si.sin_family = AF_INET;
si.sin_addr.s_addr = inet_addr(_serverip.c_str());
si.sin_port = htons(_serverport);
std::string msg;
while(1)
{
std::cout << "Please input: ";
std::cin >> msg;
sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&si, sizeof si);
}
}
3.4 結(jié)果
服務端:
客戶端:
這里因為只有一個終端所以打印的比較混亂,但是能看到現(xiàn)象就行,如果想要優(yōu)化就可以把輸出的數(shù)據(jù)重定向到管道文件中,再打開一個終端讀取管道,這樣就可以實現(xiàn)發(fā)送和獲取兩個窗口。文章來源地址http://www.zghlxwxcb.cn/news/detail-448893.html
四、源碼
//UDPClient.hpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
#include <string.h>
#include <cassert>
#include <pthread.h>
class UDPClient
{
public:
UDPClient(const std::string& serverip, const uint16_t& port)
: _serverip(serverip)
, _serverport(port)
, _sockfd(-1)
{}
void InitClient()
{
// 創(chuàng)建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
std::cerr << "socket error" << errno << " : " << strerror(errno) << std::endl;
exit(1);
}
}
static void* getmsg(void *args)
{
pthread_detach(pthread_self());
int sockfd = *(static_cast<int*>(args));
while(1)
{
char buf[1024];
sockaddr_in tmp;
socklen_t tmplen = sizeof tmp;
size_t n = recvfrom(sockfd, buf, sizeof buf - 1, 0, (struct sockaddr*)&tmp, &tmplen);
if(n > 0) buf[n] = 0;
std::cout << buf << "\n";
}
}
void start()
{
pthread_create(&_get, nullptr, getmsg, (void*)&_sockfd);
struct sockaddr_in si;
bzero(&si, sizeof(si));
si.sin_family = AF_INET;
si.sin_addr.s_addr = inet_addr(_serverip.c_str());
si.sin_port = htons(_serverport);
std::string msg;
while(1)
{
std::cout << "Please input: ";
std::cin >> msg;
sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&si, sizeof si);
}
}
private:
uint16_t _serverport;
std::string _serverip;
int _sockfd;
pthread_t _get;
};
// UDPClient.cc
#include "UDPClient.hpp"
#include <memory>
int main(int argc, char* argv[])
{
if(argc != 3)
{
std::cout << "incorrect number of parameters" << std::endl;
exit(1);
}
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
std::unique_ptr<UDPClient> ptr(new UDPClient(ip, port));
ptr->InitClient();
ptr->start();
return 0;
}
// UDPServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
#include <string.h>
#include <cassert>
#include <functional>
static const std::string defaultip = "0.0.0.0";// 默認IP
typedef std::function<void(int, std::string, uint16_t, std::string)> func_t;
class UDPServer
{
public:
UDPServer(const func_t& func, const uint16_t& port, const std::string ip = defaultip)
: _port(port)
, _ip(ip)
, _sockfd(-1)
, _func(func)
{}
void InitServer()
{
// 創(chuàng)建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
std::cerr << "socket error" << errno << " : " << strerror(errno) << std::endl;
exit(1);
}
// 綁定IP與port
struct sockaddr_in si;
bzero(&si, sizeof si);
si.sin_family = AF_INET;// 協(xié)議家族
si.sin_port = htons(_port);// 端口號,注意大小端問題
// si.sin_addr.s_addr = inet_addr(_ip.c_str());// ip
si.sin_addr.s_addr = INADDR_ANY;
// 綁定
int n = bind(_sockfd, (struct sockaddr*)&si, sizeof si);
assert(n != -1);
}
void start()
{
char buf[1024];
while(1)
{
struct sockaddr_in peer;
socklen_t len = sizeof peer;
ssize_t s = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer, &len);
if(s > 0)
{
buf[s] = 0;// 結(jié)尾
std::string cip = inet_ntoa(peer.sin_addr);
uint16_t cport = ntohs(peer.sin_port);
std::string msg = buf;
//std::cout << "[" << cip << "@" << cport << "]# " << msg << std::endl;
_func(_sockfd, cip, cport, msg);
}
}
}
private:
uint16_t _port;
std::string _ip;
int _sockfd;
func_t _func;
};
// UDPServer.cc
#include "UDPServer.hpp"
#include "users.hpp"
#include <memory>
#include <cstdio>
#include <unordered_map>
#include <fstream>
const std::string path = "./Dict.txt";
static std::unordered_map<std::string, std::string> Dict;
// 分割字符串
static bool cutstring(std::string s, std::string *key, std::string *val)
{
size_t pos = s.find(":");
if(pos == std::string::npos)
{
return false;
}
*key = s.substr(0, pos);
*val = s.substr(pos + 1);
return true;
}
static void InitDict()
{
std::ifstream ifs(path);
if(!ifs.is_open())// 打開失敗
{
std::cout << "ifstream open fail" << std::endl;
exit(1);
}
std::string sline;
std::string key, val;
while(getline(ifs, sline))// 按行讀取
{
if(cutstring(sline, &key, &val))
{
Dict.insert({key, val});
}
else
{
std::cout << "cutstring fail" << std::endl;
exit(1);
}
}
ifs.close();
std::cout << "Dict Load Success\n";
}
// 回調(diào)方法
void handler(int _sockfd, std::string ip, uint16_t port, std::string msg)
{
std::string res;
auto it = Dict.find(msg);
if(it == Dict.end())
{
res = "not find";
}
else
{
res = it->second;
}
struct sockaddr_in si;
bzero(&si, sizeof si);
si.sin_family = AF_INET;
si.sin_port = htons(port);
si.sin_addr.s_addr = inet_addr(ip.c_str());
sendto(_sockfd, res.c_str(), res.size(), 0, (struct sockaddr*)&si, sizeof si);
}
void execcommand(int _sockfd, std::string ip, uint16_t port, std::string msg)
{
std::string res;
FILE* fp = popen(msg.c_str(), "r");
if(fp == nullptr) res = msg + " execute fail";
else
{
char line[1024];
while(fgets(line, sizeof line, fp))
{
res += line;
}
}
pclose(fp);
struct sockaddr_in si;
bzero(&si, sizeof si);
si.sin_family = AF_INET;
si.sin_port = htons(port);
si.sin_addr.s_addr = inet_addr(ip.c_str());
sendto(_sockfd, res.c_str(), res.size(), 0, (struct sockaddr*)&si, sizeof si);
}
OnlineUsers onlinemap;
void groupmsg(int _sockfd, std::string ip, uint16_t port, std::string msg)
{
if(msg == "online") onlinemap.adduser(ip, port);
if(msg == "offline") onlinemap.deluser(ip, port);
if(!onlinemap.isonline(ip, port))// 不在線
{
struct sockaddr_in si;
bzero(&si, sizeof si);
si.sin_family = AF_INET;
si.sin_port = htons(port);
si.sin_addr.s_addr = inet_addr(ip.c_str());
std::string res = "你還沒有上線,請先上線";
sendto(_sockfd, res.c_str(), res.size(), 0, (struct sockaddr*)&si, sizeof si);
return;
}
// 廣播消息
onlinemap.broadmsg(_sockfd, msg, ip, port);
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
std::cout << "incorrect number of parameters" << std::endl;
exit(1);
}
//InitDict();
uint16_t port = atoi(argv[1]);
std::unique_ptr<UDPServer> ptr(new UDPServer(groupmsg, port));
ptr->InitServer();
ptr->start();
return 0;
}
// users.hpp
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
#include <string.h>
class User
{
public:
User(const std::string& ip, const uint16_t& port)
: _ip(ip)
, _port(port)
{}
public:
std::string _ip;
uint16_t _port;
};
class OnlineUsers
{
public:
void adduser(const std::string& ip, const uint16_t& port)
{
std::string id = ip + "@" + std::to_string(port);
users.insert({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();
}
void broadmsg(int _sockfd, const std::string& msg, std::string ip, uint16_t port)
{
for(auto& e : users)
{
struct sockaddr_in si;
bzero(&si, sizeof si);
si.sin_family = AF_INET;
si.sin_port = htons(e.second._port);
si.sin_addr.s_addr = inet_addr(e.second._ip.c_str());
std::string res = ip + "@" + std::to_string(port) + "# ";
res += msg;
sendto(_sockfd, res.c_str(), res.size(), 0, (struct sockaddr*)&si, sizeof si);
}
}
private:
std::unordered_map<std::string, User> users;
};
到了這里,關于【網(wǎng)絡編程】UDP簡單實現(xiàn)翻譯軟件與網(wǎng)絡聊天室的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!