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

服务好的网站建设公司美食网站设计的基本思路

服务好的网站建设公司,美食网站设计的基本思路,大连建设网官网网上办公大厅,域名备案和icp备案区别文章目录 UDP套接字简单通信1、服务端1、创建文件#xff0c;写框架2、用命令行参数调起程序3、服务端运行逻辑 2、客户端1、创建套接字2、发送数据 3、测试4、通信5、加功能1、处理数据2、群聊 6、Windows下socket编程的不同 UDP套接字简单通信 1、服务端 1、创建文件… 文章目录 UDP套接字简单通信1、服务端1、创建文件写框架2、用命令行参数调起程序3、服务端运行逻辑 2、客户端1、创建套接字2、发送数据 3、测试4、通信5、加功能1、处理数据2、群聊 6、Windows下socket编程的不同 UDP套接字简单通信 1、服务端 1、创建文件写框架 接下来通过代码来理解套接字。先写一个echo server的代码一个客户端一个服务端客户端发消息服务端接收后再返回来。 创建套接字函数 domain用来表示要进行什么通信传AF_INET AF_UNIXtype对应的套接字种类 其中第二个SOCK_DGRAM就是指数据报第一个就是TCP协议protocol默认为0会自动推演出用什么协议TCP或者UDP。返回值成功返回文件描述符失败返回-1并设置错误码 初始化时用这个接口。我们创建四个文件两个client两个serverudp_client.ccudp_client.hppudp_server.ccudp_server.hpp。 makefile .PHONY: all all:udp_client udp_serverudp_client:udp_client.ccg -o $ $^ -stdc11 udp_server:udp_server.ccg -o $ $^ -stdc11 .PHONY: clean clean:rm -f udp_client udp_server在server的头文件中 #pragma once#include iostream #include memory #include sys/type.h #include sys/socket.h #include cerrno #include cstring #include cstdlibnamespace ns_server {enum{SOCKET_ERR1,//返回时用枚举里的错误码这样用户就知道是什么原因错误的};class UdpServer{public:UdpServer(){}void InitServer(){//1. 创建socket套接字接口打开网络文件sock_ socket(AF_INET, SOCK_DGRAM, 0);if(sock_ 0){std::cerr create socket error: strerror(errno) std::endl;exit(SOCKET_ERR);}std::cout create socket succeed: sock_ std::endl;//默认文件描述符应当是3}void Start(){}~UdpServer(){}private:int sock_;}; }接下来给服务器指明IP地址和端口号用到bind函数。 sockfd就是刚才用socket函数创建的套接字。第二个参数addr因为是网络通信我们要用的是AF_INET。传参数前我们需要填充一下IP地址和端口号。 in_port_t是重命名了uint16_tsin_port就是端口号sin_addr是IP地址是一个in_addr结构体类型其实这个结构体只有一个uint32_t类型的变量32位的整数剩下的sin_zero是一个填充字段。而AF_INET在最一开始的__SOCKADDR_COMMON里。 里面的双井号传进来一个sin_后sa_prefix就是sin_双井号后是family它们就会被组合起来最终就是sa_family_t sin_familysa_family_t是一个整数类型。 接下来要填充这个结构体首先要清空也可以不清空清空用bzero可以把缓冲区写为0。 我们在类内定义一个端口号port_填充端口号时不能直接给port_因为这台主机不仅发消息还得接收消息接收消息的话端口号就得在网络中被其它主机知晓而这个port_是在类内定义的所以需要本地序列号转网络序列号。 #pragma once#include iostream #include memory #include sys/types.h #include sys/socket.h #include cerrno #include cstring #include strings.h #include cstdlib #include netinet/in.h #include arpa/inet.hnamespace ns_server {enum{SOCKET_ERR1,BIND_ERR,};const static int default_port 8080;class UdpServer{public:UdpServer(std::string ip, uint16_t port default_port): ip_(ip), port_(port){std::cout server addr: ip : port_ std::endl;}void InitServer(){//1. 创建socket套接字接口打开网络文件sock_ socket(AF_INET, SOCK_DGRAM, 0);if(sock_ 0){std::cerr create socket error: strerror(errno) std::endl;exit(SOCKET_ERR);}std::cout create socket succeed: sock_ std::endl;//默认文件描述符应当是3//2. 给服务器指明IP地址Port号struct sockaddr_in local;//网络通信要引入头文件netinet/in.h, arpa/inet.hbzero(local, sizeof(local));local.sin_family AF_INET;//上面的AF_INET用来创建套接字这个用来初始化sockaddr_in结构体local.sin_port htons(port_);//端口号的主机转网络序列的接口//字符串转为4字节int不能强转需要转化并且转化完后要再变成网络序列//in_addr_t inet_addr函数就可以解决这个问题它的作用就是字符串转4字节int并转网络序列传一个const char*的就行local.sin_addr.s_addr inet_addr(ip_.c_str());//现在要把这个local绑定到套接字中但这个local是在函数内的也就是临时变量它在用户空间的特定函数的栈帧上不在内核中if(bind(sock_, (struct sockaddr*)local, sizeof(local))){std::cerr bind socket error: strerror(errno) std::endl;exit(BIND_ERR);}std::cout bind socket succeed: sock_ std::endl;}void Start(){}~UdpServer(){}private:int sock_;unit16_t port_;std::string ip_;}; } udp_server.cc文件里这样写 #include udp_server.hpp using namespace std; using namespace ns_server;int main() {unique_ptrUdpServer usvr(new UdpServer(1.1.1.1, 8082));usvr-InitServer();usvr-Start();return 0; }但是运行起来后会出现绑定错误。我们把1.1.1.1换成自己的云服务器的公网IP后它依旧报错Cannot assign requested address。 2、用命令行参数调起程序 云服务器不需要绑定IP地址我们要让云服务器自己去指向IP地址。而自己本地装的虚拟机或者物理机器是允许用户去绑定IP地址的。这样的话我们就写成命令行参数的形式main里只用端口号UdpServer这个类里就不去初始化ip地址。为了方便把报的错误统一放到一个头文件中。这样改动后UdpServer的类里还需要绑定IP地址现在已经没有ip_这个成员变量了那么如何去绑定云服务器不应当绑定某一个IP因为云服务器可能有多个IP地址如果只绑定一个那就只能收到这一个的数据报我们应当接收所有发送到这个主机的数据根据端口发到这个端口的数据会被使用。 err.hpp #pragma onceenum {USAGE_ERR1,SOCKET_ERR,BIND_ERR, };udp_server.cc #include udp_server.hpp #include string using namespace std; using namespace ns_server;static void usage(string proc) {std::cout Usage:\n\t proc port\n std::endl; }//既然不传IP地址那就将main变成带参的我们手动写上端口号里面也需要做改动 //像这样写来启动程序 ./udp_server port int main(int argc, char* argv[]) {if(argc ! 2)//上面写启动程序那里是两个命令行参数所以这里是2{usage(argv[0]);exit(USAGE_ERR);}uint16_t port atoi(argv[1]);//unique_ptrUdpServer usvr(new UdpServer(1.1.1.1, 8082));unique_ptrUdpServer usvr(new UdpServer(port));usvr-InitServer();usvr-Start();return 0; }udp_server.hpp 这样改完后我们写./udp_server 8080就可以接收8080这个端口号的数据。只是现在服务器还没写运行逻辑所以只是能绑定成功并没有看到数据处理的过程接下来写这部分。 3、服务端运行逻辑 recvfrom接口从一个套接字获取数据。 sockfd就是我们代码中的sock_用socket接口创建好后返回的东西。buf是自己定义的缓冲区把数据放在这里len是缓冲区长度flags是读取方式默认设为0阻塞方式。接口返回值是实际读了多少数据len是整个长度。src_addr和addrlen是输入输出型参数这个接口既然是接收数据用的那么就得知道是谁发过来数据的在网络中网络间通信就是进程间通信要标识是哪一个进程就得知道它所在的主机这需要IP地址还要知道是哪一个进程这需要端口号Port所以src_addr这个参数我们用这个接口的时候就得传入客户端的IP和Port号因为是服务端调用这个接口这个接口输出时得返回一个结构体里面有客户端的IP和Port号addrlen就是实际结构体的大小。 void Start(){char buffer[1024];while(true)//服务端得一直运行来保证客户端随时发送请求都能被处理就和操作系统一样{//接收struct sockaddr_in peer;socklen_t len sizeof(peer);//这里一定要写清楚未来传入的缓冲区大小int n recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)peer, len);if(n 0) buffer[n] \0;else continue;}}recvfrom那里写sizeof(buffer) - 1是因为考虑到这些原因。数据具体是什么形式的由程序员定义这里不涉及这些因为这些系统接口是C写的我们要把数据当作字符串使用就需要最后一个位置是\0这个位置得我们自己写上所以得预留出这个位置所以要-1。后面返回值大于0时接口调用没有错误那么就在最后一个位置加上\0结尾整体就是一个字符串了。 接收后服务端也不知道这是谁的IP和Port号我们需要提取出来。 //提取client信息std::string clientip inet_ntoa(peer.sin_addr);uint16_t clientport ntohs(peer.sin_port);std::cout clientip - clientport # buffer std::endl;接收完后要发送数据用sendto接口。 buf就是发送的数据所在的缓冲区。服务端接收客户端的数据然后再发送给客户端所以dest_addr也是客户端的IP和Port号。 sendto(sock_, buffer, strlen(buffer), 0, (struct sockaddr*)peer, sizeof(peer));这里的strlen(buffer)这样写是因为无论是向谁写比如这里是向套接字Linux下一切皆文件文件中存储的可不是C语言中的字符串形式所以不需要最后的\0。 程序运行起来后服务端一直在运行中我们可以用netstat -naup来查看n是把所有能显示成数字的显示出来u是显示udpp显示进程信息a代表所有进程。进行查看时会发现一个新建立的PID端口号就是调用程序时自己写的端口号前面有一个随机绑定的IP地址是全0的就相当于给这个IP地址做了一个定义只要发到这个端口号的数据都会发到这个主机上。 hpp #pragma once#include iostream #include memory #include sys/types.h #include sys/socket.h #include cerrno #include cstring #include string #include strings.h #include cstdlib #include netinet/in.h #include arpa/inet.h #include err.hppnamespace ns_server {const static int default_port 8080;class UdpServer{public:UdpServer(uint16_t port default_port): port_(port){std::cout server addr: port_ std::endl;}void InitServer(){//1. 创建socket套接字接口打开网络文件sock_ socket(AF_INET, SOCK_DGRAM, 0);if(sock_ 0){std::cerr create socket error: strerror(errno) std::endl;exit(SOCKET_ERR);}std::cout create socket succeed: sock_ std::endl;//默认文件描述符应当是3//2. 给服务器指明IP地址Port号struct sockaddr_in local;//网络通信要引入头文件netinet/in.h, arpa/inet.hbzero(local, sizeof(local));local.sin_family AF_INET;//上面的AF_INET用来创建套接字这个用来初始化sockaddr_in结构体local.sin_port htons(port_);//字符串转为4字节int不能强转需要转化并且转化完后要再变成网络序列//in_addr_t inet_addr函数就可以解决这个问题它的作用就是字符串转4字节int并转网络序列传一个const char*的就行//local.sin_addr.s_addr inet_addr(ip_.c_str());//云服务器或者一款服务器一般不要指明某一个确定的IPlocal.sin_addr.s_addr INADDR_ANY;//让我们的udpserver在启动的时候绑定本主机上的任意一个IP地址//现在要把这个local绑定到套接字中但这个local是在函数内的也就是临时变量它在用户空间的特定函数的栈帧上不在内核中if(bind(sock_, (struct sockaddr*)local, sizeof(local)) 0){std::cerr bind socket error: strerror(errno) std::endl;exit(BIND_ERR);}std::cout bind socket succeed: sock_ std::endl;}void Start(){char buffer[1024];while(true)//服务端得一直运行来保证客户端随时发送请求都能被处理就和操作系统一样{//接收struct sockaddr_in peer;socklen_t len sizeof(peer);//这里一定要写清楚未来传入的缓冲区大小int n recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)peer, len);if(n 0) buffer[n] \0;else continue;//提取client信息std::string clientip inet_ntoa(peer.sin_addr);uint16_t clientport ntohs(peer.sin_port);std::cout clientip - clientport # buffer std::endl;//发送sendto(sock_, buffer, strlen(buffer), 0, (struct sockaddr*)peer, sizeof(peer));}}~UdpServer(){}private:int sock_;uint16_t port_;//std::string ip_;}; } cc #include udp_server.hpp #include string using namespace std; using namespace ns_server;static void usage(string proc) {std::cout Usage:\n\t proc port\n std::endl; }//既然不传IP地址那就将main变成带参的我们手动写上端口号里面也需要做改动 //像这样写来启动程序 ./udp_server port int main(int argc, char* argv[]) {if(argc ! 2)//上面写启动程序那里是两个命令行参数所以这里是2{usage(argv[0]);exit(USAGE_ERR);}uint16_t port atoi(argv[1]);//unique_ptrUdpServer usvr(new UdpServer(1.1.1.1, 8082));unique_ptrUdpServer usvr(new UdpServer(port));usvr-InitServer();usvr-Start();return 0; }err.hpp #pragma onceenum {USAGE_ERR1,SOCKET_ERR,BIND_ERR, };2、客户端 1、创建套接字 udp_client.hpp #pragma once#include iostream #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include cstring #include stringudp_client.cc #include udp_client.hpp #include err.hppint main() {int sock socket(AF_INET, SOCK_DGRAM, 0);if(sock 0){std::cerr create socket error std::endl;exit(SOCKET_ERR);}return 0; }客户端也需要绑定因为网络通信就是客户端和服务端互相标识对方唯一的进程后进行网络版本的进程间通信所以需要标识但不用我们自己去标识。绑定是一个系统调用客户端的端口号绑定由操作系统来自动绑定防止客户端自己绑定端口号会导致两个软件绑定同一个端口号那就不能同时打开。 既然这样为什么服务端要自己绑定端口号服务器的端口号不能自己改变它要服务于众多客户就像一个官方一样它不能随便更改。但服务端也有很多为什么可以自己定端口号客户端来自众多源头但是服务端由一家公司自己制定的所以公司自己来规定好不冲突就行。 2、发送数据 套接字创建完客户端就可以开始发送数据了。用sendto来发消息但是这时候我们需要知道服务端的IP和Port号。服务端是临时的一个IP地址这如何获取客户端在调起运行时也是写命令行参数要写ip和端口号ip其实用服务器的公网IP就可以传送过去数据端口号就是我们自己定的那个。 udp_client.cc #include udp_client.hpp #include err.hppstatic void usage(std::string proc) {std::cout Usage:\n\t proc serverip serverport\n std::endl; }int main(int argc, char* argv[]) {if(argc ! 3){usage(argv[0]);exit(USAGE_ERR);}std::string serverip argv[1];uint16_t serverport atoi(argv[2]);int sock socket(AF_INET, SOCK_DGRAM, 0); if(sock 0){std::cerr create socket error std::endl;exit(SOCKET_ERR);}//客户端由系统自己来绑定端口号//明确服务器是谁填充sockaddr这个结构体struct sockaddr_in server;memset(server, 0, sizeof(server));//就和bzero一样server.sin_family AF_INET;server.sin_port htons(serverport);server.sin_addr.s_addr inet_addr(serverip.c_str());while(true){//用户输入std::string message;std::cout Please Enter# ;std::cin message;//发送sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)server, sizeof(server));//接收char buffer[2048];struct sockaddr_in temp;//temp就是上面server结构体的结果socklen_t len sizeof(temp);int n recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)temp, len);//recvfrom通常用于udp套接字的接收数据具体用法man一下if(n 0){buffer[n] 0;std::cout buffer std::endl;}}return 0; } 操作系统什么时候绑定客户端在我们首次系统调用发送数据的时候操作系统会在底层随机选择客户端端口号加上自己的ip绑定并构建要发送的数据报文。 3、测试 测试的时候要开两个窗口在客户端写./udp_client 127.0.0.1 8081127.0.0.1是本地环回表达的是当前主机通常用来进行本地通信或者测试。也是每一台机器默认的IP地址用它来测试的时候就走网络协议栈但不会把数据发送到网络中。 测试的时候客户端发送消息服务端能接收消息那么软件没问题如果实际中没有通过那就说明是网络问题。 sz udp_client也就是sz后面跟上客户端可执行文件就可以把客户端放到桌面上。 4、通信 想通过现在写的代码来进行通信sz udp_client就会弹出一个窗口可以选择把这个客户端下载到哪里然后启动另一个云服务器输入指令rz加上一个空格按回车就可以选择客户端把它下载到当前云服务器中但是我们还需要chmod x udp_client因为默认没有运行权限。想要启用这个客户端我们先./udp_client如果打印出来语句后说明能正常运行之后需要服务端的IP地址和端口号输入指令./udp_client IP Port就可以运行客户端了在客户端输入消息服务端就会出现对应的消息服务端在每条信息前面也会打印出来客户端的IP地址和随机分配给该客户端的端口号。让服务端停止运行服务就挂掉了客户端发信息就没用了。 5、加功能 1、处理数据 加上一个包装器functional把函数作为类内成员初始化服务端发送经过函数处理过的数据而这个函数要在类外传进来此时这个函数就不需要考虑网络只考虑数据就行。 udp_server.hpp #include cstdioconst static int default_port 8080;using func_t std::functionstd::string(std::string);class UdpServer{public:UdpServer(func_t cb, uint16_t port default_port): service_(cb), port_(port){std::cout server addr: port_ std::endl;}//......//做业务处理std::string response service_(buffer);//发送sendto(sock_, response.c_str(), response.size(), 0, (struct sockaddr*)peer, sizeof(peer));//......private:int sock_;uint16_t port_;func_t service_;//网络服务器要进行业务处理//std::string ip_;};udp_server.cc //上层的业务处理不关系网络发送只负责信息处理即可 std::string transactionString(std::string request)//request就是一个string {std::string result;char c;for(auto r : request){if(islower(r)){c toupper(r);result.push_back(c);}elseresult.push_back(r);;}return result; }//既然不传IP地址那就将main变成带参的我们手动写上端口号里面也需要做改动 //像这样写来启动程序 ./udp_server port int main(int argc, char* argv[]) {if(argc ! 2)//上面写启动程序那里是两个命令行参数所以这里是2{usage(argv[0]);exit(USAGE_ERR);}uint16_t port atoi(argv[1]);//unique_ptrUdpServer usvr(new UdpServer(1.1.1.1, 8082));unique_ptrUdpServer usvr(new UdpServer(transactionString, port));//这里就要传这个函数usvr-InitServer();usvr-Start();return 0; }这个功能比较简单。写一个服务端执行客户端命令的函数 这个接口会执行command参数指定的命令执行完后以文件指针的类型返回所以调用者要用文件操作才行type则是用什么类型的文件操作读写追加。调用这个函数后函数内部会创建管道创建子进程把处理后的结果定向到要返回的文件指针让用户能够以读的方式来获取结果。用fgets函数来获取结果从文件流中获取数据并且因为是c接口所以读取的数据自动转换成字符串形式。 在这之前我们要先防止一些命令的实现。 static bool isPass(const std::string command) {bool pass true;auto pos command.find(rm);if(pos ! std::string::npos) pass false;pos command.find(mv);if(pos ! std::string::npos) pass false;return pass; }//客户端传过来命令服务端执行 std::string excuteCommand(std::string command) {//1. 安全检查if(!isPass(command)) return Error!;//2. 业务逻辑处理FILE* fp popen(command.c_str(), r);if(fp nullptr) return None;//3. 获取结果char line[1024];std::string result;while(fgets(line, sizeof(line), fp) ! NULL){result line;}pclose(fp);return result; }这样客户端就可以给服务端发命令了发过来的命令会被执行。但是还有问题ls -a -l不会正确显示结果这是因为udp_client.cc文件里用户输入部分不够完善cin会以空格为分隔符。 //用户输入std::string message;std::cout Please Enter# ;std::cin message;换成getline就好 std::getline(std::cin, message);现在只能执行单进程的命令加上多个进程的。 pos command.find(while);if(pos ! std::string::npos) pass false;加上防止被杀。 pos command.find(kill);if(pos ! std::string::npos) pass false;2、群聊 服务端收到一个信息就把信息给所有的客户端。客户端给消息服务端作为中间人经过处理后再把消息发给所有客户端其实这就是生产消费模型。要发给所有人就需要所有人的套接字信息。 分成两个线程一个收消息另一个发消息消息放在环形队列中再给客户端。这里用到之前的RingQueue.hpp文件。 #pragma once#include iostream #include vector #include pthread.h #include ctime #include sys/types.h #include unistd.h #include semaphore.h #include string #include cstringstatic const int N 50;//从5改成了50templateclass T class RingQueue { private:void P(sem_t s) {sem_wait(s); }void V(sem_t s) {sem_post(s); }//发布信号量的接口void Lock(pthread_mutex_t m) {pthread_mutex_lock(m); }void Unlock(pthread_mutex_t m) {pthread_mutex_unlock(m); } public:RingQueue(int num N): _ring(num), _cap(num){sem_init(_data_sem, 0, 0);sem_init(_space_sem, 0, num);_c_step _p_step 0;pthread_mutex_init(_c_mutex, nullptr);pthread_mutex_init(_p_mutex, nullptr);}//生产void push(const T in){P(_space_sem);//P操作生产者需要看看空间信号量是否不为空不空才可以继续Lock(_p_mutex);//不需要判断一定有对应的空间资源给我//因为信号量本身就是描述临界资源的它可以在临界区外去申请P成功就说明可以继续执行了_ring[_p_step] in;//_p_step是生产者的位置_p_step;_p_step % _cap;Unlock(_p_mutex);//V操作V(_data_sem);//一个数据放进去了那么数据信号量就增加}//消费void pop(T* out){P(_data_sem);//P操作消费者需要看看数据信号量是否不为空不空才可以继续Lock(_c_mutex);*out _ring[_c_step];//_c_step是消费者的位置_c_step;_c_step % _cap;Unlock(_p_mutex);V(_space_sem);//一个数据被拿走消费者往后走一步空间信号量就减少}~RingQueue(){sem_destroy(_data_sem);sem_destroy(_space_sem);pthread_mutex_destroy(_c_mutex);pthread_mutex_destroy(_p_mutex);} private:std::vectorT _ring;int _cap;//环形队列大小sem_t _data_sem;//只有消费者关心sem_t _space_sem;//只有生产者关心int _c_step;//消费者位置int _p_step;//生产者位置pthread_mutex_t _c_mutex;//消费者之间的锁pthread_mutex_t _p_mutex;//生产者之间的锁 };让原先的Start函数改名成Recv它来接收消息创建一个Broadcast的函数来发消息。定义一个map来存储客户端将接收客户端的套接字结构体和客户端的IP、Port绑定在一起。处理数据前先查明是否存在于map存在就不做什么不存在就插入然后再去处理数据。不过这里就不处理数据把接收到的客户端数据放到环形队列中。发送数据就放到一个别的函数中来做。 //类前面const static int default_port 8080;using func_t std::functionstd::string(std::string);//....void addUser(const std::string name, const struct sockaddr_in peer){auto iter onlineuser.find(name);if(iter ! onlineuser.end()) return;onlineuser.insert(std::pairconst std::string, const struct sockaddr_in(name, peer));}void Recv(){char buffer[1024];while(true)//服务端得一直运行来保证客户端随时发送请求都能被处理就和操作系统一样{//接收struct sockaddr_in peer;socklen_t len sizeof(peer);//这里一定要写清楚未来传入的缓冲区大小int n recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)peer, len);if(n 0) buffer[n] \0;else continue;//提取client信息std::string clientip inet_ntoa(peer.sin_addr);uint16_t clientport ntohs(peer.sin_port);std::cout clientip - clientport # buffer std::endl;//构建一个用户并检查std::string name clientip;name -;name std::to_string(clientport);//如果不存在就插入如果存在就什么都不做addUser(name, peer);rq.push(buffer);//做业务处理//std::string message service_(buffer);}}void Broadcast()//接收消息是while(true)来死循环接收发送消息也要这样{while(true){std::string sendstring;rq.pop(sendstring);for(auto user : onlineuser){std::cout Broadcast message to user.first sendstring std::endl;sendto(sock_, sendstring.c_str(), sendstring.size(), 0, (struct sockaddr*)(user.second), sizeof(user.second));}}}~UdpServer(){}private:int sock_;uint16_t port_;func_t service_;//网络服务器要进行业务处理std::unordered_mapstd::string, struct sockaddr_in onlineuser;RingQueuestd::string rq;//std::string ip_;}; } onlineuser需要受到保护防止被不知名的客户端插入给更改所以加锁引入之前写的LockGuard.hpp。 LockGuard.hpp #pragma once#include iostream #include pthread.hclass Mutex//自己不维护锁由外部传入 { public:Mutex(pthread_mutex_t *mutex):_pmutex(mutex){} void lock(){pthread_mutex_lock(_pmutex);}void unlock(){pthread_mutex_unlock(_pmutex);}~Mutex(){} private:pthread_mutex_t *_pmutex; };class LockGuard//自己不维护锁由外部传入 { public:LockGuard(pthread_mutex_t *mutex):_mutex(mutex){_mutex.lock();}~LockGuard(){_mutex.unlock();} private:Mutex _mutex; };UdpServer(func_t cb, uint16_t port default_port): service_(cb), port_(port){std::cout server addr: port_ std::endl;pthread_mutex_init(lock, nullptr);}void addUser(const std::string name, const struct sockaddr_in peer){LockGuard lockguard(lock);auto iter onlineuser.find(name);if(iter ! onlineuser.end()) return;onlineuser.insert(std::pairconst std::string, const struct sockaddr_in(name, peer));}void Broadcast(){std::string sendstring;rq.pop(sendstring);LockGuard lockguard(lock);for(auto user : onlineuser){std::cout Broadcast message to user.first sendstring std::endl;sendto(sock_, sendstring.c_str(), sendstring.size(), 0, (struct sockaddr*)(user.second), sizeof(user.second));}}~UdpServer(){pthread_mutex_destroy(lock);}private:int sock_;uint16_t port_;func_t service_;//网络服务器要进行业务处理std::unordered_mapstd::string, struct sockaddr_in onlineuser;RingQueuestd::string rq;pthread_mutex_t lock;//std::string ip_;};但Broadcast函数那里加锁并不是最优解。我们需要保护的是客户端的套接字而不是发送的这个过程这里可以这样写 void Broadcast(){while(true){std::string sendstring;rq.pop(sendstring);std::vectorstruct sockaddr_in v;{LockGuard lockguard(lock);for(auto user: onlineuser){v.push_back(user.second);}}for(auto user : v){//std::cout Broadcast message to user.first sendstring std::endl;sendto(sock_, sendstring.c_str(), sendstring.size(), 0, (struct sockaddr*)(user), sizeof(user));}}}拷贝到v里是用范围for是内存级拷贝是STL容器进行的拷贝消耗少。而sendto是系统调用接口有IO开销。 写完这些后还要支持多线程引入之前写的Thread.hpp并且还要改一下多线程执行的方法。 以前的Thread.hpp #pragma once#include iostream #include pthread.h #include string #include cstdlib using namespace std;typedef void (*func_t)();class Thread { public:typedef enum{NEW 0,RUNNING,EIXTED}ThreadStatus;typedef void (*func_t)(void*); public:Thread(int num, func_t func, void* args):_tid(0), _status(NEW), _func(func), _args(args)//num是线程编号{char name[128];snprintf(name, sizeof(name), thread-%d, num);_name name;}int status() {return _status;}string threadname() {return _name;}pthread_t threadid(){if(_status RUNNING) return _tid;else{return 0;}}//runHelper是成员函数类的成员函数具有默认参数this也就是括号有一个Thread* thispthread_create的参数应当是void*类型就不符合所以加上static放在静态区就没有this但是又有新问题了//static成员函数无法直接访问类内属性和其他成员函数所以create那里要传this指针this就是当前线程对象传进来才能访问类内的东西static void* runHelper(void* args)//args就是执行方法的参数{Thread* ts (Thread*)args;//就拿到了当前对象//函数里可以直接调用func这个传过来的方法参数就是_args(*ts)();//调用了func函数return nullptr;}void operator()()//仿函数{if(_func ! nullptr) _func(_args);}void run()//run函数这里传方法{int n pthread_create(_tid, nullptr, runHelper, this);//为什么传thisif(n ! 0) exit(1);_status RUNNING;}void join(){int n pthread_join(_tid, nullptr);if(n ! 0){cerr main thread join thread _name error endl;return ;} _status EXITED;}~Thread(){} private:pthread_t _tid;string _name;func_t func;//线程未来要执行的函数方法void* _args;//也可以不写这个那么typedef的函数指针就没有参数。当然参数也可用模板来写ThreadStatus _status; };改后的不需要传参了让线程执行接收和发送数据的工作所以初始化这里就传进来编号和函数即可。 #pragma once#include iostream #include pthread.h #include string #include cstdlib #include functional using namespace std;typedef void (*func_t)();class Thread { public:typedef enum{NEW 0,RUNNING,EXITED}ThreadStatus;//typedef void (*func_t)(void*);using func_t functionvoid (); public:Thread(int num, func_t func):_tid(0), _status(NEW), _func(func)//num是线程编号{char name[128];snprintf(name, sizeof(name), thread-%d, num);_name name;}int status() {return _status;}string threadname() {return _name;}pthread_t threadid(){if(_status RUNNING) return _tid;else{return 0;}}//runHelper是成员函数类的成员函数具有默认参数this也就是括号有一个Thread* thispthread_create的参数应当是void*类型就不符合所以加上static放在静态区就没有this但是又有新问题了//static成员函数无法直接访问类内属性和其他成员函数所以create那里要传this指针this就是当前线程对象传进来才能访问类内的东西static void* runHelper(void* args)//args就是执行方法的参数{Thread* ts (Thread*)args;//就拿到了当前对象//函数里可以直接调用func这个传过来的方法参数就是_args(*ts)();//调用了func函数return nullptr;}void operator()()//仿函数{if(_func ! nullptr) _func();}void run()//run函数这里传方法{int n pthread_create(_tid, nullptr, runHelper, this);//为什么传thisif(n ! 0) exit(1);_status RUNNING;}void join(){int n pthread_join(_tid, nullptr);if(n ! 0){cerr main thread join thread _name error endl;return ;} _status EXITED;}~Thread(){} private:pthread_t _tid;string _name;func_t _func;//线程未来要执行的函数方法//void* _args;//也可以不写这个那么typedef的函数指针就没有参数。当然参数也可用模板来写ThreadStatus _status; };udp_server.hpp那里两个线程c和p把它设置成指针的那么成员就是Thread* c和Thread* p初始化时new出来传进去的函数应当是std库里的bind接口 template class F, class… Args std::functionF(Args…) std::bind( F f, Args… args ); F 是一个可调用对象的类型可以是函数指针、函数对象、成员函数指针等。 Args… 是 F 所接受的参数类型。 这样就可以把接收和发送数据的函数传进去了。原先的InitServer函数在最后加上c-run()p-run()让两个线程运行起来析构函数那里也要加上两个线程指针的析构。 UdpServer(uint16_t port default_port): port_(port){std::cout server addr: port_ std::endl;pthread_mutex_init(lock, nullptr);c new Thread(1, std::bind(UdpServer::Recv, this));p new Thread(1, std::bind(UdpServer::Broadcast, this));}void Start(){//1. 创建socket套接字接口打开网络文件sock_ socket(AF_INET, SOCK_DGRAM, 0);if(sock_ 0){std::cerr create socket error: strerror(errno) std::endl;exit(SOCKET_ERR);}std::cout create socket succeed: sock_ std::endl;//默认文件描述符应当是3//2. 给服务器指明IP地址Port号struct sockaddr_in local;//网络通信要引入头文件netinet/in.h, arpa/inet.hbzero(local, sizeof(local));local.sin_family AF_INET;//上面的AF_INET用来创建套接字这个用来初始化sockaddr_in结构体local.sin_port htons(port_);//字符串转为4字节int不能强转需要转化并且转化完后要再变成网络序列//in_addr_t inet_addr函数就可以解决这个问题它的作用就是字符串转4字节int并转网络序列传一个const char*的就行//local.sin_addr.s_addr inet_addr(ip_.c_str());//云服务器或者一款服务器一般不要指明某一个确定的IPlocal.sin_addr.s_addr INADDR_ANY;//让我们的udpserver在启动的时候绑定本主机上的任意一个IP地址//现在要把这个local绑定到套接字中但这个local是在函数内的也就是临时变量它在用户空间的特定函数的栈帧上不在内核中if(bind(sock_, (struct sockaddr*)local, sizeof(local)) 0){std::cerr bind socket error: strerror(errno) std::endl;exit(BIND_ERR);}std::cout bind socket succeed: sock_ std::endl;p-run();c-run();}~UdpServer(){pthread_mutex_destroy(lock);c-join();p-join();delete c;delete p;}private:int sock_;uint16_t port_;//func_t service_;//网络服务器要进行业务处理std::unordered_mapstd::string, struct sockaddr_in onlineuser;RingQueuestd::string rq;pthread_mutex_t lock;Thread *c;Thread *p;//std::string ip_;};makefile里因为服务端用了多线程所以要在后面加上-lpthread。启动服务端后用ps -aL |grep udp_server来查看会发现有三个线程接收发送和主线程。 实际运行起来后会发现客户端只能发一个消息才能接收一个服务端的消息这是因为客户端是阻塞式运行的只能先发再收所以客户端也要改成多线程的。 把接收消息的部分放进一个函数里 void* recver(void* args) {int sock *(static_castint*(args));while(true){//接收消息char buffer[2048];struct sockaddr_in temp;socklen_t len sizeof(temp);int n recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)temp, len);if(n 0){buffer[n] 0;std::cout buffer std::endl;//往1号描述符输出}} } //......pthread_t tid;pthread_create(tid, nullptr, recver, sock);while(true){//用户输入std::string message;std::cerr Please Enter# ;//往2号文件描述符输出std::getline(std::cin, message);//发送sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)server, sizeof(server));}接收和输入用两个输出流。 开启服务端后我们输入命令mkfifo message_pipe就创建了一个命名管道。将客户端的结果重定向到管道./udp_client 127.0.0.1 8081 message_pipe那么客户端发消息服务端收到再发送给客户端这个消息就会被放到管道服务端发来的消息从管道中能够读取出来但是读取出来的消息并不知道是哪个客户端发的再改一下代码。 udp_server.hpp中有发消息者的信息 //构建一个用户并检查std::string name clientip;name -;name std::to_string(clientport);//如果不存在就插入如果存在就什么都不做addUser(name, peer);rq.push(buffer);原本是消息放到buffer里然后把buffer放到环形队列中现在加工一下 //构建一个用户并检查std::string name clientip;name -;name std::to_string(clientport);//如果不存在就插入如果存在就什么都不做addUser(name, peer);std::string message name buffer;rq.push(message);这样服务端发送消息前这个消息就带上了源头信息。 6、Windows下socket编程的不同 需要引入头文件WinSock2.h。 #pragma comment(lib, ws2_32.lib)//引入一个库WSADATA WSAData; if(WSAStartup(MAKEWORD(2, 2), WSAData) ! 0) {std::cout init error std::endl;return -1; } //......最后 WSACleanup();WSADATA全拼就是windows socket addr data用这个MAKEWORD宏来构建2.2版本把这个版本的数据结果放到WSAData中WSAStartup用来检查引入的库和期望用的版本是否一致不一致就打印init error。WSACleanup就是把程序在库中打开的各种数据用到的东西给清理掉然后结束程序。 其它部分都和上面没有什么不同。 #include iostream #include string #include WinSock2.h#pragma comment(lib, ws2_32.lib)uint16_t serverport 8080;//把服务端ip和端口号单独拎出来这样就直接用就好改的时候改这里 std::string serverip 106.75.12.79;int main() {WSADATA WSAData;if (WSAStartup(MAKEWORD(2, 2), WSAData) ! 0){std::cout init error std::endl;return -1;}SOCKET sock socket(AF_INET, SOCK_DGRAM, 0);if (sock 0){std::cerr create socket error std::endl;exit(-2);}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 (true){std::string message;std::cout Please Enter# ;std::getline(std::cin, message);sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)server, sizeof(server));char buffer[2048];struct sockaddr_in temp;int len sizeof(temp);int n recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)temp, len);if (n 0){buffer[n] 0;std::cout buffer std::endl;//往1号描述符输出}}WSACleanup();return 0; }运行起来时会报错这一行有错误server.sin_addr.s_addr inet_addr(serverip.c_str())需要用inet_pton()我们也可以屏蔽这条警告。 #include iostream #include string #include winsock2.h#pragma warning(disable:4996)//屏蔽警告让编译器自行解决 #pragma comment(lib, ws2_32.lib)也可以解决它。inet_pton这个函数将IP地址转网络地址需要用到头文件WS2tcpip.hint inet_pton(int af, const char *src, void *dst)。第一个参数填通信方式第二个参数填IP地址第三个参数是一个用来存放转换后的网络地址的缓冲区。 inet_pton(AF_INET, 106.75.12.79, (server.sin_addr));运行起来就可以创建出一个客户端窗口。 Linux Udp部分 Windows Udp部分 下一篇写TCP套接字通信。 结束。
http://wiki.neutronadmin.com/news/67514/

相关文章:

  • 网站开发众包平台新手怎么做网站内容维护
  • 温州专业网站托管制作网页的软件免费
  • 江西邮电建设工程有限公司网站大丰做网站建设的公司
  • 赣州做网站的公司青提wifi小程序开发教程
  • 一个网站开发环境是什么网页制作网站受众群体分析
  • 泰州专业制作网站望城经开区建设开发公司门户网站
  • wordpress网站做h5分类安卓手机app下载软件
  • 网站集约化建设存在的问题网站可以放多少视频
  • 如何做社交网站宿州建设网站公司哪家好
  • 如何做网站描述怎样做静态网站
  • 本地计算机做网站服务器建设厅电工证查询网站
  • 常州市经开区建设局网站成都网销网站
  • 企业展示网站网站后台图片模板
  • 高端型网站给大家黄页推广网站
  • 郑州做网站优化最好的公司重庆网页制作设计营销
  • 网站友情链接检测网站安全设计
  • 大理旅游网站建设梅州市住房和城乡建设局官网网站
  • 餐饮业网站建设域名服务器ip地址
  • 个旧云锡建设集团网站广告设计软件哪个好用
  • 网站模板制作教程河北邯郸建网站
  • 网站免费做app网页设计网站建设招聘
  • 在中国可以做国外的域名网站吗软文优化
  • 网络推广渠道都有哪些镇江网站推广优化
  • 360阻止建设银行网站宝安网站设计招聘信息
  • 网站正在建设中热网站建设的源代码
  • 织梦配置手机网站温州网站改版公司哪家好
  • 深圳营销型网站策划建设银行U盾不自己弹网站了
  • 自己做网站出证书网站优化基本技巧
  • 广告 网站举例核工业华南建设集团网站
  • 莱山做网站的公司网站建设哪儿济南兴田德润什么活动