【Linux網絡編程】基于udp套接字實現的網絡通信

目錄

一、實現目標:

二、實驗步驟:

1、服務端代碼解析:

Init():

Run():

2、客戶端代碼:

主函數邏輯:

send_message發送數據:

recv_message接收數據:

三、實驗結果:

四、拓展:

五、全部代碼:


一、實現目標:

實現基于udp套接字實現的網絡通信,這里我們實現客戶端和服務端

首先在服務端中維護一張哈希表,存儲的kv值是客戶端的ip地址和sockaddr_in,然后服務端用于接收客戶端發送的信息,并進行處理,如果當前客戶端在哈希表中就不做處理,如果不在就添加到哈希表中,并且廣播給哈希表中的所有用戶

對于客戶端,為了完成類似于QQ這樣的方式,能夠一邊發送信息給服務端,并且能夠保證在不發送的時候也能從服務端中讀取到數據,所以就需要用到多線程并發了,一個線程從服務端中讀取數據,并且打印出來看看;另一個線程向服務端中發送數據

通信的原理就是向_sockfd這個網絡文件描述符中同時進行讀寫

二、實現代碼:

其中log.hpp是在系統部分學到的,當時封裝好的一個日志文件

在本次實驗中將服務端進行封裝了,客戶端未進行封裝

1、服務端代碼解析:

如下是服務端的main.cc,就是通過智能指針實現服務端的類,然后初始化,啟動服務器即可

void Usege(char* proc)
{std::cout<<"\n\tUsage: "<<proc<<"port[1024+]\n"<<std::endl;
}int main(int argc,char* argv[])
{if(argc != 2){Usege(argv[0]);exit(1);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> svr(new UdpServer(port));svr->Init();svr->Run(/*ExcuteCommand*/);return 0; 
}

接著是服務端的核心代碼框架:

    #pragma once
#include <iostream>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <functional>
#include <unordered_map>
#include <arpa/inet.h>#include "log.hpp"
#define SIZE 1024
Log lg;
// enum 搞一個錯誤碼合集
enum
{SOCKET_ERR = 1,BIND_ERR = 2
};
// const uint16_t defaultport = 3306;
const std::string defaultip = "0.0.0.0";
class UdpServer
{
public:UdpServer(const uint16_t& port/* = defaultport*/,const std::string& ip = defaultip):_sockfd(0),_port(port),_ip(ip),_isrunning(false){}void Init(){}void CheckUser(const struct sockaddr_in& client,const uint16_t& clientport,const std::string& clientip){}void Broadcast(const std::string& info,const uint16_t& clientport,const std::string& clientip){}void Run(){}~UdpServer(){}
private:int _sockfd; // 網絡文件描述符std::string _ip; // uint16_t _port; // 服務器進程的端口號bool _isrunning; // 服務器在啟動后要一直保證運行中std::unordered_map<std::string,struct sockaddr_in> online_user;// 將在聊天室中的人都存儲在哈希表中
};

其中成員變量:

_sockfd就是網絡文件描述符

_ip就是指定服務器綁定的IP地址,并且這里給了缺省值,也就是在外部如果沒有傳ip就采用默認值表示綁定所有可用網絡接口

_port表示服務器進程的端口號

_isrunning表示服務器是否在運行中的狀態

online_user是一個哈希表,表示當前聊天室中存在的人

接下來依次實現各個函數的功能即可

Init():

在初始化服務端這里:

首先就是床加你socket,這里是采用的IPV4,所以需要初始化struct sockaddr_in,初始化里面的IP地址和端口號等等的成員變量,然后進行bind綁定,這步就是將棧上的數據綁定到內核中

    void Init(){// socket創建,記得加上log日志_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){lg(FATAL,"socket create fail, sockfd : %d",_sockfd);exit(SOCKET_ERR);}lg(INFO,"socket create success, sockfd = %d",_sockfd);// 首先搞一個sockaddr_in,然后將里面都初始化為0,并且初始化內部成員,這里有三個struct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family = AF_INET; // 設置為AF_INET表示IPv4local.sin_port = htons(_port); // 端口轉換local.sin_addr.s_addr = inet_addr(_ip.c_str());// local.sin_addr.s_addr = INADDR_ANY; // 定義為0.0.0.0,這里是必須填在sin_addr里面的s.addr的,// 因為第一個sin_addr里面還是一個結構體,這個結構體里面才是s_addr// 在進行bind綁定,這步才是將棧上的數據都綁定到內核中,將數據轉到網絡文件描述符中int n = bind(_sockfd,(const struct sockaddr*)&local,sizeof(local));if(n < 0){lg(FATAL, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}lg(INFO, "bind success, errno: %d, err string: %s", errno, strerror(errno));}

Run():

這個函數就是將我們的服務端啟動起來,所以首先要將_isrunning修改為true,接著通過recvfrom接收從客戶端發來的消息,然后進行CheckUser檢查,最后進行Broadcast廣播

    void Run(){// 修改_isrunning_isrunning = true;// 進入while循環char inbuffer[SIZE];while(_isrunning){memset(inbuffer, 0, sizeof(inbuffer));// recvfrom函數,這里把接收來的當做字符串,也就是在sizeof(inbuffer)的大小上-1,最后接收完成后,將第n的位置設置為\0也就是0struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&client,&len);if(n < 0){lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}// inbuffer[n] = 0; // 這里為什么要去掉// 這里需要拿到client中的端口號和ip然后傳給CheckUser,進行通信室中的人員是否上線管理uint16_t clientport = ntohl(client.sin_port);std::string clientip = inet_ntoa(client.sin_addr);CheckUser(client,clientport,clientip);// 當一個用戶上線后,并且發送消息了,此時將消息和用戶人員進行廣播std::string info = inbuffer; // 此時這個info也就要傳參到Broadcast進行統一處理Broadcast(info,clientport,clientip);}}

接下來就是實現CheckUser和Broadcast了

CheckUser

    void CheckUser(const struct sockaddr_in& client,const uint16_t& clientport,const std::string& clientip){// 在哈希表中進行查找,如果沒找到就添加,找到了什么都不用做auto pos = online_user.find(clientip);if(pos == online_user.end()){online_user.insert({clientip,client});std::cout << "[" << clientip << ":" << clientport << "] add to online user." << std::endl;}}

Broadcast

void Broadcast(const std::string& info,const uint16_t& clientport,const std::string& clientip){// 遍歷整個哈希表,給這個哈希表中的所有人都發送messagefor(const auto& ch : online_user){std::string message = "[";message += clientip;message += ":";message += to_string(clientport);message += "]#";message += info;socklen_t len = sizeof(ch.second);  // 然后處理完后的數據用sendto接口發送回給對方sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)(&ch.second),len);}}

2、客戶端代碼:

客戶端代碼采用兩個線程進行并發執行,并且沒有對客戶端進行封裝,直接就是主函數

如下是框架:

#include <iostream>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>#define SIZE 1024void Use(const std::string proc)
{std::cout << "\n\t" << proc << " serveip serveport\n" << std::endl;
}struct ThreadDate
{struct sockaddr_in server;int sockfd;
};void* recv_message(void* argv)
{}void* send_message(void* argv)
{}int main(int argc,char* argv[])
{// pthread_create(&recvr,nullptr,recv_message,&td);// pthread_create(&sender,nullptr,send_message,&td);// pthread_join(recvr,nullptr);// pthread_join(sender,nullptr);
}

主函數邏輯:

首先通過命令行拿到服務端的端口號和服務端的IP地址,接著創建ThreadDate結構體,方便后續的線程中進行傳參,最后創建好線程然后實現好對應的方法即可

// 這個是多線程版本的,思路是創建兩個線程,一個線程從服務端中讀數據,一個線程向服務端中發送數據
int main(int argc,char* argv[])
{// 檢查命令行if(argc != 3){Use(argv[0]);exit(1);}// .udpclient serveip serveportstd::string serveip = argv[1];uint16_t serveport = std::stoi(argv[2]);// 初始化server sockaddr_in,為了在后面sendto給serverThreadDate td;bzero(&td.server,sizeof(td.server)); td.server.sin_family = AF_INET;td.server.sin_port = htons(serveport); // 需要從主機序列轉換成網絡序列td.server.sin_addr.s_addr = inet_addr(serveip.c_str()); // 把點分十進制格式的IPv4地址轉換為網絡字節序的 32 位無符號整數// sockfd,類似與創建文件描述符,在最后記得關閉,其實不關閉也行,畢竟最后程序都結束了,sockfd的生命周期是隨進程的td.sockfd = socket(AF_INET,SOCK_DGRAM,0);if(td.sockfd < 0){std::cout<<"socket error"<<std::endl;exit(2);}// 創建兩個線程pthread_t recvr,sender;pthread_create(&recvr,nullptr,recv_message,&td);pthread_create(&sender,nullptr,send_message,&td);pthread_join(recvr,nullptr);pthread_join(sender,nullptr);close(td.sockfd);return 0;
}

注意:

client客戶端要bind嗎?要!只不過不需要用戶顯示的bind!一般有OS自由隨機選擇
一個端口號只能被一個進程bind,對server是如此,對于client,也是如此
其實client的port是多少,其實不重要,只要能保證主機上的唯一性就可以
系統什么時候給我bind呢?首次發送數據的時候

send_message發送數據:

我們通過geiline函數在標準輸入流中進行讀取,然后通過sendto接口發送給服務端

void* send_message(void* argv)
{ThreadDate* td = static_cast<ThreadDate*>(argv);std::string message;socklen_t len = sizeof(td->server);while(true){// 從cin中獲得數據std::cout<<"Please enter#";getline(std::cin,message);// std::cout<<message<<std::endl;// sendto發送數據int st = sendto(td->sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&td->server,len);if(st<0){std::cout<<"sendto error"<<std::endl;continue;}}
}

recv_message接收數據:

通過recvfrom接口接收數據后,打印出來看看

void* recv_message(void* argv)
{struct ThreadDate* td = static_cast<ThreadDate*>(argv);char buffer[SIZE];while(true){memset(buffer, 0, sizeof(buffer));// recvfrom接收數據// 在接收消息的時候,可能會從多臺主機上收消息,所以recvfrom后面的參數就不能是上述確定的某一個服務器// 但是又必須要填參數,所以這里新創建一個sockaddr_instruct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(td->sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);// std::cout << "recvfrom over"<<std::endl;if(s > 0){buffer[s] = 0;std::cerr<<buffer<<std::endl;}}
}

三、實驗結果:

如上,這樣成功的寫出了基于udp套接字實現的網絡通信

首先將服務端進程進行打開

接著打開客戶端

如下是華為云的客戶端:

如下是騰訊云的客戶端:

這里有個細節,為了將我發的消息和接收的消息分開看,我們在編碼的時候是在標準錯誤中打印,所以在啟動的時候可以直接將標準錯誤重定向到別的終端,這樣就能夠進行分離了

接著在騰訊云中發送你好

在服務端中就能夠看到我們上線的消息了

但是在華為云的客戶端中卻不能夠看到,這是因為我們的華為云還沒有上線,接著在華為云中發送haha,就能夠發現華為云這個客戶端也上線了

此時在騰訊云中發送你吃了嗎,在華為云的客戶端中就能夠看到了,注意,在華為云中,我們并沒有將發送消息和接收消息分離

這樣就能夠實現網絡通信了

四、拓展:

在本次實驗中,我們并沒有讓服務端進行處理消息,只是處理了用戶添加到哈希表時上線的消息,如果想讓服務端進行消息的處理,可以使用function包裝器實現服務端網絡通信的功能和處理數據的功能的解耦

思路就是你在服務端代碼中增加包裝器

然后在run這個成員函數中,通過包裝器實現一個回調

    // 如下是第一個版本,在這個版本中,是讓網絡接收數據和處理數據實現了解耦void Run(func_t func){// 修改_isrunning_isrunning = true;// 進入while循環char inbuffer[SIZE];while(_isrunning){// recvfrom函數,這里把接收來的當做字符串,也就是在sizeof(inbuffer)的大小上-1,最后接收完成后,將第n的位置設置為\0也就是0struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&client,&len);if(n < 0){lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}inbuffer[n] = 0;// 拼接字符串,這里充當一次數據的處理std::string info = inbuffer;// std::string echo_string = "server echo@" + info;std::string echo_string = func(info);std::cout<<echo_string<<std::endl;// 然后處理完后的數據用sendto接口發送回給對方sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&client,len);}}

最后在main.cc中實現想要讓服務端執行的代碼即可

std::string Handler(std::string str)
{std::string res = "Server get a message ";res += str; return res;
}

五、全部代碼:

main.cc

#include <memory>
#include <cstdio>
#include <vector>#include "UdpServer.hpp"void Usege(char* proc)
{std::cout<<"\n\tUsage: "<<proc<<"port[1024+]\n"<<std::endl;
}std::string Handler(std::string str)
{std::string res = "Server get a message ";res += str; return res;
}bool SafeCheck(const std::string& cmd)
{// 搞一個vector的數組,然后遍歷它,進行find查找vector<string> check = {"rm","mv","cp","kill","sudo","unlink","uninstall","yum","top","while"};for(const auto& ch : check){auto pos = cmd.find(ch);if(pos != std::string::npos) return false;}return true;
}// 理解遠端指令是怎么一回事
std::string ExcuteCommand(const std::string& cmd)
{std::cout<<"cmd:" << cmd << std::endl;// 根據SafeCheck函數做安全檢查if(!SafeCheck(cmd)) return "error";// 建立好管道// 創建子進程// 子進程執行的結果通過管道交給父進程// 父進程想讀到執行結果可以在FILE*指針也就是fp中讀到FILE* fp = popen(cmd.c_str(),"r");if(fp == nullptr){perror("popen");return "error";}std::string result;char buffer[4096];while(true){char* res = fgets(buffer,sizeof(buffer),fp);if(res == nullptr) break;result += buffer;}pclose(fp);return result;
}int main(int argc,char* argv[])
{if(argc != 2){Usege(argv[0]);exit(1);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> svr(new UdpServer(port));svr->Init();svr->Run(/*ExcuteCommand*/);return 0; 
}

log.hpp

#pragma once#include <iostream>
#include <ctime>
#include <cstdarg>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define INFO 0
#define DEBUG 1
#define WARNING 2
#define ERROR 3
#define FATAL 4#define SCREEN 1
#define ONEFILE 2
#define MOREFILE 3#define SIZE 1024
#define logname "log.txt"using namespace std;class Log
{
public:Log():printstyle(SCREEN),path("./log/")// 默認路徑是當前路徑下的log文件夾{// mkdir(path.c_str(),0765);}void change(int style){printstyle = style;}string leveltostring(int level){switch (level){case INFO:return "INFO";case DEBUG:return "DEBUG";case WARNING:return "WARNING";case ERROR:return "ERROR";case FATAL:return "FATAL";default:return "NON";}}void operator()(int level, const char *format, ...){// 處理時間time_t now = time(nullptr);// 將時間戳轉為本地時間struct tm *local_time = localtime(&now);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", leveltostring(level).c_str(),local_time->tm_year + 1900, local_time->tm_mon + 1, local_time->tm_mday,local_time->tm_hour, local_time->tm_min, local_time->tm_sec);// 處理可變參數va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 將兩個消息組合起來成為一個完整的日志消息// 默認部分+自定義部分char logbuffer[SIZE * 2];snprintf(logbuffer, sizeof(logbuffer), "%s %s", leftbuffer, rightbuffer);printlog(level, logbuffer);}void printlog(int level, const string &logbuffer) // 這里引用避免大型字符串的拷貝開銷,優化性能{switch (printstyle){case SCREEN:cout << logbuffer << endl;break;case ONEFILE:printonefile(logname, logbuffer);break;case MOREFILE:printmorefile(level, logbuffer);break;}}void printonefile(const string &_logname, const string &logbuffer){string __logname = path + _logname;int fd = open(__logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0)return;write(fd, logbuffer.c_str(), logbuffer.size());close(fd);}void printmorefile(int level, const string &logbuffer){// 思路:通過不同的文件名進行區分string _logname = logname;_logname += ".";_logname += leveltostring(level);printonefile(_logname, logbuffer);}~Log(){}private:int printstyle;string path;
};

Udpserver.hpp

#pragma once
#include <iostream>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <functional>
#include <unordered_map>
#include <arpa/inet.h>#include "log.hpp"// 網絡接收數據和處理數據的耦合度太高了,所以就需要把網絡通信的功能和處理數據的功能做一下適當的解耦
typedef std::function<std::string(const std::string&)> func_t; // function包裝器// 返回值          // 參數#define SIZE 1024Log lg;
// enum 搞一個錯誤碼合集
enum
{SOCKET_ERR = 1,BIND_ERR = 2
};// const uint16_t defaultport = 3077;
const std::string defaultip = "0.0.0.0";class UdpServer
{
public:UdpServer(const uint16_t& port/* = defaultport*/,const std::string& ip = defaultip):_sockfd(0),_port(port),_ip(ip),_isrunning(false){}void Init(){// socket創建,記得加上log日志_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd < 0){lg(FATAL,"socket create fail, sockfd : %d",_sockfd);exit(SOCKET_ERR);}lg(INFO,"socket create success, sockfd = %d",_sockfd);// 首先搞一個sockaddr_in,然后將里面都初始化為0,并且初始化內部成員,這里有三個struct sockaddr_in local;bzero(&local,sizeof(local));local.sin_family = AF_INET; // 設置為AF_INET表示IPv4local.sin_port = htons(_port); // 端口轉換local.sin_addr.s_addr = inet_addr(_ip.c_str());// local.sin_addr.s_addr = INADDR_ANY; // 定義為0.0.0.0,這里是必須填在sin_addr里面的s.addr的,// 因為第一個sin_addr里面還是一個結構體,這個結構體里面才是s_addr// 在進行bind綁定,這步才是將棧上的數據都綁定到內核中int n = bind(_sockfd,(const struct sockaddr*)&local,sizeof(local));if(n < 0){lg(FATAL, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}lg(INFO, "bind success, errno: %d, err string: %s", errno, strerror(errno));}// // 如下是第一個版本,在這個版本中,是讓網絡接收數據和處理數據實現了解耦// void Run(func_t func)// {//     // 修改_isrunning//     _isrunning = true;//     // 進入while循環//     char inbuffer[SIZE];//     while(_isrunning)//     {//         // recvfrom函數,這里把接收來的當做字符串,也就是在sizeof(inbuffer)的大小上-1,最后接收完成后,將第n的位置設置為\0也就是0//         struct sockaddr_in client;//         socklen_t len = sizeof(client);//         ssize_t n = recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&client,&len);//         if(n < 0)//         {//             lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));//             continue;//         }//         inbuffer[n] = 0;//         // 拼接字符串,這里充當一次數據的處理//         std::string info = inbuffer;//         // std::string echo_string = "server echo@" + info;//         std::string echo_string = func(info);//         std::cout<<echo_string<<std::endl;//         // 然后處理完后的數據用sendto接口發送回給對方//         sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&client,len);//     }// }void CheckUser(const struct sockaddr_in& client,const uint16_t& clientport,const std::string& clientip){// 在哈希表中進行查找,如果沒找到就添加,找到了什么都不用做auto pos = online_user.find(clientip);if(pos == online_user.end()){online_user.insert({clientip,client});std::cout << "[" << clientip << ":" << clientport << "] add to online user." << std::endl;}}void Broadcast(const std::string& info,const uint16_t& clientport,const std::string& clientip){// 遍歷整個哈希表,給這個哈希表中的所有人都發送messagefor(const auto& ch : online_user){std::string message = "[";message += clientip;message += ":";message += to_string(clientport);message += "]#";message += info;socklen_t len = sizeof(ch.second);  // 然后處理完后的數據用sendto接口發送回給對方sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)(&ch.second),len);}}// 這是第二個版本,為了實現基于udp協議的聊天室,我要讓每個人上線后,能夠知道是誰進行發送的void Run(){// 修改_isrunning_isrunning = true;// 進入while循環char inbuffer[SIZE];while(_isrunning){memset(inbuffer, 0, sizeof(inbuffer));// recvfrom函數,這里把接收來的當做字符串,也就是在sizeof(inbuffer)的大小上-1,最后接收完成后,將第n的位置設置為\0也就是0struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&client,&len);if(n < 0){lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}// inbuffer[n] = 0; // 這里為什么要去掉// 這里需要拿到client中的端口號和ip然后傳給CheckUser,進行通信室中的人員是否上線管理uint16_t clientport = ntohl(client.sin_port);std::string clientip = inet_ntoa(client.sin_addr);CheckUser(client,clientport,clientip);// 當一個用戶上線后,并且發送消息了,此時將消息和用戶人員進行廣播std::string info = inbuffer; // 此時這個info也就要傳參到Broadcast進行統一處理Broadcast(info,clientport,clientip);}}~UdpServer(){if(_sockfd>0) close(_sockfd);}
private:int _sockfd; // 網絡文件描述符std::string _ip; // uint16_t _port; // 服務器進程的端口號bool _isrunning; // 服務器在啟動后要一直保證運行中std::unordered_map<std::string,struct sockaddr_in> online_user;// 將在聊天室中的人都存儲在哈希表中
};

UdpClient.cc

#include <iostream>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>#define SIZE 1024void Use(const std::string proc)
{std::cout << "\n\t" << proc << " serveip serveport\n" << std::endl;
}struct ThreadDate
{struct sockaddr_in server;int sockfd;
};void* recv_message(void* argv)
{struct ThreadDate* td = static_cast<ThreadDate*>(argv);char buffer[SIZE];while(true){memset(buffer, 0, sizeof(buffer));// recvfrom接收數據// 在接收消息的時候,可能會從多臺主機上收消息,所以recvfrom后面的參數就不能是上述確定的某一個服務器// 但是又必須要填參數,所以這里新創建一個sockaddr_instruct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(td->sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);// std::cout << "recvfrom over"<<std::endl;if(s > 0){buffer[s] = 0;std::cerr<<buffer<<std::endl;}}
}void* send_message(void* argv)
{ThreadDate* td = static_cast<ThreadDate*>(argv);std::string message;socklen_t len = sizeof(td->server);while(true){// 從cin中獲得數據std::cout<<"Please enter#";getline(std::cin,message);// std::cout<<message<<std::endl;// sendto發送數據int st = sendto(td->sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&td->server,len);if(st<0){std::cout<<"sendto error"<<std::endl;continue;}}
}
// 這個是多線程版本的,思路是創建兩個線程,一個線程從服務端中讀數據,一個線程向服務端中發送數據
int main(int argc,char* argv[])
{// 檢查命令行if(argc != 3){Use(argv[0]);exit(1);}// .udpclient serveip serveportstd::string serveip = argv[1];uint16_t serveport = std::stoi(argv[2]);// 初始化server sockaddr_in,為了在后面sendto給serverThreadDate td;bzero(&td.server,sizeof(td.server)); td.server.sin_family = AF_INET;td.server.sin_port = htons(serveport); // 需要從主機序列轉換成網絡序列td.server.sin_addr.s_addr = inet_addr(serveip.c_str()); // 把點分十進制格式的IPv4地址轉換為網絡字節序的 32 位無符號整數// sockfd,類似與創建文件描述符,在最后記得關閉,其實不關閉也行,畢竟最后程序都結束了,sockfd的生命周期是隨進程的td.sockfd = socket(AF_INET,SOCK_DGRAM,0);if(td.sockfd < 0){std::cout<<"socket error"<<std::endl;exit(2);}// 創建兩個線程pthread_t recvr,sender;pthread_create(&recvr,nullptr,recv_message,&td);pthread_create(&sender,nullptr,send_message,&td);pthread_join(recvr,nullptr);pthread_join(sender,nullptr);close(td.sockfd);return 0;
}// // 如下是單進程版本的
// int main(int argc,char* argv[])
// {
//     // 檢查命令行
//     if(argc != 3)
//     {
//         Use(argv[0]);
//         exit(1);
//     }
//     // .udpclient serveip serveport
//     std::string serveip = argv[1];
//     uint16_t serveport = std::stoi(argv[2]);//     // 初始化server sockaddr_in,為了在后面sendto給server
//     struct sockaddr_in server;
//     bzero(&server,sizeof(server)); //     server.sin_family = AF_INET;
//     server.sin_port = htons(serveport); // 需要從主機序列轉換成網絡序列
//     server.sin_addr.s_addr = inet_addr(serveip.c_str()); // 把點分十進制格式的IPv4地址轉換為網絡字節序的 32 位無符號整數
//     socklen_t len = sizeof(server);//     // sockfd,類似與創建文件描述符,在最后記得關閉,其實不關閉也行,畢竟最后程序都結束了,sockfd的聲明周期是隨進程的
//     int sockfd = socket(AF_INET,SOCK_DGRAM,0);//     if(sockfd < 0)
//     {
//         std::cout<<"socket error"<<std::endl;
//         exit(2);
//     }//     // client客戶端要bind嗎?要!只不過不需要用戶顯示的bind!一般有OS自由隨機選擇
//     // 一個端口號只能被一個進程bind,對server是如此,對于client,也是如此
//     // 其實client的port是多少,其實不重要,只要能保證主機上的唯一性就可以
//     // 系統什么時候給我bind呢?首次發送數據的時候//     std::string message;
//     char buffer[SIZE];
//     while(true)
//     {
//         // 從cin中獲得數據
//         std::cout<<"Please enter#";
//         getline(std::cin,message);//         // std::cout<<message<<std::endl;
//         // sendto發送數據
//         int st = sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,len);
//         if(st<0)
//         {
//             std::cout<<"sendto error"<<std::endl;
//             continue;
//         }//         // recvfrom接收數據//         // 在接收消息的時候,可能會從多臺主機上收消息,所以recvfrom后面的參數就不能是上述確定的某一個服務器
//         // 但是又必須要填參數,所以這里新創建一個sockaddr_in
//         struct sockaddr_in temp;
//         socklen_t len = sizeof(temp);//         ssize_t s = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
//         // std::cout << "recvfrom over"<<std::endl;
//         if(s > 0)
//         {
//             buffer[s] = 0;
//             std::cout<<buffer<<std::endl;
//         }
//     }
//     close(sockfd);
//     return 0;
// }

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

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

相關文章

2025年想沖網安方向,該考華為安全HCIE還是CISSP?

打算2025年往網絡安全方向轉&#xff0c;現在考證是不是來得及&#xff1f;考啥證&#xff1f; 說實話&#xff0c;網絡安全這幾年熱得發燙&#xff0c;但熱歸熱&#xff0c;入門門檻也不低&#xff0c;想進這個賽道&#xff0c;技術、項目經驗、證書&#xff0c;缺一不可。 …

【系統架構設計師-2025上半年真題】綜合知識-參考答案及部分詳解(回憶版)

更多內容請見: 備考系統架構設計師-專欄介紹和目錄 文章目錄 【第1題】【第2題】【第3題】【第4題】【第5題】【第6題】【第7題】【第8題】【第9題】【第10題】【第11題】【第12題】【第13題】【第14題】【第15題】【第16題】【第17題】【第18題】【第19題】【第20~21題】【第…

「Java EE開發指南」如何用MyEclipse創建一個WEB項目?(一)

在本文中&#xff0c;您可以找到有關WEB項目的信息。將了解&#xff1a; Web項目結構和參數Web開發生產力工具JSP代碼完成和驗證 這些特性在MyEclipse中可用。 MyEclipse v2025.1離線版下載 一、Web項目結構 用最簡單的術語來說&#xff0c;MyEclipse Web項目是一個Eclips…

Elasticsearch:使用 ES|QL 進行地理空間距離搜索

作者&#xff1a;來自 Elastic Craig Taverner 在 Elasticsearch 查詢語言&#xff08;ES|QL&#xff09;中探索地理空間距離搜索&#xff0c;這是 Elasticsearch 地理空間搜索中最受歡迎和最有用的功能之一&#xff0c;也是 ES|QL 中的重要特性。 想獲得 Elastic 認證嗎&#…

列舉開源的模型和推理框架

當然可以&#xff01;下面是一個系統性的列表&#xff0c;按 開源大模型&#xff08;LLM&#xff09; 和 推理框架 兩大類列出&#xff0c;并配上簡要說明。 &#x1f9e0; 一、開源大語言模型&#xff08;LLMs&#xff09; 名稱發布者語言能力模型大小特點LLaMA 2 / 3Meta英文…

深入講解一下 Nomic AI 的 GPT4All 這個項目

我們來深入講解一下 Nomic AI 的 GPT4All 這個項目。 這是一個非常優秀和流行的開源項目&#xff0c;我會從**“它是什么”、“為什么它很重要”、“項目架構和源碼結構”以及“如何使用”**這幾個方面為你全面剖析。 一、項目概述 (Project Overview) 簡單來說&#xff0c;…

力扣HOT100之技巧:287. 尋找重復數

這道題真的是中等題嗎&#xff1f;我請問呢&#xff1f;&#xff1f;我怎么覺得是困難題呢&#xff1f; 這道題的思路太難想了&#xff0c;想不出來&#xff0c;直接去看的這位大佬的題解&#xff0c;寫得很清楚。 這道題可以將其轉化為環形鏈表問題&#xff0c;可是為什么只要…

QT log4qt 無法生成日志到中文的路徑中的解決方案

一.使用log4qt時,應用程序安裝在帶有中文路徑下,導致無法生成日志到安裝目錄中? 問題描述:如下的配置文件,log4j.appender.File.File 后面跟隨的路徑是當前路徑,你可能覺得自己的日志能夠生成在當前路徑中,如果你試著用自己的程序雙擊啟動一個文件時,你會發現日志生成在…

讓 Deepseek 寫電器電費計算器小程序

微信小程序版電費計算器 以下是一個去掉"電器名稱"后的微信小程序電費計算器代碼&#xff0c;包含所有必要文件&#xff1a; 1. app.json (全局配置) {"pages": ["pages/index/index"],"window": {"backgroundColor": &q…

第二部分-靜態路由實驗

目錄 一、什么是路由&#xff1f; 1.1.定義 1.2.路由作用 1.3.路由類型 1.3.1.直連路由 1.3.2.靜態路由 1.3.3.動態路由 1.3.4.路由表 1.5.路由器的匹配原則 1.6.路由配置 1.6.1.靜態路由配置 1.6.2.動態路由配置 二、實驗 2.1.靜態路由 2.1.1.實驗拓撲 2.1.2.實驗過程 2.2.缺省…

Could not initialize Logback logging from classpath:logback-spring.xml

jdk21、springboot 3.2.12啟動報錯找不到logback.xml Logging system failed to initialize using configuration from classpath:logback-spring.xml java.lang.IllegalStateException: Could not initialize Logback logging from classpath:logback-spring.xmlat org.sprin…

NORA:一個用于具身任務的小型開源通才視覺-語言-動作模型

25年4月來自新加坡技術和設計大學的論文“NORA: a Small Open-Sourced Generalist Vision Language Action Model for Embodied Tasks”。 現有的視覺-語言-動作 (VLA) 模型在零樣本場景中展現出優異的性能&#xff0c;展現出令人印象深刻的任務執行和推理能力。然而&#xff…

在Ubuntu中使用Apache2部署項目

1. 安裝Apache2 sudo apt update sudo apt install apache2 -y安裝完成后&#xff0c;Apache會自動啟動&#xff0c;通過瀏覽器訪問 http://服務器IP 應看到默認的Apache歡迎頁。 2. 配置防火墻&#xff08;UFW&#xff09; sudo ufw allow Apache # 允許Apache通過防火墻 …

【QT系統相關】QT文件

目錄 1. Qt 文件概述 2. 輸入輸出設備類 3 文件讀寫類 讀取文件內容 寫文件 實現一個簡單的記事本 4. 文件和目錄信息類 QT專欄&#xff1a;QT_uyeonashi的博客-CSDN博客 1. Qt 文件概述 文件操作是應用程序必不可少的部分。Qt 作為一個通用開發庫&#xff0c;提供了跨…

愛普生RX8111CE實時時鐘模塊在汽車防盜系統中的應用

在汽車智能化與電子化的發展浪潮中&#xff0c;汽車防盜系統是現代汽車安全的重要組成部分&#xff0c;其核心功能是通過監測車輛狀態并及時發出警報來防止車輛被盜或被非法操作。愛普生RX8111CE實時時鐘模塊憑借其高精度、低功耗和豐富的功能&#xff0c;能夠為汽車防盜系統提…

SQL注入攻擊原理與防御全解析

目錄 一、引言 二、SQL 注入原理 2.1 SQL 注入的概念 2.2 SQL 注入產生的原因 2.3 SQL 注入的本質 2.4 SQL 注入的關鍵點 三、SQL 注入的實現方法 3.1 常見的 SQL 注入場景 3.2 不同類型的 SQL 注入方式 3.3 SQL 注入的一般流程 四、SQL 注入的危害 4.1 數據泄露 …

寫實交互數字人:賦能消防知識科普,點亮智能交互講解新未來

在數字化浪潮席卷全球的今日&#xff0c;科技創新以前所未有的速度重塑著我們的生活方式與產業格局。消防知識科普&#xff0c;作為守護生命財產安全的關鍵防線&#xff0c;也亟待借力新興技術實現變革與突破。深聲科技以其行業領先的 2D 寫實交互數字人技術&#xff0c;為消防…

用 HTML、CSS 和 JavaScript 實現五子棋人機對戰游戲

引言 在 Web 開發的世界里&#xff0c;通過 HTML、CSS 和 JavaScript 可以創造出各種各樣有趣的互動游戲。今天&#xff0c;我們將深入探討如何實現一個簡單而又富有挑戰性的五子棋人機對戰游戲。這個游戲不僅能讓你重溫經典的五子棋玩法&#xff0c;還能通過 AI 對戰功能給你…

【QT】自動更新庫QSimpleUpdater使用實例封裝

【QT】自動更新庫QSimpleUpdater使用實例封裝 QSimpleUpdater 庫信號介紹appcastDownloaded 信號downloadFinished信號概括 參數介紹 實例編寫 QSimpleUpdater 庫 QSimpleUpdater是一個用于QT的開源自動更新庫&#xff0c;它可以幫助開發者實現應用程序的版本檢查和自動更新功…

Nginx、CDN、 DNS的關系解析

文章目錄 Nginx 與 CDN 的關系1. 角色定位2. 協作方式3. 自建 CDN vs. 第三方 CDN Nginx 與 DNS 的關系1. 角色定位2. 協作方式3. 性能優化 CDN 與 DNS 的關系1. 角色定位2. 協作方式3. 高級 DNS 技術 三者結合的典型架構總結 Nginx、CDN 和 DNS 是現代網絡架構中的三個關鍵組件…