day-03 基于TCP的服务器端/客户端

一.理解TCP和UDP

TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种常见的传输层协议,用于在计算机网络中提供可靠的数据传输。

1.TCP:

  • 连接导向:TCP是一种面向连接的协议,通信双方在数据传输前需要先建立可靠的连接。
  • 可靠性:TCP提供可靠的数据传输,通过使用序列号、确认应答、超时重传等机制来确保数据的完整性和可靠性。
  • 流量控制与拥塞控制:TCP通过流量控制和拥塞控制来调节发送端的数据发送速率,以避免网络拥堵或丢包现象。
  • 有序性:TCP保证数据按照发送顺序到达目标端,不会发生乱序问题。
  • 面向字节流:TCP将数据视为连续的字节流进行传输,没有消息边界的概念。

TCP适用于对数据完整性和可靠性要求较高的应用场景,如文件传输、电子邮件、网页浏览等。

2.UDP:

  • 无连接:UDP是一种无连接的协议,通信双方在数据传输时不需要先建立连接。
  • 不可靠性:相比TCP,UDP不提供数据的可靠性保证,数据可能会丢失、乱序或重复。
  • 无拥塞控制:UDP不对数据发送速率进行调节,因此可能会造成网络拥堵和丢包现象。
  • 无序性:UDP没有保证数据按照发送顺序到达目标端的机制,数据可能会乱序到达。
  • 面向报文:UDP将数据视为独立的报文进行传输,保留了消息的边界。

UDP适用于对实时性要求较高、数据丢失可容忍或应用自身提供可靠性机制的应用场景,如音频/视频流传输、在线游戏等。

需要根据具体应用需求选择使用TCP还是UDP。TCP提供可靠的有序数据传输,适合对数据完整性要求较高的场景;而UDP提供了更低的延迟和更高的实时性,适用于对实时性要求较高、数据丢失可容忍的场景。

TCP/IP协议栈

二.实现基于TCP的服务器端/客户端

1.TCP服务器端的默认函数调用顺序

 2.TCP客户端的默认函数调用顺序

3.基于TCP的服务器端/客户端函数调用关系

 4.实现迭代回声服务器端/客户端

(VM ubuntu编译Cpp:g++ -std=c++11  文件名 -o  生成文件名)

(1)echo_server.cpp
  • g++ -std=c++11 echo_server.cpp -o eserver
  • ./eserver 9190
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 1024void error_handling(const char *message);int main(int argc, char *argv[]) {int serv_sock, clnt_sock;char message[BUF_SIZE];int str_len, i;struct sockaddr_in serv_adr, clnt_adr;socklen_t clnt_adr_sz;if (argc != 2) {std::cout << "Usage: " << argv[0] << " <port>" << std::endl;exit(1);}serv_sock = socket(PF_INET, SOCK_STREAM, 0);if (serv_sock == -1)error_handling("socket() error");memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)error_handling("bind() error");if (listen(serv_sock, 5) == -1)error_handling("listen() error");clnt_adr_sz = sizeof(clnt_adr);for (i = 0; i < 5; i++) {clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);if (clnt_sock == -1)error_handling("accept() error");elsestd::cout << "Connected client " << i+1 << std::endl;while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)write(clnt_sock, message, str_len);close(clnt_sock);}close(serv_sock);return 0;
}void error_handling(const char *message) {std::cout << message << std::endl;exit(1);
}
(2)echo_client.cpp
  • g++ -std=c++11 echo_client.cpp -o eclient
  • ./eclient 127.0.0.1 9190
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 1024void error_handling(const char *message);int main(int argc, char *argv[]) {int sock;char message[BUF_SIZE];int str_len;struct sockaddr_in serv_adr;if (argc != 3) {std::cout << "Usage: " << argv[0] << " <IP> <port>" << std::endl;exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);if (sock == -1)error_handling("socket() error");memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(argv[1]);serv_adr.sin_port = htons(atoi(argv[2]));if (connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)//调用conndect函数error_handling("connect() error!");elsestd::cout << "connected..........." << std::endl;while (true) {std::cout << "Input message (Q to quit): ";std::cin.getline(message, BUF_SIZE);if (strcmp(message, "q") == 0 || strcmp(message, "Q") == 0)break;write(sock, message, strlen(message));str_len = read(sock, message, BUF_SIZE-1);message[str_len] = '\0';std::cout << "Message from server: " << message << std::endl;}close(sock);//调用close函数向相应套接字发送EOF(EOF意味着中断连接)return 0;
}void error_handling(const char *message) {std::cerr << message << std::endl;exit(1);
}
存在问题:
  • 如果数据量太大,操作系统可能把数据分成多个数据包发送到客户端(但是服务器端每次接收读取之后只调用一次 write() 函数。
  • 另外,客户端有可能在尚未收到全部数据包时就调用 read()函数,导致回声不完整。

三.总结(一)

1.说明TCP/IP的4层协议栈,并说明TCP和UDP套接字经过的层级结构差异。

TCP/IP协议栈是一个网络通信的基本模型,它由四层协议组成,分别是应用层、传输层、网络层和链路层。

  1. 应用层:应用层提供了用户与网络之间的接口,负责处理特定的应用程序数据。常见的应用层协议有HTTP、FTP、SMTP等。

  2. 传输层:传输层主要负责在网络中传输数据。其中最重要的两个协议是TCP(传输控制协议)和UDP(用户数据报协议)。

    • TCP套接字经过传输层。TCP是一种面向连接的协议,提供可靠的数据传输。它通过建立连接、数据分段、流量控制、拥塞控制等机制来保证数据的可靠性和顺序性。TCP套接字在传输层使用IP地址和端口号进行标识。

    • UDP套接字也经过传输层。UDP是一种无连接的协议,不提供可靠性保证。它将数据打包成数据报发送,不关心数据是否能够到达目标地址。UDP套接字同样使用IP地址和端口号进行标识。

  3. 网络层(IP层):网络层负责将数据从源主机传输到目标主机,它使用IP协议进行数据包的路由选择和转发。IP协议为数据包分配唯一的IP地址。

  4. 链路层:链路层是网络通信的物理层和数据链路层接口,负责将网络层传输的数据转换成比特流进行物理传输。它包括了物理寻址、帧封装和错误检测等功能。

  • IP层和链路层之间的关系是协同工作。IP层将数据封装成IP数据包,在传输过程中会添加源IP地址和目标IP地址,然后将数据包交给链路层进行物理传输。链路层收到IP数据包后,会添加自己的帧头和帧尾信息,并将数据包转换成比特流发送。接收端的链路层会将接收到的比特流重新组装成帧,并去除链路层的头部和尾部信息,然后将数据包交给IP层进行进一步处理。
  • 总结来说,IP层负责寻址、路由选择和数据包的传输控制,而链路层负责物理传输和错误检测。它们共同协作,完成了数据从源主机到目标主机的传输过程。

所以,TCP和UDP套接字在协议栈的层级结构上并无差异,都经过传输层。它们的主要区别在于TCP提供面向连接的可靠传输,而UDP提供无连接的不可靠传输。根据应用场景的需要,可以选择使用TCP或UDP来进行数据传输。

2.为什么要把TCP/IP协议栈分成4层(或7层)结合开放式系统回答?

TCP/IP协议栈被分成多层的设计是出于以下几个原因,其中也与开放式系统相关:

  1. 模块化设计:将TCP/IP协议栈分成多层可以实现模块化的设计,每一层都有明确定义的功能和责任。这样可以提高系统的可维护性和可扩展性。如果需要对某一层进行修改或替换,只需关注特定层而无需改动其他层。

  2. 逻辑分离:将网络通信过程划分为不同的层级,可以实现逻辑上的分离。每一层专注于自身的任务,不需要关心其他层的具体实现。这种分离使得各层之间的接口定义清晰,降低了系统的复杂性。

  3. 标准化和互操作性:将TCP/IP协议栈分成多层使得每一层的功能和协议得到标准化。这样不同厂商、不同操作系统的设备都可以按照相同的标准实现对应的层,从而实现互操作性。这种开放式的设计使得不同设备和系统能够无缝地进行通信。

  4. 灵活性和可定制性:通过分层设计,可以根据具体需求选择性地使用不同层提供的功能。例如,某些应用场景可能只需要传输层和网络层的功能,可以选择性地使用这些层,而不需要实现其他层。这种灵活性和可定制性使得TCP/IP协议栈适应各种不同的网络和应用需求。

综上所述,将TCP/IP协议栈分成多层结构是为了实现模块化设计、逻辑分离、标准化和互操作性,以及提供灵活性和可定制性。这样的设计使得开放式系统中的不同设备和系统能够相互通信,并且能够根据具体需求进行定制和扩展。

3.客户端调用connect函数向服务器端发送连接请求。服务器端调用哪个函数后,客户端可以调用connect函数?

服务器端调用 listen 函数后,客户端可以调用 connect 函数。
在TCP通信中,服务器端需要先调用listen函数来监听指定的端口,以便接受客户端的连接请求。listen函数使得服务器端处于监听状态,等待客户端连接。
一旦服务器端调用了listen函数,客户端就可以通过调用connect函数向服务器端发送连接请求。connect函数会建立与服务器端的连接,并进行三次握手来确保连接的可靠性。
所以,客户端需要在服务器端调用listen函数之后才能调用connect函数,以发起与服务器的连接。

4.什么时候创建连接请求等待队列,有何作用,和accept有什么关系?

        连接请求等待队列是在服务器端调用listen函数后创建的它的作用是用于存储尚未被服务器端accept函数接受的客户端连接请求。

        当服务器端调用listen函数后,它会开始监听指定的端口,并将该端口设置为监听状态,等待客户端的连接请求。如果有多个客户端同时向服务器端发送连接请求,而服务器端只能逐个处理这些请求,这时就需要使用连接请求等待队列

        连接请求等待队列允许服务器端在处理一个连接请求时,将其他连接请求暂时保存起来,以便后续处理。通过使用连接请求等待队列,可以确保当服务器端正在处理一个连接请求时,其他连接请求不会被丢失。

        当服务器端调用accept函数时,它从连接请求等待队列中取出一个连接请求进行处理。accept函数会建立与客户端的连接,并返回一个新的套接字,服务器端可通过这个套接字与对应的客户端进行通信。

        因此,连接请求等待队列与accept函数密切相关。连接请求等待队列存储了尚未被接受的客户端连接请求,而accept函数则负责从队列中取出连接请求并建立连接。通过连接请求等待队列和accept函数的配合使用,服务器端能够管理和处理多个客户端的连接请求。

5.客户端中为何不需要调用bind函数分配函数地址,如果不调用bin函数,那么何时,如何向套接字分配IP地址和端口号?

        在客户端中,不需要调用bind()函数来分配函数地址,是因为客户端一般不需要绑定特定的IP地址和端口号。客户端的套接字可以通过操作系统自动分配可用的临时端口,并自动关联客户端的IP地址。

        当客户端创建套接字时,操作系统会自动选择一个可用的本地端口,并将其与套接字进行关联。这个过程通常称为"隐式绑定"(implicit binding)。

        隐式绑定可以确保客户端使用的本地端口是唯一的,并且不会与其他应用程序冲突。对于大多数客户端应用程序而言,这种自动分配是足够的,无需显式调用bind()函数。

        需要注意的是,如果你想要在客户端使用特定的本地IP地址和端口号,或者需要进行端口重用等特殊操作,那么你可能需要调用bind()函数来手动指定这些参数。但一般情况下,客户端并不需要手动调用bind()函数来分配函数地址。

四.回声客户端的功能实现(加减乘)----应用层协议设计

        要求,服务器端从客户端获得多个数字和运算符信息,然后把结果传回客户端。

1.op_server.cpp

#include<iostream>
#include<cstring>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>#define BUF_SIZE 1024
#define OPSZ 4void error_handling(const char *message);
int calculate(int opnum,int opnds[],char oprator);int main(int argc,char *argv[]){int serv_sock,clnt_sock;char opinfo[BUF_SIZE];int result,opnd_cnt,i;int recv_cnt,recv_len;struct sockaddr_in serv_adr,clnt_adr;socklen_t clnt_adr_sz;if(argc!=2){std::cout<<"Usage :"<<argv[0]<<"<port>"<<std::endl;exit(1);}serv_sock=socket(PF_INET,SOCK_STREAM,0);if(serv_sock==-1)error_handling("socket() error");memset(&serv_adr,0,sizeof(serv_adr));serv_adr.sin_family=AF_INET;serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);serv_adr.sin_port=htons(atoi(argv[1]));if(bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr))==-1)error_handling("bind() error");if(listen(serv_sock,5)==-1)error_handling("listen() error");clnt_adr_sz=sizeof(clnt_adr);for(i=0;i<5;i++){opnd_cnt=0;//运算个数clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_adr,&clnt_adr_sz);read(clnt_sock,&opnd_cnt,1);recv_len=0;while((opnd_cnt*OPSZ+1)>recv_len){//读取运算值recv_cnt=read(clnt_sock,&opinfo[recv_len],BUF_SIZE-1);recv_len+=recv_cnt;}result+=calculate(opnd_cnt,(int*)opinfo,opinfo[recv_len-1]);write(clnt_sock,(char*)&result,sizeof(result));close(clnt_sock);//当前客户端结束后关闭}close(serv_sock);return 0;
}int calculate(int opnum,int opnds[],char op){//功能函数int result=opnds[0],i;switch(op){case '+':for(i=1;i<opnum;i++) result+=opnds[i];break;case '-':for(i=1;i<opnum;i++) result-=opnds[i];break;case '*':for(i=1;i<opnum;i++) result*=opnds[i];break;}return result;
}void error_handling(const char*message){std::cout<<message<<std::endl;exit(1);
}

2.op_client.cpp

#include<iostream>
#include<cstring>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>#define BUF_SIZE 1024
#define RLT_SIZE 4
#define OPSZ 4void error_handling(const char *message);int main(int argc,char *argv[]){int sock;char opmsg[BUF_SIZE];int result,opnd_cnt,i;struct sockaddr_in serv_adr;if(argc!=3){std::cout<<"Usage:"<<argv[0]<<"<IP> <port>"<<std::endl;exit(1);}sock=socket(PF_INET,SOCK_STREAM,0);if(sock==-1)error_handling("socket() error");memset(&serv_adr,0,sizeof(serv_adr));serv_adr.sin_family=AF_INET;serv_adr.sin_addr.s_addr=inet_addr(argv[1]);serv_adr.sin_port=htons(atoi(argv[2]));if(connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr))==-1)error_handling("connect() error");elsestd::cout<<"connect ......"<<std::endl;std::cout<<"Operand count: ";std::cin>>opnd_cnt;//获取运算个数opmsg[0]=(char)opnd_cnt;for(i=0;i<opnd_cnt;i++){std::cout<<"Operand "<<i+1<<": ";std::cin>>*(int*)&opmsg[i*OPSZ+1];std::cin.ignore();//相当于换行}std::cout<<"Opreator: ";std::cin>>opmsg[opnd_cnt*OPSZ+1];//输入运算符write(sock,opmsg,opnd_cnt*OPSZ+2);//传输给服务器端read(sock,&result,RLT_SIZE);std::cout<<"Operation result: "<<result<<std::endl;close(sock);return 0;
}void error_handling(const char *message){std::cout<<message<<std::endl;exit(1);
}

五.TCP原理

(一)TCP套接字中的 I/O 缓冲

        TCP套接字的数据收发无边界。为什么这么说呢?因为 write() 函数调用后并非立即传输数据,read() 函数调用后也并非马上接收数据。如下图所示:

 缓冲特性:
  • I/O 缓冲在每个TCP套接字中单独存在
  • I/O 缓冲在创建套接字时自动生成
  • 即使关闭套接字也会继续传递输出缓冲中遗留的数据
  • 关闭套接字将丢失输入缓冲中的数据

注意:TCP有流量控制机制,所以不存在缓冲溢出

(二)TCP内部工作原理(套接字创建到关闭)

1.与对方套接字的连接(三次握手)

        套接字是全双工(Full-duplex)方式工作的,也就是可以双向地传递数据。因此,收发数据之前需要做一些准备。

  • 第一次握手:服务端知道客户端发送能力没问题
  • 第二次握手:客户端知道服务端的接收和发送能力没问题
  • 第三次握手:服务端知道客户端的接收能力没问题

2.与对方主机的数据交换

        通过三次握手过程完成了数据交换准备,下面开始正式收发数据。

         在数据包消失情况中,过了一段时间后,主机A没有收到对于SEQ:1301的ACK确认,因此在启动计时器等待ACK应答,若相应计时器发生超时(Time-out!)则重传。

3.断开与套接字的连接(四次挥手)

        TCP套接字结束的过程也非常优雅。如果对方还有数据需要传输,直接打断连接会出问题。所以断开连接需要双方协商。

         数据包内的 FIN 表示断开连接。也就是说,双方各发送一次 FIN 消息后断开连接。此过程经历四个阶段。图中主机B向主机A发送了两次ACK:501,其实第二次 FIN 数据包中的 ACK:5001 只是因为收到(主机A的)ACK消息后未接收数据而重传的。

六.总结(二)

1.请说明TCP套接字连接设置的三次握手过程。尤其是3次数据交换过程中每次收发的数据内容。

TCP套接字连接的三次握手过程如下:

第一次握手:

  • 客户端向服务器发送一个SYN(同步)报文,其中包含一个随机生成的初始序列号(ISN)。
  • 这个SYN报文的目的是告诉服务器,客户端希望建立连接,并且客户端的初始序列号是多少。

第二次握手:

  • 服务器收到客户端的SYN报文后,会发送一个SYN-ACK(同步-确认)报文给客户端。
  • 这个SYN-ACK报文中,服务器会确认收到了客户端的连接请求,并且也会生成一个自己的初始序列号(ISN)。
  • 同时,服务器也会将自己期望接收到的下一个序列号(Next Sequence Number)设置为客户端的初始序列号加一。

第三次握手:

  • 客户端收到服务器的SYN-ACK报文后,会发送一个ACK(确认)报文给服务器。
  • 这个ACK报文中,客户端会确认收到了服务器的确认,并且会将自己期望接收到的下一个序列号设置为服务器的初始序列号加一。

在这个三次握手的过程中,每次收发的数据内容如下:

第一次握手:

  • 客户端发送的数据内容是一个SYN报文,其中包含客户端的初始序列号(ISN)。

第二次握手:

  • 服务器发送的数据内容是一个SYN-ACK报文,其中包含服务器的初始序列号(ISN)和期望接收到的下一个序列号(Next Sequence Number)。

第三次握手:

  • 客户端发送的数据内容是一个ACK报文,其中确认了服务器的确认,并且设置了客户端期望接收到的下一个序列号。

2.TCP是可靠的数据传输协议,但是通过网络通信的过程中可能会丢失数据。请通过ACK和SEQ说明TCP通过何种机制保证丢失数据的可靠传输。

        SEQ顺序标识符是给信息编号。ACK是用于回复带有编号的信息。也就是说,每次传输信息时,都同时发送SEQ标识,而受到信息的主机应以SEQ信息为基础回复发送信息的主机。通过这种机制,传输数据的主机就可以确认数据是否被正确接收。在传输失败时,可以重新传送。

3.TCP套接字中调用write和read函数时数据如何移动?结合I/O缓冲进行说明。

        当write函数被调用时,数据就会向端口的输出缓冲区移动。然后经过网络传输传输到对方主机套接字的输入缓冲。这样,输入缓冲中存储的数据通过read函数的响应来读取。

​​​​​​​​​​​​​​

​​​​​​​4.对方主机的输入缓冲剩余50字节空间时,若本方主机通过write函数请求传输70字节,请问TCP如何处理这种情况?

        对方主机会把输入缓冲中可存储的数据大小传送给要传输数据的数据(本方)。因此,在剩余空间为50字节的情况,即使要求传送70字节的数据,也不能传输50字节以上,剩余的部分保存在传输方的输出缓冲中,等待对方主机的输入缓冲出现空间。而且,这种交换缓冲多余空间信息的协议被称为滑动窗口协议。

5.创建收发文件的服务器端/客户端。

  • 客户端接收用户输入的传输文件名
  • 客户端请求服务器端传输该文件名所指文件
  • 如果文件存在,服务器端九江其发给客户端,反之,则断开连接。
file_server.cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 30void error_handling(const char *message);int main(int argc, char *argv[]) {int serv_sd, clnt_sd;FILE *fp;char buf[BUF_SIZE];char file_name[BUF_SIZE];int read_cnt;struct sockaddr_in serv_adr, clnt_adr;socklen_t clnt_adr_sz;if (argc != 2) {std::cout << "Usage: " << argv[0] << " <port>" << std::endl;exit(1);}serv_sd = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));bind(serv_sd, (struct sockaddr *) &serv_adr, sizeof(serv_adr));listen(serv_sd, 5);clnt_adr_sz = sizeof(clnt_adr);clnt_sd = accept(serv_sd, (struct sockaddr *) &clnt_adr, &clnt_adr_sz);read(clnt_sd, file_name, BUF_SIZE);fp = fopen(file_name, "rb");if (fp != NULL) {while (1) {read_cnt = fread((void *) buf, 1, BUF_SIZE, fp);if (read_cnt < BUF_SIZE) {write(clnt_sd, buf, read_cnt);break;}write(clnt_sd, buf, BUF_SIZE);}}fclose(fp);close(clnt_sd);close(serv_sd);return 0;
}void error_handling(const char *message) {std::cerr << message << std::endl;exit(1);
}
file_client.cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_SIZE 30void error_handling(const char *message);int main(int argc, char *argv[]) {int sd;FILE *fp;char buf[BUF_SIZE];char file_name[BUF_SIZE];int read_cnt;struct sockaddr_in serv_adr;if (argc != 3) {std::cout << "Usage: " << argv[0] << " <IP> <port>" << std::endl;exit(1);}std::cout << "Input file name: ";std::cin >> file_name;fp = fopen(file_name, "wb");sd = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = inet_addr(argv[1]);serv_adr.sin_port = htons(atoi(argv[2]));connect(sd, (struct sockaddr *) &serv_adr, sizeof(serv_adr));write(sd, file_name, strlen(file_name) + 1);while ((read_cnt = read(sd, buf, BUF_SIZE)) != 0)fwrite((void *) buf, 1, read_cnt, fp);fclose(fp);close(sd);return 0;
}void error_handling(const char *message) {std::cerr << message << std::endl;exit(1);
}

问题:无法真正的传输文件数据。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 蓝牙开发( 四 )

前言 上一篇文章给大家分享了Kotlin版的Android蓝牙的基础知识和基础用法&#xff0c;不过上一篇都是一些零散碎片化的程序&#xff0c;&#xff0c;这一篇给大家分享Android蓝牙开发实战项目KotlinCompose的初步使用 效果演示 : Android Compose 蓝牙开发 Android蓝牙实战开发…

Sqoop实操案例-互联网招聘数据迁移

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 个人主页&#xff1a;beixi 本文章收录于专栏&#xff08;点击传送&#xff09;&#xff1a;【大数据学习】 &#x1f493;&#x1f493;持续更新中&#xff0c;感谢各位前辈朋友们支持…

【实操干货】如何开始用Qt Widgets编程?(四)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 在本文中&#xff0…

Android 蓝牙开发( 二 )

前言 上一篇文章给大家分享了Android蓝牙的基础知识和基础用法&#xff0c;不过上一篇都是一些零散碎片化的程序&#xff0c;这一篇给大家分享Android蓝牙开发实战项目的初步使用 效果演示 : Android蓝牙搜索&#xff0c;配对&#xff0c;连接&#xff0c;通信 Android蓝牙实…

数据分析师职业发展道路,工作内容是什么?

很多同学问&#xff0c;参加数据分析就业班后之的就业发展道路是怎样的&#xff0c;工作又能做什么呢&#xff1f; 市面上的常见的工作类型有有运营类、技术类及分析类等&#xff0c;可以根据自己的意愿去做适合自己的工作&#xff0c;但是任何工作其实都是需要一技之长。…

基于实例的学习方法

基于实例的学习方法 动机基本概念基于实例的学习基于实例的概念表示 1. 最近邻最近邻的例子理论结果最近邻&#xff08;1- NN&#xff09;:解释问题 K-近邻(KNN)KNN讨论1 &#xff1a;距离度量KNN 讨论2&#xff1a;属性KNN:属性归一化KNN:属性加权 KNN讨论3:连续取值目标函数K…

ssh常用操作

ssh常用操作 SSH是一种安全协议&#xff0c;ssh是该协议的客户端程序&#xff0c;openssh-server则是该协议的服务端程序 常用系统都自带了ssh客户端程序&#xff0c;服务端程序则可能要安装 密码远程登陆 前提&#xff1a;服务器安装了openssh-server&#xff0c;未安装时…

自定义TimeLine实现卡拉OK轨

系列文章目录 自定义TimeLine 自定义TimeLine 系列文章目录前言正文UI部分代码部分Data&#xff08;数据&#xff09;Clip&#xff08;片段&#xff09;Track&#xff08;轨道&#xff09;Mixer&#xff08;混合&#xff09;被控制物体 总结 前言 自定义TimeLine实际上就是自定…

Android安卓webview,网页端生成安卓项目(极速生成)教程

Android安卓webview&#xff0c;网页端生成安卓项目&#xff08;极速生成&#xff09;教程 一&#xff0c;前言 当自己做了一个PC端的页面&#xff0c;也就是前端的页面&#xff0c;或者已经上服的页面&#xff0c;但也想生成一个安卓端供用户使用&#xff0c;本教程详细讲解…

人员位置管理,点亮矿山安全之路

矿山作为一个高危行业&#xff0c;安全问题一直备受关注。人员定位置管理是现代矿山安全管理的重要一环&#xff0c;可以帮助企业更好地实现对人员的实时监控和管理。因此&#xff0c;矿山人员位置管理系统对于矿山安全生产和管理非常重要&#xff0c;可以帮助减少安全事故的发…

BEVFusion复现 (Ubuntu RTX3090)

https://github.com/ADLab-AutoDrive/BEVFusion 1.环境安装 我的机器是RTX3090&#xff0c;CUDA11.1 1.创建虚拟环境 conda create -n bevfusion python3.8.3 2.安装PyTorch 和 torchvision pip install torch1.8.0cu111 torchvision0.9.0cu111 torchaudio0.8.0 -f https://…

Java中的动态代理(JDK Proxy VS CGLib)

前言 动态代理可以说是Java基础中一个比较重要的内容&#xff0c;这块内容关系到Spring框架中的AOP实现原理&#xff0c;所以特别写了一篇作为个人对这块知识的总结。这部分内容主要包括&#xff1a;JDK Proxy和CGLib的基本介绍、二者的实现原理、代码示例等。 什么是动态代理…

C# 如何将使用的Dll嵌入到.exe应用程序中?

文章目录 前言详细实操简要步骤 前言 有没有想自己开发的exe保留一点神秘&#xff0c;不想让他人知道软件使用了哪些dll; 又或许是客户觉得一个软件里面的dll文件太多了&#xff0c;能不能简单一点&#xff0c;直接双击.exe就可以直接运行了&#xff0c;别搞那么多乱七八糟的。…

Three.js相机参数及Z-Fighting问题的解决方案

本主题讨论透视相机以及如何为远距离环境设置合适的视锥体。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 透视相机是一种投影模式&#xff0c;旨在模仿人类在现实世界中看待事物的方式。 这是渲染 3D 场景最常用的投影模式。 - three.js 如果你看一下 Three.js 文档…

优思学院|六西格玛中的概率分布有哪些?

为什么概率分布重要&#xff1f; 概率分布是统计学中一个重要的概念&#xff0c;它帮助我们理解随机变量的分布情况以及与之相关的概率。在面对具体问题时&#xff0c;了解概率分布可以帮助我们选择适当的检验或分析策略&#xff0c;以解决问题并做出合理的决策。 常见的概率…

【二】kubernetes master单节点拓展为集群

#服务器 #部署 #云原生 #k8s 一、 前言 一、ubuntu20.04上搭建containerd版&#xff08; 1.2.4 以上&#xff09;k8s及kuboard V3 接上文中&#xff0c;我们已经部署好了单节点master的k8s集群&#xff0c;在生产环境中&#xff0c;单节点的master肯定是不行的&#xff0c;那…

科技探究之旅--亲子研学活动

2023年8月26日&#xff0c;广州市从化区齐家社会工作服务中心&#xff08;以下简称“齐家”&#xff09;的“星乐园-乡村儿童公益辅导服务项目”组织了新开村及西湖村助学点24对亲子到广州市白云区文搏3D打印基地进行“科技探究之旅--亲子研学”活动&#xff0c;旨在发现、点燃…

用Go编写ChatGPT插件

ChatGPT插件平台有望成为影响深远的"下一件大事"&#xff0c;因此对于开发者来说&#xff0c;有必要对ChatGPT插件的开发有一定的了解。原文: Writing a ChatGPT Plugin With Go[1] 我工作的附带福利之一是偶尔可以接触试用一些很酷的新技术&#xff0c;最近的一项技…

实战:基于卷积的MNIST手写体分类

前面实现了基于多层感知机的MNIST手写体识别&#xff0c;本章将实现以卷积神经网络完成的MNIST手写体识别。 1. 数据的准备 在本例中&#xff0c;依旧使用MNIST数据集&#xff0c;对这个数据集的数据和标签介绍&#xff0c;前面的章节已详细说明过了&#xff0c;相对于前面章…

控制goroutine 的并发执行数量

goroutine的数量上限是1048575吗&#xff1f; 正常项目&#xff0c;协程数量超过十万就需要引起重视。如果有上百万goroutine&#xff0c;一般是有问题的。 但并不是说协程数量的上限是100多w 1048575的来自类似如下的demo代码: package mainimport ( "fmt" "ma…