本文旨在探讨TCP
(Transmission Control Protocol
,传输控制协议)通讯的相关知识。通过本文,您将了解TCP
协议的工作原理,以及如何运用这一协议进行通讯程序设计与实现。
环境
- OpenHarmony - 4.0 源码
- 九联 unionpi_whale 开发板
一、TCP 通信介绍
1、概念
传输控制协议(TCP,Transmission Control Protocol
)是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。
2、特性
- OpenHarmony是一个分布式操作系统,它允许设备之间相互通信和协作。TCP是一种可靠的通信协议,适用于跨网络的设备间通信。通过实现TCP通信,设备可以安全、可靠地进行数据传输,实现各种协作功能。
- 广泛的支持:TCP是互联网上使用最广泛的通信协议之一,几乎所有的网络设备和操作系统都支持TCP协议。这意味着使用TCP作为通信协议可以提高OpenHarmony与其他设备和系统的兼容性,降低了集成和交互的复杂性。
- 成熟的实现和开发工具:TCP协议的实现和开发工具已经非常成熟,有许多可用的库和工具可以用于快速开发和部署TCP通信功能。这可以节省开发时间和资源,并且降低了开发过程中的风险。
- 支持面向连接的通信模式:TCP是一种面向连接的通信协议,它建立了可靠的双向通信通道,适合于需要长时间持续通信的场景,如客户端和服务器之间的通信。这种连接导向的通信模式可以满足许多应用场景的需求,包括实时数据传输、远程控制等。
- 通用性:TCP协议是传输层协议,与网络类型无关,因此它可以在各种类型的网络中使用,包括有线网络(如以太网)和无线网络(如Wi-Fi、蜂窝网络)。
3、两个重要概念:客户端与服务端
- 服务器被动连接,客户端主动连接:在TCP连接中,服务器通常处于被动状态,等待客户端的连接请求。而客户端则处于主动状态,负责发起连接请求。一旦连接建立成功,双方就可以进行数据传输。
4、指令认识
- 以下是几个网络调试常用的指令:
ifconfig # 用于Linux和OpenHarmony,常用于查看IP
ipconfig # 用于Windows,常用于查看IP
ping <IP> # 测试网络连通性
5、本节课使用工具 – NetAssist
- 一个网络调试工具:下载链接
二、Socket 编程(套接字编程)
1、socket()
socket()
函数是用于创建一个新的套接字(socket)的系统调用函数。套接字是一种通信机制,允许进程通过网络进行通信。在网络编程中,socket() 函数是一种创建套接字的标准方法,它通常在客户端和服务器端代码中都会用到。
-
函数原型:
int socket(int domain, int type, int protocol);
-
domain
:指定套接字的地址族(Address Family),常见的包括:AF_INET
:IPv4地址族AF_INET6
:IPv6地址族AF_UNIX:UNIX
本地域套接字
-
type
:指定套接字的类型,常见的包括:SOCK_STREAM
:流套接字,用于面向连接的可靠数据传输,常用于TCP通信。SOCK_DGRAM
:数据报套接字,用于无连接的不可靠数据传输,常用于UDP通信。
-
protocol
:指定协议,通常为0,表示使用默认协议。
2、close()
close()
函数用于关闭一个已经建立的TCP连接。关闭连接的目的是释放资源并告知对方连接的结束。- 函数原型:
close(int fd)
fd
:表示待绑定的套接字的文件描述符。
3、sockaddr_in
-
sockaddr_in
结构体通常在网络编程中用于指定套接字的地址信息。 -
包含在头文件
include <netinet/in.h>
中 -
结构体成员:
struct sockaddr_in{ sa_family_t sin_family; /* 指定地址族,即网络通信所使用的协议类型。*/ in_port_t sin_port; /* 表示端口号*/ struct in_addr sin_addr; /* 通配地址*/ unint8_t sin_zero[8]; /* 指定套接字的通信地址,从而确立通信的目标。通常未使用 };
sin_addr
用于表示 IPv4 地址,可以是特定的 IP 地址,也可以是通配地址INADDR_ANY
;sin_port
则表示端口号,用于标识一个网络服务。sin_family
用于指定地址族,即网络通信所使用的协议类型。在IPv4的上下文中,它的值通常是AF_INET
。sin_zero
是一个长度为8的字节数组,通常未使用通过填充字段,可以指定套接字的通信地址,从而确立通信的目标。
-
与系统调用交互:在进行套接字编程时,常常需要将
sockaddr_in
结构体作为参数传递给一些系统调用函数,例如bind()、connect()、accept()
等。这些函数通过读取sockaddr_in
结构体中的地址信息,可以确定套接字的本地或远程地址,从而进行相应的操作,如绑定、连接或接受连接。
4、bind()
-
bind()
函数是用于将一个套接字(socket)与一个特定的地址(通常是 IP 地址和端口号)绑定在一起的系统调用。在网络编程中,bind() 函数通常用于服务器端程序,在其创建套接字后,将该套接字绑定到一个特定的端口上,以便监听该端口并接受客户端的连接请求。 -
函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd
:指定要绑定的套接字文件描述符。addr
:指向 sockaddr 结构体的指针,该结构体包含了要绑定的地址信息。addrlen
:指定地址结构体的长度。- 如果绑定成功,bind() 函数返回 0,否则返回 -1
-
示例:
if (bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1) { perror("Error binding socket"); close(sockfd); }
5、listen()
-
listen()
函数用于将指定的套接字设置为监听状态,开始接受客户端的连接请求。在服务器端编程中,通常在调用 bind() 函数绑定地址之后,使用 listen() 函数来准备套接字接受连接请求。 -
函数原型:
int listen(int sockfd, in MAX_CLIENTS);
sockfd
:表示待绑定的套接字的文件描述符,即通过 socket() 函数创建的套接字。MAX_CLIENTS
:参数指定了内核允许在套接字队列中等待的连接的最大数量。如果队列已满,后续的连接请求将被拒绝,直到有连接被接受或队列中的连接被处理。这个参数通常设置为一个适当的值,以确保服务器能够处理所有传入的连接请求。- 如果绑定成功,listen() 函数返回 0,否则返回 -1。
-
示例:
if (listen(server_sock, MAX_CLIENTS) == -1) { perror("Failed to listen"); close(server_sock); }
6、accept()
-
accept()
函数是在服务器端套接字上调用的系统调用,用于接受客户端的连接请求,并创建一个新的套接字用于与客户端进行通信。这个函数通常在调用了 listen() 函数之后,用于实际接受传入的连接。 -
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd
:指定要接受连接请求的监听套接字文件描述符。addr
:用于存储客户端地址信息的结构体指针。如果不需要获取客户端地址信息,可以传入NULL
。addrlen
:指向一个整数的指针,表示客户端地址信息结构体的长度。在调用accept()
函数之前,应该将其初始化为sizeof(struct sockaddr)
。- 当调用 accept() 函数时,它会阻塞程序的执行,直到有客户端连接请求到达服务器套接字 sockfd。一旦有连接请求到达,accept() 函数会从服务器的待处理连接队列中取出一个连接请求,并创建一个新的套接字来处理该连接。这个新的套接字将用于与客户端进行通信,而服务器原始的套接字继续监听其他连接请求。
- 如果绑定成功,bind() 函数返回 0,否则返回 -1
-
示例:
if (accept(sockfd,(struct sockaddr *)&client_addr,&client_len) == -1) { perror("Error accepting connection"); close(sockfd); }
7、connect()
-
connect()
函数用于客户端套接字向服务器发起连接请求。当客户端需要与远程服务器建立连接时,就可以使用 connect() 函数。