目录
1.server 服务端
1.1.完整代码展示:
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"
// using func_t = std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string&)> func_t;
Log lg;
enum{
SOCKET_ERR=1,
BIND_ERR
};
uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;
class UdpServer{
public:
UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip):sockfd_(0), port_(port), ip_(ip),isrunning_(false)
{}
void Init()
{
// 1. 创建udp socket
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
if(sockfd_ < 0)
{
lg(Fatal, "socket create error, sockfd: %d", sockfd_);
exit(SOCKET_ERR);
}
lg(Info, "socket create success, sockfd: %d", sockfd_);
// 2. bind socket
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()); //1. string -> uint32_t 2. uint32_t必须是网络序列的 // ??
// local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
{
lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));
exit(BIND_ERR);
}
lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));
}
void Run(func_t func) // 对代码进行分层
{
isrunning_ = true;
char inbuffer[size];
while(isrunning_)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);
if(n < 0)
{
lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
continue;
}
inbuffer[n] = 0;
std::string info = inbuffer;
std::string echo_string = func(info);
sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);
}
}
~UdpServer()
{
if(sockfd_>0) close(sockfd_);
}
private:
int sockfd_; // 网路文件描述符
std::string ip_; // 任意地址bind 0
uint16_t port_; // 表明服务器进程的端口号
bool isrunning_;
};
1.2.代码解析:
1.2.1 给服务端创建套接字
// 1. 创建udp socket
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
if(sockfd_ < 0)
{
lg(Fatal, "socket create error, sockfd: %d", sockfd_);
exit(SOCKET_ERR);
}
lg(Info, "socket create success, sockfd: %d", sockfd_);
// 2. bind socket
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()); //1. string -> uint32_t 2. uint32_t必须是网络序列的 // ??
// local.sin_addr.s_addr = htonl(INADDR_ANY);
创建套接字时,我们要将字符型的port和ip转化为网络序列
参考如下:
整数ip和字符串ip之间的相互转化()_uint ip转string-优快云博客
1.2.2 绑定套接字
if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
{
lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));
exit(BIND_ERR);
}
lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));
}
绑定套接字应注意:ip和port可能绑定失败:
参考如下:
关于bind绑定时出现 Permission denied_bind: permission denied-优快云博客
云服务器不允许绑定(bind)公网ip_bind主机-优快云博客
1.2.3 服务端接受数据并返回
void Run(func_t func) // 对代码进行分层
{
isrunning_ = true;
char inbuffer[size];
while(isrunning_)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);
if(n < 0)
{
lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
continue;
}
inbuffer[n] = 0;
std::string info = inbuffer;
std::string echo_string = func(info);
sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);
}
}
~UdpServer()
{
if(sockfd_>0) close(sockfd_);
}
使用 func() 函数回调,从main()函数中获取数据
2.客户端:
2.1 完整代码展示:
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
void Usage(std::string proc)
{
std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
<< std::endl;
}
// ./udpclient serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport); //?
server.sin_addr.s_addr = inet_addr(serverip.c_str());
socklen_t len = sizeof(server);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
cout << "socker error" << endl;
return 1;
}
// client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!
// 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!
// 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!
// 系统什么时候给我bind呢?首次发送数据的时候
string message;
char buffer[1024];
while (true)
{
cout << "Please Enter@ ";
getline(cin, message);
// std::cout << message << std::endl;
// 1. 数据 2. 给谁发
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
if(s > 0)
{
buffer[s] = 0;
cout << buffer << endl;
}
}
close(sockfd);
return 0;
}
2.2 代码解析
2.2.1 客户端使用手则:
void Usage(std::string proc)
{
std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
<< std::endl;
}
// ./udpclient serverip serverport
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
2.2.2 创建客户端套接字
struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport); //?
server.sin_addr.s_addr = inet_addr(serverip.c_str());
socklen_t len = sizeof(server);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
cout << "socker error" << endl;
return 1;
}
2.2.3 关于客户端是否绑定问题
(1)client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!
(2)一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!
(3)其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!
(4)系统什么时候给我bind呢?首次发送数据的时候
参考如下:
客户端需要bind吗?(端口号绑定的理解)_客戶端bind-优快云博客
2.2.4 客户端传输信息给服务端
string message;
char buffer[1024];
while (true)
{
cout << "Please Enter@ ";
getline(cin, message);
// std::cout << message << std::endl;
// 1. 数据 2. 给谁发
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
if(s > 0)
{
buffer[s] = 0;
cout << buffer << endl;
}
}
close(sockfd);
return 0;
}
3.主函数
3.1完整代码
#include "UdpServer.hpp"
#include <memory>
#include <cstdio>
// "120.78.126.148" 点分十进制字符串风格的IP地址
void Usage(std::string proc)
{
std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}
std::string Handler(const std::string &str)
{
std::string res = "Server get a message: ";
res += str;
std::cout << res << std::endl;
// pid_t id = fork();
// if(id == 0)
// {
// // ls -a -l -> "ls" "-a" "-l"
// // exec*();
// }
return res;
}
std::string ExcuteCommand(const std::string &cmd)
{
// SafeCheck(cmd);
FILE *fp = popen(cmd.c_str(), "r");
if(nullptr == fp)
{
perror("popen");
return "error";
}
std::string result;
char buffer[4096];
while(true)
{
char *ok = fgets(buffer, sizeof(buffer), fp);
if(ok == nullptr) break;
result += buffer;
}
pclose(fp);
return result;
}
// ./udpserver port
int main(int argc, char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<UdpServer> svr(new UdpServer(port));
svr->Init(/**/);
svr->Run(Handler);
return 0;
}
3.2代码解析
3.2.1 回调函数
std::string Handler(const std::string &str)
{
std::string res = "Server get a message: ";
res += str;
std::cout << res << std::endl;
// pid_t id = fork();
// if(id == 0)
// {
// // ls -a -l -> "ls" "-a" "-l"
// // exec*();
// }
return res;
}
服务端接受数据返回时的回调函数fun():Handler
3.2.2 功能拓展
std::string ExcuteCommand(const std::string &cmd)
{
// SafeCheck(cmd);
FILE *fp = popen(cmd.c_str(), "r");
if(nullptr == fp)
{
perror("popen");
return "error";
}
std::string result;
char buffer[4096];
while(true)
{
char *ok = fgets(buffer, sizeof(buffer), fp);
if(ok == nullptr) break;
result += buffer;
}
pclose(fp);
return result;
}
远程执行命令!!!
参考如下:
4.log(日志代码)
#pragma once
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define SIZE 1024
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
#define Screen 1
#define Onefile 2
#define Classfile 3
#define LogFile "log.txt"
class Log
{
public:
Log()
{
printMethod = Screen;
path = "./log/";
}
void Enable(int method)
{
printMethod = method;
}
std::string levelToString(int level)
{
switch (level)
{
case Info:
return "Info";
case Debug:
return "Debug";
case Warning:
return "Warning";
case Error:
return "Error";
case Fatal:
return "Fatal";
default:
return "None";
}
}
// void logmessage(int level, const char *format, ...)
// {
// time_t t = time(nullptr);
// struct tm *ctime = localtime(&t);
// char leftbuffer[SIZE];
// snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
// ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
// ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
// // va_list s;
// // va_start(s, format);
// char rightbuffer[SIZE];
// vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
// // va_end(s);
// // 格式:默认部分+自定义部分
// char logtxt[SIZE * 2];
// snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
// // printf("%s", logtxt); // 暂时打印
// printLog(level, logtxt);
// }
void printLog(int level, const std::string &logtxt)
{
switch (printMethod)
{
case Screen:
std::cout << logtxt << std::endl;
break;
case Onefile:
printOneFile(LogFile, logtxt);
break;
case Classfile:
printClassFile(level, logtxt);
break;
default:
break;
}
}
void printOneFile(const std::string &logname, const std::string &logtxt)
{
std::string _logname = path + logname;
int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
if (fd < 0)
return;
write(fd, logtxt.c_str(), logtxt.size());
close(fd);
}
void printClassFile(int level, const std::string &logtxt)
{
std::string filename = LogFile;
filename += ".";
filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
printOneFile(filename, logtxt);
}
~Log()
{
}
void operator()(int level, const char *format, ...)
{
time_t t = time(nullptr);
struct tm *ctime = localtime(&t);
char leftbuffer[SIZE];
snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
va_list s;
va_start(s, format);
char rightbuffer[SIZE];
vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
va_end(s);
// 格式:默认部分+自定义部分
char logtxt[SIZE * 2];
snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
// printf("%s", logtxt); // 暂时打印
printLog(level, logtxt);
}
private:
int printMethod;
std::string path;
};
// int sum(int n, ...)
// {
// va_list s; // char*
// va_start(s, n);
// int sum = 0;
// while(n)
// {
// sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
// n--;
// }
// va_end(s); //s = NULL
// return sum;
// }
5.makefile代码:
.PHONY:all
all:udpserver udpclient
udpserver:Main.cc
g++ -o $@ $^ -std=c++11
udpclient:UdpClient.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f udpserver udpclient