lv7 嵌入式开发-网络编程开发 07 TCP服务器实现

news2024/12/23 6:38:56

目录

1 函数介绍

1.1 socket函数 与 通信域

1.2 bind函数 与 通信结构体

1.3 listen函数 与 accept函数

2 TCP服务端代码实现

 3 TCP客户端代码实现

4 代码优化

5 练习


1 函数介绍

其中read、write、close在IO中已经介绍过,只需了解socket、bind、listen、accept等

1.1 socket函数 与 通信域

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


参数:

  • domain:指定套接字的协议域(protocol family),可以是 AF_INET(IPv4)或 AF_INET6(IPv6)等。
  • type:指定套接字的类型,可以是 SOCK_STREAM(流套接字,用于可靠的、面向连接的通信)或 SOCK_DGRAM(数据报套接字,用于无连接的通信)等。
  • protocol:指定使用的协议,可以是 IPPROTO_TCP(TCP)或 IPPROTO_UDP(UDP)等。所以无需要指定协议,设为0即可

返回值:

  • 成功创建套接字时,返回一个非负整数,代表新创建的套接字描述符。
  • 创建套接字失败时,返回 -1,并设置 errno 来表示具体的错误原因。

 示例:

#include <sys/types.h>
#include <sys/socket.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  //最后一个参数也可以是0
    if (sockfd == -1) {
        // 处理创建套接字失败的情况
        return -1;
    }
    // 套接字创建成功,可以进行后续操作

    return 0;
}

1.2 bind函数 与 通信结构体

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数解释:

  • sockfd:要进行绑定的套接字描述符。
  • addr:指向sockaddr结构体的指针,其中包含了要绑定的地址信息。
  • addrlenaddr指向的结构体的大小。

返回值:

  • 成功时,返回0。
  • 失败时,返回-1,并且在错误码中设置相应的错误标志,可以通过errno全局变量获取具体错误信息。

ipv4结构体 

struct sockaddr_in {
    sa_family_t    sin_family; /* 地址族: AF_INET */
    in_port_t      sin_port;   /* 网络字节序的端口号 */
    struct in_addr sin_addr;   /*IP地址结构体 */
};

/* IP地址结构体 */
struct in_addr {
    uint32_t       s_addr;     /* 网络字节序的IP地址 */
};

/*通用地址族结构体*/
struct sockaddr {
    sa_family_t sa_family;
    char sa_data[14];
}

注意事项:

  • 调用bind()函数之前,需要先创建一个套接字,并确保该套接字是未绑定的。
  • bind()函数通常在服务器端使用,用于将服务器的套接字与指定的本地地址绑定,从而监听并接收该地址发来的连接请求。
  • 在调用bind()函数时,要根据实际情况提供正确的地址信息,如IP地址和端口号等。
  • 在IPv4中,地址信息存储在sockaddr_in结构体中;而在IPv6中,地址信息存储在sockaddr_in6结构体中。

示例:强制转换

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    int sockfd;
    struct sockaddr_in server_addr;

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // 设置服务器地址信息
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    // 绑定套接字和地址
    if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        return 1;
    }

    // 其他操作...

    return 0;
}

1.3 listen函数 与 accept函数

/*监听套接字*/
int listen(int sockfd, int backlog);


参数:

  • sockfd:要监听的套接字描述符。
  • backlog:定义允许排队等待的连接请求的最大数量。

返回值:

  • 成功调用 listen() 函数时,返回 0 表示成功。
  • 调用 listen() 函数失败时,返回 -1 并设置 errno 来表示具体的错误原因。

函数功能: listen() 函数被用于 TCP 服务器端,用于将指定的套接字标记为被动套接字(passive socket),开始监听传入的连接请求。在调用 listen() 之前,服务器需要使用 socket() 函数创建一个套接字,并使用 bind() 函数将套接字与特定的地址和端口绑定。

一旦套接字被标记为监听状态,它就可以开始接受传入的连接请求。这些连接请求会被放置在一个连接请求队列中,等待服务器进程使用 accept() 函数来接受这些请求并建立连接。

注意事项:

  • backlog 参数指定了连接请求队列的最大长度。如果队列已满,则新的连接请求将被拒绝。实际允许的队列长度可能会受到系统限制。
  • 在调用 listen() 之后,通常需要调用 accept() 函数来接受连接请求并建立连接。

示例:

#include <sys/types.h>
#include <sys/socket.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd == -1) {
        // 处理创建套接字失败的情况
        return -1;
    }
    // 套接字创建成功,可以进行后续操作

    if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        // 处理绑定地址和端口失败的情况
        return -1;
    }

    if (listen(sockfd, 10) == -1) {
        // 处理监听套接字失败的情况
        return -1;
    }
    // 套接字处于监听状态,可以接受连接请求并建立连接

    return 0;
}

/*处理客户端发起的连接,生成新的套接字*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-sockfd: 函数socket生成的套接字
-addr:客户端的地址族信息
-addrlen:地址族结构体的长度

参数:

  • sockfd:监听套接字描述符,即之前调用 listen() 函数返回的套接字描述符。
  • addr:指向用于存储客户端地址信息的结构体 sockaddr 的指针,可以为 NULL
  • addrlen:指向存储客户端地址长度的变量的指针,如果 addr 不为 NULL,则需要将 addrlen 设置为 sizeof(struct sockaddr)

返回值:

  • 成功调用 accept() 函数时,返回一个新的套接字描述符,用于处理与客户端的连接。
  • 调用 accept() 函数失败时,返回 -1 并设置 errno 来表示具体的错误原因。

函数功能: accept() 函数用于监听套接字上接受传入的连接请求,并创建一个新的套接字来处理与客户端的连接。该新的套接字用于与客户端进行通信。在调用 accept() 函数之前,需要先使用 socket()bind()listen() 函数来准备监听套接字。

当有一个连接请求到达监听套接字时,accept() 函数会从连接请求队列中取出一个请求,创建一个新的套接字来处理该连接,并返回新创建的套接字描述符。可以通过新创建的套接字描述符进行与客户端的通信。

如果传入的 addr 不为 NULL,则 accept() 函数会将客户端的地址信息存储在 addr 指向的结构体中。同时,addrlen 也需要传入一个指向存储客户端地址长度的变量的指针。

注意事项:

  • accept() 函数是一个阻塞调用,当没有连接请求时,它会一直等待,直到有连接请求到达或出现错误才返回。
  • 通常在多线程或多进程环境中使用 accept() 函数来实现并发处理多个连接请求的功能。

示例: 

#include <sys/types.h>
#include <sys/socket.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd == -1) {
        // 处理创建套接字失败的情况
        return -1;
    }
    // 套接字创建成功,可以进行后续操作

    if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        // 处理绑定地址和端口失败的情况
        return -1;
    }

    if (listen(sockfd, 10) == -1) {
        // 处理监听套接字失败的情况
        return -1;
    }
    // 套接字处于监听状态,可以接受连接请求并建立连接

    struct sockaddr_in client_addr;
    socklen_t client_addrlen = sizeof(client_addr);
    int client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addrlen);
    if (client_sockfd == -1) {
        // 处理接受连接请求失败的情况
        return -1;
    }
    // 成功接受连接请求,可以使用 client_sockfd 进行与客户端的通信

    return 0;
}

2 TCP服务端代码实现

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 5001
#define BACKLOG 5

int main(int argc, char *argv[])
{
	int fd, newfd;
	char buf[BUFSIZ] = {}; //BUFSIZ 8142
	struct sockaddr_in addr;
	/*创建套接字*/
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.s_addr = 0;
	/*绑定通信结构体*/
	if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("bind");
		exit(0);
	}
	/*设置套接字为监听模式*/
	if(listen(fd, BACKLOG) == -1){
		perror("listen");
		exit(0);
	}
	/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
	newfd = accept(fd, NULL, NULL);
	if(newfd < 0){
		perror("accept");
		exit(0);
	}
	printf("BUFSIZ = %d\n", BUFSIZ);
	read(newfd, buf, BUFSIZ);
	printf("buf = %s\n", buf);
	close(fd);
	return 0;
}

 3 TCP客户端代码实现

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 5001
#define BACKLOG 5
#define STR "Hello World!"

int main(int argc, char *argv[])
{
	int fd;
	struct sockaddr_in addr;
	/*创建套接字*/
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	/*向服务端发起连接请求*/
	if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("connect");
		exit(0);
	}
	write(fd, STR, sizeof(STR) );
	printf("STR = %s\n", STR);
	close(fd);
	return 0;
}

4 代码优化

服务端

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BACKLOG 5

int main(int argc, char *argv[])
{
	int fd, newfd, ret;
	char buf[BUFSIZ] = {}; //BUFSIZ 8142
	struct sockaddr_in addr;
	
	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}

	/*创建套接字*/
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) );
	if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}

	/*绑定通信结构体*/
	if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("bind");
		exit(0);
	}
	/*设置套接字为监听模式*/
	if(listen(fd, BACKLOG) == -1){
		perror("listen");
		exit(0);
	}
	/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
	newfd = accept(fd, NULL, NULL);
	if(newfd < 0){
		perror("accept");
		exit(0);
	}
	while(1){
		memset(buf, 0, BUFSIZ);
		ret = read(newfd, buf, BUFSIZ);
		if(ret < 0)
		{
			perror("read");
			exit(0);
		}
		else if(ret == 0)
			break;
		else
			printf("buf = %s\n", buf);
	}
	close(newfd);
	close(fd);
	return 0;
}

客户端

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BACKLOG 5

int main(int argc, char *argv[])
{
	int fd;
	struct sockaddr_in addr;
	char buf[BUFSIZ] = {};

	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}

	/*创建套接字*/
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}

	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) );
	if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}

	/*向服务端发起连接请求*/
	if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("connect");
		exit(0);
	}
	while(1){
		printf("Input->");
		fgets(buf, BUFSIZ, stdin);
		write(fd, buf, strlen(buf) );
	}
	close(fd);
	return 0;
}

5 练习

实现TCP通信代码,并使用Makefile进行编译。提交代码和完成通信的截图

client

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

#define CLIENT_MAX_NUM 5

int main(int argc, char * argv[])
{
	int clientfd;
	struct sockaddr_in server_addr;
	char buf[BUFSIZ];
	int ret;

	if( argc < 3)
	{
		printf("%s <ip> <port>\n",argv[0]);
		return 0;
	}

	clientfd = socket(AF_INET, SOCK_STREAM,0);
	if(clientfd == -1)
	{
		perror("socket");
		return 0;
	}

	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons( atoi(argv[2]) ) ;
	if( inet_aton(argv[1], &server_addr.sin_addr) == 0)
	{
		printf("Invalid address:%s\n",argv[1]);
		return 0;
	}

	if(connect(clientfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
	{
		perror("connect");
		return 0;
	}


	while(1)
	{
		printf(">");
		fgets(buf, BUFSIZ, stdin);
		write(clientfd, buf, strlen(buf));
	}
	close(clientfd);
	return 0;
}

server

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

#define CLIENT_MAX_NUM 5

int main(int argc, char * argv[])
{
	int sockfd, clientfd;
	struct sockaddr_in server_addr;
	char buf[BUFSIZ];
	int ret;
	
	if( argc < 3)
	{
		printf("%s <ip> <port>\n",argv[0]);
		return 0;
	}

	sockfd = socket(AF_INET, SOCK_STREAM,0);
	if(sockfd == -1)
	{
		perror("socket");
		return 0;
	}

	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons( atoi(argv[2]) ) ;
	if( inet_aton(argv[1], &server_addr.sin_addr) == 0)
	{
		printf("Invalid address:%s\n",argv[1]);
		return 0;
	}

	if(bind(sockfd, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
	{
		perror("bind");
		return 0;
	}

	if(listen(sockfd, CLIENT_MAX_NUM) == -1)
	{
		perror("listen");
		return 0;
	}

	clientfd = accept(sockfd, NULL, NULL); 
	if( clientfd == -1)
	{
		perror("accept");
		return 0;
	}

	while(1)
	{
		memset(buf, 0, BUFSIZ);
		ret = read(clientfd, buf, BUFSIZ);
		if(ret < 0)
		{
			perror("read");
			return 0;
		}
		else if( ret == 0 )
		{
			break;
		}
		else
		{
			printf("buf = %s\n", buf);
		}	
	}
	close(clientfd);
	close(sockfd);
	return 0;
}

makefile

CC=gcc
CFLAGS=-Wall
all:tcp_client tcp_server

clean:
	rm tcp_server tcp_client

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

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

相关文章

Web版Photoshop来了,用到了哪些前端技术?

经过 Adobe 工程师多年来的努力&#xff0c;并与 Chrome 等浏览器供应商密切合作&#xff0c;通过 WebAssembly Emscripten、Web Components Lit、Service Workers Workbox 和新的 Web API 的支持&#xff0c;终于在近期推出了 Web 版 Photoshop&#xff08;photoshop.adobe…

JUC第十四讲:JUC锁: ReentrantReadWriteLock详解

JUC第十四讲&#xff1a;JUC锁: ReentrantReadWriteLock详解 本文是JUC第十四讲&#xff1a;JUC锁 - ReentrantReadWriteLock详解。ReentrantReadWriteLock表示可重入读写锁&#xff0c;ReentrantReadWriteLock中包含了两种锁&#xff0c;读锁ReadLock和写锁WriteLock&#xff…

动态规划-状态机(188. 买卖股票的最佳时机 IV)

状态分类&#xff1a; f[i,j,0]考虑前i只股票&#xff0c;进行了j笔交易&#xff0c;目前未持有股票 所能获得最大利润 f[i,j,1]考虑前i只股票&#xff0c;进行了j笔交易&#xff0c;目前持有股票 所能获得最大利润 状态转移&#xff1a; f[i][j][0] Math.max(f[i-1][j][0],f[…

注册阿里云账号,免费领取云服务器!

注册阿里云账号&#xff0c;免费领云服务器&#xff0c;最高领取4台云服务器&#xff0c;每月750小时&#xff0c;3个月免费试用时长&#xff0c;可快速搭建网站/小程序&#xff0c;部署开发环境&#xff0c;开发多种企业应用。阿里云服务器网分享阿里云服务器免费领取入口、免…

6 个最佳免费 Android 数据恢复软件

如果您是 Android 用户&#xff0c;您可能会发现没有回收站。然而&#xff0c;聪明的开发人员已经创建了各种 Android 数据恢复软件程序&#xff0c;可以解决各种与数据丢失相关的问题。 Android 数据恢复软件如何工作&#xff1f; 问题是当你删除一个文件时&#xff0c;它的数…

如何利用mp进行条件查询

在mp中进行条件查询发函数是selectList(); 使用上面的方式参数容易传错&#xff0c;所以可以使用下面的方式进行条件查询&#xff1a; 但是使用这种方式可能传的值为空 使用下面的方式可以避免这种情况发生 总结

ReactNative学习笔记

文章目录 基础环境搭建创建项目安装vscode插件调试工具基础语法掌握ReactStyleSheetRN中的样式和CSS的不同通过style属性直接声明在style属性中调用StyleSheet声明的样式 Flexbox术语属性响应式布局 组件和API简介核心组件**最常用的一些核心组件**各核心组件的演示代码Alert和…

【QT】Qt Application Manager启动应用源码分析

Qt Application Manager启动应用源码分析 Qt Application Manager&#xff08;以下简称QTAM&#xff09;是QT推出的一款应用管理程序&#xff0c;可以把它简单理解成Android的LauncherSystemUI。但是&#xff0c;QTAM又集成了Wayland功能&#xff0c;并且自身实现了一套Compos…

MyBatis-plus快速代码生成,使用MybatisX插件

概述 MyBatis提供逆向工程&#xff0c;可以快速生成代码。 而MyBatis-plus也提供了一个代码生成器&#xff0c;它需要执行一些代码来实现。 而MybatisX 是一款基于 IDEA 的快速开发插件&#xff0c;为效率而生&#xff0c;你不需要写代码&#xff0c;直接界面化操作&#xf…

Ultralytics(YoloV8)开发环境配置,训练,模型转换,部署全流程测试记录

关键词&#xff1a;windows docker tensorRT Ultralytics YoloV8 配置开发环境的方法&#xff1a; 1.Windows的虚拟机上配置&#xff1a; Python3.10 使用Ultralytics 可以得到pt onnx&#xff0c;但无法转为engine&#xff0c;找不到GPU&#xff0c;手动转也不行&#xff0…

【Java 进阶篇】JDBC 登录案例详解

在本文中&#xff0c;我们将通过一个简单的 JDBC 登录案例来详细介绍如何使用 Java 数据库连接&#xff08;JDBC&#xff09;来连接数据库、进行用户身份验证等操作。这个案例将适用于数据库初学者&#xff0c;我们将从头开始构建一个简单的登录系统。 什么是 JDBC&#xff1f…

剑指offer——JZ18 删除链表的节点 解题思路与具体代码【C++】

一、题目描述与要求 删除链表的节点_牛客题霸_牛客网 (nowcoder.com) 题目描述 给定单向链表的头指针和一个要删除的节点的值&#xff0c;定义一个函数删除该节点。返回删除后的链表的头节点。 1.此题对比原题有改动 2.题目保证链表中节点的值互不相同 3.该题只会输出返回…

视频讲解|基于DistFlow潮流的配电网故障重构代码

目录 1 主要内容 2 视频链接 1 主要内容 该视频为基于DistFlow潮流的配电网故障重构代码讲解内容&#xff0c;对应的资源下载链接为基于DistFlow潮流的配电网故障重构(输入任意线路)&#xff0c;对该程序进行了详尽的讲解&#xff0c;基本做到句句分析和讲解&#xff08;讲解…

反序列化漏洞

原理 反序列化是对象变成可以传输的字符串。 PHP反序列化的时候&#xff0c;基本都是围绕着serialize()&#xff0c;unserialize()这两个函数 serialize() 和 unserialize() 在 PHP内部没有漏洞的&#xff0c;产生反序列化漏洞是因为应用程序在处理对象、魔术函数以及序列化…

《C和指针》笔记33:指针数组

除了创建整型数组一样&#xff0c;也可以声明指针数组。 int *api[10];为了弄清这个复杂的声明&#xff0c;我们假定它是一个表达式&#xff0c;并对它进行求值。下标引用的优先级高于间接访问&#xff0c;所以在这个表达式中&#xff0c;首先执行下标引用。因此&#xff0c;a…

[软件工具]opencv-svm快速训练助手教程解决opencv C++ SVM模型训练与分类实现任务支持C# python调用

opencv中已经提供了svm算法可以对图像实现多分类&#xff0c;使用svm算法对图像分类的任务多用于场景简单且对时间有要求的场景&#xff0c;因为opencv的svm训练一般只需要很短时间就可以完成训练任务。但是目前网上没有一个工具很好解决训练问题&#xff0c;大部分需要自己编程…

分布式锁:四种方案解决商品超卖的方案

一 分布式锁 1.1 分布式锁的作用 在多线程高并发场景下&#xff0c;为了保证资源的线程安全问题&#xff0c;jdk为我们提供了synchronized关键字和ReentrantLock可重入锁&#xff0c;但是它们只能保证一个工程内的线程安全。在分布式集群、微服务、云原生横行的当下&#xff…

仿牛客论坛项目 笔记

文章目录 环境配置bean是什么最终成品功能数据库与缓存一致性整个web系统后端的结构spring mvc相关controller常见的代码写法mybatis相关常识测试、调试相关计网相关component相关注解spring全家桶族谱spring衍生框架 run之后发生了什么什么是spring&#xff0c;spring和bean的…

剑指offer——JZ22 链表中倒数最后k个结点 解题思路与具体代码【C++】

一、题目描述与要求 链表中倒数最后k个结点_牛客题霸_牛客网 (nowcoder.com) 题目描述 输入一个长度为 n 的链表&#xff0c;设链表中的元素的值为 ai &#xff0c;返回该链表中倒数第k个节点。 如果该链表长度小于k&#xff0c;请返回一个长度为 0 的链表。 数据范围&…

计算机网络(第8版)第一章概述笔记

6 性能指标 带宽&#xff1a; 在单位时间内从网络中的某一点到另一点所能通过的“最高数据率”。 7 分层结构、协议、接口、服务 1、实体&#xff1a;第n层的活动元素称为n层实体。同一层的实体叫对等实体。 2、协议&#xff1a;为进行网络中的对等实体数据交换而建立的规则、…