Article directory
1. Introduction
In the previous chapter [Network Programming] the demo version of the UDP network server realized the sending and receiving of data between the client and the server. In the previous chapter, we directly asked the server to print out the received data.
But the server does not just receive the data, it also needs to process the task .
So we can set a callback function on the server side:
typedef std::function<void(std::string, uint16_t, std::string)> func_t;
Used to process received information.
2. Realization of translation software
2.1 Loading the dictionary
To realize that the client sends a word and receives the translation result of the server.
Then first you need to load English and Chinese into unordered_map
the dictionary.
static std::unordered_map<std::string, std::string> Dict;
Load data into a dictionary from a file:
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 Process data and pass it to the client
Next, the data is processed and the results of the processing are fed back to the client.
Handling the data is relatively simple:
std::string res;
auto it = Dict.find(msg);
if(it == Dict.end())
{
res = "not find";
}
else
{
res = it->second;
}
Next, we need to res
send it to the client. After the previous learning, sendto
we need to build a structure to indicate who to send it to.
When the structure is built and used, sendto
it is found that there must be a file descriptor, so it must be _sockfd
passed in when calling the callback function.
// 回调方法
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 The client gets the result
// 获取结果
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";
2.4 Results
Client:
Server:
2.5 Executing the naming function
Now we can make a small change, instead of letting the server do the translation, but to process the task, for example, if we send pwd, it will return the path of the server at this time.
The other logic here does not need to be changed, only the callback function needs to be modified.
Introduce a command line parsing interface :
popen
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
Let’s first recall the previous handwritten suggestion shell [linux] process control details , we first need to split the command line, extract each option, and then fork to create a child process and then replace the program.
And this function contains all the above functions , and there is one more function to create a pipeline.
Parameter introduction:
command
: The string passed in, such asls -a -l
type
: how to open the file (r/w/a)
For example, if it is opened in r read-only mode now, the result will be directly extracted from the pipeline.
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);
}
Client:
Server:
3. Realization of network chat room
What's the chat room away from? The message we send will be forwarded by the server, so that every online client can see the sent message, thus realizing group chat.
Each client sends a online
message indicating that it is online, and the server will send all the data to the online client.
So first of all, we need to manage all users.
3.1 Manage users
For each user we use IP and port to identify uniqueness.
class User
{
public:
User(const std::string& ip, const uint16_t& port)
: _ip(ip)
, _port(port)
{
}
public:
std::string _ip;
uint16_t _port;
};
For each user we can use a hash table to manage:
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;
};
In the callback function, if the received message is online, add the user to the hash table. If it is offline, delete it from the hash table.
if(msg == "online") onlinemap.adduser(ip, port);
if(msg == "offline") onlinemap.deluser(ip, port);
3.2 Send message
When we want to send a message, we actually send a message to all users in the hash table sendto
. Before this, it is necessary to determine whether it is online :
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;
}
// 广播消息
}
Now to broadcast a message, add OnlineUsers
a member function to broadcast a message in the class.
Its parameters should include _sockfd, msg
, but also who sent it ( 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 Multi-thread processing
Because the client cannot immediately receive the message and print it out (blocking stays at receiving the message), in order to solve this problem we can use multi-threading , one thread is dedicated to receiving messages, and one thread is dedicated to sending messages.
Then we can let the main thread be responsible for sending messages, and the child thread is responsible for receiving messages.
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 Results
Server: Client
:
Because there is only one terminal here, the printing is confusing, but you can see the phenomenon. If you want to optimize, you can redirect the output data to the pipeline file, and then open a terminal to read the pipeline, so that You can send and get two windows.
4. Source code
//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()
{
// 创建套接字
_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()
{
// 创建套接字
_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;// 协议家族
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;// 结尾
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";
}
// 回调方法
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;
};