文章目录
- UdpSocket
- UdpServer.hpp
- UdpServer类
- 成员变量解释
- 成员函数解释
- UdpServer的实现
- ServerIinit的实现
- socket
- bind
- htons
- inet_addr
- 具体实现
- ServerStart的实现
- recvfrom
- sendto
- ntohs
- inet_ntoa
- 具体实现
- ~UdpServer函数实现
- UdpServer.hpp整体完整代码
- UdpServer.cc
- UdpClient.cc
- Thread.hpp
- UdpClient.cc实现
- Common.h
- Log.hpp
UdpSocket
我们要用udp协议来实现网络通信。
我们要用udp协议实现两样:
UdpServer(服务器)
UdpClient(客户端)
客服端可以向服务器发送一些数据或者请求,服务器对其进行响应
UdpServer.hpp
UdpServer类
class UdpServer
{
public:
UdpServer(uint16_t port, std::string ip = "")
: _port(port),
_ip(ip)
{}
bool ServerIinit()
{}
void ServerStart()
{}
~UdpServer()
{}
private:
uint16_t _port;
std::string _ip;
int _sock;
std::unordered_map<std::string, struct sockaddr_in> _users;
};
成员变量解释
uint16_t _port;
std::string _ip;
int _sock;
std::unordered_map<std::string, struct sockaddr_in> _users;
_port
:服务器进程要绑定的端口号
_ip
:指定连接该服务器的客户端IP,可以设为INADDR_ANY
支持任意客户端连接服务器
_sock
:套接字
_users
:存储所有连接该服务器的端口号和sockaddr_in
成员函数解释
UdpServer(uint16_t port, std::string ip = "")
: _port(port),
_ip(ip)
{}
bool ServerIinit()
{}
void ServerStart()
{}
~UdpServer()
{}
UdpServer
:构造函数
参数解释:
port:服务器要绑定的端口号
ip:服务器要绑定的IP地址,如果不传默认为缺省值“”,如果为缺省值我们可以设为INADDR_ANY
支持任意IP绑定
ServerIinit
:初始化服务器
ServerStart
:启动服务器
~UdpServer
:析构函数,释放一些资源
UdpServer的实现
UdpServer(uint16_t port, std::string ip = "")
: _port(port),
_ip(ip)
{}
ServerIinit的实现
需要用到的接口介绍
socket
参数解释:
domain:
type:
protocol:
return value:
bind
参数介绍:
socetfd:
创建套接字文件的文件描述符
addr:
表示struct sockaddr的地址,用于设定要绑定的ip和端口
addr_len:
struct sockaddr结构体的大小
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
sa_family
用于指定AF_***
表示使用什么协议族的ip
sa_data
存放ip和端口
这里有一个问题,直接向sa_data
中写入ip和端口号有点麻烦,内核提供struct sockaddr_in
结构体进行写入,通过/usr/include/linux/in.h
可以看到结构体原型
使用该结构体时需要包含<netinet/in.h>头文件,且sockaddr_in
结构体是专门为tcp/ip协议族使用,其他协议族需要使用其对应的转换结构体,比如**“域通信协议族**” 使用的是sockaddr_u
n结构体
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. 设置IP端口号这个成员暂时用不到 */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
/* Internet address.填补相比于struct sockaddr所缺的字节数,保障强制转换不要出错 */
struct in_addr {
__be32 s_addr; // __be32是32位的unsigned int ,因为ipv4是无符号32位整型
};
sockaddr_in
结构体中存放的端口和ip是分开的,使用struct sockaddr_in
设置后,让后将其强制转换为struct sockaddr
类型,然后传递给bind
函数即可
htons
inet_addr
具体实现
bool ServerIinit()
{
//1.创建套接字
//AF_INET 表示是网络编程
//SOCK_DGRAM 表示使用的是udp协议
_sock = socket(AF_INET, SOCK_DGRAM, 0);
//如果返回值小于0,说明创建套接字失败
if (_sock < 0)
{
//打出创建的日志信息
LogMessage(FATAL, "socket %s", strerror(errno));
exit(2);
}
//打出创建的日志信息
LogMessage(NORMAL, "udp socket success");
//定义sockaddr_in 对象
struct sockaddr_in local;
//初始化sockaddr_in ,初始化为0
bzero(&local, sizeof(local));
//向sockaddr_in对象 local填入一些数据
//local.sin_port server要绑定的端口号
//local.sin_family server使用的协议家族,指明要使用什么协议
//local.sin_addr.s_addr server要绑定的IP地址
//htons()将端口号转化为大端
local.sin_port = htons(_port);
local.sin_family = AF_INET;
//判断要绑定的IP地址是否为空,如果为空填入INADDR_ANY表示绑定任意IP地址
//如果不为空填入该IP
//inet_addr()做了两个工作
//1.将字符串IP转化为32位整数 2.将32位整数转化为大端
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
//2.开始绑定
int n = bind(_sock, (struct sockaddr *)&local, sizeof(local));
//如果绑定失败n bind会返回-1
if (n == -1)
{
//打印出绑定的日志信息
LogMessage(FATAL, "bind %s", strerror(errno));
exit(3);
}
//打印出绑定的日志信息
LogMessage(NORMAL, "udp bind success");
//到最后成功打印出初始化成功的日志信息
LogMessage(NORMAL, "init udp server done ... %s", strerror(errno));
return true;
}
ServerStart的实现
需要用到的接口介绍
recvfrom
参数解释:
sockfd
:创建套接字文件的文件描述符
buffer
:输出型参数,数据缓冲区数组,会把从网络中获取到的数据放入其中
len
:buffer的大小
flages
:
src_addr
:指向一个sockaddr结构体,用于存储发送方的地址信息。
addrlen:
src_addr结构体的大小
return value:
sendto
参数解释:
sockfd
:创建套接字文件的文件描述符
buffe
r:要输入数据的数组
len
:输入数据的长度
dest_addr
:指向一个sockaddr结构体,用于存储接受方的地址信息。
addrlen:
dest_addr结构体的大小
flags:
return value:
ntohs
inet_ntoa
具体实现
void ServerStart()
{
//数据缓冲区
char buffer[BUFFER_SIZE];
//客户端的IP和port的临时存放区
char key[64];
//服务器肯定是一直循环执行的
while (true)
{
//定义peer结构体,peer是输出型参数,可以获取客户端的IP和port
struct sockaddr_in peer;
//计算peer结构体的大小
socklen_t len = sizeof(peer);
//初始化peer结构体,初始化为0
memset(&peer, 0, len);
//recvfrom从_sock文件中获取数据
int s = recvfrom(_sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, &len);
//s > 0 代表成功获取数据,我们要对该数据进行处理
if (s > 0)
{
buffer[s] = '\0';
//因为端口是从网络中来的,所以是大端序列
//ntohs将大端的port转化为小端
uint16_t src_port = ntohs(peer.sin_port);
//inet_ntoa做了两个工作
//1.将网络中获取到的32位整数IP地址转化为小端序列
//2.将IP地址转化为字符串
std::string src_ip = inet_ntoa(peer.sin_addr);
//处理获取到的数据
printf("[%s:%d]# %s\n", src_ip.c_str(), src_port, buffer);
//将网络中的IP地址和端口号存入key中
snprintf(key, sizeof(key), "%s-%u", src_ip.c_str(), src_port);
LogMessage(NORMAL, "key: %s", key);
//在_users中查找key,如果存在说明这个客户端之前有连接果不用在添加进_user中
auto ret = _users.find(key);
//如果不存在如果存在说明这个客户端第一次连接服务器,将其添加进_user中
if (ret == _users.end())
{
LogMessage(NORMAL, "add new user : %s", key);
_users.insert(std::make_pair(key, peer));
}
}
//将收到的消息广播给所有主机
for (auto &iter : _users)
{
std::string send_message(key);
send_message += "# ";
send_message += buffer;
LogMessage(NORMAL, "push message to %s", iter.first.c_str());
//std::cout << send_message << std::endl;
//sendto发送数据到_sock文件中,并指明要发送主机的IP地址和端口号
//主机的IP地址和端口号在(struct sockaddr *)&iter.second
sendto(_sock, send_message.c_str(), send_message.size(), 0,
(struct sockaddr *)&iter.second, len);
}
}
}
~UdpServer函数实现
~UdpServer()
{
if (_sock >= 0)
{
close(_sock);
}
}
UdpServer.hpp整体完整代码
#include "Common.h"
#include "Log.hpp"
class UdpServer
{
public:
UdpServer(uint16_t port, std::string ip = "")
: _port(port),
_ip(ip)
{
}
bool ServerIinit()
{
_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (_sock < 0)
{
LogMessage(FATAL, "socket %s", strerror(errno));
exit(2);
}
LogMessage(NORMAL, "udp socket success");
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_port = htons(_port);
local.sin_family = AF_INET;
local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
int n = bind(_sock, (struct sockaddr *)&local, sizeof(local));
if (n == -1)
{
LogMessage(FATAL, "bind %s", strerror(errno));
exit(3);
}
LogMessage(NORMAL, "udp bind success");
LogMessage(NORMAL, "init udp server done ... %s", strerror(errno));
return true;
}
void ServerStart()
{
char buffer[BUFFER_SIZE];
char key[64];
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
memset(&peer, 0, len);
int s = recvfrom(_sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
buffer[s] = '\0';
uint16_t src_port = ntohs(peer.sin_port);
std::string src_ip = inet_ntoa(peer.sin_addr);
printf("[%s:%d]# %s\n", src_ip.c_str(), src_port, buffer);
snprintf(key, sizeof(key), "%s-%u", src_ip.c_str(), src_port);
LogMessage(NORMAL, "key: %s", key);
auto ret = _users.find(key);
if (ret == _users.end())
{
LogMessage(NORMAL, "add new user : %s", key);
_users.insert(std::make_pair(key, peer));
}
}
for (auto &iter : _users)
{
std::string send_message(key);
send_message += "# ";
send_message += buffer;
LogMessage(NORMAL, "push message to %s", iter.first.c_str());
//std::cout << send_message << std::endl;
sendto(_sock, send_message.c_str(), send_message.size(), 0,
(struct sockaddr *)&iter.second, len);
}
}
}
~UdpServer()
{
if (_sock >= 0)
{
close(_sock);
}
}
private:
uint16_t _port;
std::string _ip;
int _sock;
std::unordered_map<std::string, struct sockaddr_in> _users;
};
UdpServer.cc
#include "udp_server.hpp"
static void Usage(std::string proc)
{
std::cout << "Usage:\t\n " << proc << " port\n" << std::endl;
}
int main(int argc,char* argv[])
{
//使用规则必须是 ./server port
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t port = atoi(argv[1]);
//使用智能指针管理UdpServer
std::unique_ptr<UdpServer> svr(new UdpServer(port));
svr->ServerIinit();
svr->ServerStart();
return 0;
}
UdpClient.cc
Thread.hpp
#include "Common.h"
#include "Log.hpp"
typedef void *(*fun_t)(void *);
class ThreadData
{
public:
std::string _name;
void *_args;
};
class Thread
{
public:
Thread(std::string name, fun_t routine, void *args)
{
_thread_data._name = name;
_func = routine;
_thread_data._args = args;
}
void Start()
{
pthread_create(&_tid, nullptr, _func, (void*)&_thread_data);
//std::cout << _thread_data._name << " create success" << std::endl;
LogMessage(NORMAL,"%s creadte success",_thread_data._name.c_str());
}
void Join()
{
pthread_join(_tid, nullptr);
//std::cout << _thread_data._name << " join success" << std::endl;
LogMessage(NORMAL,"%s join success",_thread_data._name.c_str());
}
std::string GetName()
{
return _thread_data._name;
}
private:
ThreadData _thread_data;
pthread_t _tid;
fun_t _func;
};
UdpClient.cc实现
#include "Common.h"
#include "Log.hpp"
#include "Thread.hpp"
std::string serverip;
uint16_t serverport = 0;
static void Usage(std::string proc)
{
std::cout << "Usage:\t\n " << proc << " serverip serverport\n"
<< std::endl;
}
//发送数据的线程
void *udpSend(void *args)
{
int sock = *((int *)((ThreadData *)args)->_args);
//std::cout << sock << std::endl;
struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_port = htons(serverport);
//std::cout << serverport << std::endl;;
//std::cout << serverip << std::endl;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(serverip.c_str());
std::string message;
while (true)
{
std::cerr << "Please Entery your message: ";
std::getline(std::cin, message);
if (message.c_str() == "quit")
{
LogMessage(NORMAL, "client quit");
break;
}
sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
}
}
//接收数据的线程
void *udpRecv(void *args)
{
int sock = *((int *)((ThreadData *)args)->_args);
//std::cout << sock << std::endl;
char buffer[BUFFER_SIZE];
while (true)
{
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int s = recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
//std::cout << buffer;
if(s > 0)
{
buffer[s] = '\0';
std::cout << buffer << std::endl;
}
}
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
LogMessage(FATAL, "socket %s", strerror(errno));
exit(1);
}
LogMessage(NORMAL, "socket %s", strerror(errno));
serverip = argv[1];
serverport = atoi(argv[2]);
//多线程版
std::unique_ptr<Thread> sender(new Thread("send_thread", udpSend, (void *)&sock));
std::unique_ptr<Thread> recver(new Thread("recv_thread", udpRecv, (void *)&sock));
sender->Start();
recver->Start();
sender->Join();
recver->Join();
// LogMessage(NORMAL, "%s%d",serverip.c_str(),serverport);
//单线程版 弊端:一直卡在发送数据处,当别人发消息时,无法及时接收
/* struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_port = htons(serverport);
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(serverip.c_str());
std::string message;
char buffer[BUFFER_SIZE];
while (true)
{
std::cout << "Please Entery your message: ";
std::getline(std::cin, message);
if (message.c_str() == "quit")
{
LogMessage(NORMAL, "client quit");
break;
}
//std::cout << message;
sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int s = recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
//std::cout << buffer;
if(s > 0)
{
buffer[s] = '\0';
std::cout << buffer << std::endl;
}
} */
return 0;
}
Common.h
共用的头文件
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdarg.h>
#include <pthread.h>
#define BUFFER_SIZE 1024
Log.hpp
打印日志的函数
#pragma once
#include "Common.h"
// 日志是有日志级别的
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
#define STANDARD_NUM 1024
#define CUSTOM_NUM 1024
const char *gLevelMap[] = {
"DEBUG",
"NORMAL",
"WARNING",
"ERROR",
"FATAL"};
void LogMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
if(level== DEBUG) return;
#endif
// 标准部分
char standard_buffer[STANDARD_NUM];
snprintf(standard_buffer, sizeof(standard_buffer), "[%s][%ld]", gLevelMap[level], time(nullptr));
// 自定义部分
char custom_buffer[CUSTOM_NUM];
va_list args;
va_start(args, format);
vsnprintf(custom_buffer, sizeof(custom_buffer), format, args);
va_end(args);
printf("%s%s\n", standard_buffer, custom_buffer);
}