华清远见嵌入式培训_第七周回顾与反思(下)

目录

前言

周四

一、网络编程概述

1、协议就是指通信双方约定好的数据发送和接受以及解析的规则。

2、网络的发展阶段可分为四个:

 二、网络基础知识

1、字节序

2、如何判断自己的主机是大端存储还是小端存储

3、什么情况需要转换字节序,什么情况不需要

4、字节序转换的函数

5、socket函数

6、IP地址

7、端口号

三、TCP/UDP的概念和异同

1、TCP(即传输控制协议)概念:

2、UDP(User Datagram Protocol)概念:

3、TCP/UDP的异同

四、TCP网络编程

1、TCP网络模型

2、TCP网络编程流程

3、函数说明

4、搭建简易循环服务器及代码

周五

一、TCP网络编程注意事项

1、TCP的粘包问题

二、UDP网络编程

1、UDP网络编程流程

2、recvfrom 函数

3、sendto 函数

4、简单UDP服务器模型的实现

三、TFTP协议

总结


前言

        连续七天课中的最后两天,开了一门新课:网络编程,课程为时八天,最后一天需要利用到目前为止所有学到的内容,独立完成一个综合项目。

        这两天的学习的主要内容有:第一天,网络编程概述、网络基础知识和TCP网络编程;第二天,UDP网络编程。

        这两天的重点难点有:开放系统互联模型的结构TCP/IP协议族体系的结构以及体系中各层次的协议TCP和UDP的异同,以及TCP和UDP网络编程的使用

        网络编程的课程特点是:框架较为固定,更加注重思维逻辑,代码量较大,本次总结将围绕这两天的重点难点,深化理论知识理解、强化TCP和UDP网络编程的代码逻辑。

周四

一、网络编程概述

1、协议就是指通信双方约定好的数据发送和接受以及解析的规则。

2、网络的发展阶段可分为四个:

        1、ARRAnet阶段

                使用的协议为NCP(network control protocol),特点是不能互联不同类型的计算机和不同类型的系统,没有纠错功能。

        2、TCP/IP两个协议阶段

                TCP协议:用来检测网络传输中的差错的传输控制协议;

                IP协议:专门负责对不同的网络进行互联的互联网协议;

        3、网络体系结构和开放系统互联模型

                网络体系结构:分层而治之,层次之间相互协同,是网络的层次结构和每层所使用的协议的集合。

                开放系统互联模型(OSI):是一个理想化的模型,分为七层,从上到下依次是:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层(物数网传会表应)。

华清远见嵌入式培训_第七周回顾与反思(下)_第1张图片

         4、TCP/IP协议族体系结构

                TCP/IP协议族是可以应用的工业标准,分为四层,从上往下分别为:应用层,传输层,网络层和链路层。

华清远见嵌入式培训_第七周回顾与反思(下)_第2张图片

TCP/IP与OSI模型对应关系:

华清远见嵌入式培训_第七周回顾与反思(下)_第3张图片华清远见嵌入式培训_第七周回顾与反思(下)_第4张图片

 二、网络基础知识

1、字节序

        小端字节序(little-endian):低字节存储在地址低位、高字节存储在地址高位;

        大端字节序(big-endian):高字节存储在地址地位、低字节存储在地址高位;

        主机字节序:不同CPU的主机,可能是小端序,可能是大端序。

        网络字节序:规定为大端序,发送数据前,需要将主机字节序转为网络字节序,以保证发送和读取的数据一致。

华清远见嵌入式培训_第七周回顾与反思(下)_第5张图片

2、如何判断自己的主机是大端存储还是小端存储

方法1:使用指针截取

#include 

int main(int argc, const char *argv[])
{
	int num = 0x12345678;
	char *p = (char *)#
	if(0x12 == *p){
		printf("大端\n");
	}else if(0x78 == *p){
		printf("小端\n");
	}
	return 0;
}

 方式2:使用共用体

#include 

union Test{
	int a;
	char b;
};

int main(int argc, const char *argv[])
{
	union Test t;
	t.a = 0x12345678;
	if(0x12 == t.b){
		printf("大端\n");
	}else if(0x78 == t.b){
		printf("小端\n");
	}

	return 0;
}

3、什么情况需要转换字节序,什么情况不需要

        需要转换:数据是多字节作为一个整体;

        不需要转换:已知传输数据的双方主机为相同的字节序,或者传输的字节序是字符串。

4、字节序转换的函数

        h是host,代表主机字节序,n为network,代表网络字节序

        l是long,代表4字节,s为short,代表2字节

#include 
uint32_t htonl(uint32_t hostlong);	//主机转网络 4字节
uint16_t htons(uint16_t hostshort);	//主机转网络 2字节
uint32_t ntohl(uint32_t netlong);	//网络转主机 4字节
uint16_t ntohs(uint16_t netshort);	//网络转主机 2字节

5、socket函数

        socket函数可用于同主机的进程间通信;

        socket函数与TCP/IP协议族配合使用,可实现不同主机的进程间通信;

        socket是一个编程接口,是一种特殊的文件描述符,并不仅限于TCP/IP协议,还有UDP协议,它将复杂的网络通信过程封装成IO操作。

        套接字的分类:

                流式套接字(SOCK_STREAM)--TCP

                数据套接字(SOCK_DGRAM)--UDP

                原始套接字(SOCK_RAM)

6、IP地址

        IPV4  4字节  32bit  由网络号和主机号组成

        IPV6 16字节 128bit

        对于IPV4而言,IP地址的表示形式 "192.168.80.10" 这种叫做点分十进制,是一个字符串

计算机中存储IP地址是用的无符号4字节整型(unsigned int)。就涉及到了字节序问题:

//将点分十进制的字符串 转换成  网络字节序的 无符号四字节整型
in_addr_t inet_addr(const char *cp);

//将网络字节序的无符号四字节整型的ip地址 转换成 点分十进制的字符串
char *inet_ntoa(struct in_addr in);

7、端口号

        如果使用pid表示端口号,会有两个问题:一是pid由系统随机分配,无法指定,二是系统重启后,进程号会改变,无法统一管理。利用端口号,人为标识某个进程,可以方便使用和管理;

        linux系统端口号的范围是[0-65535],共计6k个,使用unsigned short存储;

        实际开发中,为防止指定的端口号与系统冲突,一般指定特殊数字,例如6666 8888 5678等;

        常见的服务使用的端口号:ftp 21;ssh 22;tftp 69;http 80 8080。

三、TCP/UDP的概念和异同

1、TCP(即传输控制协议)概念:

        是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)

        适用情况:适合于对传输质量要求较高,以及传输大量数据的通信。在需要可靠数据传输的场合,通常使用TCP协议微信/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议。

2、UDP(User Datagram Protocol)概念:

        用户数据报协议是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。

        适用情况:发送小尺寸数据(如对DNS服务器进行IP地址查询时)在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)适合于广播/组播式通信中。MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输。

3、TCP/UDP的异同

        共同点:都是传输层的协议;

        不同点:  TCP是面向连接的,可靠的;

                        UDP是无连接的,不保证可靠的。

四、TCP网络编程

1、TCP网络模型

        c/s 模型:客户端服务器模型;

        b/s 模型:浏览器服务器模型。

2、TCP网络编程流程

        服务器流程:

                创建流式套接字 socket()

                填充网络信息结构体

                将服务器的网络信息结构体和套接字绑定 bind()

                将套接字设置成被动监听状态 listen()

                阻塞等待客户端连接 accept()

                收发数据 send/recv

                关闭套接字 close()

        客户端流程:

                创建流式套接字 socket()

                填充网络信息结构体

                与服务器建立连接 connect()

                收发数据 send/recv

                关闭套接字 close()

华清远见嵌入式培训_第七周回顾与反思(下)_第6张图片

3、函数说明

3.1 socket函数

功能:
	创建一个套接字
头文件:
	#include 
	#include 
函数原型:
	int socket(int domain, int type, int protocol);
参数:
	domain:
		通信域
			AF_UNIX 或 AF_LOCAL		本地通信
			AF_INET					IPV4使用
			AF_INET6				IPV6使用
			AF_PACKET				原始套接字使用
	type:
		套接字的类型
			SOCK_STREAM				TCP使用
			SOCK_DGRAM				UDP使用
			SOCK_RAW				原始套接字使用
	protocol:
			附加协议 如果没有 传 0 就可以
返回值:
	成功 套接字
	失败 -1  重置错误码

3.2 bind函数

功能:
	将套接字和网络信息结构体绑定
头文件:
	#include 
	#include 
函数原型:
	int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);
参数:
	sockfd:套接字
	addr:网络信息结构体
		struct sockaddr {
			sa_family_t sa_family;
			char        sa_data[14];
		}
		//上面的结构体只是用于强转 防止编译警告
		//我们使用的是下面的结构体
		struct sockaddr_in {
			sa_family_t    sin_family; /*  AF_INET */
			in_port_t      sin_port;   /* 网络字节序的端口号 */
			struct in_addr sin_addr;   /* 地址 */
		};

		/* Internet address. */
		struct in_addr {
			uint32_t       s_addr;     /* 网络字节序的无符号4字节的IP地址 */
		};

	addrlen:addr的大小
	
返回值:
	成功 0
	失败 -1 重置错误码

3.3 listen函数

功能:
	将套接字设置成被动监听状态
头文件:
	#include 
	#include 
函数原型:
	int listen(int sockfd, int backlog);
参数:
	sockfd:套接字
	backlog:半连接队列的长度 填多少都可以
			一般填 5  10  都行  只要不是 0 就行
返回值:
	成功 0
	失败 -1 重置错误码

3.4 accept函数

功能:
	在半连接队列中获取一个新的连接
	如果调用成功,说明有一个新的客户端连到服务器了
	这是accept会返回一个新的文件描述符用于和当前的客户端通信
头文件:
	#include 
函数原型:
	int accept(int socket, struct sockaddr * address,
           socklen_t * address_len);
参数:
	socket:套接字
	address:用于保存客户端网络信息结构体的缓冲区的首地址
			如果不关心客户端的信息,可以传 NULL
	address_len:address的大小 如果不关心时 也要传 NULL
返回值:
	成功 用于和新客户端通信的套接字
	失败 -1  重置错误码

3.5 send/recv函数

功能:
	在套接字上接收一条消息
头文件:
	#include 
	#include 
函数原型:
	ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
	socket:套接字
	buf:	用来保存接收的数据的缓冲区的首地址
	len:	想要接收的大小
	flags:	如果是0的 和read就一样了
			如果 MSG_DONTWAIT 非阻塞
返回值:
	成功 实际接到的字节数
	失败 -1  重置错误码
	如果对端关闭了 recv会返回0

下面的三种用法是等价的:
	read(sockfd, buff, 128);
	recv(sockfd, buff, 128, 0);
	recvfrom(sockfd, buff, 128, 0, 0, NULL, NULL);

功能:
	向套接字上接收一条数据
头文件:
	#include 
	#include 
函数原型:
	ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
	socket:套接字
	buf:	要发送的数据的首地址
	len:	想要发送的大小
	flags:	如果是0的 和write就一样了
			如果 MSG_DONTWAIT 非阻塞
返回值:
	成功 实际发送的字节数
	失败 -1  重置错误码
	如果对端关闭了 第一次send会返回0 第二次send会产生 SIGPIPE 信号

下面的三种用法是等价的:
	write(sockfd, buff, 128);
	send(sockfd, buff, 128, 0);
	sendto(sockfd, buff, 128, 0, 0, NULL, NULL);

3.6 connect

功能:
	与服务器建立连接
头文件:
	#include 
	#include 
函数原型:
	int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);
参数:
	socket:套接字
	addr:	用于保存服务器网络信息结构体的缓冲区的首地址
	addrlen:addr的大小
返回值:
	成功 0
	失败 -1  重置错误码

4、搭建简易循环服务器及代码

4.1 服务器代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERRLOG(errmsg)                                      \
    do                                                      \
    {                                                       \
        printf("%s %s %d\n", __FILE__, __func__, __LINE__); \
        perror(errmsg);                                     \
        exit(-1);                                           \
    } while (0)

int main(int argc, const char *argv[])
{
    //入参合理性检查
    if (3 != argc)
    {
        printf("usage : %s  \n", argv[0]);
        exit(-1);
    }
    //创建流式套接字
    int scokfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == scokfd)
    {
        ERRLOG("socket error");
    }
    //填充网络信息结构体
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    server_addr.sin_port = htons(atoi(argv[2]));

    socklen_t server_addr_len = sizeof(server_addr);

    //绑定网络信息结构体
    if (-1 == bind(scokfd, (struct sockaddr *)&server_addr, server_addr_len))
    {
        ERRLOG("bind error");
    }

    //将套接字设置为被动监听状态
    if (-1 == listen(scokfd, 5))
    {
        ERRLOG("listen error");
    }

    //创建空的网络信息结构体保存客户端信息
    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(client_addr));
    socklen_t client_addr_len = sizeof(client_addr);

    int ret = 0;
    int acceptfd;
    char buff[128] = {0};

    while (1)
    {
        //阻塞等待客户端连接
        acceptfd = accept(scokfd, (struct sockaddr *)&client_addr, &client_addr_len);
        if (-1 == acceptfd)
        {
            ERRLOG("accept error");
        }
        printf("有客户端[%s:%d]连入服务器\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        //接收数据
        while (1)
        {
            memset(buff, 0, sizeof(buff));
            ret = recv(acceptfd, buff, sizeof(buff), 0);
            if (-1 == ret)
            {
                ERRLOG("recv error");
            }
            else if (0 == ret)
            {
                printf("客户端[%s:%d]断开连接\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                break;
            }
            if (!strcmp(buff, "quit"))
            {
                printf("客户端[%s:%d]退出连接\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                break;
            }
            printf("客户端[%s:%d]发来数据:[%s]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port),buff);
            strcat(buff,"--已收到");
            if(-1 == send(acceptfd,buff,sizeof(buff),0)){
                ERRLOG("send error");
            }
            
        }
        close(acceptfd);
    }
    close(scokfd);
    return 0;
}

4.2 客户端代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERRLOG(errmsg) do{\
            printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
            perror(errmsg);\
            exit(-1);\
        }while(0)

int main(int argc, const char *argv[]){
    //入参合理性检查
    if(3 != argc){
        printf("Usage : %s  \n", argv[0]);
        exit(-1);
    }

    //1.创建流式套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }
    
    //2.填充服务器的网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    socklen_t serveraddr_len = sizeof(serveraddr);

    //3.尝试与服务器建立连接
    if(-1 == connect(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("connect error");
    }
    printf("与服务器建立连接成功..\n");

    //进行数据交互
    char buff[128] = {0};
    while(1){
        memset(buff, 0, 128);
        fgets(buff, 128, stdin);
        buff[strlen(buff) - 1] = '\0';
        //发送数据
        if(-1 == send(sockfd, buff, 128, 0)){
            ERRLOG("send error");
        }
        if(!strncmp(buff, "quit", 4)){
            break;
        }
        //接收应答
        recv(sockfd, buff, 128, 0);
        //输出应答消息
        printf("应答:[%s]\n", buff);
    }
    //关闭套接字
    close(sockfd);

    return 0;
}

周五

一、TCP网络编程注意事项

1、TCP的粘包问题

        TCP底层有一个Nagle算法,将一定短的时间内发往同一个接收端的数据,组装成一个整体发给对方,而接收方没法区分消息的类型,这就可能导致有冲突;

        解决方式1,将文件内容 和 结束标志 间隔一段时间再发,但是我们服务器程序里面是坚决不允许使用 sleep 的;

         解决方式2,只要保证双发每次收发数据都用一样大的结构 就能解决粘包问题,发送文件结束的标志。

二、UDP网络编程

1、UDP网络编程流程

        服务器流程:

                创建用户数据报套接字

                填充服务器的网络信息结构体

                将套接字和服务器的网络信息结构体绑定

                收发数据 sendto / recvfrom

                关闭套接字

        客户端流程

                创建用户数据报套接字

                填充服务器的网络信息结构体

                收发数据 sendto recvfrom

                关闭套接字

华清远见嵌入式培训_第七周回顾与反思(下)_第7张图片

2、recvfrom 函数

        可以理解为是 recv 函数和 accept 函数的结合。

功能:
	在套接字上接收一条消息
头文件:
	#include 
	#include 
函数原型:
	ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                    struct sockaddr *src_addr, socklen_t *addrlen);
参数:
	前4个参数和recv的4个参数一模一样
	src_addr:用于保存客户端网络信息结构体的缓冲区的首地址
			如果不关心客户端的信息,可以传 NULL
	addrlen:src_addr的大小 如果不关心  也可以传 NULL
返回值:
	成功 实际接到的字节数
	失败 -1  重置错误码
	recvfrom不会返回 0

3、sendto 函数

        可以理解为是 send 函数和 bind 函数的结合。

功能:
	向套接字上接收一条数据
头文件:
	#include 
	#include 
函数原型:
	ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
	前4个参数和send的4个参数一模一样
	dest_addr:	消息接受者的网络信息结构体--也就是发送给谁就写谁
	addrlen:	dest_addr的大小
			
返回值:
	成功 实际发送的字节数
	失败 -1  重置错误码

4、简单UDP服务器模型的实现

        服务器代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERRLOG(errmsg) do{\
            printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
            perror(errmsg);\
            exit(-1);\
        }while(0)

int main(int argc, const char *argv[]){
    //入参合理性检查
    if(3 != argc){
        printf("Usage : %s  \n", argv[0]);
        exit(-1);
    }

    //创建用户数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }

    //填充网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    socklen_t serveraddr_len = sizeof(serveraddr);

    //3.绑定
    if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("bind error");
    }

    //定义结构体保存客户端的信息
    //如果使用UDP时不保存客户端的网络信息结构体
    //可以接受到客户端发来的数据  但是没法回信给客户端 因为sendto的后两个参数需要用到
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);

    char buff[128] = {0};

    //进行数据交互
    while(1){
        memset(buff, 0, 128);
        //接收数据
        if(-1 == recvfrom(sockfd, buff, 128, 0, (struct sockaddr *)&clientaddr, &clientaddr_len)){
            ERRLOG("recvfrom error");
        }
        printf("客户端[%s:%d]发来数据[%s]\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buff);
        //组装应答消息
        strcat(buff, "--hqyj");
        //回复应答消息
        if(-1 == sendto(sockfd, buff, 128, 0, (struct sockaddr *)&clientaddr, clientaddr_len)){
            ERRLOG("sendto error");
        }
    }

    //关闭套接字
    close(sockfd);

    return 0;
}

        客户端代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERRLOG(errmsg) do{\
            printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
            perror(errmsg);\
            exit(-1);\
        }while(0)

int main(int argc, const char *argv[]){
    //入参合理性检查
    if(3 != argc){
        printf("Usage : %s  \n", argv[0]);
        exit(-1);
    }

    //1.创建用户数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }
    
    //2.填充服务器的网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    socklen_t serveraddr_len = sizeof(serveraddr);

    //进行数据交互
    char buff[128] = {0};
    while(1){
        memset(buff, 0, 128);
        fgets(buff, 128, stdin);
        buff[strlen(buff) - 1] = '\0';
        //发送数据
        if(-1 == sendto(sockfd, buff, 128, 0, (struct sockaddr *)&serveraddr, serveraddr_len)){
            ERRLOG("sendto error");
        }
        //接收应答  客户端无需再保存服务器的网络信息结构体了 因为 serveraddr 没改过
        if(-1 == recvfrom(sockfd, buff, 128, 0, NULL, NULL)){
            ERRLOG("recvfrom error");
        }
        //输出应答消息
        printf("应答:[%s]\n", buff);
    }
    //关闭套接字
    close(sockfd);

    return 0;
}

三、TFTP协议

利用tftpd32拷贝windows文件到虚拟机ubuntu中_Anccccc的博客-CSDN博客将tftpd32放在Windows要做传输文件的目录下,双击运行tftpd32,选择当前win电脑主机IP,保持软件前台运行。编写TFTP客户端,从TFTP服务器上下载文件,即实现利用tftpd32拷贝windows文件到虚拟机ubuntu中。https://blog.csdn.net/Anccccc/article/details/127396872

总结

        老师说网络编程在今后的工作中,使用是很频繁的,这两天的学习,主要是熟悉网络传输协议和传输方式,代码量大,尤其是TCP和UDP网络编程,但是结构是很清晰的,主要是考验分析问题,解决问题的能力,需要更缜密的代码逻辑,代码还需要多敲,编程逻辑图还需多画。

        写在最后:写这篇文章是笔者一边看老师的目录,一边回想老师讲的内容,仅仅选取了我自己认为比较重要的,或者自己之前没接触过的进行汇总、总结,知识体系不完善,内容也不详细,仅供笔者复习使用。如果是有需要笔记,或者对这方面感兴趣,可以私信我,发你完整的知识体系和详细内容的笔记。写的仓促、水平有限,如有任何错误请多指正,也欢迎友好交流,定会虚心听取大佬的宝贵意见!

你可能感兴趣的:(华清远见嵌入式培训,学习总结,网络,网络协议,tcp/ip,udp,服务器)