(。・∀・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~https://blog.csdn.net/ky233?type=blog
点个关注不迷路⌯'▾'⌯
实现一个简单的对话发消息的功能!
目录
一.新增的接口:
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.h>int socket(int domain, int type, int protocol);
- 参数一:叫做域,用来判断是那个类型的,AF_UNIX, AF_LOCAL(用于本地通信)AF_INET(用于ipv4的网络通信)
- 参数二:类型,通信的种类是什么,SOCK_DGRAM(套接字的类别,数据报的方式)
- 参数三:协议,比如用AF_INET,SOCK_DGRAM,一般直接写0
- 创建成功会得到一个文件描述符,失败返回-1
2.bing
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int 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.h>FILE *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_family=AF_INET;//服务器的IP和端口号,未来也是要发送给对方主机才能互相通信的//所以要先把数据发送到网络,要用htonslocal.sin_port=htons(port_);//同上要先把点分十进制转换成四字节ip//还需要要把四字节主机序列,转换成网络序列//所以用inet_addr一次性转成网络四字节序列//local.sin_addr.s_addr=inet_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(s>0){buffer[s]=0;//我们目前数据当作字符串//1.输出发送的数据信息//2.是谁?提取!uint16_t cil_port=ntohs(peer.sin_port);//从网络中来的,所以要进行转成主机端口号std::string cli_p=inet_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 <cstdlib>static 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 port=atoi(argv[1]);//std::string ip=argv[1];std::unique_ptr<UDPServer> 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.hpp"static 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_family=AF_INET;server.sin_port=htons(atoi(argv[2]));server.sin_addr.s_addr=inet_addr(argv[1]);char buffer[1024];while(1){std::cout<<"请输入你的信息# ";std::getline(std::cin,message);if(message=="quit"){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(s>0){buffer[s]=0;std::cout<<"server echo# "<<buffer<<std::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); //ap=nullptrchar stdBuffer[1024]; //标准部分time_t timestamp = time(nullptr);// struct tm *localtime = localtime(×tamp);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.hpp"uint16_t serverport=0;
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_family=AF_INET;server.sin_port=htons(serverport);server.sin_addr.s_addr=inet_addr(serverip.c_str());//开始不间断的发送while(1){std::cerr<<"请输入你的信息# ";std::getline(std::cin,message);if(message=="quit"){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(s>0){buffer[s]=0;std::cout<<buffer<<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);}serverport=atoi(argv[2]);serverip=argv[1];//client要不要bind?要bind,但是client不会显示的bind,程序员不会自己bind//client是一个客户端,是普通人下载并使用的//如果需要显示的bind,也就要求了客户端也bind了一个固定的端口//万一其他的客户端提前真用了端口号了呢,这时这个客户端就无法启动了//所以不用显示的bind指定端口号,直接让OS自动随机选择//爆改多线程,一个线程发送数据到各个主机,一个线程接受各个数据。std::unique_ptr<Thread> sender(new Thread(1,udpSend,(void*)&sock));std::unique_ptr<Thread> 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_family=AF_INET;// server.sin_port=htons(atoi(argv[2]));// server.sin_addr.s_addr=inet_addr(argv[1]);// char buffer[1024];// while(1)// {// std::cout<<"请输入你的信息# ";// std::getline(std::cin,message);// if(message=="quit")// {// 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(s>0)// {// buffer[s]=0;// std::cout<<"server echo# "<<buffer<<std::endl;// }// }// close(sock);return 0;
}
2.udp_server.cc
#include "udp_server.hpp"
#include <memory>
#include <cstdlib>static 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 port=atoi(argv[1]);//std::string ip=argv[1];std::unique_ptr<UDPServer> 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_addr=inet_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::cout<<err_message<<buffer<<std::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(it==users_.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 sendMessage=key;sendMessage+="# ";sendMessage+=buffer;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_map<std::string, struct sockaddr_in> users_;
};#endif
4.thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstdio>using 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.结果
利用管道可以更好的观看