网站域名查主机名,网络营销中心,泉州网站关键词排名,免费行情软件app网站直播(#xff61;#xff65;∀#xff65;)#xff89;#xff9e;嗨#xff01;你好这里是ky233的主页#xff1a;这里是ky233的主页#xff0c;欢迎光临~https://blog.csdn.net/ky233?typeblog 点个关注不迷路⌯▾⌯ 实现一个简单的对话发消息的功能#xff01;
目录… (∀)嗨你好这里是ky233的主页这里是ky233的主页欢迎光临~https://blog.csdn.net/ky233?typeblog 点个关注不迷路⌯▾⌯ 实现一个简单的对话发消息的功能
目录
一.新增的接口
1.socket
2.bing
3.inet_addr
4.recvform
5.inet_ntoa
5.sendto
6.popen
二、初步代码以及结果
1.udp_server.hpp
2.udp_server.cc
3.udp_client
4.log.hpp
5.结果
三、爆改多线程
1.udp_client.cc
2.udp_server.cc
3.udp_server.hpp 4.thread.hpp
5.结果 一.新增的接口
1.socket
#include sys/types.h /* See NOTES */
#include sys/socket.hint socket(int domain, int type, int protocol);参数一叫做域用来判断是那个类型的AF_UNIX, AF_LOCAL用于本地通信AF_INET用于ipv4的网络通信参数二类型通信的种类是什么SOCK_DGRAM套接字的类别数据报的方式参数三协议比如用AF_INETSOCK_DGRAM一般直接写0 创建成功会得到一个文件描述符失败返回-1
2.bing
#include sys/types.h /* See NOTES */
#include sys/socket.hint bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);参数一创建的套接字参数二填充sockaddr结构体参数三所传入这个结构体对象的长度
3.inet_addr
#include sys/socket.h#include netinet/in.h#include arpa/inet.h
n_addr_t inet_addr(const char *cp);
将点分十进制转换成四字节并且将主机序列转换成网络序列参数一传入的ip
4.recvform
#include sys/types.h
#include sys/socket.h
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
用于从套接字接收数据。该函数通常与无连接的数据报服务参数一套接字参数二和参数三读取数据的缓冲区用于存放读取到的数据len叫做最终的大小参数四读取的方式0为阻塞的方式读取参数五和参数六是输出型参数我们还要知道是谁给我们发的消息
5.inet_ntoa
#include sys/socket.h#include netinet/in.h#include arpa/inet.h
char *inet_ntoa(struct in_addr in);
将点分四字节转换成点分十进制并且将网络序列转换成主机序列参数一从网络中获取到的ip
5.sendto
#include sys/types.h
#include sys/socket.h
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数一套接字参数二和参数三要发送的数据和长度参数四默认为0阻塞参数五要把数据发给谁参数六这个缓冲区的长度是多少
6.popen
#include stdio.hFILE *popen(const char *command, const char *type);二、初步代码以及结果
1.udp_server.hpp
#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP#include iostream
#include string
#include sys/types.h
#include sys/socket.h
#include log.hpp
#include cerrno
#include cstring
#include cstdlib
#include netinet/in.h
#include arpa/inet.h
#include strings.h
#include cstdio
#include unistd.h#define SIZE 1024class UDPServer
{
public:UDPServer(uint16_t port,std::string ip):port_(port),ip_(ip),sock_(-1){}bool initServer(){//这里开始使用网络部分//1.创建套接字sock_socket(AF_INET,SOCK_DGRAM,0);if(sock_0){logMessage(FATAL,%d:%s,errno,strerror(errno));exit(2);}//2.bind绑定:将用户设置的ip和端口号在内核中和我们的进程强关联//192.168.1.0-点分十进制//以.来分隔每个区域的取值范围为0——255//正好为1字节四个区域理论上需要四字节//所以我们的看的时候会由4字节显示转换为点分十进制//初始化结构体完成struct sockaddr_in local;bzero(local,sizeof(local));//填充结构体local.sin_familyAF_INET;//服务器的IP和端口号未来也是要发送给对方主机才能互相通信的//所以要先把数据发送到网络要用htonslocal.sin_porthtons(port_);//同上要先把点分十进制转换成四字节ip//还需要要把四字节主机序列转换成网络序列//所以用inet_addr一次性转成网络四字节序列//local.sin_addr.s_addrinet_addr(ip_.c_str());//一般情况下只要是这个端口的那么我们就直接全部都接受而不是指定端口local.sin_addr.s_addr ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());//这里需要做强转因为bind需要的是sockaddr类型而不是sockaddr_in类型if(bind(sock_,(struct sockaddr*)local,sizeof(local))0){logMessage(FATAL,%d:%s,errno,strerror(errno));exit(2);}//至此初始化服务器写完首先创建套接字接着用bind来绑定ip和端口号写进内核中logMessage(NORMAL, init udp server done ... %s, strerror(errno));return true;}void strat(){//作为一款网络服务器是永远不退出的// 服务器启动- 进程 - 常驻进程 - 永远在内存中存在除非挂了//echo服务器主机给我们发送消息我们原封不动的返回char buffer[SIZE];for( ; ;){//1.读取数据//注意peer纯输出型参数struct sockaddr_in peer;bzero(peer,sizeof(peer));//输入peer 缓冲区大小//输入实际读到的peer大小//所以len的大小要为实际的peer大小socklen_t len sizeof(peer);//这样就把数据读取到buffer里了ssize_t s recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)peer,len);if(s0){buffer[s]0;//我们目前数据当作字符串//1.输出发送的数据信息//2.是谁提取uint16_t cil_portntohs(peer.sin_port);//从网络中来的所以要进行转成主机端口号std::string cli_pinet_ntoa(peer.sin_addr);//将点分四字节转换成点分十进制并且将网络序列转换成主机序列printf([%s:%d]# %s\n,cli_p.c_str(),cil_port,buffer);}//2.分析和处理数据//3.写回数据sendto(sock_,buffer,sizeof(buffer),0,(struct sockaddr*)peer,len);}}~UDPServer(){if(sock_0){close(sock_);}}private:
uint16_t port_;
std::string ip_;
int sock_;
};#endif
2.udp_server.cc
#include udp_server.hpp
#include memory
#include cstdlibstatic void usage(std::string proc)
{std::cout \nUsage: proc port\n std::endl;
}int main(int argc,char *argv[])
{//检查传入ip和端口号是否错误if(argc!2){usage(argv[0]);exit(1);}//初始化服务器利用unique_ptruint16_t portatoi(argv[1]);//std::string ipargv[1];std::unique_ptrUDPServer svr(new UDPServer(port));svr-initServer();svr-strat();return 0;
}
3.udp_client
#include iostream
#include string
#include cstring
#include unistd.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include memory
#include thread.hppstatic void usage(std::string proc)
{std::cout \nUsage: proc serverIp serverPort\n std::endl;
}int main(int argc, char *argv[])
{if (argc ! 3){usage(argv[0]);exit(1);}int sock socket(AF_INET, SOCK_DGRAM, 0);if (sock 0){std::cerr socket error std::endl;exit(2);}//client要不要bind要bind但是client不会显示的bind程序员不会自己bind//client是一个客户端是普通人下载并使用的//如果需要显示的bind也就要求了客户端也bind了一个固定的端口//万一其他的客户端提前真用了端口号了呢这时这个客户端就无法启动了//所以不用显示的bind指定端口号直接让OS自动随机选择std::string message;//给谁发的信息如下struct sockaddr_in server;memset(server,0,sizeof(server));server.sin_familyAF_INET;server.sin_porthtons(atoi(argv[2]));server.sin_addr.s_addrinet_addr(argv[1]);char buffer[1024];while(1){std::cout请输入你的信息# ;std::getline(std::cin,message);if(messagequit){break;}//当client首次发送消息给服务器的时候OS会自动给client bind它的ip和portsendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)server,sizeof(server));//至此客户端和服务器已经建成//这里需要定义两个占位的struct sockaddr_in temp;socklen_t len sizeof(temp);ssize_t s recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)temp,len);if(s0){buffer[s]0;std::coutserver echo# bufferstd::endl;}}close(sock);return 0;
}
4.log.hpp
#pragma once#include iostream
#include cstdio
#include cstdarg
#include ctime
#include string// 日志是有日志级别的
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4const char *gLevelMap[] {DEBUG,NORMAL,WARNING,ERROR,FATAL
};#define LOGFILE ./threadpool.log// 完整的日志功能至少: 日志等级 时间 支持用户自定义(日志内容, 文件行文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOWif(level DEBUG) return;
#endif// va_list ap;// va_start(ap, format);// while()// int x va_arg(ap, int);// va_end(ap); //apnullptrchar stdBuffer[1024]; //标准部分time_t timestamp time(nullptr);// struct tm *localtime localtime(timestamp);snprintf(stdBuffer, sizeof stdBuffer, [%s] [%ld] , gLevelMap[level], timestamp);char logBuffer[1024]; //自定义部分va_list args;va_start(args, format);// vprintf(format, args);vsnprintf(logBuffer, sizeof logBuffer, format, args);va_end(args);//FILE *fp fopen(LOGFILE, a);printf(%s%s\n, stdBuffer, logBuffer);//fprintf(fp, %s%s\n, stdBuffer, logBuffer);//fclose(fp);
}
5.结果
这个叫做本地环回client和server发送数据只在本地协议栈中进行数据流动不会把我们的数据发送到网络中通常用于本地网络服务器的测试 —————————————————————————————————————————— 其中客户端时OS随机绑定的全0IP是因为只要是这个端口的我都要不要具体IP的
三、爆改多线程
我们要把这个代码改成一个类似QQ群一样的模式所有人发的消息都可以看到
1.udp_client.cc
#include iostream
#include string
#include cstring
#include unistd.h
#include sys/types.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include memory
#include thread.hppuint16_t serverport0;
std::string serverip;//发现无论是多线程读还是写用的sock都是一个sock代表就是文件UDP是全双工的- 可以同时进行收发而不受干扰static void usage(std::string proc)
{std::cout \nUsage: proc serverIp serverPort\n std::endl;
}//发送逻辑的回调
static void *udpSend(void *args)
{//根据线程的封装先转换成data*的然后获取里面的args_然后在转会int*//获得一个指针指向args里面的sock然后解引用int sock*(int*)((ThreadData*)args)-args_;std::string name ((ThreadData*)args)-name_;//下面逻辑与之前一样构建发送的字符串,套接字信息std::string message;//给谁发的信息如下struct sockaddr_in server;memset(server,0,sizeof(server));server.sin_familyAF_INET;server.sin_porthtons(serverport);server.sin_addr.s_addrinet_addr(serverip.c_str());//开始不间断的发送while(1){std::cerr请输入你的信息# ;std::getline(std::cin,message);if(messagequit){break;}//当client首次发送消息给服务器的时候OS会自动给client bind它的ip和portsendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)server,sizeof(server));//至此客户端和服务器已经建成}return nullptr;
}//接受逻辑的回调
static void *udpRecv(void *args)
{int sock*(int*)((ThreadData*)args)-args_;std::string name ((ThreadData*)args)-name_;//需要不断地去读,要去指定的套接字中读取while(1){char buffer[1024];struct sockaddr_in temp;socklen_t len sizeof(temp);ssize_t s recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)temp,len);if(s0){buffer[s]0;std::coutbufferstd::endl;}}
}int main(int argc, char *argv[])
{if (argc ! 3){usage(argv[0]);exit(1);}int sock socket(AF_INET, SOCK_DGRAM, 0);if (sock 0){std::cerr socket error std::endl;exit(2);}serverportatoi(argv[2]);serveripargv[1];//client要不要bind要bind但是client不会显示的bind程序员不会自己bind//client是一个客户端是普通人下载并使用的//如果需要显示的bind也就要求了客户端也bind了一个固定的端口//万一其他的客户端提前真用了端口号了呢这时这个客户端就无法启动了//所以不用显示的bind指定端口号直接让OS自动随机选择//爆改多线程一个线程发送数据到各个主机一个线程接受各个数据。std::unique_ptrThread sender(new Thread(1,udpSend,(void*)sock));std::unique_ptrThread recver(new Thread(2,udpRecv,(void*)sock));sender-start();recver-start();sender-join();recver-join();close(sock);// std::string message;// //给谁发的信息如下// struct sockaddr_in server;// memset(server,0,sizeof(server));// server.sin_familyAF_INET;// server.sin_porthtons(atoi(argv[2]));// server.sin_addr.s_addrinet_addr(argv[1]);// char buffer[1024];// while(1)// {// std::cout请输入你的信息# ;// std::getline(std::cin,message);// if(messagequit)// {// break;// }// //当client首次发送消息给服务器的时候OS会自动给client bind它的ip和port// sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)server,sizeof(server));// //至此客户端和服务器已经建成// //这里需要定义两个占位的// struct sockaddr_in temp;// socklen_t len sizeof(temp);// ssize_t s recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)temp,len);// if(s0)// {// buffer[s]0;// std::coutserver echo# bufferstd::endl;// }// }// close(sock);return 0;
}
2.udp_server.cc
#include udp_server.hpp
#include memory
#include cstdlibstatic void usage(std::string proc)
{std::cout \nUsage: proc port\n std::endl;
}int main(int argc,char *argv[])
{//检查传入ip和端口号是否错误if(argc!2){usage(argv[0]);exit(1);}//初始化服务器利用unique_ptruint16_t portatoi(argv[1]);//std::string ipargv[1];std::unique_ptrUDPServer svr(new UDPServer(port));svr-initServer();svr-strat();return 0;
}
3.udp_server.hpp
#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP#include iostream
#include string
#include sys/types.h
#include sys/socket.h
#include log.hpp
#include cerrno
#include cstring
#include cstdlib
#include netinet/in.h
#include arpa/inet.h
#include strings.h
#include cstdio
#include unistd.h
#include unordered_map#define SIZE 1024class UDPServer
{
public:UDPServer(uint16_t port, std::string ip ): port_(port), ip_(ip), sock_(-1){}bool initServer(){// 这里开始使用网络部分// 1.创建套接字sock_ socket(AF_INET, SOCK_DGRAM, 0);if (sock_ 0){logMessage(FATAL, %d:%s, errno, strerror(errno));exit(2);}// 2.bind绑定:将用户设置的ip和端口号在内核中和我们的进程强关联//192.168.1.0-点分十进制// 以.来分隔每个区域的取值范围为0——255// 正好为1字节四个区域理论上需要四字节// 所以我们的看的时候会由4字节显示转换为点分十进制// 初始化结构体完成struct sockaddr_in local;bzero(local, sizeof(local));// 填充结构体local.sin_family AF_INET;// 服务器的IP和端口号未来也是要发送给对方主机才能互相通信的// 所以要先把数据发送到网络要用htonslocal.sin_port htons(port_);// 同上要先把点分十进制转换成四字节ip// 还需要要把四字节主机序列转换成网络序列// 所以用inet_addr一次性转成网络四字节序列// local.sin_addr.s_addrinet_addr(ip_.c_str());// 一般情况下只要是这个端口的那么我们就直接全部都接受而不是指定端口local.sin_addr.s_addr ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());// 这里需要做强转因为bind需要的是sockaddr类型而不是sockaddr_in类型if (bind(sock_, (struct sockaddr *)local, sizeof(local)) 0){logMessage(FATAL, %d:%s, errno, strerror(errno));exit(2);}// 至此初始化服务器写完首先创建套接字接着用bind来绑定ip和端口号写进内核中logMessage(NORMAL, init udp server done ... %s, strerror(errno));return true;}void strat(){// 作为一款网络服务器是永远不退出的// 服务器启动- 进程 - 常驻进程 - 永远在内存中存在除非挂了// echo服务器主机给我们发送消息我们原封不动的返回char buffer[SIZE];for (;;){// 1.读取数据// 注意peer纯输出型参数struct sockaddr_in peer;bzero(peer, sizeof(peer));// 输入peer 缓冲区大小// 输入实际读到的peer大小// 所以len的大小要为实际的peer大小socklen_t len sizeof(peer);// 这样就把数据读取到buffer里了ssize_t s recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)peer, len);char result[256];char key[64];std::string cmd_echo;if (s 0){buffer[s] 0; // 我们目前数据当作字符串// 1.输出发送的数据信息// 2.是谁提取// if(strcasestr(buffer,rm)!nullptr||strcasestr(buffer,rmdir)!nullptr)// {// std::string err_message坏人;// std::couterr_messagebufferstd::endl;// sendto(sock_, err_message.c_str(), err_message.size(), 0, (struct sockaddr *)peer, len);// continue;// }// FILE *fp popen(buffer, r);// if (nullptr fp)// {// logMessage(ERROR, %d:%s, errno, strerror(errno));// continue;// }// while (fgets(result, sizeof(result), fp) ! nullptr)// {// cmd_echo result;// }// fclose(fp);uint16_t cil_port ntohs(peer.sin_port); // 从网络中来的所以要进行转成主机端口号std::string cil_ip inet_ntoa(peer.sin_addr); // 将点分四字节转换成点分十进制并且将网络序列转换成主机序列// printf([%s:%d]# %s\n,cli_p.c_str(),cil_port,buffer);snprintf(key, sizeof(key), %s-%d, cil_ip.c_str(), cil_port);logMessage(NORMAL,key: %s,key);auto it users_.find(key);if(itusers_.end()){logMessage(NORMAL,add new user:%s,key);users_.insert({key,peer});}}// 2.分析和处理数据// 3.写回数据// sendto(sock_,buffer,sizeof(buffer),0,(struct sockaddr*)peer,len);//sendto(sock_, cmd_echo.c_str(), cmd_echo.size(), 0, (struct sockaddr *)peer, len);for(auto iter:users_){std::string sendMessagekey;sendMessage# ;sendMessagebuffer;logMessage(NORMAL,push message to %s,iter.first.c_str());sendto(sock_, sendMessage.c_str(), sendMessage.size(), 0, (struct sockaddr *)(iter.second), sizeof(iter.second));}}}~UDPServer(){if (sock_ 0){close(sock_);}}private:uint16_t port_;std::string ip_;int sock_;std::unordered_mapstd::string, struct sockaddr_in users_;
};#endif 4.thread.hpp
#pragma once
#include iostream
#include string
#include functional
#include cstdiousing namespace std;typedef void*(*fun_t)(void*);class ThreadData
{
public:void* args_;string name_;
};class Thread
{
public:Thread(int num,fun_t callback,void* args):func_(callback){char namebuffer[64];snprintf(namebuffer,sizeof(namebuffer),Thread-%d,num);name_namebuffer;tdata_.args_args;tdata_.name_name_;}void start(){pthread_create(tid_,nullptr,func_,tdata_.args_);}void join(){pthread_join(tid_,nullptr);}string name(){return name_;}~Thread(){}private:string name_;fun_t func_;ThreadData tdata_;pthread_t tid_;
};
5.结果
利用管道可以更好的观看