【嵌入式Linux】<总览> 网络编程(更新中)

文章目录

前言

一、网络知识概述

1. 网路结构分层

2. socket

3. IP地址

4. 端口号

5. 字节序

二、网络编程常用API

1. socket函数

2. bind函数

3. listen函数

4. accept函数

5. connect函数

6. read和recv函数

7. write和send函数

三、TCP编程

1. TCP介绍

2. TCP通信流程

3. TCP服务端和客户端程序

4. TCP并发服务器(多进程)

5. TCP并发服务器(多线程)


前言

记录学习嵌入式Linux网络编程的知识重点与难点,若涉及版权问题请联系本人删除!


一、网络知识概述

1. 网路结构分层

①主要存在两种分层模型:OSI七层模型(理论)和TCP/IP四层模型(实际)。

②分层思想:采用分治法,将复杂问题划分为各个层级上的子问题。每一层向上提供服务,同时使用下层提供的服务。

③网络封包和拆包:如下图所示,从主机A传输数据到主机B的流程一般为:主机A的应用层封装好了传输数据后,传输层会给上层数据包添加TCP头部,网络层会给上层数据包添加IP头部,网络接口层会给上层数据包添加对应的头部和尾部(一般为CRC校验);路由器将接收到的帧去掉头部和尾部传输给上一层,传输层将解析IP地址并查表转发,然后继续封装为帧,传输到指定的主机B;主机B逐层拆包,最终在应用层获取接收到的真正数据。

2. socket

①概念:socket是一个特殊的文件描述符,用于网络通信。

②socket分类:

  • 流式套接字(SOCK_STREAM):对应TCP,面向连接、可靠。
  • 数据报套接字(SOCK_DGRAM):对应UDP,无连接、不可靠。
  • 原始套接字(SOCK_RAW):对应多个协议,可以直接访问IP、ICMP,跨过了传输层。

3. IP地址

①概念:IP地址是网络中主机地址的标识。通过IP可以找到对应的主机。

②IP地址分类:

  • IPv4:一个32位(4字节)的整数,每个字节用.来分隔。每个字节的数据范围为0~255。例如: 192.168.5.11  不够用,引入局域网可以解决不够用的问题
  • IPv6:一个128(16字节)的整数,每两个字节为一部分,总共有8部分,每部分用:分隔。例如:2001:0db8:3c4d:0015:0000:0000:1a2f:1a2b   “地球上每一粒沙子都能分配一个”

③特殊的IP地址:

  • 局域网IP:192.XXX.XXX.XXX    10.XXX.XXX.XXX
  • 广播IP:XXX.XXX.XXX.255    255.255.255.255(全网广播)
  • 组播IP:224.XXX.XXX.XXX到239.XXX.XXX.XXX

4. 端口号

①概念:是一个16位的整数,范围:1~65535.

②端口号分类:

  • 众所周知的端口:1~1023. (FTP: 21, SSH: 22, HTTP: 80, HTTPS: 469)
  • 保留端口:1024~5000  不建议使用
  • 可以使用端口:5001~65535

③注意事项:

  • TCP端口和UDP端口是相互独立的。(因为它们在内核中的处理路径不同)
  • 网络通信由IP地址+端口号来决定。IP地址确定主机位置,端口号确定主机中哪个进程来处理。

5. 字节序

①概念:当涉及内存中存取多字节数据时,就会遇到字节序的问题。

②两种字节序:

  • 小端:低字节序内容存储到低地址的内存中。
  • 大端:低字节序内容存储到高地址的内存中。

③注意事项:

  • 一般本地采用小端模式,网络传输采用大端模式。
  • 在发送数据和接收数据前,应该先将本地字节序和网络字节序进行转换。

④主机字节序和网络字节序转换函数:

#include <arpa/inet.h>// 主要用于网络通信过程中IP和端口的转换
uint16_t htons(uint16_t hostshort);   //短整形,主机字节序->网络字节序uint32_t htonl(uint32_t hostlong);    //整形,主机字节序->网络字节序	uint16_t ntohs(uint16_t netshort);    //短整形,网络字节序->主机字节序uint32_t ntohl(uint32_t netlong);     //整形,网络字节序->主机字节序

⑤IP地址转换函数:

inet_pton函数:将IP地址从主机字节序->网络字节序。

【1】头文件:#include <arpa/inet.h>

【2】函数原型:int inet_pton(int af, const char *src, void *dst);

【3】参数说明:

  • af:地址族,填写AF_INET(IPv4)或者AF_INET6(IPv6)
  • src:传入参数,即要转换的IP地址。例如:192.168.5.11
  • dst:传出参数,存放转换后的IP地址。

【4】返回值:成功返回1,第一个参数无效返回-1,第二个参数无效返回0.

inet_ntop函数:将IP地址从网络字节序->主机字节序。

【1】头文件:#include <arpa/inet.h>

【2】函数原型:const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

【3】参数说明:

  • af:地址族,填写AF_INET(IPv4)或者AF_INET6(IPv6)
  • src:传入参数,其中存储了网络字节序的IP地址。
  • dst:传出参数,存放转换后的IP地址。
  • size:dst指向的内存中最多可以存储多少个字节。

【4】返回值:成功返回指针指向第三个参数对应的地址,失败返回NULL。


二、网络编程常用API

以下函数除read和write外都需包含头文件#include <sys/types.h>、#include <sys/socket.h>

1. socket函数

【1】功能:创建网络套接字。

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

【3】参数说明:

domainAF_INETIPv4协议
AF_INET6IPv6协议
AF_LOCAL本地通信
typeSOCK_STREAM流式套接字,对应TCP
SOCK_DGRAM数据报套接字,对应UDP
SOCK_RAW原始套接字
protocol一般写0即可,使用默认协议。非0一般用于原始套接字。

【4】返回值:成功返回套接字fd,失败返回-1。

【5】代码示例:

int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {perror("socket error");return -1;
}

2. bind函数

【1】功能:将本地的IP、端口与套接字绑定。

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

【3】参数说明:

sockfd套接字,由socket函数生成
addrstruct sockaddr类型的变量的地址
addrlenaddr指向的内存的地址大小

struct sockaddr结构体:(写数据时一般不用)

struct sockaddr {sa_family_t sa_family;       // 地址族协议, ipv4char        sa_data[14];     // 端口(2字节) + IP地址(4字节) + 填充(8字节)
}

struct sockaddr_in结构体:(常用)一般将端口和IP地址保存在该类型的变量中,然后强转为struct sockaddr类型。(它们的大小完全相同)

struct sockaddr_in
{sa_family_t sin_family;		/* 地址族 */in_port_t sin_port;         /* 端口号, 2字节 -> 网路字节序 */struct in_addr sin_addr;    /* IP地址, 4字节 -> 网络字节序 *//* 填充8字节,初始化为0 */unsigned char sin_zero[sizeof (struct sockaddr) - sizeof(sin_family) -sizeof (in_port_t) - sizeof (struct in_addr)];
};struct in_addr
{in_addr_t s_addr;
};

【4】返回值:成功返回0,失败返回-1。

【5】代码示例:

#define  SERV_PORT    9999
#define  SERV_IP      "192.168.5.12"struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;        //IPv4协议
sin.sin_port = htons(SERV_PROT); //端口号,转化为网络字节序
//IP地址,转化为网络字节序
if (inet_pton(AF_INET, SERV_IP, &sin.sin_addr.s_addr) != 1) {perror("inet_pton error");return -1;
}
if (bind(fd, (struct sockaddr*)&sin, sizeof(sin))) {perror("bind error");return -1;
}

3. listen函数

【1】功能:设置套接字监听。调用之前需要bind绑定。

【2】函数原型:int listen(int sockfd, int backlog);

【3】参数说明:

sockfd监听的文件描述符,由socket函数生成
backlog同时处理的最大连接数,一般可取5,最大值为128。表示系统允许2*backlog+1个客户端同时进行三次握手。

【4】返回值:成功返回0,失败返回-1。

【5】代码示例:

if (listen(fd, 128)) {perror("listen error");return -1;
}

4. accept函数

【1】功能:服务器阻塞等待客户端的连接请求,建立新连接,得到通信使用的套接字。

【2】函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

【3】参数说明:

sockfd监听的文件描述符,由socket函数生成
addr传出参数,保存客户端的地址信息
addrlenaddr指向的内存的大小

【4】返回值:成功返回与客户端进行通信的套接字,失败返回-1。

【5】代码示例:

struct sockaddr_in clientAddr;
int clientLen = sizeof(clientAddr);
int cfd = accept(fd, (struct sockaddr*)&clientAddr, &clientLen);
if (cfd < 0) {perror("accept error");return -1;
}

5. connect函数

【1】功能:客户端发起连接请求。成功连接服务器后,客户端会自动随机绑定一个端口。

【2】函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

【3】参数说明:

sockfd套接字,由socket函数生成
addr存储要连接的服务端的IP和端口信息(需要网络字节序)
addrlenaddr指向的内存的大小

【4】返回值:成功返回0,失败返回-1。

【5】代码示例:

#define  SERV_PORT    9999
#define  SERV_IP      "192.168.5.12"struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;//IPv4协议
sin.sin_port = htons(SERV_PORT);//目标服务端的端口号,网络字节序
//目标服务端的IP地址,网络字节序
if (inet_pton(AF_INET, SERV_IP, &sin.sin_addr.s_addr) != 1) {perror("inet_pton error");return -1;
}
//调用connect函数
if (connect(fd, (struct sockaddr*)&sin, sizeof(sin)) == -1) {perror("connect error");return -1;
}

6. read和recv函数

【1】功能:接收数据。

【2】函数原型:

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

【3】参数说明:

sockfd用于通信的文件描述符
buf存储接收的数据
lenbuf指向的内存的容量
flags特殊属性,一般不使用,指定为0

【4】返回值:>0表示实际接收的字节数,==0表示对方断开了连接,-1表示失败。

7. write和send函数

【1】功能:接收数据。

【2】函数原型:

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

【3】参数说明:

sockfd用于通信的文件描述符
buf发送的数据
lenbuf的长度
flags特殊属性,一般不使用,指定为0

【4】返回值:>0表示实际发送的字节数(与len相等),-1表示失败。


三、TCP编程

1. TCP介绍

TCP(Transmission Control Protocol)是面向连接的、可靠的、基于字节流的传输层通信协议。

  • 面向连接:建立连接需要三次握手,断开连接需要四次挥手。
  • 可靠安全:在TCP通信过程中,对每个发送的数据包都会进行校验,若数据丢失则重传。
  • 流式传输:发送端和接收端的处理速度、数据量都可以不一致。

2. TCP通信流程

参考爱编程的大丙:

3. TCP服务端和客户端程序

服务端程序:接收客户端数据并打印,同时将收到的数据重新发回客户端。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>#define SERV_PORT   9999int main(int argc, char **argv)
{/* 1.创建TCP的套接字 */int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd == -1) {perror("server socket error");return -1;}/* 2.绑定IP和端口号 */struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(SERV_PORT);addr.sin_addr.s_addr = INADDR_ANY;//INADDR_ANY宏表示本机所有IPint ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));if (ret == -1) {perror("server bind error");return -1;}/* 3.设置监听 */ret = listen(fd, 128);if (ret == -1) {perror("server listen error");return -1;}/* 4.等待客户端连接 */struct sockaddr_in clientAddr;int clientLen = sizeof(clientAddr);int cfd = accept(fd, (struct sockaddr*)&clientAddr, &clientLen);if (cfd == -1) {perror("server accept error");return -1;}//打印客户端信息char ip[24] = {0};printf("客户端IP: %s, 端口: %d\n",inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, ip, sizeof(ip)),ntohs(clientAddr.sin_port));/* 5.与客户端通信 */while (1) {//接收数据char buf[1024];memset(buf, 0, sizeof(buf));ret = read(cfd, buf, sizeof(buf));if (ret == 0) {printf("服务端:与客户端断开连接\n");break;} else if (ret < 0) {perror("server read error");break;} else {//打印接收数据,并重新发回接收的数据printf("服务端接收: %s\n", buf);write(cfd, buf, sizeof(buf));}}/* 6.关闭文件描述符 */close(cfd);close(fd);return 0;
}

客户端程序:每1秒发送指定数据,同时接收来自服务端的数据。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>#define SERV_PORT   9999
#define SERV_IP     "192.168.124.6"int main(int argc, char **argv)
{/* 1.创建TCP的套接字 */int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd == -1) {perror("client socket error");return -1;}/* 2.与服务端建立连接 */struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(SERV_PORT);inet_pton(AF_INET, SERV_IP, &addr.sin_addr.s_addr);int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));if (ret == -1) {perror("client connect error");return -1;}//打印连接的服务端信息printf("连接的服务端IP: %s, 端口: %d\n", SERV_IP, SERV_PORT);/* 3.与服务端通信 */int number = 0;while (1) {//发送数据char buf[1024] = {0};sprintf(buf, "客户端: number = %d\n", number++);write(fd, buf, sizeof(buf));//接收数据memset(buf, 0, sizeof(buf));ret = read(fd, buf, sizeof(buf));if (ret == 0) {printf("客户端:与服务端断开连接\n");break;} else if (ret < 0) {perror("client read error");break;} else {printf("客户端接收: %s\n", buf);}sleep(1);//发送数据慢一些}/* 4.关闭文件描述符 */close(fd);return 0;
}

4. TCP并发服务器(多进程)

单个服务器需要能够与多个客户端进行通信,因此本节采用多进程方式来实现服务器的并发。

服务器中的父进程:

  • 循环accept,每次与客户端建立新连接后,fork子进程。
  • sigaction捕捉SIGCHLD信号,回收子进程资源。

服务器中的子进程:负责与新连接的客户端进行通信。

注意事项:accept函数阻塞时,若捕捉到SIGCHLD信号那么会取消阻塞并执行信号处理函数,那么处理完毕后accept函数返回值为-1并且错误号为EINTR。我们需要编写相关代码来处理这种情况,让服务器重新调用accept而不是退出。

服务器程序:监听所有的客户端连接,每次建立新连接后都创建子进程来与客户端进行通信。子进程读取从客户端接收的数据并显示。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>#define SERV_PORT   6666/* SIGCHLD处理函数 */
void cycle(int arg)
{while (1) {pid_t cPid = waitpid(-1, NULL, WNOHANG);if (cPid <= 0) {break;} else {printf("子进程%d被回收\n", cPid);}}
}/* 子进程与客户端通信 */
int serverCommu(int fd)
{char buf[1024] = {0};int len = read(fd, buf, sizeof(buf));if (len < 0) {printf("server read error\n");} else if (len == 0) {printf("服务端: 与客户端断开连接\n");} else {printf("服务端接收: %s\n", buf);}return len;
}int main(int argc, char **argv)
{/* 1.创建socket套接字 */int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) {perror("server socket error");return -1;}/* 2.绑定IP和端口 */struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(SERV_PORT);addr.sin_addr.s_addr = INADDR_ANY;int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));if (ret < 0) {perror("server bind error");return -1;}/* 3.设置监听  */ret = listen(fd, 5);if (ret < 0) {perror("server listen error");return -1;}/* 捕捉信号SIGCHLD, 回收子进程 */struct sigaction sact;sact.sa_flags = 0;sact.sa_handler = cycle;sigemptyset(&sact.sa_mask);sigaction(SIGCHLD, &sact, NULL);/* 4.循环等待客户端连接 */while (1) {//accept阻塞等待struct sockaddr_in clientAddr;int clientLen = sizeof(clientAddr);int cfd = accept(fd, (struct sockaddr*)&clientAddr, &clientLen);if (cfd == -1) {if (errno == EINTR) {//SIGCHLD处理后continue;}perror("server accept error");break;}//打印客户端IP和Portchar IPAddr[24];printf("连接的客户端IP: %s, 端口: %d\n",inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, IPAddr, sizeof(IPAddr)),ntohs(clientAddr.sin_port));//创建子进程与客户端通信pid_t pid = fork();if (pid < 0) {//出错perror("fork error");break;} else if (pid == 0) {//子进程close(fd);while (1) {int len = serverCommu(cfd);if (len <= 0) {break;}}close(cfd);return 0;} else {//父进程close(cfd);}}/* 5.父进程关闭套接字 */close(fd);return 0;
}

客户端程序:与服务器建立连接,发送用户输入的数据。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define SERV_PORT   6666
#define SERV_IP     "192.168.5.12"int main(int argc, char **argv)
{/* 1.创建socket套接字*/int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) {perror("client socket error");return -1;}/* 2.建立连接 */struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(SERV_PORT);inet_pton(AF_INET, SERV_IP, &addr.sin_addr.s_addr);int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));if(ret < 0) {perror("client connect error");return -1;}/* 3.与服务端通信 */while (1) {//发送消息char buf[1024] = {0};char *tips = "客户端: ";int tipsLen = strlen(tips);strcpy(buf, tips);if (!fgets(buf+tipsLen, sizeof(buf)-tipsLen, stdin)) {perror("input error");continue;}write(fd, buf, sizeof(buf));}/* 4.关闭套接字 */close(fd);return 0;
}

测试结果:运行一个服务器程序、两个客户端程序,查看对应的结果。

5. TCP并发服务器(多线程)

本节采用多线程的方式来实现TCP并发服务器。

服务器中的主线程:

  • 循环accept,将新建立的连接存储与某个结构体数组元素中。
  • 每有一个客户端连接,就创建一个子线程来与其通信。
  • 子线程分离,使子线程结束后让系统回收资源。

服务器中的子线程:负责与新连接的客户端进行通信。

注意事项:由于线程只有栈空间是独立的,需要注意数据覆盖的问题。在下面的程序案例中,采用一个全局的结构体数组来保存每一个线程对应操作的通信套接字、线程ID、客户端信息。

服务器程序:监听所有的客户端连接,在建立新连接前找到一块没有被占用的内存,保存建立新连接的通讯套接字、子线程ID和客服端信息。创建的子线程用来和客户端通信。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>#define SERV_PORT   6666typedef struct SockInfo {int fd;                 //通信的套接字pthread_t tid;          //线程tidstruct sockaddr_in addr;//保存客户端信息
} SockInfo;SockInfo sInfos[128];       //每个线程对应一个结构体/* 子线程通信操作 */
void* working(void* arg)
{SockInfo *sinfo = (SockInfo*)arg;//打印客户端信息char IP[24] = {0};inet_ntop(AF_INET, &sinfo->addr.sin_addr.s_addr, IP, sizeof(IP));printf("连接的客户端IP: %s, 端口: %d\n", IP, ntohs(sinfo->addr.sin_port));//接收客户端的信息while (1) {char buf[1024] = {0};int ret = read(sinfo->fd, buf, sizeof(buf));if (ret < 0) {perror("server read error");break;} else if (ret == 0) {printf("服务端: 与客户端断开连接\n");break;} else {printf("服务端接收: %s\n", buf);}}//结束前清除数据sinfo->fd = -1;sinfo->tid = -1;memset(&sinfo->addr, 0, sizeof(sinfo->addr));return NULL;
}int main(int argc, char **argv)
{/* 1.创建socket套接字 */int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) {perror("server socket error");return -1;}/* 2.绑定IP和端口 */struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(SERV_PORT);addr.sin_addr.s_addr = INADDR_ANY;int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));if (ret < 0) {perror("server bind error");return -1;}/* 3.设置监听  */ret = listen(fd, 5);if (ret < 0) {perror("server listen error");return -1;}/* 4.主线程循环监听,子线程通信 */int sInfosLen = sizeof(sInfos) / sizeof(sInfos[0]);//初始化结构体数组for (int i = 0; i < sInfosLen; i++){sInfos[i].fd = -1;sInfos[i].tid = -1;memset(&sInfos[i].addr, 0, sizeof(sInfos[i].addr));}//循环监听while (1) {//遍历sInfos,找到未占用的元素SockInfo *pinfo = NULL;for (int i = 0; i < sInfosLen; i++){if (sInfos[i].fd == -1) {pinfo = &sInfos[i];break;}if (i == sInfosLen - 1) {//若全部被占用,停留在最后sleep(1);--i;}}//存在新连接,创建子线程,保存于pinfo指向的结构体int sockaddrLen = sizeof(pinfo->addr);int connfd = accept(fd, (struct sockaddr*)&pinfo->addr, &sockaddrLen);if (connfd < 0) {perror("server accept error");break;}pinfo->fd = connfd;pthread_create(&pinfo->tid, NULL, working, pinfo);pthread_detach(pinfo->tid);}/* 5.主线程关闭套接字 */close(fd);return 0;
}

客户端程序:与服务器建立连接,发送用户输入的数据。(与多进程的版本相同)

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define SERV_PORT   6666
#define SERV_IP     "192.168.5.12"int main(int argc, char **argv)
{/* 1.创建socket套接字*/int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) {perror("client socket error");return -1;}/* 2.建立连接 */struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(SERV_PORT);inet_pton(AF_INET, SERV_IP, &addr.sin_addr.s_addr);int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));if(ret < 0) {perror("client connect error");return -1;}/* 3.与服务端通信 */while (1) {//发送消息char buf[1024] = {0};char *tips = "客户端: ";int tipsLen = strlen(tips);strcpy(buf, tips);if (!fgets(buf+tipsLen, sizeof(buf)-tipsLen, stdin)) {perror("input error");continue;}write(fd, buf, sizeof(buf));}/* 4.关闭套接字 */close(fd);return 0;
}

测试结果:运行一个服务器程序、两个客户端程序,查看对应的结果。

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

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

相关文章

Android-- 集成谷歌地图

引言 项目需求需要在谷歌地图&#xff1a; 地图展示&#xff0c;设备点聚合&#xff0c;设备站点&#xff0c;绘制点和区域等功能。 我只针对我涉及到的技术做一下总结&#xff0c;希望能帮到开始接触谷歌地图的伙伴们。 集成步骤 1、在项目的modle的build.gradle中添加依赖如…

WSL-Ubuntu20.04部署环境配置

1.更换Ubuntu软件仓库镜像源 为了在WSL上使用TensorRT进行推理加速&#xff0c;需要安装以下环境&#xff0c;下面将按以下顺序分别介绍安装、验证以及删除环境&#xff1a; #1.C环境配置 gcc、gdb、g #2.gpu环境 cuda、cudnn #3.Cmake环境 CMake #4.OpenCV环境 OpenCV #5.Ten…

在mybatis-plus中关于@insert注解自定义批处理sql导致其雪花算法失效而无法自动生成id的解决方法

受到这位作者的启发 > 原文在点这里 为了自己实现批量插入&#xff0c;我在mapper层使用insert注解写了一段自定义sql //自定义的批量插入方法 Insert("<script>" "insert into rpt_material_hour(id,sample_time,rounding_time,cur_month,machine_no…

Web3时代的教育技术革新:智能合约在学习管理中的应用

随着区块链技术的发展和普及&#xff0c;Web3时代正在为教育技术带来前所未有的革新和机遇。智能合约作为区块链技术的核心应用之一&#xff0c;不仅在金融和供应链管理等领域展示了其巨大的潜力&#xff0c;也在教育领域中逐渐探索和应用。本文将探讨智能合约在学习管理中的具…

分词任务介绍-(十)

分词任务 中文分词正向最大匹配实现方式一实现方式二 反向最大匹配双向最大匹配jieba分词上述分词方法的缺点总结基于机器学习 总结分词技术经验总结 中文分词 正向最大匹配 分词的步骤 1.收集整理一个词表&#xff0c;类似于字典。如下图 2.对于待分词的句子&#xff0c;或者…

总结单例模式的写法

一、单例模式的概念 1.1 单例模式的概念 单例模式&#xff08;Singleton Pattern&#xff09;是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。就是当前进程确保一个类全局只有一个实例。 1.2 单例模式的优…

2024 China Joy 前瞻 | 腾讯网易发新作,网易数智携游戏前沿科技、创新产品以及独家礼盒,精彩不断!

今年上半年&#xff0c;CES、MWC和AWE三大国际科技展轮番轰炸&#xff0c;吸引全球科技爱好者的高度关注&#xff0c;无论是新潮的科技产品&#xff0c;还是对人工智能的探索&#xff0c;每一项展出的技术和产品都引起了市场的热议。而到了下半年&#xff0c;一年一度的China J…

Kafka消息队列python开发环境搭建

目录 引言 Kafka 的核心概念和组件 Kafka 的主要特性 使用场景 申请云服务器 安装docker及docker-compose VSCODE配置 开发环境搭建 搭建Kafka的python编程环境 Kafka的python编程示例 引言 Apache Kafka 是一个分布式流处理平台&#xff0c;由 LinkedIn 开发并在 2…

Android View的绘制流程

1.不管是View的添加&#xff0c;还是调用View的刷新方法invalidate()或者requestLayout()&#xff0c;绘制都是从ViewRootImpl的scheduleTraversals()方法开始 void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled true;mTraversalBarrier mHandler…

SpringCloud教程 | 第九篇: 使用API Gateway

1、参考资料 SpringCloud基础篇-10-服务网关-Gateway_springcloud gateway-CSDN博客 2、先学习路由&#xff0c;参考了5.1 2.1、建了一个cloudGatewayDemo&#xff0c;这是用来配置网关的工程&#xff0c;配置如下&#xff1a; http://localhost:18080/aaa/name 该接口代码如…

科普文:详解23种设计模式

概叙 设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结&#xff0c;其中最出名的当属 Gang of Four&#xff08;GoF&#xff09;的分类了&#xff0c;他们将设计模式分类为 23 种经典的模式&#xff0c;根据用途我们又可以分为三大类&#xff0c;分别为创建型模式…

等保-Linux等保测评

等保-Linux等保测评 1.查看相应文件&#xff0c;账户xiaoming的密码设定多久过期 rootdengbap:~# chage -l xiaoming Last password change : password must be changed Password expires : pass…

理解类与对象:面向对象基础

目录 1. 类的定义1.1 格式1.2 访问限定符1.3 类域 2.实例化2.1 实例化概念2.2 对象大小 3.this指针 1. 类的定义 1.1 格式 class为定义类的关键字&#xff0c;Date为类的名字&#xff0c;{ }中为类的主体&#xff0c;注意类定义结束后面的分号不能省略。类体中内容称为类的成…

【博士每天一篇文献-算法】连续学习算法之HNet:Continual learning with hypernetworks

阅读时间&#xff1a;2023-12-26 1 介绍 年份&#xff1a;2019 作者&#xff1a;Johannes von Oswald&#xff0c;Google Research&#xff1b;Christian Henning&#xff0c;EthonAI AG&#xff1b;Benjamin F. Grewe&#xff0c;苏黎世联邦理工学院神经信息学研究所 期刊&a…

如何在项目中打印sql和执行的时间

目标&#xff1a;打印DAO方法中sql和执行的时间 一种方式是去实现Mybatis的拦截器Interceptor &#xff0c;比较麻烦&#xff1b; 这里介绍一种比较简单的实现方式&#xff1b; 1、如何打印sql&#xff1f; 配置文件加这个可以打印出com.zhenhui.ids.busi.watch包下执行的sq…

3D线上展厅:元宇宙时代的营销利器,流量暴增的秘密武器!

在体验经济蓬勃发展的当下&#xff0c;企业营销领域正以前所未有的热情探索创新路径&#xff0c;元宇宙这一融合了无限想象与未来科技的概念&#xff0c;成为了众多品牌竞相追逐的新蓝海。3D技术、增强现实&#xff08;AR&#xff09;、虚拟现实&#xff08;VR&#xff09;以及…

【ProtoBuf】proto 3 语法 -- 详解

这个部分会对通讯录进行多次升级&#xff0c;使用 2.x 表示升级的版本&#xff0c;最终将会升级如下内容&#xff1a; 不再打印联系人的序列化结果&#xff0c;而是将通讯录序列化后并写入文件中。 从文件中将通讯录解析出来&#xff0c;并进行打印。 新增联系人属性&#xff…

谈谈大数据采集和常见问题

01 什么是数据采集 数据采集是大数据的基石&#xff0c;不论是现在的互联网公司&#xff0c;物联网公司或者传统的IT公司&#xff0c;每个业务流程环节都会产生大量的数据&#xff0c;同时用户操作的日志也会产生大量的数据&#xff0c;为了将这些结构化和非结构化的数据进行…

【常见开源库的二次开发】基于openssl的加密与解密——单向散列函数(四)

目录&#xff1a; 目录&#xff1a; 一、什么是单项散列函数&#xff1f; 1.1 如何验证文件是否被修改过 1.2 单项散列函数&#xff1a; 二、单向hash抗碰撞 2.1 弱抗碰撞&#xff08;Weak Collision Resistance&#xff09; 2.2 强抗碰撞&#xff08;Strong Collision Resista…

Webpack详解

Webpack Webpack 是一个现代 JavaScript 应用程序的静态模块打包器&#xff08;module bundler&#xff09;。它允许开发者将项目中的资源&#xff08;如 JavaScript、CSS、图片等&#xff09;视为模块&#xff0c;通过分析和处理这些模块之间的依赖关系&#xff0c;将它们打包…