linux socket編程之udp(實現客戶端和服務端消息的發送和接收)

目錄

一.創建socket套接字(服務器端)

二.bind將prot與端口號進行綁定(服務器端)

2.1填充sockaddr_in結構

2.2bind綁定端口

三.直接通信(服務器端)

3.1接收客戶端發送的消息

3.2給客戶端發送消息

四.客戶端通信

4.1創建socket套接字

4.2客戶端bind問題

4.3直接通信即可

4.3.1構建目標主機的socket信息

4.3.2給服務端發送消息

4.3.3.接收服務端發送過來的消息

五.效果展示

5.1使用127.0.0.1本地環回測試

5.2使用公網ip測試

六.代碼演示

6.1UdpServer.hpp

6.2UdpClient.cc

6.3InetAddr.hpp

6.4LockGuard.hpp

6.5Log.hpp

6.6main.cc

6.7makefile


一.創建socket套接字(服務器端)

int socket(int domain, int type, int protocol);

domain:選擇你要使用的網絡層協議 一般是ipv4,也就是AF_INET

type:選擇你要使用的應用層協議,這里我們選擇udp,也就是SOCK_DGRAM

protocol:這里我們先設置成0

成功返回文件描述符,失敗返回-1

//1.創建socket套接字
_socketfd = socket(AF_INET,SOCK_DGRAM,0);
if(_socketfd < 0)
{LOG(FATAL, "SOCKET ERROR, %s, %d\n", strerror(errno), errno);exit(SOCKET_ERROR);
}

二.bind將prot與端口號進行綁定(服務器端)

2.1填充sockaddr_in結構

uint16_t htons(uint16_t hostshort);//將端口號從主機序列轉成網絡序列
in_addr_t inet_addr(const char *cp);//將ip從主機序列轉成網絡序列 + 字符串風格ip轉成點分十進制ip
uint16_t ntohs(uint16_t netshort);//將端口號從網絡序列轉成主機序列
char *inet_ntoa(struct in_addr in);//將ip從網絡序列轉成主機序列 + 點分十進制ip轉成字符串風格ip

網絡通信:struct sockaddr_in

本地通信:sockaddr_un

16位地址類型表明了他們是網絡通信還是本地通信?

16位地址類型:sin_family

16位端口號:sin_port

32位ip地址:sin_addr.s_addr

//填充sockaddr_in結構
struct sockaddr_in local;
local.sin_family = AF_INET;//表明是網絡通信
local.sin_port = htons(_prot);//將主機序列轉成網絡序列
local.sin_addr.s_addr = inet_addr("0.0.0.0");//將字符串類型的點分十進制ip轉成四字節ip,并轉成網絡序列

2.2bind綁定端口

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

sockfd:要綁定的socket套接字的文件描述符

struct sockaddr *:包含ip地址+端口號的結構體(類型不一樣需要進行強轉)

socklen_t addrlen:sockaddr_in結構體的大小

//bind綁定端口號
int n = bind(_socketfd,(sockaddr *)&local,sizeof(local));//需要將sockaddr_in強轉成sockaddr
if(n < 0)
{LOG(FATAL, "BIND ERROR, %s, %d\n", strerror(errno), errno);exit(BIND_ERROR);
}

三.直接通信(服務器端)

3.1接收客戶端發送的消息

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

sockfd:套接字的文件描述符

buf:緩存區

len:緩存區的大小,單位是字節

flags:暫時設置為0

src_addr:數據來自于哪????????????????????????????????

addrlen:struct sockaddr結構體的大小

//接收客戶端發送來的消息
char buffer[1024];
sockaddr_in peer;
socklen_t len = sizeof(peer);
int n = recvfrom(_socketfd,buffer,sizeof(buffer) - 1,0,(sockaddr*)&peer,&len);  

3.2給客戶端發送消息

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

sockfd:套接字的文件描述符

buf:緩存區

len:緩存區的大小,單位是字節

flags:設置為0

src_addr:要將數據發送給誰

addrlen:struct sockaddr結構體的大小

//處理客戶端發送的消息,并且將結果返回給客戶端
buffer[n] = 0;
sendto(_socketfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);

四.客戶端通信

4.1創建socket套接字

// 1. 創建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{std::cerr << "socket error" << std::endl;
}

4.2客戶端bind問題

客戶端不需要顯示的bind,os會自動幫你綁定端口號!!!

試想一下,你的手機上有抖音和微信兩個客戶端小程序,如果抖音客戶端bind了8080這個端口,微信也想要bind8080這個端口,那么這時候就會出現一個問題,一個端口號被兩個進程競爭!!!結果就是,抖音和微信不可能同時啟動。

所以解決方法就是:udp client首次發送數據的時候,OS會自己自動隨機的給client進行bind

4.3直接通信即可

4.3.1構建目標主機的socket信息

struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());

4.3.2給服務端發送消息

//給服務端發送消息
std::cout << "Please Enter# ";
std::getline(std::cin, message);
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));

4.3.3.接收服務端發送過來的消息

//接收服務端發送過來的消息
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char buffer[1024];
ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
if(n > 0)
{buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;
}

五.效果展示

5.1使用127.0.0.1本地環回測試

5.2使用公網ip測試

六.代碼演示

6.1UdpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"enum 
{SOCKET_ERROR = 1,//創建套接字失敗BIND_ERROR,//bind綁定端口失敗USAGE_ERROR//啟動udp服務失敗
};int default_socketfd = -1;
class UdpServer
{
public:UdpServer(uint16_t prot): _socketfd(default_socketfd),_prot(prot){}   ~UdpServer(){}void Init(){//1.創建socket套接字_socketfd = socket(AF_INET,SOCK_DGRAM,0);if(_socketfd < 0){LOG(FATAL, "SOCKET ERROR, %s, %d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success, sockfd: %d\n", _socketfd);//2.bind 將socket套接字和端口號進行綁定//填充sockaddr_in結構struct sockaddr_in local;local.sin_family = AF_INET;//表明是網絡通信local.sin_port = htons(_prot);//將主機序列轉成網絡序列local.sin_addr.s_addr = inet_addr("0.0.0.0");//將字符串類型的點分十進制ip轉成四字節ip,并轉成網絡序列//local.sin_addr.s_addr = INADDR_ANY;//bind綁定端口號int n = bind(_socketfd,(sockaddr *)&local,sizeof(local));//需要將sockaddr_in強轉成sockaddrif(n < 0){LOG(FATAL, "BIND ERROR, %s, %d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");}void Stat(){//3.直接開始通信while(true){//接收客戶端發送來的消息char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);           ssize_t n = recvfrom(_socketfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);  if(n > 0){InetAddr addr(peer);LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);//處理客戶端發送的消息,并且將結果返回給客戶端buffer[n] = 0;sendto(_socketfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);}}}private:uint16_t _prot;int _socketfd;
};

6.2UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1. 創建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "socket error" << std::endl;}// 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要顯式[和server一樣用bind函數]的bind?不能!不建議!!// a. 如何bind呢?udp client首次發送數據的時候,OS會自己自動隨機的給client進行bind --- 為什么?防止client port沖突。要bind,必然要和port關聯!// b. 什么時候bind呢?首次發送數據的時候// 構建目標主機的socket信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());std::string message;// 2. 直接通信即可while(true){//給服務端發送消息std::cout << "Please Enter# ";std::getline(std::cin, message);std::cout<<message<<std::endl;sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));//接收服務端發送過來的消息struct sockaddr_in peer;socklen_t len = sizeof(peer);char buffer[1024];ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if(n > 0){buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;}}return 0;
}

6.3InetAddr.hpp

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
private:void GetAddress(std::string *ip, uint16_t *port){*port = ntohs(_addr.sin_port);*ip = inet_ntoa(_addr.sin_addr);}public:InetAddr(const struct sockaddr_in &addr) : _addr(addr){GetAddress(&_ip, &_port);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};

6.4LockGuard.hpp

#include <iostream>
#include <pthread.h>class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex); // 構造加鎖}~LockGuard(){pthread_mutex_unlock(_mutex);// 析構釋放鎖}
private:pthread_mutex_t *_mutex;
};

6.5Log.hpp

#pragma once
#include <cstdio>
#include <iostream>
#include <string>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h>
#include <fstream>#include "LockGuard.hpp"bool IsSave = false;//是否向文件中寫入
const std::string logname = "log.txt";//日志信息寫入的文件路徑// 日志是有等級的
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};// 將日志的登記由整形轉換為字符串
std::string LevelToString(int level)
{switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknown";}
}// 獲取時間
std::string GetTimeString()
{time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None"; // 沒有獲取成功char time_buffer[1024];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900, // 這里的year是減去1900之后的值,需要加上1900format_time->tm_mon + 1,     // 這里的mon是介于0-11之間的,需要加上1format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return time_buffer;
}//將日志信息寫入到文件中
void SaveFile(const std::string &filename, const std::string &message)
{std::ofstream out(filename, std::ios::app);if (!out.is_open()){return;}out << message;out.close();
}pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定義鎖 支持多線程
// 日志是有格式的
// 日志等級 時間 代碼所在的文件名/行數 日志的內容
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfid = getpid();char buffer[1024];va_list arg;//定義一個void* 指針va_start(arg, format);//初始化指針,將指針指向可變參數列表開始的位置vsnprintf(buffer, sizeof(buffer), format, arg);//將可變參數列表寫入到buffer中va_end(arg);//將指針置空std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +"[" + std::to_string(selfid) + "]" +"[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer + "\n";LockGuard lockguard(&lock);if (!issave){std::cout << message;//將日志信息打印到顯示器中}else{SaveFile(logname, message);//將日志信息寫入到文件}
}// C99新特性__VA_ARGS__
#define LOG(level, format, ...)  do{ LogMessage(__FILE__, __LINE__,IsSave,level, format, ##__VA_ARGS__); }while(0)
#define EnableFile()    do{ IsSave = true; }while(0)
#define EnableScreen()  do{ IsSave = false; }while(0) 

6.6main.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;
}// ./udpserver port
int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(USAGE_ERROR);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);usvr->Init();usvr->Stat();return 0;
}

6.7makefile

.PHONY:all
all:udpserver udpclient
udpclient:UdpClient.ccg++ -o udpclient UdpClient.cc -std=c++14
udpserver:main.ccg++ -o udpserver main.cc -std=c++14
.PHONY:clean
clean:rm -f udpserver

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/902087.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/902087.shtml
英文地址,請注明出處:http://en.pswp.cn/news/902087.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

第1期:Python基礎語法入門

1.1 Python簡介 Python是一種解釋型、面向對象、動態數據類型的高級編程語言。它設計簡潔&#xff0c;易于學習&#xff0c;適合初學者。Python廣泛應用于數據科學、人工智能、Web開發、自動化腳本等領域。它的語法簡潔易懂&#xff0c;強調代碼的可讀性。 1.2 安裝Python與配…

使用EXCEL繪制平滑曲線

播主播主&#xff0c;你都多少天沒更新了&#xff01;&#xff01;&#xff01;泥在干什么&#xff1f;你還做這個賬號麻&#xff1f;&#xff01;&#xff01;&#xff01; 做的做的&#xff08;哭唧唧&#xff09;&#xff0c;就是最近有些忙&#xff0c;以及…… 前言&…

當算力遇上馬拉松:一場科技與肉身的極限碰撞

目錄 一、從"肉身苦修"到"科技修仙" 二、馬拉松的"新大陸戰爭" 三、肉身會被算法"優化"嗎? 馬拉松的下一站是"人機共生"時代 當AI能預測你的馬拉松成績,算法能規劃最佳補給方案,智能裝備讓訓練效率翻倍——你還會用傳…

MLLMs for TSAD ?

項目鏈接:Multimodal LLMs Advance Time Series Analysis 代碼鏈接:https://github.com/mllm-ts/VisualTimeAnomaly 出處:ICLR 2025 一 文章動機 多模態 LLM (MLLM) 通過 “視覺” 方式處理時序的潛力仍未充分探索; 人類檢測 “時序異常” 的自然方式:可視化、文本描…

開發基于python的商品推薦系統,前端框架和后端框架的選擇比較

開發一個基于Python的商品推薦系統時&#xff0c;前端和后端框架的選擇需要綜合考慮項目需求、開發效率、團隊熟悉度以及系統的可擴展性等因素。 以下是一些推薦的框架和建議&#xff1a; 后端框架 Flask 優點&#xff1a; 輕量級&#xff1a;Flask的核心非常簡潔&#xff0c;…

chili3d調試筆記2+添加web ui按鈕

onclick 查找 打個斷點看看 挺可疑的&#xff0c;打個斷點看看 挺可疑的&#xff0c;打個斷點看看 打到事件監聽上了 加ui了 加入成功 新建彈窗-------------------------------------- 可以模仿這個文件&#xff0c;寫彈窗 然后在這里注冊一下&#xff0c;外部就能調用了 對了…

【重學Android】1.關于@Composer注解的一點知識筆記

最新因為一些原因&#xff0c;開始重新學習Android及kotlin編程&#xff0c;也覺得可以順帶記錄下這個過程中的一些知識點&#xff0c;也可以用作日后自己查找復習。 Composable 注解在 Android 開發中的使用 Composable 是 Jetpack Compose&#xff08;Android 的現代聲明式…

qt+mingw64+cmake+libqrencode項目編譯和搭建成功記錄

最近要使用高拍儀拍照獲取照片&#xff0c;然后識別照片中的二維碼數據、使用QZxing只能識別出一個條碼、另外一個條碼準備測試用其他的開源項目&#xff08;如libqrencode-4.1.1&#xff09;來進行測試&#xff0c;故進行本文的項目環境搭建測試&#xff0c;最后成功。 本機開…

【今日三題】判斷是不是平衡二叉樹(遞歸) / 最大子矩陣(二維前綴和) / 小蔥的01串(滑動窗口)

??個人主頁&#xff1a;小羊 ??所屬專欄&#xff1a;每日兩三題 很榮幸您能閱讀我的文章&#xff0c;誠請評論指點&#xff0c;歡迎歡迎 ~ 目錄 判斷是不是平衡二叉樹(遞歸)最大子矩陣(二維前綴和)小蔥的01串(滑動窗口) 判斷是不是平衡二叉樹(遞歸) 判斷是不是平衡二叉…

【Linux】線程ID、線程管理、與線程互斥

&#x1f4da; 博主的專欄 &#x1f427; Linux | &#x1f5a5;? C | &#x1f4ca; 數據結構 | &#x1f4a1;C 算法 | &#x1f310; C 語言 上篇文章&#xff1a; 【Linux】線程&#xff1a;從原理到實戰&#xff0c;全面掌握多線程編程&#xff01;-CSDN博客 下…

定制一款國密瀏覽器(10):移植SM2算法前,解決錯誤碼的定義問題

上一章中,我給大家介紹了 SM4 在 BoringSSL 上的移植要點,本來計劃本章介紹 SM2 算法的移植要點。在移植 SM2 過程中,遇到了一個攔路虎,所以先掃除這個攔路虎,這就是錯誤碼的定義問題。 在銅鎖中,引入了幾個錯誤碼和錯誤字符串,在文件 sm2_err.c 中: static const ER…

JDOM處理XML:Java程序員的“樂高積木2.0版“

各位代碼建筑師們&#xff01;今天我們要玩一款比原生DOM更"Java友好"的XML積木套裝——JDOM&#xff01;它像樂高得寶系列&#xff08;Duplo&#xff09;一樣簡單易用&#xff0c;卻能讓你的XML工程穩如霍格沃茨城堡&#xff01;&#xff08;溫馨提示&#xff1a;別…

【后端開發】Spring日志

文章目錄 Spring日志日志作用日志測試日志信息日志級別日志配置配置日志級別日志持久化日志文件分割 注解的使用 Spring日志 日志作用 系統監控&#xff1a;可以通過日志記錄這個系統的運行狀態&#xff0c;對數據進行分析&#xff0c;設置不同的規則&#xff0c;超過閾值時進…

探索大語言模型(LLM):Transformer 與 BERT從原理到實踐

Transformer 與 BERT&#xff1a;從原理到實踐 前言一、背景介紹二、核心公式推導1. 注意力機制&#xff08;Attention Mechanism&#xff09;2. 多頭注意力機制&#xff08;Multi-Head Attention&#xff09;3. Transformer 編碼器&#xff08;Transformer Encoder&#xff09…

計算機網絡八股——HTTP協議與HTTPS協議

目錄 HTTP1.1簡述與特性 1. 報文清晰易讀 2. 靈活和易于擴展 3. ?狀態 Cookie和Session 4. 明?傳輸、不安全 HTTP協議發展過程 HTTP/1.1的不足 HTTP/2.0 HTTP/3.0 HTTPS協議 HTTP協議和HTTPS協議的區別 HTTPS中的加密方式 HTTPS中建立連接的方式 前言&#xff…

QML中的3D功能--入門開發

Qt Quick 提供了強大的 3D 功能支持,主要通過 Qt 3D 模塊實現。以下是 QML 中開發 3D 應用的全面指南。 1. 基本配置 環境要求 Qt 5.10 或更高版本(推薦 Qt 6.x) 啟用 Qt 3D 模塊 支持 OpenGL 的硬件 項目配置 在 .pro 文件中添加: QT += 3dcore 3drender 3dinput 3dex…

Git合并分支的兩種常用方式`git merge`和`git cherry-pick`

Git合并分支的兩種常用方式git merge和git cherry-pick 寫在前面1. git merge用途工作方式使用git命令方式合并使用idea工具方式合并 2. git cherry-pick用途工作方式使用git命令方式合并使用idea工具方式合并 3. 區別總結 寫在前面 一般我們使用git合并分支常用的就是git mer…

Web三漏洞學習(其三:rce漏洞)

靶場&#xff1a;NSSCTF 三、RCE漏洞 1、概述 在Web應用開發中會讓應用調用代碼執行函數或系統命令執行函數處理&#xff0c;若應用對用戶的輸入過濾不嚴&#xff0c;容易產生遠程代碼執行漏洞或系統命令執行漏洞 所以常見的RCE漏洞函數又分為代碼執行函數和系統命令執行函數…

從零開始:Python運行環境之VSCode與Anaconda安裝配置全攻略 (1)

從零開始&#xff1a;Python 運行環境之 VSCode 與 Anaconda 安裝配置全攻略 在當今數字化時代&#xff0c;Python 作為一種功能強大且易于學習的編程語言&#xff0c;被廣泛應用于數據科學、人工智能、Web 開發等眾多領域。為了順利開啟 Python 編程之旅&#xff0c;搭建一個穩…

從FPGA實現角度介紹DP_Main_link主通道原理

DisplayPort&#xff08;簡稱DP&#xff09;是一個標準化的數字式視頻接口標準&#xff0c;具有三大基本架構包含影音傳輸的主要通道&#xff08;Main Link&#xff09;、輔助通道&#xff08;AUX&#xff09;、與熱插拔&#xff08;HPD&#xff09;。 Main Link&#xff1a;用…