目錄
一、端口號(hào)
二、UDP報(bào)頭格式
三、UDP的特點(diǎn)
四、UDP協(xié)議實(shí)現(xiàn)網(wǎng)絡(luò)聊天群
一、端口號(hào)
端口號(hào)port標(biāo)識(shí)了一個(gè)主機(jī)上進(jìn)行通信的不同的應(yīng)用程序。
- 0 ~ 1023:系統(tǒng)端口號(hào),HTTP、FTP、SSH等這些廣為使用的應(yīng)用層協(xié)議,它們的端口號(hào)都是固定的系統(tǒng)端口號(hào)(知名端口號(hào))
- 1024 ~ 65535:操作系統(tǒng)動(dòng)態(tài)分配的端口號(hào),客戶端程序的端口號(hào),可有操作系統(tǒng)分配或程序員分配(普通端口號(hào))
知名端口號(hào)(Well-Know Port Number):
- ftp服務(wù)器:21
- ssh服務(wù)器:22
- telnet服務(wù)器:23
- http服務(wù)器:80
- https服務(wù)器:443
查看知名端口號(hào):cat /etc/services
netstat:一個(gè)用來(lái)查看網(wǎng)絡(luò)狀態(tài)的重要工具
語(yǔ)法:netstat -[選項(xiàng)]
- n:拒絕顯示別名,能顯示數(shù)字的全部顯示數(shù)字
- l:僅列出有在 listen 狀態(tài)的服務(wù)狀態(tài)
- p:顯示建立相關(guān)連接的程序名
- t:僅顯示tcp相關(guān)選項(xiàng)
- u:僅顯示udp相關(guān)選項(xiàng)
- a:顯示所有選項(xiàng),默認(rèn)不顯示 listen 相關(guān)
pidof:查看服務(wù)器的進(jìn)程id(通過(guò)進(jìn)程名查看進(jìn)程id)
語(yǔ)法:pidof [進(jìn)程名]
端口號(hào)? ?----->? ?進(jìn)程(唯一關(guān)系)
- 一個(gè)端口號(hào)是否可以被多個(gè)進(jìn)程bind?不可以,端口號(hào)指向進(jìn)程必須唯一
- 一個(gè)進(jìn)程是否可以綁定多個(gè)端口號(hào)?可以,進(jìn)程指向端口號(hào)不一定唯一
二、UDP報(bào)頭格式
- 16位UDP長(zhǎng)度,表示整個(gè)數(shù)據(jù)報(bào)(UDP首部+UDP數(shù)據(jù))的最大長(zhǎng)度(64K)
- 如果檢驗(yàn)和出錯(cuò),數(shù)據(jù)直接丟棄?
- 定長(zhǎng)報(bào)頭,報(bào)頭的本質(zhì)是結(jié)構(gòu)化數(shù)據(jù)
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-567965.html
三、UDP的特點(diǎn)
UDP傳輸?shù)倪^(guò)程類(lèi)似于寄信:
- 無(wú)連接:知道對(duì)端的IP和端口號(hào)就直接進(jìn)行傳輸,不需要建立連接
- 不可靠:沒(méi)有確認(rèn)機(jī)制,沒(méi)有重傳機(jī)制,如果因?yàn)榫W(wǎng)絡(luò)故障導(dǎo)致數(shù)據(jù)段無(wú)法發(fā)送,UDP協(xié)議層也不會(huì)給應(yīng)用層返回任何錯(cuò)誤信息
- 面向數(shù)據(jù)報(bào):應(yīng)用層交給UDP多長(zhǎng)的報(bào)文,UDP原樣發(fā)送,既不會(huì)拆分,也不會(huì)合并,不能夠靈活的控制讀寫(xiě)數(shù)據(jù)的次數(shù)和數(shù)量
????????
UDP的緩沖區(qū):
- UDP沒(méi)有真正意義上的發(fā)送緩沖區(qū),調(diào)用sendto會(huì)直接交給內(nèi)核,由內(nèi)核將數(shù)據(jù)報(bào)傳給網(wǎng)絡(luò)層協(xié)議進(jìn)行后續(xù)的傳輸處理
- UDP具有接收緩沖區(qū),但是這個(gè)接收緩沖區(qū)不能保證收到的UDP報(bào)文的順序和發(fā)送UDP報(bào)文的順序一致;如果緩沖區(qū)滿了,再到達(dá)的UDP數(shù)據(jù)就會(huì)被丟棄
UDP的socket既能讀,也能寫(xiě),這就是全雙工概念。
基于UDP的常用應(yīng)用層協(xié)議:
- NFS:網(wǎng)絡(luò)文件系統(tǒng)
- TFTP:簡(jiǎn)單的文件傳輸協(xié)議
- DHCP:動(dòng)態(tài)主機(jī)配置協(xié)議
- BOOTP:?jiǎn)?dòng)協(xié)議(用于無(wú)盤(pán)設(shè)備啟動(dòng))
- DNS:域名解析協(xié)議
UDP協(xié)議的應(yīng)用場(chǎng)景多為視頻網(wǎng)站、直播平臺(tái)等不擔(dān)心數(shù)據(jù)包丟失的情況。
四、UDP協(xié)議實(shí)現(xiàn)網(wǎng)絡(luò)聊天群
設(shè)計(jì)思路:
- 服務(wù)器與客戶端通過(guò)udp協(xié)議通信,即server和client都需要?jiǎng)?chuàng)建自己的socket套接字
- server創(chuàng)建完socket套接字之后,需要將server自身的ip和port和套接字bind綁定,client的套接字不需要顯式bind綁定(由OS自動(dòng)綁定)
- server先運(yùn)行,通過(guò)recvfrom()向數(shù)據(jù)的接收緩沖區(qū)循環(huán)讀取數(shù)據(jù)
- client在運(yùn)行的時(shí)候指定server的ip和port,通過(guò)sendto()向服務(wù)器的接收緩沖區(qū)發(fā)送數(shù)據(jù)
- User.h頭文件,進(jìn)行在線用戶管理,通過(guò)ip和port形成用戶信息結(jié)構(gòu)體,可以進(jìn)行在線用戶的添加、刪除、判斷,并且在server接收到數(shù)據(jù)的時(shí)候,進(jìn)行廣播(對(duì)所有在線用戶發(fā)送剛剛接收到的數(shù)據(jù))
- 所有在線用戶信息通過(guò)一個(gè)靜態(tài)全局變量的哈希表管理,key值是ip-port組成的字符串
- client在運(yùn)行的時(shí)候就會(huì)進(jìn)行一次線程分離,分離出去的線程循環(huán)讀取并打印client自身的接收緩沖區(qū)內(nèi)的數(shù)據(jù)(來(lái)自廣播的數(shù)據(jù))
設(shè)計(jì)效果:當(dāng)多個(gè)client連接server之后,只要是online登錄了的用戶,都可以向server發(fā)送數(shù)據(jù)的同時(shí)看到自己和其他登錄用戶發(fā)送的數(shù)據(jù),這就是網(wǎng)絡(luò)聊天群
User.h
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <unordered_map>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
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<std::string, User> _users;
};
server.cpp
#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <functional>
#include <memory>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "User.h"
using namespace std;
typedef function<void(int, string, uint16_t, string)> 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. 創(chuàng)建套接字
_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); // 網(wǎng)絡(luò)序列 ---> 整型 ---> 點(diǎn)分十進(jìn)制
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;
};
// 聊天群在線用戶,靜態(tài)全局變量
static OnlineUser g_online_users;
// 處理message,server與業(yè)務(wù)邏輯解耦
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 = "你還沒(méi)有登錄,請(qǐng)輸入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<UdpServer> udp_server(new UdpServer(RouteMessage, port));
udp_server->Init();
udp_server->Start();
return 0;
}
client.cpp
#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <functional>
#include <pthread.h>
#include <memory>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#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. 創(chuàng)建套接字
_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<int*>(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<UdpClient> udp_client(new UdpClient(server_ip, server_port));
udp_client->Init();
udp_client->Run();
return 0;
}
運(yùn)行效果:
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-567965.html
?
到了這里,關(guān)于【Linux后端服務(wù)器開(kāi)發(fā)】UDP協(xié)議的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!