当前位置: 首页 > news >正文

【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;
// }

相关文章:

  • WebView工作原理全解析:如何实现混合开发的无缝衔接
  • 69、JS中如何调用上位机接口
  • 深入讲解一下 Nomic AI 的 GPT4All 这个项目
  • 局域网内电脑与安卓设备低延迟同屏技术【100ms - 200ms】
  • 开疆智能ModbusTCP转Devicenet网关连接三菱PLC与ABB机器人配置案例
  • 解决U盘安装Win11无法命令行跳过联网激活的问题
  • Python内存互斥与共享深度探索:从GIL到分布式内存的实战之旅
  • java发送excel附件的邮件
  • 低成本同屏方案:电脑 + 路由器实现 50 台安卓平板实时同屏
  • 电脑在使用过程中频繁死机怎么办
  • 组合模式深度解析:Java设计模式实战指南与树形结构处理架构设计
  • React Native 构建与打包发布(iOS + Android)
  • 电脑虚拟网卡安装(添加以太网2)
  • 将包含父子关系的扁平列表 List<Demo> 转换成树形结构的 List<DemoVO>,每个节点包含自己的子节点列表
  • Python 轻量化环境管理利器 UV 入门与 Windows 下安装实战
  • 【结合JSR380自定义校验】
  • 神经网络压缩
  • PHP基础-运算符
  • 用AI思维重塑人生:像训练神经网络一样优化自己
  • Java EE 导读
  • wordpress文章和页面/百度seo培训要多少钱
  • 广东网站制作多少钱/淘宝搜索关键词排名查询工具
  • r语言做网站/怎么免费建个人网站
  • 高端网站建设 骆诗/中国十大网络销售公司
  • 有哪些网站可以做兼职/推广文章的推广渠道
  • 手工蛋糕网站开发报告/seo推广哪家好