Linux socket编程

目录

  • 基础概念
    • 端口和端口号
    • Socket(套接字)
    • UDP和TCP的概念
  • Socket编程实战
    • socket的类型
    • struct sockaddr
    • struct sockaddr_in
    • 网络字节序
    • socket API
    • TCP网络编程流程分析
    • UDP Demo样例
  • other
    • 概念补充
    • setsockopt函数
    • 心跳机制
    • 面向字节流与面向报文的理解
  • 参考

基础概念

端口和端口号

我们常说的端口有两种:物理端口和逻辑端口。物理端口指的是用于连接物理设备之间的接口,如集线器、交换机、路由器上用于连接其他网络设备的接口。逻辑端口指的是指逻辑意义上用于区分服务的端口,比如用于浏览网页服务的80端口,用于FTP服务的21端口等。网络编程场景下所指的端口是逻辑端口,本文所说端口如果没有特指都是指的的逻辑端口。

物理端口比较好理解,那么逻辑端口是怎么一回事呢?端口在操作系统中用于表示一种特定的服务,每个端口都有一个独一无二的端口号,不会出现重复端口号的情况。既然要提供服务,那么每个端口号之下一定是要绑定对应的进程的。但需要注意的是,一个端口下只能绑定一个进程,而一个进程可以同时绑定多个端口号,这就像一个银行的窗口柜台下只能有一个工作人员,而一个工作人员却可以同时在多个窗口下工作。可以参考下图理解端口号的概念:
端口号

其实,网络通信的本质就是进程间通信。但与本地通信不同的是,网络通信是两台计算机上的两个进程跨网络之间的通信。进程在本地通信时需要根据进程的pid进行辨别,那么进程在网络之间通信时,同样也需要根据端口号进行辨别。

之所以要有端口号这个东西,这是因为在网络环境中的计算机可以通过IP地址和MAC地址来被找到,但IP+MAC地址并不能表示一台计算机上的特定进程,如果想要访问到特定的进程就一定需要再对其细分,于是端口就成了一个很好的解决方案。

那么为什么不直接使用进程pid,却要额外引入一个端口号,增加通信的复杂程度呢?其实这个问题可以从多个角度来回答,下面先简单列举一些:

  1. 对于服务器而言,进程可以在任何时候启动、关闭或重新启动,因此进程的pid随时可能会发生变化。而如果用端口号,则每次让进程启动时绑定到端口号就可以做到固定服务绑定固定端口号的效果了。
  2. 进程pid 是一个操作系统级别的标识符,只在单机环境中有效。当涉及到不同操作系统之间的通信时,进程pid 就不再适用了。而端口号则可以很好的解决这种不同环境下进程表示的差异性问题。
  3. 端口号与进程pid之间的解耦,可以使得服务与进程之间独立开来,使得服务的配置操作更加灵活。

端口号是一个2字节的整数,范围是1至65535,0不使用。其中 [1, 1023] 是知名端口号,这些端口号一般固定分配给一些服务,是不会改变的,一般也不允许修改(Linux下需要root权限才能操作)。例如80端口表示HTTP服务、21端口表示FTP 文件传输服务、53端口表示DNS 域名解析服务、443端口表示HTTPS 加密的超文本传输服务等。而1024到65535表示动态端口号,是普通用户可以申请的使用的端口号。 如果一个可执行程序程序没有设置端口号,那么操作系统会在动态端口号这个范围内随机挑选一个没有被占用端口号可执行程序使用。

Socket(套接字)

socket 的原意是“插座”,我们把插头插到插座上就能从电网获得电力供应,相应的,应用程序通过socket就可以连接到因特网,进而就可以通过互联网与远程计算机进行数据传输了。也就是说socket 就是用来连接到因特网的工具,通过socket,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

严格来说socket并不属于任何一个网络协议层,socket是一种介于应用层和传输层的一个抽象层,起到一个承上启下的作用。它为应用层提供了访问传输层网络协议的接口,在Linux中,socket API 封装了操作系统内核中的 TCP/IP 协议栈所提供的服务,使得开发者能够通过这些接口收发数据。

一般来说,IP和端口号是socket的基础属性,大多数场景下的socket操作都需要用到这两个值。

在这里插入图片描述
从Linux的角度来看,socket套接字是一种抽象的文件格式,和管道文件一样,它是一种伪文件,存在于内核的缓冲区中,大小不变,一直是0。
其中,套接字是全双工的通信方式,分别有读写缓冲区。全双工的概念如下:

单工通信只支持信号在一个方向上传输(正向或反向),任何时候不能改变信号的传输方向。
半双工通信允许信号在两个方向上传输,但某一时刻只允许信号在一个信道上单向传输。
全双工通信允许数据同时在两个方向上传输,即有两个信道,因此允许同时进行双向传输。

UDP和TCP的概念

TCP是一种传输层协议,是一种面向字节流的有连接可靠传输。UDP也是一种传输层协议,但它是一种面向数据报的无连接不可靠传输。

TCP的特点是:面向字节流、有连接、可靠传输
UDP的特点是:面向数据报、无连接、不可靠传输

之所以TCP是可靠的传输,是因为TCP比UDP多了很多安全检查和差错处理,而UDP只管发送数据报,所以UDP的丢包率理论上要比TCP的高。但并不是说UDP就是一无是处的,TCP在得到安全的同时,也导致了效率的下降。所以相比之下,TCP的可靠性好,丢包率低,但UDP的效率高。所以TCP主要被用于可靠性较高的一些场景,例如HTTP、HTTPS、FTP等;UDP主要被用于对可靠性要求不是那么高的场景,例如各种直播和语音通话等,偶尔丢一两帧数据并不影响整体的体验。

需要注意的是UDP虽然理论上丢包率要略高于TCP,但这并不是就说明UDP一定丢包率很高,一般来说UDP的丢包率只会略高于TCP,但并不会高很多。

Socket编程实战

socket的类型

套接字的主要类型有三种:

  1. 数据报套接字(SOCK_DGRAM):数据报格式套接字(Datagram Sockets)也叫无连接的套接字,在代码中使用 SOCK_DGRAM 表示。只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。使用的是UDP协议。

  2. 流式套接字(SOCK_STREAM):流式套接字(Stream Sockets)也叫面向连接的套接字,在代码中使用 SOCK_STREAM 表示。SOCK_STREAM 是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。

  3. 原始套接字(SOCK_RAW):一种不同于SOCK_STREAM、SOCK_DGRAM的套接字,它实现于系统核心。原始套接字允许直接发送和接收 IP 数据包,而无需任何特定于协议的传输层格式,而且可以读写内核没有处理过的 IP 数据包。

本文我们只关注数据包套接字和流式套接字。

struct sockaddr

在正式认识socke apit之前,需要先搞清struct sockaddrstruct sockaddr_in这两个结构体。

网络编程的时候,有各种各样不同的应用场景,理论上而言,我们应该给每种场景都设计一套编程接口,但由于Linux内核是由C语言编写的缘故,并没有多态这种语法,如果要实现所有场景的接口,就需要定义多种功能相似的函数。

所以为了实现接口的统一性,统一便用struct sockaddr来描述一个网络字段,其格式为:类型+地址,而其它的类型则固定首16位字节一定是地址类型,这样就能通过类型+地址的方式解析所有的结构,进而统一接口(只需要在传参时进行对应的类型转换),参考下图理解。
在这里插入图片描述
struct sockaddr的定义如下:

struct sockaddr {
	u_short	sa_family;
	char	sa_data[14];
};

struct sockaddr_in

struct sockaddr_in为IPv4的结构体,struct sockaddr_un结构体主要为同一台机器上的进程间进行高效通信,struct sockaddr_in6则表示IPv6的结构体。本文的主要以struct sockaddr_in的使用为主。

sockaddr_in中有4个成员:sin_family、sin_port、sin_addr、sin_zero,其结构体定义(在netinet/in.h中定义)如下:

struct sockaddr_in {
	short int sin_family;                /* Address family */
	unsigned short int sin_port;      	 /* Port number */
	struct in_addr sin_addr;             /* Internet address */
	unsigned char sin_zero[8];           /* Same size as struct sockaddr */
};
  • sin_family
    sin_family表示要使用的网络协议簇,协议簇的在“linux/socket.h”里有详细定义,常见的有如下几个:
  1. AF_UNIX (本机通信)
  2. AF_INET (TCP/IP & IPv4)
  3. AF_INET6 (TCP/IP & IPv6)

在当前IPv4的场景下,sin_family一般设为AF_INET。

  • sin_port
    sin_port就表示的是要使用的端口号。

  • sin_addr
    sin_addr是一个 struct in_addr 类型的成员,其定义如下:

    struct in_addr {
    	unsigned long s_addr;
    };
    

    也就是说,其本质是一个unsigned long类型的整型数据。其中,可以用inet_addr函数将char*的ip地址转为4字节序列,并转为网络字节序。
    0.0.0.0表示任意IP地址绑定,也就是不限IP的意思,在编程时可以使用INADDR_ANY宏来表示,其定义为:

    #define    INADDR_ANY		((in_addr_t) 0x00000000)
    
  • sin_zero
    sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节,一般用不到。

网络字节序

由于不同的机器之间有大端和小端之间的差异,所以规定:所有到达网络的数据,必须是大端格式的。也就是说,网络字节序就是大端字节序。不知道大端和小段的可以参考:大端和小端

其中在编程时,对于网络字节序的操作有如下一些封装好的函数,用以简化我们的操作:

#include <arpa/inet.h>
	
uint32_t htonl(uint32_t hostlong);	  // 本地字节序转成网络字节序 - 32位整型
uint16_t htons(uint16_t hostshort);	  // 本地字节序转成网络字节序 - 16位整型
uint32_t ntohl(uint32_t netlong);	  // 网络字节序转成本地字节序 - 32位整型
uint16_t ntohs(uint16_t netshort);	  // 网络字节序转成本地字节序 - 16位整型

其中,h表示host,n表示net,l表示长的32位整型,s表示短的16位整型。注意,避免对同一个数多次调用上述函数,因为这只些只是单纯的转换,并不会做大端小端的检查,如果调用两次可能就又转回去了。

除此之外还有inet_addrinet_aton函数用来将字符串类型的点分十进制IP地址(例如 “192.168.1.1”)转换成可以用于网络传输的32位整型数字,函数声明如下:

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

int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);

socket API

下面来介绍Linux下常用的套接字函数

  • socket

    #include <sys/types.h>        
    #include <sys/socket.h>
    
    int socket(int domain, int type, int protocol);
    

    用于创建一个套接字文件,并返回其文件描述符。

    domain参数:表示所选的协议簇,如下是几种常用的宏定义:

    定义含义
    PF_UNIX / PF_LOCAL本地通讯
    AF_INET / PF_INETIPv4 Internet协议
    PF_INET6IPv6 Internet协议

    一般IPv4的情况下使用AF_INET 就可以了
    type参数:表示套接字通信的类型,常见的几种type参数如下:

    定义含义
    SOCK_DGRAM数据报套接字,无连接的套接字,与UDP协议对应
    SOCK_STREAM流式套接字,有连接的套接字,与TCP协议对应

    protocol参数:用于指定协议的特定类型。如果为零,则表示自动选择一个。一般来说每种协议都只有一种类型,所以一般设为0即可。如下是一些参数示例:

    IPPROTO_TCP - tcp协议
    IPPROTO_UDP - udp协议


  • bind

    #include <sys/types.h>        
    #include <sys/socket.h>
    
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    用于将addr绑定到指定套接字,主要起到i端口号绑定的作用,一般用于服务端套接字绑定的工作。如果没有调用这个bind函数,那么在socket通信时,会绑定到一个本机的随机空闲端口号。
    一般来说,bind函数只在服务端用,客户端不进行bind,这是因为服务器的服务需要有一个明确的固定端口号,这样才能够很好的被访问到,而客户端一般有各种各样的进程,手动指定端口号很容易就造成端口号冲突的问题,所以就不调用bind函数,让系统自动为我们选择一个空闲的端口号。

    sockfd参数:需要绑定的套接字文件描述符
    addr参数:sockaddr相关信息
    addrlen参数:addr参数的长度


  • sendto 和 send
    sendto:sendto函数一般用于UDP协议的数据传输工作。

    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
    		 	   const struct sockaddr *dest_addr, socklen_t addrlen);
    

    sockfd:表示要通过哪个socket发送
    buf:表示要发送的内容
    len:表示要发送内容的大小
    flags:标志位,一般设为0即可
    dest_addr:表示dest_addr对应字段的字节数,要是为了应对多种 struct sockaddr_* 的情况。这个参数不能省略或者直接填0,必须是dest_addr的字节数。

    send:send函数主要用于TCP协议的数据传输工作。

    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    

    send与sendto的参数基本类似,send函数只是少了dest_addr和addrlen函数,这是因为TCP是面向连接的,一旦建立连接之后,就可以直接收发数据了,所以就不用再指定sockaddr 了。
    其中,当flags参数设为0时,send函数就等同于write函数:

    With a zero flags argument, send() is equivalent to write(2).

    注意,send函数和write函数在socket写入时,如果出错则会触发SIGPIPE信号。


  • recvfrom 和 recv
    recvfrom :recvfrom 函数一般用于UDP协议的数据传输工作。

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                     struct sockaddr *src_addr, socklen_t *addrlen);
    

    其参数含义与上面的sendto一样,只是这里的src_addr表示的是要从哪里接受数据的参数,而不再是目标地的参数了。

    recv:recv函数主要用于TCP协议的数据传输工作。

    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    

    recv的参数含义与send的类似,而且当flags为0时,recv函数就等同于read函数了。


  • connect、listen、accept

这三个函数都是TCP协议特有的,所以就放在一起说了。
connect:通常由客户端调用,与服务器建立连接

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

该函数的功能为主动建立连接(通过三次握手),而这个连接的过程是由内核完成的,不是这个函数完成的,这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成 TCP 三次握手连接。通常情况下,客户端的 connect() 函数默认会一直阻塞,直到三次握手成功或超时失败才返回(正常的情况,这个过程很快完成)。


listen:设为监听状态,等待客户端的连接请求

int listen(int sockfd, int backlog);

listen函数的主要作用就是将套接字( sockfd )变成被动的连接监听套接字(被动的等待客户端的连接),至于参数 backlog 的作用是设置内核中连接对应的消息队列的长度,TCP 三次握手也不是由这个函数完成,listen()的作用仅仅告诉内核一些信息。

需要注意的是,listen函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen函数就结束。这样的话,当有一个客户端主动连接(connect),Linux 内核就自动完成TCP 三次握手,将建立好的链接自动存储到队列中,如此重复。所以,只要 TCP 服务器调用了listen函数,客户端就可以通过connect函数和服务器建立连接,而这个连接的过程是由内核完成。


accept:等待连接并为其创建一个新的套接字

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数功能是,从处于 established 状态的连接队列头部取出一个已经完成的连接,并为其创建一个新的套接字,这样我们就可以拿着这个套接字进行操作了。如果这个队列没有已经完成的连接,accept函数就会阻塞等待,直到取出队列中已完成的用户连接为止。

TCP网络编程流程分析

本文暂不提供tcp协议的demo样例,感兴趣的话可以参考:Linux下的socket演示程序,如下是tcp协议的 client-server 常规流程:

TCP流程图

UDP Demo样例

如下是基于udp协议的 client-server 的demo样例,为了便于查看,略去了相关安全检查的部分。流程图如下所示:
UDP网络编程流程图

  • server端
int main()
{
    uint16_t port = 8008;
    const int buf_len = 1024;

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

    // 设置套接字属性
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);

    // bind操作
    bind(sock, (struct sockaddr*)&addr, sizeof(addr));

    // 服务器收发操作
    char buf[buf_len];
    while (true)
    {
        struct sockaddr_in rcv_addr;
        socklen_t rcv_len = sizeof(rcv_addr);
        // 接收(等待)操作
        int rcv_size = recvfrom(sock, buf, buf_len - 1, 0, (struct sockaddr*)&rcv_addr, &rcv_len);
        buf[rcv_size] = '\0';
        // 发送(回复)操作
        const char *reply = "this is reply";
        sendto(sock, reply, strlen(reply), 0, (struct sockaddr*)&rcv_addr, rcv_len);
    }
}

  • client端
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

// 参数设置
const char* dst_ip = "127.0.0.1";
const int dst_port = 8008;
const int buf_len = 1024;

// 关键函数:socket sendto recvfrom
int main()
{
    // 创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    // 设置套接字属性
    struct sockaddr_in send_sockaddr;
    memset(&send_sockaddr, 0, sizeof(send_sockaddr));
    send_sockaddr.sin_family = AF_INET;
    send_sockaddr.sin_addr.s_addr = inet_addr(dst_ip);
    send_sockaddr.sin_port = htons(dst_port);

    // 收发操作
    while (true)
    {
        // 发送数据
        const char* msg = "a test msg.";
        sendto(sock, msg, strlen(msg), 0, (struct sockaddr*)&send_sockaddr, sizeof(send_sockaddr));

        // 接收数据
        char buf[buf_len];
        struct sockaddr_in recv_sockaddr;
        socklen_t len = sizeof(recv_sockaddr);
        recvfrom(sock, buf, buf_len - 1, 0, (struct sockaddr*)&recv_sockaddr, &len);
    }

    return 0;
}

other

概念补充

  1. 127.0.0.1是Linux的本地环回地址IP,相当于把数据自己转给自己,一般用于测试。
  2. 测试socket程序时,需要检查防火墙对应的端口号是否是开发状态,否则消息可能会被防火墙拦截。
  3. 0.0.0.0表示任意IP地址绑定,也就是不限IP的意思。
  4. socket是全双工的,可以同时读写,不会出现多线程的读写的问题。所以socket的多线程的使用是安全的。

setsockopt函数

setsockopt函数,用于设置套接字的属性,其函数定义如下:

#include <sys/types.h>
#include <sys/socket.h>

int setsockopt(int sockFd, int level, int optname, const void *optval, socklen_t optlen);

sockfd参数:将要被设置或者获取选项的套接字。

level参数:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。一般设成SOL_SOCKET以存取socket层。

> SOL_SOCKET				通用套接字选项.
> IPPROTO_IP				IP选项.IPv4套接口
> IPPROTO_TCP				TCP选项.
> IPPROTO_IPV6				IPv6套接口

optname参数: 设置的选项,有如下几种选项:

在这里插入图片描述
optval参数:对于setsockopt,指针,指向存放选项待设置的新值的缓冲区。获得或者是设置套接字选项.根据选项名称的数据类型进行转换。

optlen参数:optval缓冲区长度。

心跳机制

当服务器上的某个服务配置好了之后,或者建立连接止呕,那么该如何知道这个的服务未来在任何一个时刻,都是健康的呢?我们可以定期(例如30s)向对应的服务发送小请求,类似于ping服务,如果得到了回复,就说明我们的服务是正常的。这个机制,我们就称之为心跳机制,

面向字节流与面向报文的理解

可以将tcp和upd看成不同公司的出租车,tcp这个公司的出租车司机(tcp头)在拉客的时候,一看来了一个乘客,可是自己车上还有三个位置,司机就会继续等,直到自己车上去同一个目的地的乘客坐满了才开车,因为tcp公司认为遵循Nagle算法可以提高效率,节省能源,从socket学校走出来三个团体的学生,每一个团体只有一个人,可能只要消耗一个tcp出租车。如果从socket学校出来了一个团队的学生,但是这个团队有6个学生,一号tcp出租车看看自己车上还有两个个空位置,就让这个团队的两个学生上车了,剩下的学生只能做下一辆车了。这也就造成了一个问题,一号出租车开到了城市中的一个小餐馆,餐馆老板并不知道他们四个学生是不是一个团队的,这也就是粘包粘包的问题。

udp公司的出租车与tcp公司的出租车不一样,只要有一个团队的人走过来,不管是一个人还是7个人,udp出租车都可以一次性给你送走(当然下层的ip层还是可能会分包的,这些我们不用管),不需要等待。到餐馆后,餐馆老板一看是udp公司的出租车,就知道这是一个团队的(也就是不会出现粘包粘包的问题)。

  • 摘自:面向字节流与面向报文的通俗解释

参考

  1. socket编程入门 | C语言中文网
  2. 【计算机网络】端口详解
  3. socket 函数参数详解
  4. 网络编程:UDP网路编程
  5. TCP网络编程中connect()、listen()和accept()
  6. socket 网络编程——端口复用技术

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

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

相关文章

OBS学习

OBS简介 OBS是Open Broadcaster Software的简称&#xff0c;是一款开源&#xff0c;用于视频录制以及直播串流的软件&#xff0c;它支持Windows、Mac以及Linux操作系统。 OBS使用场景 使用OBS软件&#xff0c;可以借助单反、摄像机、无人机等摄影设备在电脑上直播。OBS可以把…

SQL Server:调用的目标发生了异常。(mscorlib)

我之前安装的SQL Server是2014版本&#xff0c;SSMS运行也很流畅&#xff0c;有一次有个同事让我链接云服务器SQL地址&#xff0c;直接报上图的错误&#xff0c;把我弄的一愣一愣的。 后面才发现&#xff0c;这是版本太低导致的&#xff0c;但是你如果使用Navicat是没有问题的…

dolphinScheduler 任务调度

#Using docker-compose to Start Server #下载&#xff1a;https://dlcdn.apache.org/dolphinscheduler/3.1.9/apache-dolphinscheduler-3.1.9-src.tar.gz $ DOLPHINSCHEDULER_VERSION3.1.9 $ tar -zxf apache-dolphinscheduler-"${DOLPHINSCHEDULER_VERSION}"-src.t…

“为您的家电穿上防震铠甲:优质电器缓冲器

在地震频发地区或日常生活中&#xff0c;确保家电的安全和稳定至关重要。为了防止地震、意外碰撞或其他外力对家电造成损害&#xff0c;采用优质的电器缓冲器就像是为家电穿上了一层坚固的“防震铠甲”。这不仅能够有效减少因震动导致的损坏风险&#xff0c;还能显著延长家电的…

Qlib上手学习记录

Qlib 是一个面向人工智能的量化投资平台&#xff0c;其目标是通过在量化投资中运用AI技术来发掘潜力、赋能研究并创造价值&#xff0c;从探索投资策略到实现产品化部署。该平台支持多种机器学习建模范式&#xff0c;包括有监督学习、市场动态建模以及强化学习等。 真的是走了很…

web前端设计1

熟悉流行框架、熟练掌握CSS的写法&#xff0c;以及JAVASCRIPT库的应用&#xff0c;最主要的是能按要求改出相应的界面&#xff0c;因为我们基本没有自己手写代码的&#xff0c;所以得会拿别的界面改成想要的界面。 前端比较吃能力的就是CSS的写法&#xff0c;如何用已写好的框…

2020年国赛高教杯数学建模E题校园供水系统智能管理解题全过程文档及程序

2020年国赛高教杯数学建模 E题 校园供水系统智能管理 原题再现 校园供水系统是校园公用设施的重要组成部分&#xff0c;学校为了保障校园供水系统的正常运行需要投入大量的人力、物力和财力。随着科学技术的发展&#xff0c;校园内已经普遍使用了智能水表&#xff0c;从而可以…

JAVA |日常开发中连接Sqlite数据库详解

JAVA &#xff5c;日常开发中连接Sqlite数据库详解 前言一、SQLite 数据库概述1.1 定义与特点1.2 适用场景 二、Java 连接 SQLite 数据库的准备工作2.1 添加 SQLite JDBC 驱动依赖2.2 了解 JDBC 基础概念 三、建立数据库连接3.1 代码示例3.2 步骤解析 四、执行 SQL 语句4.1 创建…

图像超分辨率技术新进展:混合注意力聚合变换器HAAT

目录 1. 引言&#xff1a; 2. 混合注意力聚合变换器&#xff08;HAAT&#xff09;&#xff1a; 2.1 Swin-Dense-Residual-Connected Block&#xff08;SDRCB&#xff09;&#xff1a; 2.2 Hybrid Grid Attention Block&#xff08;HGAB&#xff09;&#xff1a; 3. 实验结…

【Appium】AttributeError: ‘NoneType‘ object has no attribute ‘to_capabilities‘

目录 1、报错内容 2、解决方案 &#xff08;1&#xff09;检查 &#xff08;2&#xff09;报错原因 &#xff08;3&#xff09;解决步骤 3、解决结果 1、报错内容 在PyCharm编写好脚本后&#xff0c;模拟器和appium也是连接成功的&#xff0c;但是运行脚本时报错&…

1.1 Beginner Level学习之“创建 ROS msg 和 srv”(第十节)

学习大纲&#xff1a; 1. msg 和 srv msg 文件是描述 ROS 消息字段的简单文本文件。它们用于为不同语言生成消息的源代码。srv 文件则描述了一个服务&#xff0c;包括两部分&#xff1a;请求和响应。Srv 文件用于生成服务的源代码。msg 文件存储在包的 msg 目录中。srv 文件存…

Android仿美团左右联动购物列表

Android仿美团左右联动购物列表 左右联动购物列表&#xff0c;不难。 一、思路&#xff1a; 两个RecycleView 二、效果图&#xff1a; 三、关键代码&#xff1a; public class MainActivity extends AppCompatActivity {private RecyclerView rl_left;private RecyclerVie…

微信小程序 运行出错 弹出提示框(获取token失败,请重试 或者 请求失败)

原因是&#xff1a;需要登陆微信公众平台在开发管理 中设置 相应的 服务器域名 中的 request合法域名 // index.jsPage({data: {products:[],cardLayout: grid, // 默认卡片布局为网格模式isGrid: true, // 默认为网格布局page: 0, // 当前页码size: 10, // 每页大小hasMore…

室联人形机器人:家政服务任务结构化、技术要点、深入应用FPGA的控制系统框架设计(整合版)

目录&#xff1a; 0 引言 1 人形机器人对室内家政服务任务的结构化 1.1人形机器人在室内家政服务中的比较优势 1.1.1 人形机器人拟人性的7个维度 1.1.2 拟人性在室内家政服务工作中的比较优势 1.1.3 潜在的重要用户&#xff1a;宠物爱好者 1.2 居所室内环境的特征与结构…

【YOLO部署Android安卓手机APP】YOLOv11部署到安卓实时目标检测识别——以火焰烟雾目标检测识别举例(可自定义更换其他目标)

前言:本项目基于YOLOv11部署到手机APP实现对火焰烟雾的检测识别,当然,以此你可以按照本项目开发步骤扩展更换为其他目标进行检测,例如更换为车牌、手势、人脸面部活动、人脸表情、火焰烟雾、行人、口罩、行为、水果、植物、农作物等等部署手机APP进行检测。本文为详细设计/…

python 执行celery

1、redis安装并启动redis安装与使用-CSDN博客 2、安装 celery 、eventlet 3. Task handler raised error: ValueError(not enough values to unpack (expected 3, got 0)) - Redskaber - 博客园 pip install celery pip install eventlet 3、python 版本3.10 #创建异步任…

未完成_RFdiffusion应用案例_从头设计pMHC的结合剂

目录 1. 论文导读1&#xff09;摘要2&#xff09;设计流程3&#xff09;设计流程的验证 2. 实战 1. 论文导读 Liu, Bingxu, et al. “Design of high specificity binders for peptide-MHC-I complexes.” bioRxiv (2024): 2024-11. 1&#xff09;摘要 MHC-I 将胞内抗原肽递呈…

【css】基础(一)

本专栏内容为&#xff1a;前端专栏 记录学习前端&#xff0c;分为若干个子专栏&#xff0c;html js css vue等 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;css专栏 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &a…

【Python高级语法与正则表达式】

目录 1.正则表达式 1.1概述&#xff1a; 1.2re模块介绍 1.3re模块相关方法&#xff08;常用&#xff09;&#xff1a; 1.4案例 1.5正则表达式详解 1.5.1查什么 1.5.2查多少 1.5.3 从哪查 1.6重要概念 1.6.1子表达式 1.7 正则表达式的其他方法 1.7.1选择匹配符 1.7.2…

Vue03

目录 一、今日目标 1.生命周期 2.综合案例-小黑记账清单 3.工程化开发入门 4.综合案例-小兔仙首页 二、Vue生命周期 三、Vue生命周期钩子 四、生命周期钩子小案例 1.在created中发送数据 六、工程化开发模式和脚手架 1.开发Vue的两种方式 2.Vue CLI脚手架 基本介绍…