目录
一、端口号
二、UDP报头格式
三、UDP的特点
四、UDP协议实现网络聊天群
端口号port标识了一个主机上进行通信的不同的应用程序。
知名端口号(Well-Know Port Number):
查看知名端口号:cat /etc/services
netstat:一个用来查看网络状态的重要工具
语法:netstat -[选项]
pidof:查看服务器的进程id(通过进程名查看进程id)
语法:pidof [进程名]
端口号 -----> 进程(唯一关系)
UDP传输的过程类似于寄信:
UDP的缓冲区:
UDP的socket既能读,也能写,这就是全双工概念。
基于UDP的常用应用层协议:
UDP协议的应用场景多为视频网站、直播平台等不担心数据包丢失的情况。
设计思路:
设计效果:当多个client连接server之后,只要是online登录了的用户,都可以向server发送数据的同时看到自己和其他登录用户发送的数据,这就是网络聊天群
User.h
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
class User
{
public:
User(const std::string& ip, const uint16_t& port)
: _ip(ip), _port(port)
{}
std::string ip()
{
return _ip;
}
uint16_t port()
{
return _port;
}
private:
std::string _ip;
uint16_t _port;
};
class OnlineUser
{
public:
OnlineUser()
{}
void AddUser(const std::string& ip, const uint16_t& port)
{
std::string id = ip + "-" + std::to_string(port);
_users.insert(std::make_pair(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 BroadcastMessage(int sockfd, const std::string& message, const std::string& ip, const uint16_t& port)
{
for (auto& user : _users)
{
struct sockaddr_in client;
client.sin_family = AF_INET;
client.sin_addr.s_addr = inet_addr(user.second.ip().c_str());
client.sin_port = htons(user.second.port());
std::string s = "[" + ip + "-" + std::to_string(port) + "]" + "# " + message;
sendto(sockfd, s.c_str(), s.size(), 0, (struct sockaddr*)&client, sizeof(client));
}
}
private:
std::unordered_map _users;
};
server.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "User.h"
using namespace std;
typedef function func_t;
static string defaultIP = "0.0.0.0";
class UdpServer
{
public:
UdpServer(const func_t& func, const uint16_t& port, const string& ip = defaultIP)
: _cb(func), _port(port), _ip(ip)
{}
void Init()
{
// 1. 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{
cerr << "socket error: " << errno << strerror(errno) << endl;
exit(errno);
}
// 2. 绑定 ip:port
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_ANY); // Address to accept any incomit message -------> 任意地址绑定
// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // string ---> uint32_t ---> htonl
local.sin_port = htons(_port);
int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
if (n < 0)
{
cerr << "bind error: " << errno << strerror(errno) << endl;
exit(errno);
}
}
void Start()
{
char buf[1024];
while (true)
{
struct sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
ssize_t n = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer, &peer_len);
if (n > 0)
{
buf[n] = 0;
string client_ip = inet_ntoa(peer.sin_addr); // 网络序列 ---> 整型 ---> 点分十进制
uint16_t client_port = ntohs(peer.sin_port);
string message = buf;
cout << client_ip << "[" << client_port << "]# " << message << endl;
_cb(_sockfd, client_ip, client_port, message);
}
}
}
private:
string _ip;
uint16_t _port;
int _sockfd;
func_t _cb;
};
// 聊天群在线用户,静态全局变量
static OnlineUser g_online_users;
// 处理message,server与业务逻辑解耦
void RouteMessage(int sockfd, string client_ip, uint16_t client_port, string message)
{
if (message == "online")
g_online_users.AddUser(client_ip, client_port);
if (message == "offline")
g_online_users.DelUser(client_ip, client_port);
if (g_online_users.IsOnline(client_ip, client_port))
{
g_online_users.BroadcastMessage(sockfd, message, client_ip, client_port);
}
else
{
struct sockaddr_in client;
bzero(&client, sizeof(client));
client.sin_family = AF_INET;
client.sin_addr.s_addr = inet_addr(client_ip.c_str());
client.sin_port = htons(client_port);
string response = "你还没有登录,请输入online登录加入聊天群";
sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&client, sizeof(client));
}
}
void Usage()
{
cout << "Usage: \n\t" << "server port" << endl;
exit(1);
}
int main(int args, char* argv[])
{
if (args != 2)
Usage();
uint16_t port = atoi(argv[1]);
unique_ptr udp_server(new UdpServer(RouteMessage, port));
udp_server->Init();
udp_server->Start();
return 0;
}
client.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "User.h"
using namespace std;
class UdpClient
{
public:
UdpClient(const string& server_ip, const uint16_t& server_port)
: _server_ip(server_ip), _server_port(server_port), _sockfd(-1), _quit(false)
{}
void Init()
{
// 1. 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
assert(_sockfd != -1);
cout << "socket seccess: " << _sockfd << endl;
// 2. 绑定bind,不需要显示绑定,由os分配
}
static void* ReadMessage(void* args)
{
int sockfd = *(static_cast(args));
pthread_detach(pthread_self()); // 线程分离
while (true)
{
char buf[1024];
struct sockaddr_in tmp;
socklen_t tmp_len = sizeof(tmp);
ssize_t n = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&tmp, &tmp_len);
if (n >= 0)
buf[n] = 0;
cout << "\r" << buf << "\n";
}
return nullptr;
}
void Run()
{
pthread_create(&_reader, nullptr, ReadMessage, (void*)&_sockfd);
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_server_ip.c_str());
server.sin_port = htons(_server_port);
string message;
while (!_quit)
{
fprintf(stderr, "Enter:# ");
fflush(stderr);
getline(cin, message);
message[message.size()] = 0;
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
usleep(100);
}
}
private:
string _server_ip;
uint16_t _server_port;
int _sockfd;
bool _quit;
pthread_t _reader;
};
void Usage()
{
cout << "Usage: \n\t" << "client ip port" << endl;
}
int main(int args, char* argv[])
{
if (args != 3)
Usage();
string server_ip = argv[1];
uint16_t server_port = atoi(argv[2]);
unique_ptr udp_client(new UdpClient(server_ip, server_port));
udp_client->Init();
udp_client->Run();
return 0;
}
运行效果: