• Linux系统编程_网络编程:字节序、socket、server&client、ftp 云盘


    1. 网络编程概述(444.1)

    TCP/UDP对比

    1. TCP 面向连接(如打电话要先拨号建立连接);UDP 是无连接的,即发送数据之前不需要建立连接
    2. TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,即不保证可靠交付
    3. TCP 面向字节流,实际上是 TCP 把数据看成一连串无结构的字节流;UDP 是面向报文的 UDP 没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如 IP 电话,实时视频会议等)
    4. 每一条 TCP 连接只能是点到点的;UDP 支持一对一,一对多,多对一和多对多的交互通信
    5. TCP 首部开销 20 字节;UDP 的首部开销小,只有 8 个字节
    6. TCP 的逻辑通信信道是全双工的可靠信道,UDP 则是不可靠信道

    端口号作用

    • 一台拥有 IP 地址的主机可以提供许多服务,比如 Web 服务、FTP 服务、SMTP 服务等
    • 这些服务完全可以通过 1 个 IP 地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠 IP 地址,因为 IP 地址与网络服务的关系是一对多的关系。
    • 实际上是通过 “IP 地址 + 端口号” 来区分不同的服务的。端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。例如,对于每个 TCP/IP 实现来说,FTP 服务器的 TCP 端口号都是 21,每个 Telnet 服务器的 TCP 端口号都是 23,每个 TFTP (简单文件传送协议)服务器的 UDP 端口号都是 69。

    在这里插入图片描述

    2. 字节序(445.2)

    • 字节序,即字节在电脑中存放时的序列与输入(输出)时的序列是先到的在前还是后到的在前。
    • 字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
    1. Little endian:将低序字节存储在起始地址
    2. Big endian:将高序字节存储在起始地址
    • LE little-endian 小端字节序
    • BE big-endian 大端字节序
    • 网络字节序 = 大端字节序
      在这里插入图片描述

    字节序转换api

    #include 
    
    uint16_t htons(uint16_t host16bitvalue);    //返回网络字节序的值
    uint32_t htonl(uint32_t host32bitvalue);    //返回网络字节序的值
    uint16_t ntohs(uint16_t net16bitvalue);     //返回主机字节序的值
    uint32_t ntohl(uint32_t net32bitvalue);     //返回主机字节序的值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • h代表host主机,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取

    3. socket编程步骤(446.3)

    在这里插入图片描述

    Sockt服务器和客户端的开发步骤

    在这里插入图片描述

    • TCP Server
    1. 创建套接字(socket)
    2. 为套接字添加信息(IP地址和端口号)(bind)
    3. 监听网络连接(listen)
    4. 监听到有客户端接入,接受一个连接(accept)
    5. 数据交互(read、write、read)
    6. 关闭套接字,断开连接(close)

    4. Linux提供的API简析(447.4)

    1. 指定讲“汉语”(连接协议)
      在这里插入图片描述
    • int socket(int domain, int type, int protocol);
      在这里插入图片描述
    1. 地址准备好
      在这里插入图片描述
      在这里插入图片描述
    • 地址转换API
      • int inet_aton(const char* straddr,struct in_addr *addrp);
        把字符串形式的“192.168.1.123”转为网络能识别的格式
      • char* inet_ntoa(struct in_addr inaddr);
        把网络格式的ip地址转为字符串形式
    1. 监听
      在这里插入图片描述
      在这里插入图片描述

    2. 连接
      在这里插入图片描述

    3. 数据收发
      5.1 数据收发常用第一套API
      在这里插入图片描述

      5.2 数据收发常用第二套API
      在这里插入图片描述

    4. 客户端的connect函数
      在这里插入图片描述

    5. socket服务端代码实现一(448.5)

    man 2 socketman 2 bindman htonsman inet_aton

    查找某段源码在哪个头文件之下的方法

    • cd /usr/include
    • ls
    • grep "struct sockaddr_in {" * -nir
      • 搜索其在哪个头文件下被定义/声明
      • ※ (星号) 在当前目录底下
      • n 找出时显示其所在行号
      • i 不区分大小写
      • r 递归的方式
    • vi linux/in.h +261
      在这里插入图片描述
      在这里插入图片描述
    • grep "struct in_addr {" * -nir
    • vi linux/in.h +89
      在这里插入图片描述
      在这里插入图片描述

    代码相关

    • SOCKET/server.c
    #include //printf
    #include //socket bind          /* See NOTES */
    #include //socket bind
    //#include //struct sockaddr_in//和冲突
    #include //htons
    #include //inet_aton
    #include //exit
    //int socket(int domain, int type, int protocol);
    //int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    /*struct sockaddr_in {//也可在中找到
      __kernel_sa_family_t  sin_family;     /* Address family               */
    //__be16                sin_port;       /* Port number                  */
    //struct in_addr        sin_addr;       /* Internet address             */
    
      /* Pad to size of `struct sockaddr'. */
    //unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -
    //                      sizeof(unsigned short int) - sizeof(struct in_addr)];
    //};*/
    /*struct in_addr {
            __be32  s_addr;
    };*/
    //int listen(int sockfd, int backlog);
    //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//fd,客户端地址,客户端地址长度地址,返回新的建立的socket的通道
    int main(int argc, char **argv)
    {
    	int s_fd;
    	
    	//1. socket 获取/创建套接字
    	s_fd = socket(AF_INET, SOCK_STREAM, 0);//IPv4 因特网域,TCP协议,自动选择type类型对应的默认协议(也可用宏:IPPROTO_TCP TCP)
    	if(s_fd == -1){//判断建立socket通道是否成功
    		perror("socket");//把错误的问题打出来
    		exit(-1);//退出这个程序
    	}
    	//2. bind 绑定
    	struct sockaddr_in s_addr;
    	s_addr.sin_family = AF_INET;//协议族,跟domain一致
    	s_addr.sin_port = htons(8989);//端口号一般3000以下为操作系统来用,建议用户5000以上  端口号由主机to网络 返回网络字节序的值
    	inet_aton("192.168.2.13",&s_addr.sin_addr);//本机ip地址由字符串形式to网络格式//ifconfig
    
    	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//把ip地址端口号和s_fd绑定起来//fd,强转为struct sockaddr型,结构体的长度
    
    	//3. listen
    	listen(s_fd,10);//监听10个连接,不阻塞
    	
    	//4. accept 接受
    	int c_fd = accept(s_fd,NULL,NULL);//先不关心客户端是谁//如果没连接到,会卡在这//后续的操作用新的c_fd
    	
    	//5. read
    	//6. write
    	
    	printf("connected!\n");
    	while(1)//有客户端连上,不让其退出
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    在这里插入图片描述
    在这里插入图片描述

    • ifconfig 本机网络地址和本机回环地址:
      在这里插入图片描述

    win10 telnet不是内部或外部命令(已解决)

    • telnet 在win10下默认是不开启的,所以需要我们自己手动开启。
    1. win+s 搜索:控制面板,点击进入;
      在这里插入图片描述
    2. 在控制面板中,选择程序——启动或关闭 windows 功能,勾选 Telnet 客户端选项,确定进行安装。
      在这里插入图片描述
      勾选 “Telnet 客户端”
      在这里插入图片描述
      更改 windows 设置需要一分钟左右的时间,不要关闭。安装完成后重启计算机生效。(实测可不重启)
      在这里插入图片描述
    3. windows功能的telnet功能已经开启,我们测试下是否可以正常使用,输入telnet+ip地址。
      在这里插入图片描述

    6. socket服务端代码实现二(449.6)

    • SOCKET/server2.c(建立服务器并等待客户端连接 后收发)
    #include //printf
    #include //socket bind          /* See NOTES */
    #include //socket bind
    //#include //struct sockaddr_in//和冲突
    #include //htons
    #include //inet_aton
    #include //exit
    #include //memset
    #include //read write
    //int socket(int domain, int type, int protocol);
    //int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    /*struct in_addr {
            __be32  s_addr;
    };*/
    //int listen(int sockfd, int backlog);
    //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//fd,客户端地址,客户端地址长度,返回新的建立的socket的通道
    int main()
    {
    	int s_fd;//1. socket
    	int c_fd;//4. accept
    	int n_read;//5. read
    	char readBuf[128];//5. read//定义一个数组就不用像指针还要开辟空间
    	char *msg = "I get your connection\n";//6. write
    	struct sockaddr_in s_addr;//2. bind
    	struct sockaddr_in c_addr;//2. bind
    	memset(&s_addr,0,sizeof(struct sockaddr_in));//清空
    	memset(&c_addr,0,sizeof(struct sockaddr_in));//清空
    	//1. socket 获取/创建套接字
    	s_fd = socket(AF_INET, SOCK_STREAM, 0);//IPv4 因特网域,TCP协议,自动选择type类型对应的默认协议(也可用宏:IPPROTO_TCP TCP)
    	if(s_fd == -1){//判断建立socket通道是否成功
    		perror("socket");//把错误的问题打出来
    		exit(-1);//退出这个程序
    	}
    	//2. bind 绑定
    	s_addr.sin_family = AF_INET;//协议族,跟domain一致
    	s_addr.sin_port = htons(8989);//端口号一般3000以下为操作系统来用,建议用户5000以上  端口号由主机to网络 返回网络字节序的值
    	inet_aton("192.168.2.13",&s_addr.sin_addr);//本机ip地址由字符串形式to网络格式//ifconfig
    	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//把ip地址端口号和s_fd绑定起来
    	//3. listen
    	listen(s_fd,10);//监听10个连接
    	//4. accept 接受
    	int clen = sizeof(struct sockaddr_in);
    	c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);//如果没连接到,会卡在这
    	if(c_fd == -1){
    		perror("accept");
    	}
    	printf("get connected: %s\n",inet_ntoa(c_addr.sin_addr));//连上后打印 “已连接” 和客户端的 ip地址
    	//5. read
    	n_read = read(c_fd, readBuf, 128);
    	if(n_read == -1){
    		perror("read");
    	}else{
    		printf("get message:%d,%s\n",n_read,readBuf);//打印收到了多少个字节的消息,和消息的内容
    	}
    	//6. write
    	write(c_fd,msg,strlen(msg));//用提前定义的字符串,如直接写入字符串,字节大小也随意写如128,会造成乱码因有无用的字节
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    在这里插入图片描述

    7. socket客户端代码实现(450.7)

    • man 2 connect
    • SOCKET/client.c(连接服务器后 客户端发收)
    #include 
    #include           /* See NOTES */
    #include 
    //#include 
    #include 
    #include 
    #include 
    #include 
    #include //read write
    //int socket(int domain, int type, int protocol);
    //int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    int main(int argc, char **argv)
    {
    	int c_fd;//1. socket
    	int n_read;//4. read
    	char readBuf[128];//4. read
    	char *msg = "I'm a msg from Client";//3. write
    	//1. socket 获取/创建套接字
    	c_fd = socket(AF_INET, SOCK_STREAM, 0);
    	if(c_fd == -1){
    		perror("socket");
    		exit(-1);
    	}
    	//2.connect//连接服务器	
    	struct sockaddr_in c_addr;
    	memset(&c_addr,0,sizeof(struct sockaddr_in));
    	c_addr.sin_family = AF_INET;
    	c_addr.sin_port = htons(8989);
    	inet_aton("192.168.2.13",&c_addr.sin_addr);//服务器的ip地址
    	if(connect(c_fd, (struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){//连接时会阻塞,直到出结果为之
    		perror("connect");
    		exit(-1);
    	}
    	//3. write//写
    	write(c_fd,msg,strlen(msg));
    	//4. read//读		
    	n_read = read(c_fd, readBuf, 128);
    	if(n_read == -1){
    		perror("read");
    	}else{
    		printf("get message from server:%d,%s\n",n_read,readBuf);//打印收到了多少个字节的消息,和消息的内容
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    在这里插入图片描述

    8. 实现双方聊天(451.8)

    在这里插入图片描述

    • SOCKET/server3.c(服务器可以一直接收多个客户端的连接,并和某个客户端互发消息)
    #include //printf
    #include //socket bind          /* See NOTES */
    #include //socket bind
    //#include //struct sockaddr_in//冲突
    #include //htons
    #include //inet_aton
    #include //exit
    #include //memset
    #include //read write
    //int socket(int domain, int type, int protocol);
    //int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    /*struct in_addr {
            __be32  s_addr;
    };*/
    //int listen(int sockfd, int backlog);
    //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//fd,客户端地址,客户端地址长度,返回新的建立的socket的通道
    int main(int argc, char **argv)
    {
    	int s_fd;//1. socket
    	int c_fd;//4. accept
    	int n_read;//5. read
    	char readBuf[128];//5. read
    	//	char *msg = "I get your connection";
    	char msg[128] = {0};//6. write
    	struct sockaddr_in s_addr;//2. bind
    	struct sockaddr_in c_addr;//2. bind
    	memset(&s_addr,0,sizeof(struct sockaddr_in));
    	memset(&c_addr,0,sizeof(struct sockaddr_in));
    	
    	if(argc != 3){//如果没有输入参数 会提示
    		printf("param is not good\n");
    		exit(-1);
    	}
    	//1. socket 获取套接字
    	s_fd = socket(AF_INET, SOCK_STREAM, 0);//IPv4 因特网域,TCP协议,自动选择type类型对应的默认协议(也可用宏:IPPROTO_TCP TCP)
    	if(s_fd == -1){//判断建立socket通道是否成功
    		perror("socket");//把错误的问题打出来
    		exit(-1);//退出这个程序
    	}
    
    	//2. bind 绑定
    	s_addr.sin_family = AF_INET;//协议族,跟domain一致
    	s_addr.sin_port = htons(atoi(argv[2]));//字符串转换为整形数
    	inet_aton(argv[1],&s_addr.sin_addr);//本机ip地址由字符串形式to网络格式//ifconfig
    	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//把ip地址端口号和s_fd绑定起来
    
    	//3. listen
    	listen(s_fd,10);//监听10个连接
    	
    	//4. accept 接受连接
    	int clen = sizeof(struct sockaddr_in);
    	while(1){//不让程序退出 一直循环进行//不要在while(1)里定义变量
    	//同时收和发
    		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);//如果没连接到,会卡在这
    		if(c_fd == -1){
    			perror("accept");
    		}
    		printf("get connected: %s\n",inet_ntoa(c_addr.sin_addr));//连上后打印 “已连接” 和客户端的 ip地址
    		if(fork() == 0){//在网络服务进程中父进程等待客户端的服务请求 当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达
    			//6. write
    			if(fork()==0){//如果要用两个while(1),要么用多线程,要么用fork创建子进程
    				while(1){
    					memset(msg,0,sizeof(msg));
    					printf("input: ");
    					//gets(msg);//gets不安全,已经被废弃
    					fgets(msg,sizeof(msg),stdin);//阻塞
    					msg[strcspn(msg, "\n")] = '\0';//移除输入中的换行符
    					write(c_fd,msg,strlen(msg));//6. write
    				}	
    			}	
    			//5. read
    			while(1){
    				memset(readBuf,0,sizeof(readBuf));//每次写之前清空
    				n_read = read(c_fd, readBuf, 128);//阻塞
    				if(n_read == -1){
    					perror("read");
    				}else{
    					printf("get message from client:%d,%s\n",n_read,readBuf);//打印收到了多少个字节的消息,和消息的内容
    				}
    			}
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • SOCKET/client2.c(多个客户端分别连接服务器,并和服务器互发消息)
    #include 
    #include           /* See NOTES */
    #include 
    //#include 
    #include 
    #include 
    #include 
    #include 
    #include //read write
    //int socket(int domain, int type, int protocol);
    //int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    //char *fgets(char *s, int size, FILE *stream);
    int main(int argc, char **argv)
    {
    	int c_fd;//1. socket
    	int n_read;//4. read
    	char readBuf[128];//4. read
    	//char *msg = "I'm a msg from Client";//3. write	
    	char msg[128] = {0};
    	if(argc != 3){//如果没有输入参数 会提示
    		printf("param is not good\n");
    		exit(-1);
    	}
    	//1. socket 获取/创建套接字
    	c_fd = socket(AF_INET, SOCK_STREAM, 0);
    	if(c_fd == -1){
    		perror("socket");
    		exit(-1);
    	}
    	//2.connect 连接服务器	
    	struct sockaddr_in c_addr;
    	memset(&c_addr,0,sizeof(struct sockaddr_in));
    	c_addr.sin_family = AF_INET;
    	c_addr.sin_port = htons(atoi(argv[2]));
    	inet_aton(argv[1],&c_addr.sin_addr);//服务器的ip地址
    	if(connect(c_fd, (struct sockaddr *)&c_addr,sizeof(struct sockaddr)) == -1){//连接时阻塞 成功后往下
    		perror("connect");
    		exit(-1);
    	}
    		
    	while(1){//不让程序退出 一直循环进行
    	//同时发和收
    		//3. write 写
    		if(fork()==0){//如果要用两个while(1),要么用多线程,要么用fork创建子进程
    			while(1){
    				memset(msg,0,sizeof(msg));//每次写之前清空
    				printf("input: ");
    				//gets(msg);//gets不安全,已经被废弃
    				fgets(msg,sizeof(msg),stdin);//阻塞
    				msg[strcspn(msg, "\n")] = '\0';//移除输入中的换行符
    				write(c_fd,msg,strlen(msg));
    			}
    		}
    		//4. read 读
    		while(1){
    			memset(readBuf,0,sizeof(readBuf));
    			n_read = read(c_fd, readBuf, 128);//阻塞
    			if(n_read == -1){
    				perror("read");
    			}else{
    				printf("get message from server:%d,%s\n",n_read,readBuf);//打印收到了多少个字节的消息,和消息的内容
    			}
    		}
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    在这里插入图片描述

    9. 多方消息收发(452.9)

    • 上节课的逻辑:shell终端获得输入,并不知道是哪个子进程的输入,所以返回给客户端也并不知道是哪个(子进程之间存在资源竞争)
      在这里插入图片描述
    • SOCKET/server4.c(自动主动回复给客户端消息,类似心跳包)
    #include //printf
    #include //socket bind          /* See NOTES */
    #include //socket bind
    //#include //struct sockaddr_in//冲突
    #include //htons
    #include //inet_aton
    #include //exit
    #include //memset
    #include //read write
    //int socket(int domain, int type, int protocol);
    //int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    /*struct in_addr {
            __be32  s_addr;
    };*/
    //int listen(int sockfd, int backlog);
    //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//fd,客户端地址,客户端地址长度,返回新的建立的socket的通道
    int main(int argc, char **argv)
    {
    	int s_fd;//1. socket
    	int c_fd;//4. accept
    	int n_read;//5. read
    	int mark = 0;//第几个客户端
    	char readBuf[128];//5. read
    	//	char *msg = "I get your connection";
    	char msg[128] = {0};//6. write
    	struct sockaddr_in s_addr;//2. bind
    	struct sockaddr_in c_addr;//2. bind
    	memset(&s_addr,0,sizeof(struct sockaddr_in));
    	memset(&c_addr,0,sizeof(struct sockaddr_in));
    	
    	if(argc != 3){//如果没有输入参数 会提示
    		printf("param is not good\n");
    		exit(-1);
    	}
    	//1. socket 获取套接字
    	s_fd = socket(AF_INET, SOCK_STREAM, 0);//IPv4 因特网域,TCP协议,自动选择type类型对应的默认协议(也可用宏:IPPROTO_TCP TCP)
    	if(s_fd == -1){//判断建立socket通道是否成功
    		perror("socket");//把错误的问题打出来
    		exit(-1);//退出这个程序
    	}
    
    	//2. bind 绑定
    	s_addr.sin_family = AF_INET;//协议族,跟domain一致
    	s_addr.sin_port = htons(atoi(argv[2]));//字符串转换为整形数
    	inet_aton(argv[1],&s_addr.sin_addr);//本机ip地址由字符串形式to网络格式//ifconfig
    	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//把ip地址端口号和s_fd绑定起来
    
    	//3. listen
    	listen(s_fd,10);//监听10个连接
    	
    	//4. accept 接受连接
    	int clen = sizeof(struct sockaddr_in);
    	while(1){//不让程序退出 一直循环进行//不要在while(1)里定义变量
    	//同时收和发
    		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);//如果没连接到,会卡在这
    		if(c_fd == -1){
    			perror("accept");
    		}
    		mark++;
    		printf("get connected: %s\n",inet_ntoa(c_addr.sin_addr));//连上后打印 “已连接” 和客户端的 ip地址
    		if(fork() == 0){//在网络服务进程中父进程等待客户端的服务请求 当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达//一有客户端接入就调用子进程
    			//6. write
    			if(fork()==0){//如果要用两个while(1),要么用多线程,要么用fork创建子进程
    				while(1){
    					sprintf(msg,"welcome NO.%d Client",mark);//每个客户端都能知道其连接有无丢失
    					write(c_fd,msg,strlen(msg));//6. write//对于服务端都能收到客户端的请求
    					sleep(3);//类似心跳包,每隔3s发一串话
    				}	
    			}	
    			//5. read
    			while(1){//接收每一个客户端发来的消息
    				memset(readBuf,0,sizeof(readBuf));//每次写之前清空
    				n_read = read(c_fd, readBuf, 128);//阻塞
    				if(n_read == -1){
    					perror("read");
    				}else{
    					printf("get message from NO.%d Client :%d,%s\n",mark,n_read,readBuf);//打印收到了多少个字节的消息,和消息的内容
    				}
    			}
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    在这里插入图片描述

    10. 项目运行结果(453.1)

    在这里插入图片描述

    11. 项目研发思路(454.2)

    客户端要实现的指令:

    • get a.c:获取服务器的文件 a.c
    • put a.c:上传文件 a.c 到服务器
    • cd a:进入服务器某文件夹 a
    • ls:展示服务器有哪些文件和文件夹
    • pwd:显示当前文件夹所在路径
    • lcd aa:进入客户端本地的某文件夹 aa
    • lls:展示客户端本地有哪些文件和文件夹
    • quit:退出连接

    服务端去解析这些指令,执行这些指令,返回这些指令的结果

    • 自行搜索 “Linux 判断文件是否存在” 的函数,写 demo 测试,再整合进项目中
      在这里插入图片描述
    • recv 和 read 区别:
      • recv 第四个参数 MSG_PEEK 就可以多次读,其返回值等0时表示客户端连接断开,小于0读数据失败
      • read 只能读一次套接字中的数据就无了
        在这里插入图片描述
        在这里插入图片描述

    12. 项目指导(455.3)

    • LinuxC实现FTP云盘
      • https://blog.csdn.net/weixin_54076783/article/details/127807745?spm=1001.2014.3001.5506
    • 服务端
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    • 客户端
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    • FTP/server.c
    #include //socket bind..          /* See NOTES */
    #include //socket bind listen accept
    #include //open
    #include //open
    #include //htons
    #include //fflush printf popen
    #include //exit
    #include //memset
    #include //read write
    
    #define LS  8
    #define CD  5
    #define PWD 9
    #define PUT 6
    #define GET 10
    #define QUIT 4
    #define GETFILE 11
    //int access(const char * pathname,int mode);
    //int chdir(const char *path);
    struct Msg 
    {
    	int type;
    	char cmd[50];
    	char data[5120];
    };
    
    int get_cmd_type(char *cmd)//命令识别
    {
    	cmd = strtok(cmd,"\n");//客户端获取用户输入时会使用enter键,so传输到服务器的命令都带有换行符,把\n分割走
    	if(!strcmp("ls",cmd))		return LS;//俩字符串相等返回0
    	if(strstr(cmd,"cd") != NULL)	return CD;//如果前包含后,返回后在前首次出现的地址
    	if(strstr(cmd,"get") != NULL)	return GET;
    	if(strstr(cmd,"put") != NULL)	return PUT;
    	if(!strcmp("pwd",cmd))		return PWD;	
    	if(!strcmp("quit",cmd))		return QUIT;	
    	
    	return 100;//在未识别的命令类型时可以执行特定的操作
    }
    
    char *str_decompose(char *cmd)//命令分割//将带有文件名、路径名的命令进行处理分割
    {				       //如命令“get 1.c”,该函数就会返回1.c
    	strtok(cmd," ");//第一个单词
    	cmd = strtok(NULL," ");//第二个单词往后
    	return cmd;
    }
    
    void cmd_handle(int ret,struct Msg msg,int c_fd)//命令处理
    {
    	FILE *fp  = NULL;
    	int n_fread;	
    	char *dir = NULL;
    	char *file = NULL;
    	//char *file_path = "./";//当前路径
    	int file_fd;
    	int file_size;
    	switch(ret){
    		case LS:
    		case PWD:
    			msg.type = 0;
    			fp = popen(msg.cmd,"r");//调用终端执行ls/pwd命令,并将数据存放到fp文件流中
    			if(fp == NULL){
    				perror("popen");
    			}
    			memset(msg.data,0,sizeof(msg.data));
    			n_fread = fread(msg.data,1,sizeof(msg.data),fp);//读取文件流的数据到msg.data
    			write(c_fd,&msg,sizeof(msg));//向客户端发送/返回数据
    			fflush(stdout);//强制刷新标准输出缓冲区,把输出缓冲区里的东西强制打印到标准输出设备(终端)
    			break;
    		case CD:
    			dir = str_decompose(msg.cmd);
    			printf("dir:%s\n",dir);//打印需要cd的目录名
    			if(chdir(dir) <0){//修改当前的工作目录
    				perror("chdir");
    			}
    			break;
    		case GET://下载文件内容到本地的文件
    			file = str_decompose(msg.cmd);
    			if(access(file,F_OK) < 0){  //判断服务器是否存在客户端想要下载的文件
    				msg.type = 0;
    				memset(msg.data,0,sizeof(msg.data));			
    				strcpy(msg.data,"Target file doesn't exist!");
    				write(c_fd,&msg,sizeof(msg));
    			}else if(access(file,R_OK) < 0){  //判断服务器是否对该文件有读权限
    				msg.type = 0;
    				memset(msg.data,0,sizeof(msg.data));
    				strcpy(msg.data,"The target file doesn't have read-permission!");
    				write(c_fd,&msg,sizeof(msg));
    			}else{  //获取该文件的内容,发送给客户端
    				msg.type = GETFILE;//下载文件
    				file_fd = open(file,O_RDWR);//打开文件
    				if(file_fd < 0){
    					perror("open");
    				}
    				file_size = lseek(file_fd,0,SEEK_END);
    				if(file_size < 0){
    					perror("lseek");
    				}
    				lseek(file_fd,0,SEEK_SET);//光标偏移至头
    				memset(msg.data,0,sizeof(msg.data));
    				if(read(file_fd,msg.data,file_size) < 0){//把文件内容读到msg.data
    					perror("read");
    				}
    				close(file_fd);
    				write(c_fd,&msg,sizeof(msg));
    			}
    			break;	
    		case PUT://创建一个文件,写入客户端想要存放的文件的数据(上传)
    			file = str_decompose(msg.cmd);
    			file_fd = open(file,O_RDWR|O_CREAT,0666);
    			if(file_fd < 0){
    				perror("open");
    			}
    			if(write(file_fd,msg.data,strlen(msg.data)) < 0){
    				perror("write");
    			}
    			close(file_fd);
    			break;
    		case QUIT:
    			printf("client quit!\n");
    			exit(0);
    			break;
    	}
    }
    
    int main(int argc,char **argv)
    {	
    	if(argc != 3){//参数不足或不一致的提示
    		printf("Insufficient or inconsistent parameters\n");
    		exit(-1);
    	}
    	
    	//1. socket	
    	int s_fd = socket(AF_INET,SOCK_STREAM,0); //创建套接字:IPV4因特网域,流式套接字、TCP传输协议
    	if(s_fd == -1){
    		printf("socket failed\n");
    		perror("socket");
    		exit(-1);
    	}
    	
    	//2. bind
    	struct sockaddr_in s_addr;
    	memset(&s_addr,0,sizeof(struct sockaddr_in));	
    	s_addr.sin_family = AF_INET;//IPV4因特网域
    	s_addr.sin_port = htons(atoi(argv[2]));//将端口号转化成网络字节序
    	inet_aton(argv[1],&s_addr.sin_addr);//将IP地址转化为网络字节序
    	
    	int bd = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//为套接字添加网络信息		
    	if(bd == -1){
    		printf("bind failed\n");
    		perror("bind");
    		exit(-1);	
    	}
    	
    	//3. listen
    	int ln= listen(s_fd,10);//监听10个客户端的连接
    	if(ln == -1){
    		printf("listen failed\n");
    		perror("listen");
    		exit(-1);
    	}
    	
    	//4. accept
    	struct sockaddr_in c_addr;
    	memset(&c_addr,0,sizeof(struct sockaddr_in));
    	int c_fd;//连接文件描述符
    	int clt = sizeof(struct sockaddr_in); 
    	int mark = 0;//客户端数量标志
    	pid_t pid;
    	while(1){
    		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clt);//等待客户端的连接,返回c_fd
    		if(c_fd == -1){
    			printf("accept failed\n");
    			perror("accept");
    			exit(-1);
    		}
    		mark++;//客户端标志+1
    		printf("get No.%d connection: %s\n",mark,inet_ntoa(c_addr.sin_addr));
    		
    		pid = fork();//当有客户端连接时,创造一个子进程去读取客户端发送的命令
    		if(pid < 0){		
    			perror("fork");
    			exit(-1);
    		}
    		else if(pid == 0){
    			struct Msg msg;
    			int c_read;
    			int ret;
    			while(1){
    				memset(msg.cmd,0,sizeof(msg.cmd));
    				c_read = read(c_fd,&msg,sizeof(msg));//读取客户端发送的命令
    				if(c_read <= 0){	
    					perror("read");
    					break;
    				}else{
    					printf("get message %d's cmd from NO.%d Client :%s\n",c_read,mark,msg.cmd);
    					ret = get_cmd_type(msg.cmd);//对客户端传送的命令进行识别
    					cmd_handle(ret,msg,c_fd);//对相关命令进行处理
    				} 
    			}
    		}
    		close(c_fd);
    	}
    	close(s_fd);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • FTP/client.c(客户端可以发送 ls cd pwd 等指令)
    #include //socket bind..          /* See NOTES */
    #include //socket bind listen accept
    #include 
    #include //open
    #include //htons
    #include //fflush printf popen
    #include //exit
    #include //memset
    #include //read write
    
    #define LLS  1
    #define LCD  2
    #define LPWD 3
    #define QUIT 4//本地执行的命令
    
    #define CD   5//本地发送指令,远程要执行的命令无返回数据
    #define PUT  6//本地发送数据,远程要执行的命令无返回数据
    
    #define CMD  7//ifgo是否返回数据
    
    #define LS   8
    #define PWD  9
    #define GET  10//远程要执行的远程命令会返回数据到本地
    
    #define GETFILE 11//msg.type
    // int access(const char * pathname,int mode);
    struct Msg 
    {
    	int type;
    	char cmd[50];
    	char data[5120];
    };
    
    int get_cmd_type(char *cmd)//命令识别
    {
    	cmd = strtok(cmd,"\n");//客户端获取用户输入时会使用enter键,so传输到服务器的命令都带有换行符,把\n分割走
    	if(!strcmp("lls",cmd))		return LLS;
    	if(strstr(cmd,"lcd") != NULL)	return LCD;
    	if(!strcmp("lpwd",cmd))		return LPWD;		
    	if(!strcmp("quit",cmd))		return QUIT;//本地执行的命令
    		
    	if(strstr(cmd,"cd") != NULL)	return CD;
    	if(strstr(cmd,"put") != NULL)	return PUT;
    	
    	if(!strcmp("ls",cmd))		return LS;
    	if(!strcmp("pwd",cmd))		return PWD;
    	if(strstr(cmd,"get") != NULL)	return GET;
    			
    	return -1;//后续可处理未识别的命令
    }
    
    char *str_decompose(char *cmd)//命令分割//将带有文件名、路径名的命令进行处理分割
    {				       //如命令“get 1.c”,该函数就会返回1.c
    	strtok(cmd," ");//第一个单词
    	cmd = strtok(NULL," ");//第二个单词往后
    	return cmd;
    }
    
    int cmd_handle(int ret,struct Msg msg,int s_fd)//命令处理 
    
    {
    	FILE *fp = NULL;
    	//char *file_path = "./";
    	char *file = NULL;
    	int file_fd;
    	int file_size;
    	char buf[32];
    	switch(ret){
    		case LLS:
    			printf("--------------------------------------\n");
    			system("ls");//调用终端执行ls命令,并将数据输出到标准输出上
    			printf("--------------------------------------\n");
    			break;
    		case LCD:
    			if(chdir(str_decompose(msg.cmd)) < 0){//修改当前工作目录
    				perror("chdir");
    			}                       
    			break;
    		case LPWD:
    			printf("--------------------------------------\n");
    			system("pwd"); //调用终端执行pwd命令,并将数据输出到标准输出上
    			printf("--------------------------------------\n");
    			break;
    		case QUIT:
    			write(s_fd,&msg,sizeof(msg));
    			printf("--------------------------------------\n");
    			printf("client is quit\n");
    			printf("--------------------------------------\n");
    			exit(0);//退出当前进程
    			break;
    		case CD:
    		case LS:	
    		case PWD:
    		case GET:
    			msg.type = 0;	
    			write(s_fd,&msg,sizeof(msg));//将用户输入发送给服务器,让服务器根据命令进行相应的行为
    			break;
    		case PUT:
    			strcpy(buf,msg.cmd); //put 1.c -> msg.cmd
    			file = str_decompose(buf);//1.cc
    			if(access(file,F_OK) < 0){//判断客户端是否拥有想要存放到服务器的文件
    				printf("--------------------------------------\n");
    				printf("Target file doesn't exist!\n");
    				printf("--------------------------------------\n");
     
    			}else if(access(file,R_OK) < 0){//判断客户端是否对该文件有读权限
    				printf("--------------------------------------\n");
    				printf("The target file doesn't have read-permission!\n");   	
    				printf("--------------------------------------\n");
    			}else{//获取该文件的内容,发送给服务器
    				file_fd = open(file,O_RDWR);
    				if(file_fd < 0){
    					perror("open");
    				}
    				file_size = lseek(file_fd,0,SEEK_END);
    				if(file_size < 0){
    					perror("lseek");
    				}
    				lseek(file_fd,0,SEEK_SET);
    				memset(msg.data,0,sizeof(msg.data));
    				if(read(file_fd,msg.data,sizeof(msg.data)) < 0){
    					perror("read");
    				}
    				close(file_fd);
    				write(s_fd,&msg,sizeof(msg));
    				printf("--------------------------------------\n");
    				printf("Target file is on server!\n");   	
    				printf("--------------------------------------\n");
    			}
    			break;
    		default:
    			printf("--------------------------------------\n");
    			printf("Input error, please re-enter!\n");   	
    			printf("--------------------------------------\n");
    			break;
    	}
    	return ret;	
    }
    void from_socket_data_handle(struct Msg msg,int s_fd)//对服务器发送的数据进行处理
    {
    	struct Msg socket_msg;
    	int n_read;
    	int file_fd;
    	char file_path[20] = "./";
     
    	n_read = read(s_fd,&socket_msg,sizeof(socket_msg));
    	if(n_read == -1){
    		perror("read socket");
    		exit(-1);
    	}else if(socket_msg.type == GETFILE){//如果是GETFILE指令返回的数据,就存进文件中
    		strcat(file_path,str_decompose(msg.cmd));//./demo.c//也可不用拼接./
    		file_fd = open(file_path,O_RDWR|O_CREAT,0666);//创建文件a
    		write(file_fd,socket_msg.data,strlen(socket_msg.data));//向a中写入下载的文件内容
    		fflush(stdout);//强制刷新
    		close(file_fd);
    		printf("--------------------------------------\n");
    		printf("%s","Get target file from server!\n");
    		printf("--------------------------------------\n");
    	}else{//如果是其他数据就直接打印出来
    		printf("--------------------------------------\n");
    		printf("%s",socket_msg.data);
    		printf("--------------------------------------\n");
    	}
     
    }
    
    int main(int argc,char **argv)
    {	
    	if(argc != 3){//参数不足或不一致的提示
    		printf("Insufficient or inconsistent parameters\n");
    		exit(-1);
    	}
    	
    	//1. socket
    	int s_fd = socket(AF_INET,SOCK_STREAM,0); //创建套接字:IPV4因特网域,流式套接字、TCP传输协议
    	if(s_fd == -1){
    		printf("socket failed\n");
    		perror("socket");
    		exit(-1);
    	}
    	
    	//2. connect
    	struct sockaddr_in c_addr;
    	memset(&c_addr,0,sizeof(struct sockaddr_in));
    	c_addr.sin_family = AF_INET;//IPV4因特网域
    	c_addr.sin_port = htons(atoi(argv[2]));//将端口号转化成网络字节序
    	inet_aton(argv[1],&c_addr.sin_addr);//将IP地址转化为网络字节序
    			
    	if(connect(s_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){//客户端连接服务器
    		perror("connect");
    		exit(-1);	
    	}
    		
    	//3. write & read
    	struct Msg msg;
    	int ret;
    	int tmp;
    	while(1){
    		memset(msg.cmd,0,sizeof(msg.cmd));
    		memset(msg.data,0,sizeof(msg.data));
     		printf(">");
    		fgets(msg.cmd,sizeof(msg.cmd),stdin);  //获取用户输入的命令
    		ret = get_cmd_type(msg.cmd);  //对用户键入的命令进行识别
    		tmp = cmd_handle(ret,msg,s_fd);  //对命令进行处理
    		if(tmp > CMD){
    			from_socket_data_handle(msg,s_fd);//对服务器传送的数据进行处理
    		}
    		fflush(stdout);//强制刷新
    	}
    	close(s_fd);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
  • 相关阅读:
    【Java万花筒】跨越云平台的无服务器开发:使用Java构建弹性、高效的应用
    Docker超详细基础教程
    在ios系统上实现更改IP地址
    [附源码]Python计算机毕业设计Django校园疫情防范管理系统
    使用C语言实现并查集
    springcloud3-服务到服务调用ribbon及openfeign
    安利个神器, Python 脚本可轻松打包为 exe
    封装 Encapsulation
    MT6989(天玑9300)芯片性能参数_MTK联发科5G处理器
    mac制作ssl证书|生成自签名证书,nodejs+express在mac上搭建https+wss(websocket)服务器
  • 原文地址:https://blog.csdn.net/Jaci133/article/details/134044301