网络编程套接字(2): 简单的UDP网络程序

文章目录

  • 网络编程套接字(2): 简单的UDP网络程序
    • 3. 简单的UDP网络程序
      • 3.1 服务端创建
        • (1) 创建套接字
        • (2) 绑定端口号
        • (3) sockaddr_in结构体
        • (4) 数据的接收与发送
          • 接收
          • 发送
      • 3.2 客户端创建
      • 3.3 代码编写
        • (1) v1_简单发送消息
        • (2) v2_小写转大写
        • (3) v3_模拟命令行解释器
        • (4) v4_多线程版本的群聊系统
        • (5) v5_Windows与Linux配合聊天室

网络编程套接字(2): 简单的UDP网络程序

3. 简单的UDP网络程序

3.1 服务端创建

(1) 创建套接字

在这里插入图片描述

create an endpoint for communication: 创建用于通信的端点

头文件:
        #include <sys/types.h>         
        #include <sys/socket.h>

函数原型:
        int socket(int domain, int type, int protocol);

参数说明:
		第一个参数domain:   指定套接字的通信域
        第二个参数type:     指定套接字的服务类型(套接字的种类) 
        第三个参数protocol: 代表创建套接字的协议(默认为0),0,系统会自动判断是tcp还是udp

返回值: 
	    套接字创建成功: 返回一个文件描述符
        套接字创建失败: 返回-1, 并且设置错误码

关于socket参数详细介绍:

(1) domain: 指定套接字的通信域,相当于 struct sockaddr结构体的前16比特位(2字节)

在这里插入图片描述

domain的选项是以宏的形式给出的,我们直接选用即可。常用就是上面框住的两个:

  • AF_UNIX,本地通信
  • AF_INET(IPv4)或者 AF_INET6(IPv6),网络通信

(2) type: 指定套接字的服务类型

在这里插入图片描述

该参数的选项也是像domain一样以宏的形式给出,直接选用。常用的是上面两个:

  • SOCK_STREAM: 基于TCP的网络通信,流式套接字,提供的是流式服务(对应TCP的特点:面向字节流)

  • SOCK_DGRAM: 基于UDP的网络通信,套接字数据报,提供的用户数据报服务(对应UDP的特点:面向数据报)

(2) 绑定端口号

在这里插入图片描述

bind a name to socket:将名称绑定到套接字

头文件:
       #include <sys/types.h>          
       #include <sys/socket.h>

函数原型:
       int bind(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);

参数说明:
	   第一个参数sockfd:  文件描述符, 即要绑定的套接字
	   第二个参数addr:    网络相关的结构体, 包含IP地址、端口号等
	   第三个参数addrlen: 传入结构体addr(第二个参数)的实际长度大小
           
返回值:
	   绑定成功: 返回0
	   绑定失败: 返回-1,并且设置错误码

参数addr的类型是:struct sockaddr *,也就是如图的结构体

在这里插入图片描述

我们需要做的就是:定义一个 sockaddr_in 的结构体,即上图的第二个结构体,然后对该结构体进行内容填充,填完就把给结构体传给第二个参数addr,需要强制类型转换

(3) sockaddr_in结构体

在这里插入图片描述

  • __SOCKADDR_COMMON是一个宏

在这里插入图片描述

#define	__SOCKADDR_COMMON(sa_prefix) sa_family_t sa_prefix##family

sa_prefix:代表外面传入的参数sin_

sa_prefix##family:##代表合并拼接,意思是sa_prefix与family合并拼接

sa_prefix就是sin_,则sa_prefix##family表示sin_family

sa_family_t:代表16位整数

在这里插入图片描述

就是这16位地址类型

在这里插入图片描述

  • sin_port: 是当前服务器需要绑定的端口号,它的类型是 in_port_t,代表16位的整数

在这里插入图片描述

  • sin_addr: 代表IP地址,它的类型是一个in_addr的结构体,它里面的内容是32位的整数

在这里插入图片描述

  1. 关于这个IP地址:我们要传入字符串风格的,但是这里需要4字节整数风格,所以需要转化,比如"1.1.1.1"-> uint32_t,问:能不能强转呢?
    不能强转, 强转只能改变类型, 不改变二进制构成

  2. 我们转化完了还是本主机的4字节序列,需要网络序列,所以要将主机序列转化成为网络序列

上面的2步用 inet_addr函数就可以完成

在这里插入图片描述

  1. 但是我们的云服务器,或者一款服务器,一般不要指明某一个确定的IP

所以这里的ip地址我们填 INADDR_ANY,这是一个宏,代表 0.0.0.0,叫做任意地址绑定

  • sin_zero: 表示该结构体的填充字段(即上面讲的sin_family,sin_port,sin_addr.s_add)

总结: 未来使用这个函数时,需要所以填充:sin_family,sin_port,sin_addr.s_addr这3个字段,因为不关注其他字段,所以在填充之前需要对该结构体清空,我们可以采用 memset或 bzero函数来完成。

bind的作用:

上面如果我们只设置了sockaddr_in这个结构体,它只是在用户空间的特定函数栈帧上,不在内核中,所以bind的作用就是把文件字段进行绑定关联,这样这个文件就是网络文件

(4) 数据的接收与发送

接收

在这里插入图片描述

receive a message from a socket:从套接字接收消息

头文件: 
        #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);

参数说明:
	     第一个参数sockfd: 文件描述符,就是上面我们绑定好的套接字
    	 第二个参数buf: 接收数据的缓冲区(自己定义)
         第三参数len:   缓冲区的长度
         第四个参数flags: 读取方式,默认设为0表示阻塞式读取
         第五个参数src_addr: (输入)对应套接字的接收缓冲区
         第六个参数addrlen:(输出)src_addr结构体的长度

返回值:
         成功: 返回实际读到的字节数
         失败: 失败返回-1,并设置错误码

socklen_t 是一个32位的无符号整数

  • 参数src_addr与addrlen:输入输出型参数

  • src_addr: 输入时传入对应套接字的接收缓冲区,输出时包含客户端的ip和port

  • addrlen: 输入时传入对应套接字的接收缓冲区,输出时表示实际输出的结构体大小

我们做的是定义一个 sockaddr_in 的结构体,把结构体传给参数src_addr,需要强制类型转换

发送

在这里插入图片描述

send a message on a socket: 在套接字上发送消息

头文件:
		   #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);
参数说明:
			
           第一个参数sockfd: 文件描述符,从哪个套接字去发送消息
           第二个参数buf: 发送数据的缓冲区
           第三参数len: 要发送的数据长度
           第四个参数flags:发送方式,默认设为0表示阻塞式发送
           第五个参数dest_addr:下面解释
           第六个参数addrlen:dest_addr结构体的长度

返回值:
		   成功: 实际发送的字节数
		   失败: 失败返回-1,并设置错误码
  • dest_addr和addrlen 是一个输入型参数

  • dest_addr:指向目的地址的结构体指针,表示要发给谁

  • addrlen:表示目的地址结构体的长度

我们做的是定义一个 sockaddr_in 的结构体,然后对该结构体进行内容填充,填完就把给结构体传给dest_addr**,需要强制类型转换**

3.2 客户端创建

还是3步:创建套接字,bind(不需要自己绑定,由OS自动分配),处理数据接收与发送

3.3 代码编写

这里一共提供5个版本的udp代码

err.hpp:这个代码是公用的后续不在给出

#pragma once
enum
{
    USAGE_ERR=1,
    SOCKET_ERR,
    BIND_ERR,
};

(1) v1_简单发送消息

客户端向服务端发送消息,服务端收到后再把消息发回给客户端

udp_server.hpp

#include<iostream>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include"err.hpp"
using namespace std;


namespace ns_server
{

    const static uint16_t default_port=8080;

    class Udpserver
    {
    public:
        Udpserver(uint16_t port=default_port)
            :port_(port)
        {
            cout<<"server addr: "<<port_<<endl;
        }

        void InitServer()
        {
            // 1. 创建socket接口, 打开网络文件(本质)
            sock_=socket(AF_INET,SOCK_DGRAM,0);
            if(sock_<0)
            {
                cerr<<"create socket error: "<<strerror(errno)<<endl;
                exit(SOCKET_ERR);
            }
            cout<<"create socket success: "<<sock_<<endl;   // sock_ = 3

            // 2. 给服务器指明IP地址和Port端口号

            // 填充一下服务器的IP和Port
            struct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!
            
            // 清空local
            bzero(&local,sizeof(local));  // 用memset也可以

            // 填充sockaddr_in结构
            local.sin_family=AF_INET;
            local.sin_port=htons(port_);         // 端口号要出现在网络中, 主机序列转网络序列

            // 使用 inet_addr就可以做下面两件事情:
            // (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成
            // (2) 需要将主机序列转化成为网络序列

            // (3)云服务器,或者一款服务器,一般不要指明某一个确定的IP
            local.sin_addr.s_addr=INADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    

            // 把套接字字段和文件字段进行关联  --- 网络文件
            int n=bind(sock_,(struct sockaddr*)&local,sizeof(local));    
            if(n<0)
            {
                cerr<<"bind socket error: "<<strerror(errno)<<endl;
                exit(BIND_ERR);
            }
            cout<<"bind socket success: "<<sock_<<endl;   

        }

        void Start()
        {
            char buffer[1024];       // 保存用户数据的缓冲区
            while(true)
            {
                // 收到来自客户端发送的消息

                struct sockaddr_in peer;          // 远端
                socklen_t len = sizeof(peer);     // 这里一定要写清楚, 未来你传入的缓冲区大小
                // 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加'\0'
                int n = recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,                                  &len);  
                
                if(n>0)    // 读取数据成功
                    buffer[n]='\0';
                else
                    continue;

                // 提取client信息   --- debug
                string clientip=inet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的
                uint16_t clientport=ntohs(peer.sin_port);  // 网络序列转主机序列
                cout<< clientip << "-" <<clientport<< "# "<<buffer<<endl;

                // 把消息发给别人

                // 网络套接字本质是文件, 往文件中写入时\0并不需要写到文件中, \0是C语言的规定
                // 谁给我发的, 我就把消息转给谁
                // peer结构体字段是从网络中拿的, 本来就是网络序列, 直接发就行
                sendto(sock_,buffer,strlen(buffer),0,(struct sockaddr*)&peer, 		  	                        sizeof(peer));
            }
        }

        ~Udpserver()
        {}

    private:
        int sock_;         //套接字(文件描述符)
        uint16_t port_;    //端口号(本地主机序列构建的port)
    };
}

udp_server.cc

#include"udp_server.hpp"
using namespace ns_server;


// 运行格式: ./udp_server port

// 使用手册
static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}

int main(int argc,char*argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port=atoi(argv[1]);         //命令行参数转换成uint16_t类型

    unique_ptr<Udpserver> usvr(new Udpserver(port));

    usvr->InitServer();     // 服务器初始化

    usvr->Start();

    return 0;
}

udp_client.cc

#pragma once

#include<iostream>
#include"err.hpp"
#include<sys/types.h>
#include<sys/socket.h>
#include<cstring>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;

// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试

static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}

// udp_client serverip serverport
int main(int argc,char*argv[])
{
    if(argc !=3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    // 拿到服务端的ip和端口号
    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    // 1. 创建套接字
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if (sock < 0)
    {
        cerr << "create socket error: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    // 2. 关于客户端的绑定
    // client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip:        serverport]
    // 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?
    // client的port要随机让OS分配防止client出现启动冲突 
    // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化

    // 明确服务器是谁
    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());

    // 3. 向服务器发送消息(这里暂时由用户充当)
    while(true)
    {
        // 用户输入
        string message;
        cout<< "please Enter# ";
        cin>>message;
    
        // 什么时候bind呢?
        // 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP, (1)bind (2)构建发送的数据报文

        // 发送
        sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)&server,                        sizeof(server));

        // 把消息再收回来(回显回来)

        char buffer[1024];
        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';
            cout<<"server echo# "<<buffer<<endl;
        }
    }
}

运行结果:

先运行服务端,再启动客户端,客户端先用本地环回进行测试,测试成功

在这里插入图片描述

运行程序后看到套接字是创建成功的,对应得到到的文件描述符是3,这也很好理解,因为0、1、2默认被标准输入流、标准输出流和标准错误流占用了,此时最小的、未被使用用的文件描述符就是3

(2) v2_小写转大写

v2在v1版本的基础增加了业务处理,上层使用了回调函数实现大小写转换

udp_server.hpp

#pragma once

#include<iostream>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<functional>
#include"err.hpp"
using namespace std;


namespace ns_server
{

    const static uint16_t default_port=8080;
    using func_t =function<string(string)>;    //这是一个函数

    class Udpserver
    {
    public:
        Udpserver(func_t cb, uint16_t port=default_port)
            :service_(cb)
            ,port_(port)
        {
            cout<<"server addr: "<<port_<<endl;
        }

        void InitServer()
        {
            // 1. 创建socket接口, 打开网络文件(本质)
            sock_=socket(AF_INET,SOCK_DGRAM,0);
            if(sock_<0)
            {
                cerr<<"create socket error: "<<strerror(errno)<<endl;
                exit(SOCKET_ERR);
            }
            cout<<"create socket success: "<<sock_<<endl;   // sock_ = 3

            // 2. 给服务器指明IP地址和Port端口号

            // 填充一下服务器的IP和Port
            struct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!
            
            // 清空local
            bzero(&local,sizeof(local));  // 用memset也可以

            // 填充sockaddr_in结构
            local.sin_family=AF_INET;
            local.sin_port=htons(port_);         // 端口号要出现在网络中, 主机序列转网络序列

            // 使用 inet_addr就可以做下面两件事情:
            // (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成
            // (2) 需要将主机序列转化成为网络序列

            // (3)云服务器,或者一款服务器,一般不要指明某一个确定的IP
            local.sin_addr.s_addr=INADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    

            // 把套接字字段和文件字段进行关联  --- 网络文件
            int n=bind(sock_,(struct sockaddr*)&local,sizeof(local));    
            if(n<0)
            {
                cerr<<"bind socket error: "<<strerror(errno)<<endl;
                exit(BIND_ERR);
            }
            cout<<"bind socket success: "<<sock_<<endl;   

        }

        void Start()
        {
            char buffer[1024];       // 保存用户数据的缓冲区
            while(true)
            {
                // 收到来自客户端发送的消息

                struct sockaddr_in peer;                 // 远端
                socklen_t len = sizeof(peer);             // 这里一定要写清楚, 未来你传入的缓冲区大小
                // 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加'\0'
                int n = recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,                                  &len);  
                
                if(n>0)    // 读取数据成功
                    buffer[n]='\0';
                else
                    continue;

                // 提取client信息   --- debug
                string clientip=inet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的
                uint16_t clientport=ntohs(peer.sin_port);  // 网络序列转主机序列
                cout<< clientip << "-" <<clientport<< "# "<<buffer<<endl;


                // 做业务处理
                string response=service_(buffer);

                // 把消息发给别人

                // 网络套接字本质是文件, 往文件中写入时\0并不需要写到文件中, \0是C语言的规定
                // 谁给我发的, 我就把消息转给谁
                // peer结构体字段是从网络中拿的, 本来就是网络序列, 直接发就行
                sendto(sock_,response.c_str(),response.size(),0,(struct sockaddr*)&peer, 						sizeof(peer));
            }
        }

        ~Udpserver()
        {}

    private:
        int sock_;         //套接字(文件描述符)
        uint16_t port_;    //端口号(本地主机序列构建的port)
        func_t service_;    //我们的网络服务器刚刚解决的是网络IO的问题, 要进行业务处理(一个类内的回调方法)
    };
}

udp_server.cc

#include"udp_server.hpp"
using namespace ns_server;


// 运行格式: ./udp_server port

// 使用手册
static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}

// 上层的业务处理, 不关心网络发送, 只负责信息处理即可

// 这里是小写转大写
string transactionString(string request)   // request就是一个字符串
{
    string ret;
    char c;
    for(auto&r:request)
    {
        if(islower(r))
        {
            c=toupper(r);
            ret.push_back(c);
        }
        else
        {
            ret.push_back(r);
        }
    }
    return ret;
}
int main(int argc,char*argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port=atoi(argv[1]);         //命令行参数转换成uint16_t类型

    unique_ptr<Udpserver> usvr(new Udpserver(transactionString,port));

    usvr->InitServer();     // 服务器初始化

    usvr->Start();

    return 0;
}

udp_client.cc

#pragma once

#include<iostream>
#include"err.hpp"
#include<sys/types.h>
#include<sys/socket.h>
#include<cstring>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;

// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试

static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}

// udp_client serverip serverport
int main(int argc,char*argv[])
{
    if(argc !=3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    // 拿到服务端的ip和端口号
    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    // 1. 创建套接字
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if (sock < 0)
    {
        cerr << "create socket error: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    // 2. 关于客户端的绑定
    // client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip:        		serverport]
    // 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?
    // client的port要随机让OS分配防止client出现启动冲突 
    // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化

    // 明确服务器是谁
    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());

    // 3. 向服务器发送消息(这里暂时由用户充当)
    while(true)
    {
        // 用户输入
        string message;
        cout<< "please Enter# ";
        cin>>message;
    
        // 什么时候bind呢?
        // 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP, (1)bind (2)构建发送的数据报文

        // 发送
        sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)&server,                        sizeof(server));

        // 把消息再收回来(回显回来)

        char buffer[1024];
        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';
            cout<<"server echo# "<<buffer<<endl;
        }
    }
}

运行结果:

在这里插入图片描述

(3) v3_模拟命令行解释器

v3是在v2原有的业务处理下修改了功能,只要我们在客户端输入命令服务端就会返回运行结果,popen函数可以实现简单的命令行解释

udp_server.hpp

#pragma once

#include<iostream>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<functional>
#include"err.hpp"
using namespace std;

namespace ns_server
{
    const static uint16_t default_port=8080;
    using func_t =function<string(string)>;    //这是一个函数

    class Udpserver
    {
    public:
        Udpserver(func_t cb, uint16_t port=default_port)
            :service_(cb)
            ,port_(port)
        {
            cout<<"server addr: "<<port_<<endl;
        }

        void InitServer()
        {
            // 1. 创建socket接口, 打开网络文件(本质)
            sock_=socket(AF_INET,SOCK_DGRAM,0);
            if(sock_<0)
            {
                cerr<<"create socket error: "<<strerror(errno)<<endl;
                exit(SOCKET_ERR);
            }
            cout<<"create socket success: "<<sock_<<endl;   // sock_ = 3

            // 2. 给服务器指明IP地址和Port端口号

            // 填充一下服务器的IP和Port
            struct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!
            
            // 清空local
            bzero(&local,sizeof(local));  // 用memset也可以

            // 填充sockaddr_in结构
            local.sin_family=AF_INET;
            local.sin_port=htons(port_);         // 端口号要出现在网络中, 主机序列转网络序列

            // 使用 inet_addr就可以做下面两件事情:
            // (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成
            // (2) 需要将主机序列转化成为网络序列

            // (3)云服务器,或者一款服务器,一般不要指明某一个确定的IP
            local.sin_addr.s_addr=INADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    

            // 把套接字字段和文件字段进行关联  --- 网络文件
            int n=bind(sock_,(struct sockaddr*)&local,sizeof(local));    
            if(n<0)
            {
                cerr<<"bind socket error: "<<strerror(errno)<<endl;
                exit(BIND_ERR);
            }
            cout<<"bind socket success: "<<sock_<<endl;   

        }


        void Start()
        {
            char buffer[1024];       // 保存用户数据的缓冲区
            while(true)
            {
                // 收到来自客户端发送的消息

                struct sockaddr_in peer;                 // 远端
                socklen_t len = sizeof(peer);             // 这里一定要写清楚, 未来你传入的缓冲区大小
                // 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加'\0'
                int n = recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,                                  &len);  
                
                if(n>0)    // 读取数据成功
                    buffer[n]='\0';
                else
                    continue;

                // 提取client信息   --- debug
                string clientip=inet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的
                uint16_t clientport=ntohs(peer.sin_port);  // 网络序列转主机序列
                cout<< clientip << "-" <<clientport<< "# "<<buffer<<endl;

                // 做业务处理
                string response=service_(buffer);

                // 把消息发给别人

                // 网络套接字本质是文件, 往文件中写入时\0并不需要写到文件中, \0是C语言的规定
                // 谁给我发的, 我就把消息转给谁
                // peer结构体字段是从网络中拿的, 本来就是网络序列, 直接发就行
                sendto(sock_,response.c_str(),response.size(),0,(struct sockaddr*)&peer, 						sizeof(peer));
            }
        }

        ~Udpserver()
        {}

    private:
        int sock_;         //套接字(文件描述符)
        uint16_t port_;    //端口号(本地主机序列构建的port)
        func_t service_;    //我们的网络服务器刚刚解决的是网络IO的问题, 要进行业务处理(一个类内的回调方法)
    };
}

udp_server.cc

#include"udp_server.hpp"
using namespace ns_server;


// 运行格式: ./udp_server port

// 使用手册
static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}

// 上层的业务处理, 不关心网络发送, 只负责信息处理即可
static bool isPass(string &command)
{
    auto pos=command.find("rm");
    if(pos!=string::npos) return false;
    pos=command.find("mv");
    if(pos!=string::npos) return false;
    pos=command.find("while");
    if(pos!=string::npos) return false;
    pos=command.find("kill");
    if(pos!=string::npos) return false;

    return true;
}

// 让同学们, 在你的本地把命令给我, server再把结果给你!
string excuteCommand(string command)   // command就是一个命令
{
    // 1. 安全检查
    if(!isPass(command))
        return "you are a bad man";

    // 2. 业务逻辑处理
    FILE*fp=popen(command.c_str(),"r");
    if(fp==nullptr)
        return "None";

    // 3. 获取结果
    char line[1024];
    string ret;
    while(fgets(line,sizeof(line),fp)!=NULL)
    {
        ret+=line;
    }
    pclose(fp);

    return ret;
}

int main(int argc,char*argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port=atoi(argv[1]);         //命令行参数转换成uint16_t类型

    unique_ptr<Udpserver> usvr(new Udpserver(excuteCommand,port));

    usvr->InitServer();     // 服务器初始化

    usvr->Start();

    return 0;
}

udp_server.cc

#pragma once

#include<iostream>
#include"err.hpp"
#include<sys/types.h>
#include<sys/socket.h>
#include<cstring>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;


// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试

static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}

// udp_client serverip serverport
int main(int argc,char*argv[])
{
    if(argc !=3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    // 拿到服务端的ip和端口号
    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    // 1. 创建套接字
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if (sock < 0)
    {
        cerr << "create socket error: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    // 2. 关于客户端的绑定
    // client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip: serverport]
    // 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?
    // client的port要随机让OS分配防止client出现启动冲突 
    // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化

    // 明确服务器是谁
    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());

    // 3. 向服务器发送消息(这里暂时由用户充当)
    while(true)
    {
        // 用户输入
        string message;
        cout<< "[遇健的服务器]# ";
        getline(cin,message);
    
        // 什么时候bind呢?
        // 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP, (1)bind (2)构建            发送的数据报文

        // 发送
        sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)&server,                       sizeof(server));

        // 把消息再收回来(回显回来)

        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';
            cout<<"server echo# "<<buffer<<endl;
        }

    }
}

运行结果:

在这里插入图片描述

(4) v4_多线程版本的群聊系统

v4在v3的基础上加入了之前写的生产消费者模型,多线程实现了群聊系统

LockGuard.hpp

#include<iostream>
#include<pthread.h>
using namespace std;

class 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;   //锁的指针
};

RingQueue.hpp

#include<iostream>
#include<pthread.h>
#include<vector>
#include<time.h>
#include<sys/types.h>
#include<unistd.h>
#include<semaphore.h>
#include<mutex>
using namespace std;

// 生产者和消费者要有自己的下标来表征生产和消费要访问哪个资源
static const int N=5;

template<class 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)     // 对应生产者
    {
        // 1.信号量的好处:
        // 可以不用在临界区内部做判断, 就可以知道临界资源的使用情况

        // 2.什么时候用锁, 什么时候用sem? --- 你对应的临界资源, 是否被整体使用!

        // 生产 --- 先要申请信号量
        // 信号量申请成功 - 则一定能访问临界资源
        P(_space_sem);
        Lock(_p_mutex);
        // 一定要有对应的空间资源给我!不用做判断, 是哪一个资源给生产者呢
        _ring[_p_step++]=in;
        _p_step%=_cap;
        V(_data_sem);
        Unlock(_p_mutex);
    }

    void pop(T*out)           // 对应消费者
    {
        // 消费
        P(_data_sem);    // 1.   先申请信号量是为了更高效
        Lock(_c_mutex);  // 2. 
        *out=_ring[_c_step++];
        _c_step%=_cap;
        V(_space_sem);
        Unlock(_c_mutex);
    }

    ~RingQueue()
    {
        sem_destroy(&_data_sem);
        sem_destroy(&_space_sem);

        pthread_mutex_destroy(&_c_mutex);
        pthread_mutex_destroy(&_p_mutex);
    }
private:
    vector<T> _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;
};

udp_server.hpp

#include<iostream>
#include<memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<cerrno>
#include<cstring>
#include<cstdlib>
#include<functional>
#include"err.hpp"
#include<unordered_map>
#include"ringQueue1.hpp"
#include"lockGuard.hpp"
#include"thread.hpp"
using namespace std;


// 群聊系统 --- 一个线程收消息, 一个线程发消息
// 标识一个客户端: ip+port , 使用unordered_map构建<ip+port, 客户端套接字>来表示某个用户发的消息

namespace ns_server
{

    const static uint16_t default_port=8080;
    using func_t =function<string(string)>;    //这是一个函数

    class Udpserver
    {
    public:
        Udpserver(uint16_t port=default_port)
            :port_(port)
        {
            cout<<"server addr: "<<port_<<endl;
            pthread_mutex_init(&_lock,nullptr);

            p=new Thread(1,bind(&Udpserver::Recv,this));
            c=new Thread(2,bind(&Udpserver::Broadcast,this));
        }

        void StartServer()
        {
            // 1. 创建socket接口, 打开网络文件(本质)
            sock_=socket(AF_INET,SOCK_DGRAM,0);
            if(sock_<0)
            {
                cerr<<"create socket error: "<<strerror(errno)<<endl;
                exit(SOCKET_ERR);
            }
            cout<<"create socket success: "<<sock_<<endl;   // sock_ = 3

            // 2. 给服务器指明IP地址和Port端口号

            // 填充一下服务器的IP和Port
            struct sockaddr_in local;     // 里面有很多字段  local是在用户空间的特定函数栈帧上,不在内核中!
            
            // 清空local
            bzero(&local,sizeof(local));  // 用memset也可以

            // 填充sockaddr_in结构
            local.sin_family=AF_INET;
            local.sin_port=htons(port_);         // 端口号要出现在网络中, 主机序列转网络序列

            // 使用 inet_addr就可以做下面两件事情:
            // (1) 字符串风格的IP地址,转换成为4字节int  --- 不能强转, 强转只能改变类型, 不改变二进制构成
            // (2) 需要将主机序列转化成为网络序列

            // (3)云服务器,或者一款服务器,一般不要指明某一个确定的IP
            local.sin_addr.s_addr=INADDR_ANY;   // 让我们的udp_server在启动的时候, bind本主机上的任意IP    

            // 把套接字字段和文件字段进行关联  --- 网络文件
            int n=bind(sock_,(struct sockaddr*)&local,sizeof(local));    
            if(n<0)
            {
                cerr<<"bind socket error: "<<strerror(errno)<<endl;
                exit(BIND_ERR);
            }
            cout<<"bind socket success: "<<sock_<<endl;   

            p->run();
            c->run();
        }

        void addUser(const string&name,const struct sockaddr_in&peer)
        {
            // online[name]=peer
            LockGuard lockguard(&_lock);
            auto iter=onlineuser.find(name);
            if(iter!=onlineuser.end())        // 存在(找到了)直接返回
                return;

            onlineuser.insert(make_pair(name,peer));   // 不存在(没找到)就插入
        }

        void Recv()
        {
            char buffer[1024];       // 保存用户数据的缓冲区
            while(true)
            {
                // 收到来自客户端发送的消息

                struct sockaddr_in peer;                 // 远端
                socklen_t len = sizeof(peer);             // 这里一定要写清楚, 未来你传入的缓冲区大小
                // 假设消息是字符串, -1是为缓冲区预留一个空间,方便添加'\0'
                int n = recvfrom(sock_,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,                                  &len);  
                
                if(n>0)    // 读取数据成功
                    buffer[n]='\0';
                else
                    continue;

                // 提取client信息   --- debug
                string clientip=inet_ntoa(peer.sin_addr); // 把4字节对应的IP转化成字符串风格的
                uint16_t clientport=ntohs(peer.sin_port);  // 网络序列转主机序列
                cout<< clientip << "-" <<clientport<< "# "<<buffer<<endl;


                // 构建一个用户, 并检查
                string name=clientip;
                name+="-";
                name+=to_string(clientport);

                 // 构建哈希表来存储用户 - 如果不存在,就插入;如果存在,什么都不做
                addUser(name,peer);  

                string message=name+">>"+buffer;  

                _rq.push(message);    // 消息放入环形队列中
            }
        }


        // 发消息  --- 给所有在线用户
        void Broadcast()
        {
            while(true)
            {
                string sendstring;
                _rq.pop(&sendstring);    // 从环形队列中读到了消息   

                vector<struct sockaddr_in> v;   // 把需要发送的信息放到(拷贝)一个数组中<这是内存级的拷贝>
                {
                    LockGuard lockguard(&_lock);
                    for (auto user:onlineuser)
                    {
                        v.push_back(user.second);
                    }
                }
                for(auto user: v)
                {
                    sendto(sock_,sendstring.c_str(),sendstring.size(),0,(struct sockaddr*)&user,sizeof(user));
                    cout<<"send done ..."<<sendstring<<endl;
                }
            }
        }

        ~Udpserver()
        {
            pthread_mutex_destroy(&_lock);
            p->join();
            c->join();

            delete p;
            delete c;
        }

    private:
        int sock_;         //套接字(文件描述符)
        uint16_t port_;    //端口号(本地主机序列构建的port)
        unordered_map<string, struct sockaddr_in> onlineuser;   // 保存在线用户 --- 需要加锁保证安全
        pthread_mutex_t _lock;
        RingQueue<string> _rq;
        Thread*p;
        Thread*c;
    };
}

udp_server.cc

#include"udp_server.hpp"
using namespace ns_server;


// 运行格式: ./udp_server port

// 使用手册
static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" port\n"<<endl;
}

// 上层的业务处理, 不关心网络发送, 只负责信息处理即可

int main(int argc,char*argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port=atoi(argv[1]);         //命令行参数转换成uint16_t类型

    unique_ptr<Udpserver> usvr(new Udpserver(port));

    usvr->StartServer();

    return 0;
}

udp_client.cc

#pragma once

#include<iostream>
#include"err.hpp"
#include<sys/types.h>
#include<sys/socket.h>
#include<cstring>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;

// 127.0.0.1 本地环回, 就表示当前的主机, 通常用来进行本地环回通信或者测试

static void usage(string proc)
{
    cout<<"usage:\n\t"<<proc<<" serverip serverport\n" <<endl;
}


// udp_client serverip serverport
int main(int argc,char*argv[])
{
    if(argc !=3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }

    // 拿到服务端的ip和端口号
    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    // 1. 创建套接字
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if (sock < 0)
    {
        cerr << "create socket error: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    // 2. 关于客户端的绑定
    // client这里要不要bind呢? 要的 socket通信的本质 [clienttip: clientport, serverip: serverport]
    // 要不要自己bind呢? 不需要自己bind, 也不要自己bind, OS自动给我们进行bind --- 为什么?
    // client的port要随机让OS分配防止client出现启动冲突 
    // server的端口不能随意改变, 众所周知且不能随意改变的, 同一家公司的port号需要统一规范化

    // 明确服务器是谁
    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());

    // 3. 向服务器发送消息(这里暂时由用户充当)
    while(true)
    {
        // 用户输入
        string message;
        cout<< "[遇健的服务器]# ";
        getline(cin,message);
    
        // 什么时候bind呢?
        // 在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport+自己的IP, (1)bind (2)构建发送的数据报文

        // 发送
        sendto(sock,message.c_str(),message.size(), 0, (struct sockaddr*)&server,                        sizeof(server));

        // 把消息再收回来(回显回来)

        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';
            cout<<"server echo# "<<buffer<<endl;
        }

    }
}

运行结果:

在这里插入图片描述

在这里插入图片描述

(5) v5_Windows与Linux配合聊天室

我们可以以Linux云服务器作为服务端,Windows作为客户端,在Windows下我们要修改成Windows下的接口,同时开放云服务器的端口号,使用v4版本的服务端代码

Windows下的客户端

#define _CRT_SECURE_NO_WARNINGS

#include<iostream>
#include<WinSock2.h>
#include<string>
#include<cstring>
using namespace std;

#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")

uint16_t serverport = 8080;
std::string serverip = "47.108.235.67";

//std::string serverip = "127.0.0.1";

int main()
{
    WSADATA WSAData;

    if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0)
    {
        cerr << "init error" << endl;
        return -1;
    }

    SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);

    if (sock < 0)
    {
        cerr << "create socket error: " << strerror(errno) << 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());
   
    // 3. 向服务器发送消息(这里暂时由用户充当)
    while (true)
    {
        // 用户输入
        string message;
        cout << "Please Enter Your Message# ";
        getline(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';
            cout << buffer << endl;     // 往1号文件描述符输出
        }
    }
    closesocket(sock);
    WSACleanup();

    return 0;
}

运行结果:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/91427.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

「MySQL-01」MySQL基础

目录 一、数据库概念 1. 什么是数据库 2. 为什么要有数据库&#xff1f; 3. 数据库将数据存在哪里&#xff1f; 二、知名数据库介绍 1.知名数据库介绍 2.为什么要学习MySQL 三、MySQL的基本使用 0. 安装MySQL 1. 数据库客户端链接服务端 2. Windows下的MySQL服务端管理 3. 数据…

TMS FlexCel Studio for VCL and FireMonkey Crack

TMS FlexCel Studio for VCL and FireMonkey Crack FlexCel for VCL/FireMonkey是一套允许操作Excel文件的Delphi组件。它包括一个广泛的API&#xff0c;允许本机读取/写入Excel文件。如果您需要在没有安装Excel的Windows或macOS机器上阅读或创建复杂的电子表格&#xff0c;Fle…

基于JSP+Servlet+Mysql员工信息管理系统

基于JSPServletMysql员工信息管理系统 一、系统介绍二、功能展示三.其他系统实现五.获取源码 一、系统介绍 项目类型&#xff1a;Java web项目 项目名称&#xff1a;基于JSPServlet的员工/客户/人员信息管理系统 项目架构&#xff1a;B/S架构 开发语言&#xff1a;Java语言…

行业追踪,2023-08-23

自动复盘 2023-08-23 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

Python3 元组

Python3 元组 Python 的元组与列表类似&#xff0c;不同之处在于元组的元素不能修改。 元组使用小括号 ( )&#xff0c;列表使用方括号 [ ]。 元组创建很简单&#xff0c;只需要在括号中添加元素&#xff0c;并使用逗号隔开即可。 实例(Python 3.0) >>> tup1 (Go…

pywebview 通过 JSBridge 调用 TTS

pip install pywin32 ; pip install pywebview ; 通过 JSBridge 调用本机 TTS pip install cefpython3 cefpython3-66.1-py2.py3-none-win_amd64.whl (69.0 MB) Successfully installed cefpython3-66.1 编写 pywebview_tts.py 如下 # -*- coding: utf-8 -*- ""&…

记录一次Modbus通信的置位错误

老套路&#xff0c;一图胜千言&#xff0c;框图可能有点随意&#xff0c;后面我会解释 先描述下背景&#xff0c;在Modbus线程内有一个死循环&#xff0c;一直在读8个线圈的状态&#xff0c;该线程内读到的消息会直接发送给UI线程&#xff0c;UI线程会解析Modbus数据帧&#xf…

SpringBoot实现文件上传和下载笔记分享(提供Gitee源码)

前言&#xff1a;这边汇总了一下目前SpringBoot项目当中常见文件上传和下载的功能&#xff0c;一共三种常见的下载方式和一种上传方式&#xff0c;特此做一个笔记分享。 目录 一、pom依赖 二、yml配置文件 三、文件下载 3.1、使用Spring框架提供的下载方式 3.2、通过IOUti…

【C++】priority_queue优先级队列

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录 前言一、priority_queue的介绍二、pr…

Windows商店引入SUSE Linux Enterprise Server和openSUSE Leap

在上个月的Build 2017开发者大会上&#xff0c;微软宣布将SUSE&#xff0c;Ubuntu和Fedora引入Windows 商店&#xff0c;反应出微软对开放源码社区的更多承诺。 该公司去年以铂金会员身份加入Linux基金会。现在&#xff0c;微软针对内测者的Windows商店已经开始提供 部分Linux发…

Python绘图系统9:新建绘图类型控件,实现混合类型图表

文章目录 绘图类型控件改造AxisList更改绘图逻辑源代码 Python绘图系统&#xff1a; 从0开始实现一个三维绘图系统自定义控件&#xff1a;坐标设置控件&#x1f4c9;坐标列表控件&#x1f4c9;支持多组数据的绘图系统图表类型和风格&#xff1a;散点图和条形图&#x1f4ca;混…

2023年高教社杯数学建模思路 - 案例:FPTree-频繁模式树算法

文章目录 算法介绍FP树表示法构建FP树实现代码 建模资料 ## 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模式树算法&#xff0c…

【python】Leetcode(primer-dict-list)

文章目录 260. 只出现一次的数字 III&#xff08;字典 / 位运算&#xff09;136. 只出现一次的数字&#xff08;字典&#xff09;137. 只出现一次的数字 II&#xff08;字典&#xff09;169. 求众数&#xff08;字典&#xff09;229. 求众数 II&#xff08;字典&#xff09;200…

蓝蓝设计-UI设计公司案例-HMI列车监控系统界面设计解决方案

2013年&#xff0c;为加拿大庞巴迪(Bombardier)设计列车监控系统界面设计。 2015-至今&#xff0c;为中车集团旗下若干公司提供HMI列车监控系统界面设计,综合考虑中车特点、城轨车、动车组的不同需求以及HMI硬键屏和触摸 屏的不同操作方式&#xff0c;重构框架设计、交互设计、…

五度易链最新“产业大数据服务解决方案”亮相,打造数据引擎,构建智慧产业

快来五度易链官网 点击网址【http://www.wdsk.net/】 看看我们都发布了哪些新功能!!! 自2015年布局产业大数据服务行业以来&#xff0c;“五度易链”作为全国产业大数据服务行业先锋企业&#xff0c;以“让数据引领决策&#xff0c;以智慧驾驭未来”为愿景&#xff0c;肩负“打…

PROFIBUS主站转MODBUS TCP网关

1.产品功能 YC-DPM-TCP网关在Profibus总线侧实现主站功能&#xff0c;在以太网侧实现ModbusTcp服务器功能。可将Profibus DP从站接入到ModbusTcp网络&#xff1b;通过增加DP/PA耦合器&#xff0c;也可将Profibus PA从站接入ModbusTcp网络。YC-DPM-TCP网关最多支持125个Profibu…

电商项目part07 订单系统的设计与海量数据处理

订单重复下单问题&#xff08;幂等&#xff09; 用户在点击“提交订单”的按钮时&#xff0c;不小心点了两下&#xff0c;那么浏览器就会向服务端连续发送两条创建订单的请求。这样肯定是不行的 解决办法是,让订单服务具备幂等性。什么是幂等性&#xff1f;幂等操作的特点是&a…

网关认证的技术方案

我们认证授权使用springsecurity 和oauth2技术尽心实现具体实现流程见第五章文档&#xff0c;这里就是记录一下我们的技术方案 这是最开始的技术方案&#xff0c;我们通过认证为服务获取令牌然后使用令牌访问微服务&#xff0c;微服务解析令牌即可。但是缺点就是每个微服务都要…

如何构建多域名HTTPS代理服务器转发

在当今互联网时代&#xff0c;安全可靠的网络访问是至关重要的。本文将介绍如何使用SNI Routing技术来构建多域名HTTPS代理服务器转发&#xff0c;轻松实现多域名的安全访问和数据传输。 SNI代表"Server Name Indication"&#xff0c;是TLS协议的扩展&#xff0c;用于…

打怪升级之从零开始的网络协议

序言 三个多月过去了&#xff0c;我又来写博客了&#xff0c;这一次从零开始学习网络协议。 总的来说&#xff0c;计算机网络很像现实生活中的快递网络&#xff0c;其最核心的目标&#xff0c;就是把一个包裹&#xff08;信息&#xff09;从A点发送到B点去。下面是一些共同的…