【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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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 是现代网络架构中的三个关键组件…