TCP/UDP通信协议

news2024/11/30 10:53:54

TCP通讯时序

下图是一次TCP通讯的时序图。TCP连接建立断开。包含大家熟知的三次握手和四次挥手。

在这个例子中,首先客户端主动发起连接(connet)、发送请求,然后服务器端响应请求,然后客户端主动关闭连接。两条竖线表示通讯的两端,从上到下表示时间的先后顺序,注意,数据从一端传到网络的另一端也需要时间,所以图中的箭头都是斜的。双方发送的段按时间顺序编号为1-10,各段中的主要信息在箭头上标出,例如段2的箭头上标着SYN, 8000(0), ACK1001, ,表示该段中的SYN位置1,32位序号是8000,该段不携带有效载荷(数据字节数为0),ACK位置1,32位确认序号是1001,带有一个mss(Maximum Segment Size,最大报文长度)选项值为1024(1K)。

下面的图是TCP头部的规范定义,它定义了TCP协议如何读取和解析数据

TCP协议解析:

TCP首部承载这TCP协议需要的各项信息,下面我们来分析一下:

  • TCP端口号(源IP,源端口号)+ (目地IP,目的端口号)
  • TCP的序号和确认号:32位序号 seq:Sequence number 缩写seq ,TCP通信过程中某一个传输方向上的字节流的每个字节的序号,通过这个来确认发送的数据有序,比如现在序列号为1000,发送了1000,下一个序列号就是2000(1000+1000字节)。32位确认号 ack:Acknowledge number 缩写ack,TCP对上一次seq序号做出的确认号,用来响应TCP报文段,给收到的TCP报文段的序号seq加1。
  • TCP的标志位用的最广泛的标志是 SYN,ACK 和 FIN,用于建立连接,确认成功的段传输,最后终止连接。
  1. SYN:简写为S,同步标志位,用于建立会话连接,同步序列号;
  2. ACK: 简写为.,确认标志位,对已接收的数据包进行确认;
  3. FIN: 简写为F,完成标志位,表示我已经没有数据要发送了,即将关闭连接;
  4. PSH:简写为P,推送标志位,表示该数据包被对方接收后应立即交给上层应用,而不在缓冲区排队;
  5. RST:简写为R,重置标志位,用于连接复位、拒绝错误和非法的数据包;
  6. URG:简写为U,紧急标志位,表示数据包的紧急指针域有效,用来保证连接不被阻断,并督促中间设备尽快处理;

建立TCP连接(三次握手)的过程

1.客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的段1。

        客户端发出段1,SYN位表示连接请求。序号是1000(也是记录数据的长度),这个序号在网络通讯中用作临时的地址,每发一个数据字节,这个序号要加1,这样在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况,另外,规定SYN位和FIN位也要占一个序号,这次虽然没发数据,但是由于发了SYN位,因此下次再发送应该用序号1001。mss表示最大段尺寸,如果一个段太大,封装成帧后超过了链路层的最大帧长度,就必须在IP层分片,为了避免这种情况,客户端声明自己的最大段尺寸,建议服务器端发来的段不要超过这个长度。

2.服务器端回应客户端,是三次握手中的第2个报文段,同时带ACK标志和SYN标志。它表示对刚才客户端SYN的回应;同时又发送SYN(确认连接请求)给客户端,询问客户端是否准备好进行数据通讯。

        服务器发出段2,也带有SYN位,同时置ACK位表示确认,确认序号是1001,表示“我接收到序号1000及其以前所有的段,请你下次发送序号为1001的段”,也就是应答了客户端的连接请求,同时也给客户端发出一个连接请求,同时声明最大尺寸为1024。

3.客户必须再次回应服务器端一个ACK报文,这是报文段3。

        客户端发出段3,对服务器的连接请求进行应答,确认序号是8001。在这个过程中,客户端和服务器分别给对方发了连接请求,也应答了对方的连接请求,其中服务器的请求和应答在一个段中发出,因此一共有三个段用于建立连接,称为“三方握手(three-way-handshake)”。在建立连接的同时,双方协商了一些信息,例如双方发送序号的初始值、最大段尺寸等。

        在TCP通讯中,如果一方收到另一方发来的段,读出其中的目的端口号,发现本机并没有任何进程使用这个端口,就会应答一个包含RST位的段给另一方。例如,服务器并没有任何进程使用8080端口,我们却用telnet客户端去连接它,服务器收到客户端发来的SYN段就会应答一个RST段,客户端的telnet程序收到RST段后报告错误Connection refused:

$ telnet 192.168.0.200 8080
Trying 192.168.0.200...
telnet: Unable to connect to remote host: Connection refused

数据传输的过程

1.客户端发出段4,包含从序号1001开始的20个字节数据。

2.服务器发出段5,确认序号为1021,对序号为1001-1020的数据表示确认收到,同时请求发送序号1021开始的数据,服务器在应答的同时也向客户端发送从序号8001开始的10个字节数据,这称为piggyback。

3.客户端发出段6,对服务器发来的序号为8001-8010的数据表示确认收到,请求发送序号8011开始的数据。

在数据传输过程中,ACK和确认序号是非常重要的,应用程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区中,发出数据包给对方之后,只有收到对方应答的ACK段才知道该数据包确实发到了对方,可以从发送缓冲区中释放掉了,如果因为网络故障丢失了数据包或者丢失了对方发回的ACK段,经过等待超时后TCP协议自动将发送缓冲区中的数据包重发。

关闭连接(四次挥手)的过程

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

1.客户端发出段7,FIN位表示关闭连接的请求。

2.服务器发出段8,应答客户端的关闭连接请求。

3.服务器发出段9,其中也包含FIN位,向客户端发送关闭连接请求。

4.客户端发出段10,应答服务器的关闭连接请求。

建立连接的过程是三方握手,而关闭连接通常需要4个段,服务器的应答和关闭连接请求通常不合并在一个段中,因为有连接半关闭的情况,这种情况下客户端关闭连接之后就不能再发送数据给服务器了,但是服务器还可以发送数据给客户端,直到服务器也关闭连接为止。

C/S模型-TCP

下图是基于TCP协议的客户端/服务器程序的一般流程:

服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。

数据传输的过程:

建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。

如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。

在学习socket API时要注意应用程序和TCP协议层是如何交互的: 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段。

netstat -an |grep tcp   查看所有tcp连接包括LISTEN状态

思考:为什么TIME_WAIT状态需要经过2MSL才能进入CLOSE状态?

答案:MSL指的是报文在网络中最大生存时间。在客户端发送到服务端的FIN确认包ACK后,这个ACK包有可能到达不了,服务器端如果接收不到ACK包就会重新发送FIN包。所以客户端发送ACK后需要留出2MSL时间(ACK到达服务器器+服务器发送FIN重传包,一来一回)等待确认服务器端确实收到了ACK包。也就是说客户端如果等待2MSL时间也没收到服务器端重传的FIN包,则就可以确认服务器已经收到客户端发送的ACK包。

使用read write,实现一问一答服务器

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <unistd.h>
#include <stdlib.h>


int main(int argc, char *argv[])
{
    int ret, len, value = 1;
    int serverfd, newconnetfd;
    struct sockaddr_in serveraddr;
    struct sockaddr_in clientaddr;

    char read_buff[128] = {0};

    if(argc != 3)
    {
        printf("输入格式有误,输入格式:./可执行文件  IP  端口\n");
        exit(0);
    }

    //计算绑定服务器的结构体大小
    len = sizeof(struct sockaddr);

    //建立套接字
    serverfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == serverfd)
    {
        printf("socket fail\n");
        exit(0);
    }

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons (atoi(argv[2])) ; 
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
   
    //允许端口重用
    setsockopt(serverfd, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value));
    //允许地址重用
    setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
      
    //服务器绑定自己的IP地址与端口
    ret = bind(serverfd, (struct sockaddr *)&serveraddr, len);
    if(-1 == ret)
    {
        printf("bind fail\n");
        close(serverfd);
        exit(0);        
    }
    //设置监听客户端最大的数量
    ret = listen(serverfd, 5);
    if(-1 == serverfd)
    {
        printf("listen fail\n");
        close(serverfd);
        exit(0);
    }

    //等待连接,如果没有客户端连接,则阻塞等待 连接成功,客户的信息放置在clientaddr
    newconnetfd = accept(serverfd, (struct sockaddr *)&clientaddr, &len);
    if(-1 == newconnetfd)
    {
        printf("accept fail\n");
        close(serverfd);
        exit(0);
    }
    //打印客户端IP与地址
    printf("客户端IP:%s, 端口号:%d\n",  inet_ntoa(clientaddr.sin_addr),  ntohs(clientaddr.sin_port));

    while(1)
    {
        ret = read(newconnetfd, read_buff, sizeof(read_buff));
        if(ret == 0) //客户端退出
            break;

        printf("客户端数据:%s\n", read_buff);

        //发数据给客户端。
        write(newconnetfd, "你说的对", strlen("你说的对"));

        bzero(read_buff, sizeof(read_buff));
    }

    close(newconnetfd);
    close(serverfd);

    return 0;
}

客户端

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

int clientfd;

void *rcvthread(void *arg)
{
    char read_buff[128] = {0};
    while (1)
    {
        recv(clientfd, read_buff, sizeof(read_buff), 0);
        printf("接受的数据:%s\n", read_buff);

        if (strcmp(read_buff, "quit") == 0)
            break;

        bzero(read_buff, sizeof(read_buff));
    }
}

int main(int argc, char *argv[])
{
    int len, ret;
    
    struct sockaddr_in serveraddr;
    char write_buff[128] = {0};

    if (argc != 3)
    {
        printf("输入格式有误,输入格式:./可执行文件  服务器IP  服务器端口\n");
        exit(0);
    }

    // 计算绑定服务器的结构体大小
    len = sizeof(struct sockaddr);
    // 建立套接字
    clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == clientfd)
    {
        printf("socket fail\n");
        exit(0);
    }

    // 如果不绑定,系统在connet时将自己的IP与端口(随机)进行封装发送

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    // 连接服务器
    ret = connect(clientfd, (struct sockaddr *)&serveraddr, len);
    if (-1 == ret)
    {
        printf("connect fail\n");
        close(clientfd);
        exit(0);
    }

    pthread_t thread;
    ret = pthread_create(&thread, NULL, rcvthread, NULL);
    if (ret == -1)
    {
        printf("pthread_create fail\n");
        return -1;
    }

    while (1)
    {
        printf("请输入向服务器发的数据:\n");
        scanf("%s", write_buff);

        send(clientfd, write_buff, strlen(write_buff), 0);

        if (strcmp(write_buff, "quit") == 0)
            break;

        bzero(write_buff, sizeof(write_buff));
    }


    close(clientfd);

    return 0;
}

UDP协议

1、概念

        传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议。TCP协议在网络通信中占主导地位,绝大多数的网络通信借助TCP协议完成数据传输。但UDP也是网络通信中不可或缺的重要通信手段。

        相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和正确率便得不到保证。因此,我们称UDP为“无连接的不可靠报文传递”。

那么与我们熟知的TCP相比,UDP有哪些优点和不足呢?由于无需创建连接,所以UDP开销较小,数据传输速度快,实时性较强。多用于对实时性要求较高的通信场合,如视频会议、电话会议等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅助校验协议来弥补UDP的不足,以达到数据可靠传输的目的。

与TCP类似的,UDP也有可能出现缓冲区被填满后,再接收数据时丢包的现象。由于它没有TCP滑动窗口的机制,通常采用如下两种方法解决:

1)服务器应用层设计流量控制,控制发送数据速度。

2)借助setsockopt函数改变接收缓冲区大小。如:

#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

int n = 220x1024; //默认大小为1Kbyte
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

2、C/S模型----UDP通信流程

由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,保证通讯可靠性的机制需要在应用层实现。

UDP实现编程过程

客户端:

1、建立套接字(选择UDP协议)

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

函数作用:

建立套接字

参数:

domain:你要选择哪一种地址族

PF_INET/AF_INET ------Ipv4 网络协议

PF_INET6/AF_INET6----- Ipv6 网络协议

type: 你要选择哪一种 协议(TCP UDP)

SOCK_STREAM --流式套接字 TCP

SOCK_DGRAM -数据报套接字 UDP

protocol:一般设置成 0

返回值:

成功返回 套接字文件描述符

失败 -1

2、绑定自己的IP地址和端口号

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

参数:

sockfd:套接字文件描述符

addr:自己的IP地址和端口号

addrlen:结构体的大小

返回值 :

成功则返回 0,

失败返回-1, 错误原因存于 errno 中

struct sockaddr  --旧的结构体
{
    unsigned short sa_family; /地址族/
    char sa_data[14];/14字节的协议地址,包含该socket的IP地址和端口号。/
};
//IPV4结构体      
struct sockaddr_in
{
    short int sin_family; /地址族  IPV4   IPV6/
    unsigned short int sin_port; /端口号/
    struct in_addr sin_addr; /IP地址/
    unsigned char sin_zero[8]; /填充0 以保持与struct sockaddr同样大小/
};
struct in_addr {
    in_addr_t s_addr; /in_addr_t为 32位的unsigned int,该无符号整数采用大端字节序。/
};

3、直接发送(聊天)

#include <sys/socket.h>
ssize_t sendto(int socket, const void *message, size_t length,int flags, const struct sockaddr *dest_addr,socklen_t dest_len);

函数作用

用于UDP中发送数据,注意是UDP

参数:

socket :套接字文件描述符

message:你要发送的数据

length:你要发送的数据大小,注意有多少就发多少 strlen

flags:一般设置成 0

dest_addr:对方的IP地址和端口号

dest_len:结构体的大小

返回值:

成功返回 发送出去的字节数

失败返回 -1

4、关闭

close(socketfd);

服务器端:

#include <sys/socket.h>
ssize_t recvfrom(int socket, void * buffer, size_t length,int flags, struct sockaddr * address, socklen_t * address_len);

函数作用

用于UDP中接收数据

参数:

socket :套接字文件描述符

buffer:接收的数据存储在这里

length:接收的数据的大小, 以最大的来接收 sizeof()

flags:一般设置成 0

address: 存储 客户端的IP地址和端口号 ,可以获取到是 谁 给你 发送的

address_len:结构体的大小

返回值:

成功返回 接收到的字节数

失败返回 -1

服务器代码:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <unistd.h>
#include <stdlib.h>


int main(int argc, char *argv[])
{
   
    int ret, len, serverfd;
    struct sockaddr_in serveraddr, clientaddr;

    char read_buff[128] = {0};

    if(argc != 3)
    {
        printf("输入格式有误,输入格式:./可执行文件  IP  端口\n");
        exit(0);
    }

    //计算绑定服务器的结构体大小
    len = sizeof(struct sockaddr);

    //建立套接字
    serverfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == serverfd)
    {
        printf("socket fail\n");
        exit(0);
    }

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons (atoi(argv[2])) ; 
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    //服务器绑定自己的IP地址与端口
    ret = bind(serverfd, (struct sockaddr *)&serveraddr, len);
    if(-1 == ret)
    {
        printf("bind fail\n");
        close(serverfd);
        exit(0);        
    }

    while (1)
    {
        ret = recvfrom(serverfd, read_buff, sizeof(read_buff), 0, (struct sockaddr *)&clientaddr, &len);
        if(ret == -1)
        {
            close(serverfd);
            exit(0);
        }
        //打印客户端IP与地址
        printf("客户端IP:%s, 端口号:%d\n",  inet_ntoa(clientaddr.sin_addr),  ntohs(clientaddr.sin_port));
        printf("客户端数据:%s\n", read_buff);

        bzero(read_buff, sizeof(read_buff));

    }
    


    close(serverfd);

    return 0;
}

客户端代码:

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <unistd.h>
#include <stdlib.h>


int main(int argc, char *argv[])
{
   
    int ret, len, clientfd;
    struct sockaddr_in serveraddr, clientaddr;

    char write_buff[128] = {0};

    if(argc != 3)
    {
        printf("输入格式有误,输入格式:./可执行文件  IP  端口\n");
        exit(0);
    }

    //计算绑定服务器的结构体大小
    len = sizeof(struct sockaddr);

    //创建套接字
    clientfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == clientfd)
    {
        printf("socket fail\n");
        exit(0);
    }

    //封装服务器的地址信息
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons (atoi(argv[2])) ; 
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);    


    while(1)
    {
        printf("向服务器发送数据:\n");
        scanf("%s", write_buff);

        ret = sendto(clientfd, write_buff, strlen(write_buff), 0, (struct sockaddr *)&serveraddr, len);        
        if(-1 == ret)
        {
            printf("sendto fail\n");
            close(clientfd);
            exit(0);
        }

    }


    return 0;
}

结语:

         在这篇博客中,我们深入探讨了TCP和UDP这两种常见的传输层协议,理解了它们各自的特性、优缺点以及适用场景。TCP以其可靠性和顺序传递的特性,适合于需要确保数据完整性和顺序的应用,如文件传输和网页浏览。而UDP则以其低延迟和简单的通信方式,在对速度要求严格的实时应用中表现优异,如在线游戏、视频会议和流媒体传输。

        选择合适的协议不仅影响应用的性能,还能直接影响用户体验。因此,在设计网络应用时,开发者需要根据具体的业务需求和网络环境权衡选择。

        希望通过这篇博客,读者能够更加清晰地理解TCP和UDP的特性,帮助您在未来的项目中作出明智的选择。感谢您的阅读,期待与您在网络协议的领域进行更多交流与探讨!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多功能校准仪怎么进行计量校准?

多功能校准仪计量校准是计量行业常会进行的一种校准&#xff0c;因为其多功能校准仪的普遍适用性&#xff0c;以及其计量校准技术也是在行业内比较通用&#xff0c;那么具体多功能校准仪计量校准怎么进行呢&#xff1f; 校准方法 一、在多功能校准仪的输出范围内&#xff0c;布…

观察者模式的思考

观察者模式由来 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;它的起源可以追溯到20世纪90年代初&#xff0c;由设计模式四人帮&#xff08;Erich Gamma, Richard Helm, Ralph Johnson 和 John Vlissides&#xff09;在其著作《设计模…

反走样算法(MSAA、TAA、FXAA、DLSS)

光栅化的采样过程会导致图形走样,走样有很多种形式: 锯齿 摩尔纹 走样的本质原因是采样速度跟不上信号变化的速度 采样频率低,使得我们将连续变化的信号离散化. 反走样方法 anti-alisaing MSAA 多重采样反走样 超采样 优点&#xff1a; 对几何反走样效果良好 缺点…

razor TagHelper 汇总、HtmlHelper 汇总

Tag Helper Tag Helpers 的范围由 addTagHelper 和 removeTagHelper 进行控制&#xff0c;并且 “!” 为退出字符。 addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers // 手动高亮 asp-for 》》 Label <label asp-for"userName"></label>》》生…

你的炼丹炉选对GPU卡了吗?

现在抢GPU卡搞智算、搞AI模型训练的都太火了。 无论你是一个游戏爱好者还是一个赛博炼丹师&#xff08;大模型训练&#xff09;&#xff0c;英伟达GPU卡选型都将是绕不过的一道命题。 那么重点来了&#xff0c;如何在琳琅满目的各种型号GPU卡中选取一款合适且性价比高的呢&…

zookeeper实现RMI服务,高可用,HA

这可不是目录 1.RMI原理与说明1.1含义1.2流程1.3rmi的简单实现1.4RMI的局限性 2.zookeeper实现RMI服务&#xff08;高可用、HA&#xff09;2.1实现原理2.2高可用分析2.3zookeeper实现2.3.1代码分析2.3.2公共部分2.3.3服务端2.3.4客户端2.3.5运行与部署2.3.6效果展示与说明 1.RM…

Spring Boot: 构建高效中小型医院网站

1 绪论 1.1研究背景 随着计算机技术的成熟、普及&#xff0c;现代信息技术革命的迅猛发展,正冲击并进而改变着经济和社会结构。信息化的程度已经成为一个国家&#xff0c;一个企业&#xff0c;一个组织仍至一个人发展的基础和竞争成败的关键。 在实际的生活中&#xff0c;用户都…

软件评测CNAS资质获取流程

软件评测实验室如有意向申请 CNAS 检验机构认可&#xff0c;首先需要依据 CNAS 的认可准则建立管理体系&#xff0c;正式运行6个月以上&#xff0c;自我评估满足 CNAS 认可条件后可向 CNAS 提交申请。软件评测实验室CNAS认可的整体流程如图所示&#xff0c;后面的内容针对每个环…

数据结构之单链表详解:从原理到C语言实现

一、 什么是单链表&#xff1f; 单链表&#xff08;Singly Linked List&#xff09;是一种线性数据结构&#xff0c;它的特点是每个节点通过指针链接到下一个节点。不同于顺序表&#xff08;数组&#xff09;&#xff0c;链表的每个元素&#xff08;节点&#xff09;并不存储在…

【简单版】通过 Window.performance 实现前端页面(性能)监控

1 背景 前端监控系统告警xx接口fetchError 问题&#xff1a;前端监控系统没有更多的错误信息&#xff0c;查询该fetch请求对应的接口日志返回200状态码、无请求异常记录&#xff0c;且后台能查到通过该fetch请求成功发送的数据。那是前端页面的错误还是前端监控系统的问题&…

yjs机器学习常见算法01——KNN(1)(K—近邻算法)

1.K—近邻算法 的含义&#xff1a; 简单来说就是通过你的邻居的“类别”&#xff0c;来推测你的“类别” 定义&#xff1a;如果一个样本在特征空间中的k个最相似&#xff08;即特征空间中最临近&#xff09;的样本中大多数属于某一类别&#xff0c;则该样本也属于这个类别。 2.…

【Python爬虫系列】_028.Python玩Redis

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈

Redis原理篇之网络模型

Redis原理篇之网络模型 文章目录 Redis原理篇之网络模型1 用户空间和内核空间2 阻塞IO3 非阻塞IO4 IO多路复用4.1 IO多路复用-select4.2 IO多路复用-poll4.3 IO多路复用-epoll4.4 总结 5 信号驱动IO6 异步IO7 同步和异步8 Redis网络模型8.1 Redis是单线程吗&#xff1f;为什么要…

基于Opencv中的DNN模块实现图像/视频的风格迁移

一、DNN模块的介绍 1、简介 OpenCV中的DNN&#xff08;Deep Neural Network&#xff09;模块是一个功能强大的组件&#xff0c;它支持深度学习网络模型的加载和推理。虽然DNN模块不提供模型的训练功能&#xff0c;但它可以与主流的深度学习框架&#xff08;如TensorFlow、Caf…

tigeR免疫治疗数据分析工具学习和整理

tigeR整合了多个肿瘤的数据集&#xff0c;用于探索生物标志物和构建预测免疫治疗反应模型。 该工具内置了 11 个黑色素瘤数据集、3 个肺癌数据集、2 个肾癌数据集、1 个胃癌数据集、1 个低级别胶质瘤数据集、1 个胶质母细胞瘤数据集和 1 个头颈鳞状细胞癌数据集的 1060 例具有…

网络资源模板--Android Studio 实现简易新闻App

目录 一、项目演示 二、项目测试环境 三、项目详情 四、完整的项目源码 一、项目演示 网络资源模板--基于Android studio 实现的简易新闻App 二、项目测试环境 三、项目详情 登录页 用户输入&#xff1a; 提供账号和密码输入框&#xff0c;用户可以输入登录信息。支持“记…

2022年10月自考《操作系统概论》02323试题

目录 一.选择题 二.填空题 三.简答题 四.综合体 一.选择题 1.以下各种操作系统中&#xff0c;对可靠性要求最高的是 &#xff08;书中&#xff09;P25页 A.分时操作系统 B.实时操作系统 C.多道批处理系统 D.单道批处理系统 2.一个进程正常执行完毕时&#xff0c;需要对其…

简述光密度仪日常中的用途及光密度测量方法

光密度仪在日常中的用途 光密度仪在众多领域发挥着重要作用。在医疗领域&#xff0c;它常用于检测生物样本中的物质浓度&#xff0c;如血液中特定成分的含量测定。在化学分析中&#xff0c;可精确测量溶液的浓度&#xff0c;为实验和研究提供准确数据。在工业生产中&#xff0…

go+bootstrap实现简单的注册登录和管理

概述 使用&#xff0c;gomysql实现了用户的登录&#xff0c;注册&#xff0c;和管理的简单功能&#xff0c;不同用户根据不同权限显示不同的内容 实战要求&#xff1a; 1、用户可以注册、登录&#xff1b; 2、登录后可以查看所有的注册的用户&#xff1b; 3、管理员操作对用…

PHP(一)从入门到放弃

参考文献&#xff1a;https://www.php.net/manual/zh/introduction.php PHP 是什么&#xff1f; PHP&#xff08;“PHP: Hypertext Preprocessor”&#xff0c;超文本预处理器的字母缩写&#xff09;是一种被广泛应用的开放源代码的多用途脚本语言&#xff0c;它可嵌入到 HTML…