一、服务端
1.udpServer.hpp
此文件负责实现一个udp服务器
#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
// 默认参数
static const std::string defaultIp = "0.0.0.0";
static const int gnum = 1024;
// 回调函数类型
typedef std::function<void (int, std::string, uint16_t, std::string)> func_t;
// 错误码
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
OPEN_ERR
};
// udp服务器
class udpServer
{
public:
// 构造函数
udpServer(func_t &callback, uint16_t &port, std::string &ip = defaultIp)
:_callback(callback), _port(port), _ip(ip), _sockfd(-1)
{}
// 析构函数
~udpServer(){}
// 初始化服务器
void initServer();
// 启动服务器
void start();
private:
uint16_t _port; // 服务器进程的端口号
std::string _ip; // 服务器的IP 一款网络服务器不建议指明一个IP
int _sockfd; // 创建socket后返回的文件描述符,利用这个fd进行读写
func_t _callback;// 回调函数:利用这个函数处理业务
};
(1)initServer()
void initServer()
{
// 1. 创建socket
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd == -1)
{
cerr << "socket error" << errno << " : " << strerror(errno) << endl;
exit(SOCKET_ERR);
}
std::cout << "socket success" << " : " << _sockfd << std::endl;
// 2. 绑定 port ip
// 未来服务器要明确的port,不能随意改变
struct sockaddr_in local;
bzero(&local, sizeof(local)); // 初始化结构体为全0
local.sin_family = AF_INET;
// 你如果要给别人发消息,你的port和ip要发送给对方
// 所以port和ip要从当前主机到网络 h -> n
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip.c_str());
// local.sin_addr.s_addr = htonl(INADDR_ANY);// 任意地址bind:INADDR_ANY:全0
// 把创建的fd和端口号绑定
int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
if (n == -1)
{
cerr << "bind error" << errno << " : " << strerror(errno) << endl;
exit(BIND_ERR);
}
// UDP Server 的预备工作完成
}
(2)start()
void start()
{
// 服务器的本质其实就是一个死循环
char buffer[gnum]; // 读到的数据放这里
for (;;)
{
// 读取数据
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// recvfrom读取数据
ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
// 处理数据
if (s > 0)
{
// 1.知道是谁发的(获取客户端ip/port)
std::string clientip = inet_ntoa(peer.sin_addr); // IP
uint16_t clientport = ntohs(peer.sin_port); // 端口号
// 2.知道发的什么(获取客户端发送的内容)
buffer[s] = '\0';
std::string message = buffer;
cout << clientip << "[" << clientport << "]# " << message << endl;
// 我们只把数据读上来就完了吗?还需要对数据做处理
_callback(_sockfd, clientip, clientport, message);
}
}
}
2.udpServer.cpp
此文件负责udp服务器的调用逻辑
#include "udpServer.hpp"
#include <cstdio>
#include <memory>
#include <fstream>
#include <signal.h>
// 使用手册
void Usage(std::string proc)
{
std::cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}
// 对收到的数据做处理
void handlerMessage(int sockfd, std::string clientip, uint16_t clientport, std::string message)
{
// 填写客户端信息
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());
// 处理并发送消息(服务器 -> 客户端)
std::string response_message = message;
response_message = "[server echo] " + message;
sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr*)&client, sizeof(client));
}
// ./udpServer local_port
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;
}
二、客户端
1.udpClient.hpp
此文件负责实现一个udp客户端
#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
class udpClient
{
public:
// 构造函数
udpClient(std::string &serverIp, const uint16_t &serverPort)
: _serverIp(serverIp), _serverPort(serverPort), _sockfd(-1), _quit(false)
{}
// 析构函数
~udpClient(){}
// 初始化客户端
void initClient()
// 这个线程负责 接受消息
static void *readMessage(void *args)
// 主线程负责 发送消息
void run()
private:
int _sockfd; // 自己的fd
std::string _serverIp; // 目的地
uint16_t _serverPort; // 目的地
bool _quit; // 表明是否退出
};
(1)initClient()
void initClient()
{
// 1.创建socket
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd == -1)
{
cerr << "socket error" << errno << " : " << strerror(errno) << endl;
exit(2);
}
std::cout << "socket success" << " : " << _sockfd << std::endl;
// 2.client要不要bind?要;client要不要显示的bind/程序员自己bind?不需要
}
① 客户访问服务器时需要一个确定的端口号,所以服务器的端口需要我们明确指出
② 客户端的端口号不用我们明确指出,因为写客户端的是许多家公司
假设A公司给Client_A定的端口号是8080,B公司给Client_B定的端口号也是8080
这样这两个程序就无法一起运行,不符合实际,所以客户端由OS自动形成端口进行bind!
(2)run()
// 主线程负责 发送消息
void run()
{
// 填写服务器的ip/port
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);
// 消息处理
std::string message;
char line[1024];
while (!_quit)
{
// 发送消息(客户端 -> 服务器)
std::cout << "Please Enter# ";
// 这种输入:重点在于可以输入空格
fgets(line, sizeof(line), stdin);
line[strlen(line) - 1] = '\0'; // 处理掉输入进来的回车
message = line;
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
// 没有显示绑定时,第一次发消息的时候绑定port
// 接收消息(服务器 -> 客户端)
char buffer[1024]; // 收到的消息放到这里
struct sockaddr_in serverSock;
socklen_t len = sizeof(serverSock);
size_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&serverSock, &len);
if (n >= 0) buffer[n] = 0;
std::cout << buffer << std::endl;
}
}
2.udpClient.cpp
此文件负责udp客户端的调用逻辑
#include "udpClient.hpp"
#include <memory>
// 使用手册
void Usage(std::string proc)
{
std::cout << "\nUsage:\n\t" << proc << " server_ip server_port\n\n";
}
// ./udpClient server_ip server_port -> 输入目的地址(服务器地址)
int main(int argc, char *argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string serverIp = argv[1];
uint16_t serverPort = atoi(argv[2]);
std::unique_ptr<udpClient> ucli(new udpClient(serverIp, serverPort));
ucli->initClient();
ucli->run();
return 0;
}
三、整体调用逻辑
1.双方调用逻辑
UDP服务器可以多个客户端同时访问,用一幅图展示这个过程