linux——网络套接字编程

目录

一.简单了解TCP和UDP协议

二.网络字节序

三.socket常见的编程接口

1.介绍接口

2.sockaddr结构

 四.简单的UDP网络程序

1.recvfrom和sendto

2.server.cc

3.client.cc

五.简单的TCP通信

1.client.cc

2.server.cc


一.简单了解TCP和UDP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题.

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论。

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

二.网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
  • 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  • 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>
//  htonl() 将无符号整数,从主机字节顺序转换为网络字节顺序。
uint32_t htonl(uint32_t hostlong);
//  htons() 将无符号短整数,从主机字节顺序转换为网络字节顺序。
uint16_t htons(uint16_t hostshort);
//  ntohl() 将无符号整数,从网络字节顺序转换为主机字节顺序。
uint32_t ntohl(uint32_t netlong);
//  ntohs() 将无符号短整数,从网络字节顺序转换为主机字节顺序。
uint16_t ntohs(uint16_t netshort);
  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

三.socket常见的编程接口

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr* address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr* addr,socklen_t addrlen);

1.介绍接口

socket

  • 功能:创建套接字,表明我们需要的通信服务类型,和IP协议,tcp/udp通信都需要使用。
  • 参数:_1:通信类型,_2:协议类型,_3:默认给0。
  • 返回值:成功返回一个文件描述符,失败返回-1,错误码被设置。

bind

  • 功能:将端口号与该进程绑定,tcp/udp通信都需要使用。
  • 参数:_1:socket返回的文件描述符,_2:一种特定的结构,存储IP地址和端口号,通信协议类型。_3:这种特定结构的大小。
  • 返回值:成功返回0,失败返回-1,错误码被设置。

listen

  • 功能:为套接字设置backlog,并创建半连接队列和全连接队列,将TCP状态设置为TCP_LISTEN,tcp服务端通信才能使用。
  • 参数:_1:socket返回的文件描述符,_2:输出型参数,存储客户端的IP和端口信息,_3:输出型参数,第一个参数存储客户端信息结构的大小。
  • 返回值:成功返回0,失败返回-1,错误码被设置。

accept

  • 功能:接收来自客户端的连接请求,tcp服务端通信才能使用。
  • 参数:_1:socket返回的文件描述符,_2:接受客户端的最大数量。
  • 返回值:成功返回一个新的套接字专门用于和客户端进行通信,失败返回-1。

connect

  • 功能:函数会发起一个TCP连接请求。tcp客户端使用。
  • 参数:_1:socket返回的文件描述符,_2:一种特定的结构,存储IP地址和端口号,通信协议类型。_3:这种特定结构的大小。
  • 返回值:成功返回0,失败返回-1,错误码被设置。

2.sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同.

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.
  • IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,
  • 不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
  • socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;

struct sockaddr_in结构 :

  struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;			//端口
    struct in_addr sin_addr;		//ip地址

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
			   __SOCKADDR_COMMON_SIZE -
			   sizeof (in_port_t) -
			   sizeof (struct in_addr)];
  };

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };

 四.简单的UDP网络程序

1.recvfrom和sendto

recvfrom

  • 功能:从指定的套接字上接收数据。
  • 参数:_1:文件描述符,_2:存储缓冲区,_3:标志位,一般为0,_4:数据发送者的地址和端口,_5:地址结构的大小。
  • 返回值:实际接收到的字节数,如果发生错误则返回-1。如果套接字被关闭或连接已断开,则返回0。

 sendto

  • 功能:向指定的目标地址发送数据报.
  • 参数:_1:创建好的socket的文件描述符,_2:指向需要发送数据的缓冲区的指针,_3要发送的数据的长度,_4:通常设为0,_5:这是一个指向包含数据报接收者的协议地址(如IP地址和端口号)的套接字地址结构的指针,其大小由addrlen参数指定,_6:这是一个指向sockaddr结构大小的指针。
  • 返回值:如果数据成功发送,则返回实际发送的字节数;如果出现错误,则返回-1。

2.server.cc

#include <iostream>
#include <unordered_map>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;
const static int port = 8080;

class Udp_server
{
    enum
    {
        scket_err = 1,
        bind_err
    };

public:
    Udp_server(int po = port)
        : _port(po)
    {
    }

    void Init()
    {
        // 一.创建套接字,返回文件描述符,
        // 1.AF_INET:表示支持IPv4网络通信
        // 2.SOCK_DGRAM:支持无连接的不可靠的面向数据报的传输方式,即udp传输
        // 3.0:只支持一个协议来给定套接字
        _fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_fd == -1)
        {
            cout << "create socket error:" << strerror(errno) << endl;
            exit(scket_err);
        }
        else
        {
            cout << "create socket successful" << endl;
        }

        struct sockaddr_in local;
        // 清空
        bzero(&local, sizeof(local));
        // 写入端口号,htons:主机序列转网络序列
        local.sin_port = htons(_port);
        // 写入AF_INET,表示网络通信。
        local.sin_family = AF_INET;
        // 写入IP地址,inet_addr:
        // 1.将点分十进制ip,转化为整形,
        // 2.并且将主机序列转换为网络序列
        // 3.最为一款云服务器,或者服务器,一般不止一个网卡,或者IP,在设置IP时不能直接将ip设置死
        local.sin_addr.s_addr = INADDR_ANY;
        // 二.绑定
        if (bind(_fd, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            cerr << "bind error:" << strerror(errno) << endl;
            exit(bind_err);
        }
        else
        {
            cout << "bind successful" << endl;
        }
    }

    void Start()
    {
        // 1.接受信息
        char buff[1024];
        struct sockaddr_in client;
        socklen_t client_len = sizeof(client);
        while (1)
        {
            int read_size = recvfrom(_fd, buff, sizeof(buff), 0, (struct sockaddr *)&client, &client_len);
            if (read_size > 0)
            {
                buff[read_size] = 0;
            }
            cout << "client:" << buff << endl;

            // 提取客户端信息
            string clientip = inet_ntoa(client.sin_addr); // ip
            int clientport = ntohs(client.sin_port);

            // 发送信息
            char send[1024];
            sprintf(send, "(-_-) %s (-_-)", buff);
            sendto(_fd, send, sizeof(send), 0, (const struct sockaddr *)&client, client_len);
        }
    }

    ~Udp_server()
    {
    }

private:
    int _port;
    int _fd;
};

int main()
{
    cout << "00000000" << endl;

    Udp_server server(8081);

    server.Init();
    server.Start();

    return 0;
}

3.client.cc

#include <iostream>
#include <cstdio>
#include <cstring>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;
//./client ip port

int main(int argc, char *args[])
{
    // 1.创建按套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    // 2.获取ip和端口
    string server_ip = args[1];
    int server_prot = atoi(args[2]);
    // 3.明确连接的服务器
    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    server.sin_port = htons(server_prot);

    while (1)
    {
        string str;
        str.clear();
        getline(cin, str);

        // 发送信息
        sendto(fd, str.c_str(), str.length(), 0, (struct sockaddr *)&server, sizeof(server));
        // 接受信息
        char buff[1024];
        struct sockaddr_in sersock;
        socklen_t sersock_len = sizeof(sersock);
        //
        int n = recvfrom(fd, buff, sizeof(buff), 0, (struct sockaddr *)&sersock, &sersock_len);
        if (n > 0)
            buff[n] = 0;
        else
            continue;
        cout << "server:" << buff << endl;
    }
    return 0;
}

代码测试:

五.简单的TCP通信

1.client.cc

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

enum err
{
    SOCKER_ERR = 1,
    BIND_ERR,
    LISTEN_ERR,
    ACCEPT_ERR,
    CONNECT_ERR
};

int main(int argc, char *argv[])
{
    string port = argv[2];
    string ip = argv[1];

    // 创建TCP套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    // 向服务端建立连接
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(port.c_str()));
    // net_aton()将网络主机地址cp从IPv4数字和点符号转换为二进制形式(以网络字节为单位)
    // 并将其存储在inp指向的结构中
    inet_aton(ip.c_str(), &server.sin_addr);
    int count = 1;
    while (connect(sock, (struct sockaddr *)&server, sizeof(server)) != 0)
    {
        sleep(1);
        cout << "正在尝试重连..."
             << "[" << count++ << "/5]" << endl;
        if (count == 6)
            exit(CONNECT_ERR);
    }
    // 连接成功
    while (1)
    {
        string message;
        cout << "Enter>>> ";
        getline(cin, message);
        write(sock, message.c_str(), message.length());

        char buff[1024];
        int n = read(sock, buff, sizeof(buff));
        if (n > 0) // 读取成功
        {
            buff[n] = 0;
            cout << "server:" << buff << endl;
        }
        else if (n == 0) // 服务器端关闭了
        {
            cout << "server quit,me too" << endl;
            break;
        }
        else // 读取出错
        {
            cout << "read err" << endl;
            break;
        }
    }
    close(sock);

    return 0;
}

2.server.cc

#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>

using namespace std;
const int static backlog = 32;

enum err
{
    SOCKER_ERR = 1,
    BIND_ERR,
    LISTEN_ERR,
    ACCEPT_ERR,
    CONNECT_ERR
};
class tcp_server;
struct Args
{
    Args(int sock, uint16_t port, string ip, tcp_server *server)
        : _sock(sock), _port(port), _ip(ip), _server(server)
    {
    }
    int _sock;
    uint16_t _port;
    string _ip;
    tcp_server *_server;
};

class tcp_server
{
public:
    tcp_server(uint16_t port, function<string(string)> func)
        : _port(port), _func(func)
    {
    }

    void Init()
    {
        // 1.创建套接字
        // AF_INET:使用IPv4通信。
        // SOCK_STREAM:面向字节流,可靠的,有序,双向的,tcp通信。
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock == -1)
        {
            cerr << "socket err:" << strerror(errno) << endl;
            exit(SOCKER_ERR);
        }
        else
        {
            cerr << "socket successful" << endl;
        }

        // 2.绑定
        struct sockaddr_in host;
        memset(&host, 0, sizeof(host));
        host.sin_family = AF_INET; // #define AF_INET 2
        host.sin_port = htons(_port);
        host.sin_addr.s_addr = htonl(INADDR_ANY); // #define INADDR_ANY 0
        if (bind(_listensock, (struct sockaddr *)&host, sizeof(host)) == -1)
        {
            cerr << "bind err:" << strerror(errno) << endl;
            exit(BIND_ERR);
        }
        else
        {
            cerr << "bind successful" << endl;
        }

        // 3.监听
        if (listen(_listensock, backlog) == -1)
        {
            cerr << "listen err:" << strerror(errno) << endl;
            exit(LISTEN_ERR);
        }
        else
        {
            cerr << "listen successful" << endl;
        }
    }

    void Start()
    {
        // v2.1子进程回收
        signal(SIGCHLD, SIG_IGN);

        while (1)
        {
            // 4.接受请求
            // 表明接受的客户端主机
            struct sockaddr_in client_host;
            socklen_t client_len = sizeof(client_host);
            int sock = accept(_listensock, (struct sockaddr *)&client_host, &client_len);
            if (sock == -1)
            {
                cerr << "accpet err:" << strerror(errno) << endl;
                exit(ACCEPT_ERR);
            }
            else
            {
                cerr << "socket successful,get a new client"
                     << "fd:" << sock << endl;
            }

            // 提取客户端信息
            uint16_t client_port = ntohs(client_host.sin_port);
            // inet_ntoa: 将网络主机地址(以网络字节序)转换为IPv4点分十进制的字符串.
            string client_ip = inet_ntoa(client_host.sin_addr);

            // 多线程处理接受的客户端
            pthread_t t;
            pthread_create(&t, nullptr, threadRun, new Args(sock, client_port, client_ip, this));
        }
    }

    static void *threadRun(void *args)
    {
        pthread_detach(pthread_self());
        Args *ts = (Args *)(args);
        ts->_server->service(ts->_sock, ts->_port, ts->_ip);
        return nullptr;
    }

    void service(int sock, uint16_t client_port, string client_ip)
    {
        string name = to_string(client_port) + "-" + client_ip;
        char buff[1024];
        while (1)
        {
            int n = read(sock, buff, sizeof(buff) - 1);
            if (n > 0) // 读取成功
            {
                buff[n] = 0;
                // 读取成功之后,处理也业务
                string recvmess = _func(name + ":" + buff);
                // 将信息返回
                write(sock, recvmess.c_str(), recvmess.size());
            }
            else if (n == 0)
            {
                // 对方将连接关闭了
                close(sock);
                std::cout << name << " quit, me too" << std::endl;
                break;
            }
            else
            {
                close(sock);
                cout << "read err" << strerror(errno) << endl;
                break;
            }
        }
    }

private:
    uint16_t _port;                 // 端口
    int _listensock;                // 监听套接字
    function<string(string)> _func; // 服务端处理的业务
};

string func(string message)
{
    return message + "(-_-)";
}

int main(int argc, char *argv[])
{
    int port = atoi(argv[1]);
    tcp_server server(port, func);
    server.Init();
    server.Start();

    return 0;
}

代码测试:

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

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

相关文章

零日漏洞预防

零日漏洞&#xff0c;是软件应用程序或操作系统&#xff08;OS&#xff09;中的意外安全漏洞&#xff0c;负责修复该漏洞的一方或供应商不知道该漏洞&#xff0c;它们仍然未被披露和修补&#xff0c;为攻击者留下了漏洞&#xff0c;而公众仍然没有意识到风险。 零日攻击是如何…

AI“走深向实”,蚂蚁蚁盾在云栖大会发布实体产业「知识交互建模引擎」

数字化起步晚、数据分散稀疏、专业壁垒高、行业知识依赖「老师傅」&#xff0c;是很多传统产业智能化发展面临的难题。2023年云栖大会上&#xff0c;蚂蚁集团安全科技品牌蚁盾发布“知识交互建模引擎”&#xff0c;将实体产业知识与AI模型有机结合&#xff0c;助力企业最快10分…

uniapp subNvue 写的视频播放

文件下载地址 https://download.csdn.net/download/weixin_47517731/88500016https://download.csdn.net/download/weixin_47517731/88500016 1:在pages.json中配置视频播放页面 {/* 视频详情页面 */"path": "pages/detail-video/detail","style&q…

AD1255/AD1256硬件SPI开发实战与跳坑过程

AD1255/AD1256硬件SPI开发实战与跳坑过程 以上图片我们可以知道在t17阶段&#xff0c;数据是不能被读取的。另外最小是16个τCLKIN&#xff0c;具体是多少这个跟你配置的DATA_rate的设置有关系。 1.6 同步SYNC的时序 要同步SYNC&#xff0c;要么采用管脚SYNC&#xff0c;要么…

云原生环境下JAVA应用容器JVM内存如何配置?—— 筑梦之路

Docker环境下的JVM参数非定值配置 —— 筑梦之路_docker jvm设置-CSDN博客 之前简单地记录过一篇&#xff0c;这里在之前的基础上更加细化一下。 场景说明 使用Java开发且设置的JVM堆空间过小时&#xff0c;程序会出现系统内存不足OOM&#xff08;Out of Memory&#xff09;的…

词典查询工具django-mdict

什么是 django-mdict &#xff1f; django-mdict 不是词典软件&#xff0c;是词典查询的脚本工具&#xff0c;主要目的是解决词典数量多&#xff0c;手机容量不足的问题&#xff0c;是对其他词典软件局域网在线查询功能的补充&#xff0c;是用 django 实现的 mdict 词典查询工具…

微信小程序上传图片和上传视频的组件失效

微信小程序上传图片和上传视频的组件失效 今天公司的小程序展示图片和视频文字的页面上传图片组件突然失效&#xff0c;之前用的好好的&#xff0c;突然所有使用都都发现用不了&#xff0c;以为是代码出现问题&#xff0c;反复查了很久。换了一个openid居然就可以了&#xff0…

B端设计必看的9个开源组件库,值得收藏!

如果你想开发一款To B Web端产品&#xff0c;如何选择令人眼花缭乱的开源组件库&#xff1f;行业团队常用的B端开源组件库是什么&#xff1f;今天&#xff0c;我们将为您带来入门级开源组件库的介绍。你可以先有一个大致的了解&#xff0c;希望能对你有所帮助。未来&#xff0c…

MySQL 存储引擎

存储引擎&#xff1a;MySQL当中数据用各种不同的技术存储在文件中&#xff0c;每一种技术都使用的是不同的存储机制&#xff0c;索引技巧 锁定水平。以及最终提供的不同功能和能力&#xff0c;这写就是我们说的存储引擎。 功能&#xff1a; 1&#xff0c;mysql将数据存储在文…

接口自动化测试 —— 工具、请求与响应

一、工具&#xff1a; 1.工具介绍 postman &#xff1a;很主流的API测试工具&#xff0c;也是工作里面使用最广泛的研发工具。 JMeter&#xff1a; ApiPost&#xff1a; 2.安装postman&#xff1a; 安装好直接打开&#xff0c;不用注册。 二、通信模式&#xff1a; 1、…

【golang】Reflect反射整理、值修改、反射结构体、应用

Reflect 整理 反射是用程序检查其所拥有的结构&#xff0c;尤其是类型的一种能力&#xff1b;这是元编程的一种形式。反射可以在运行时检查类型和变量&#xff0c;例如&#xff1a;它的大小、它的方法以及它能“动态地”调用这些方法。这对于没有源代码的包尤其有用。这是一个强…

vite搭建vue3项目

npm init vite-app 项目名称 如果没安装vite就按照提示安装一下 运行 Done. Now run: cd smartWaterSystemnpm install (or yarn)npm run dev (or yarn dev)运行成功页面

Pytest-Allure及Allure命令使用

一、Allure介绍 Allure是Pytest用于生成测试报告的框架&#xff0c;提供丰富的测试报告功能&#xff1b; 二、Allure安装 Allure安装分为2块&#xff0c;分别是pytest-Allure库安装&#xff0c;本地生成报告并导出的命令行allure安装&#xff1b; 1、pytest-Allure库安装 …

适合女生的副业有哪些?整理了六个靠谱副业,女生必看

在这个互联网时代下&#xff0c;女生对于经济独立变得越来越看重。她们与男生一样&#xff0c;对于工作认真努力、追求进步&#xff0c;并且对于副业有着强烈的渴望和热爱。事实上&#xff0c;她们在副业领域的表现要远远超过很多男生&#xff0c;这一点不可否认。 女生在副业方…

客服管理者如何调动客服人员的积极性?

客户是企业的财富&#xff0c;良好的客户服务体验可以有效地促进企业的销售和声誉&#xff0c;因此&#xff0c;客服工作显得尤为重要。而客服人员的积极性直接影响了整个客服部门的质量和效率。如何调动客服人员的积极性&#xff0c;成为了每个客服管理者都需要面对的难题。本…

管理如何实现制度流程化 流程表单化 表单信息化 信息标准化?

业务化、流程化、信息化、数字化、自动化、智能化&#xff0c;是企业业务管理发展路径的六个必经阶段 制度业务化&#xff0c;业务表单化&#xff0c;表单流程化、流程信息化、信息标准化、标准制度华。 制度流程化、流程表单化、表单信息化、信息标准化、标准制度华。 管理…

Ubuntu 20.04设置虚拟内存 (交换内存swap)解决内存不足

数据库服务器程序在运行起来之后&#xff0c;系统内存不足。 在系统监控中发现&#xff0c;当数据库服务程序启动后&#xff0c;占用了大量内存空间&#xff0c;导致系统的剩余的内存往往只有几十MB。 在ubuntu系统中&#xff0c;swap空间就是虚拟内存&#xff0c;所以考虑在磁…

实践笔记-docker-compose安装

docker-compose 1.在线安装2.离线安装 1.在线安装 # github下载&#xff08;太慢了或者直接下载不了&#xff09; curl -L https://github.com/docker/compose/releases/download/1.25.4/docker-compose-uname -s-uname -m -o /usr/local/bin/docker-compose # daocloud下载 c…

SSM校园设备管信息管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

选题理由 随着计算机网络及多媒体技术的广泛应用&#xff0c;互联网已成为高校办学的基础设施和必备条件&#xff0c;基于互联网的高校信息管理越来越综合化&#xff0c;越来越多的教学管理、行政管理工作将架构在互联网上&#xff0c;互联网正在变为学校实施教学、科研和管理…

Docker dnmp 多版本php安装 php8.2

Laravel9 开发需要用到php8.1以上的版本&#xff0c;而dnmp只支持到php8.0。安装php8.2的步骤如下&#xff1a; 1. 从/services/php80目录复制一份出来&#xff0c;重命名为php82&#xff0c;extensions目录只保留 install.sh 和 install-php-extensions 这两个文件 2. 修改.en…