一、前言
承接上篇文章的项目介绍,本篇文章将详细介绍如何在小熊派搭建TCP服务器,处理来自各种客户端的请求。本篇文章适合于小型系统和标准系统,大家可以将本项目移植到符合以上条件的开发板上。配置好对应的编译规则即可。
二、准备工作
1、小熊派-鸿蒙·叔(BearPi-HM Micro)一台。
2、已经配置好小熊派开发环境的电脑一台。
3、将小熊派接入到局域网。
三、流程简介
编写TCP服务器的C文件 --> 配置BUILD.gn --> 将我们的代码添加编译–>编译烧录。
四、网络开发基础知识
在OpenHarmony上进行网络开发与在Linux上进行网络开发十分相似,采用的是C语言的套接字(Socket)开发。
进行网络开发之前,如果没有相关基础,建议先了解一下OSI网络模型与TCP/IP协议的关系。本次用到的是用Socket进行传输层的开发,使用了到了一点点应用层HTTP的协议。
(1) 什么是Socket
在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
总结为一句就是:socket就是整合好TCP/IP协议的一个工具。让我们无需过度关注于底层协议的实现,直接用封装好的socket就行了。
(2) TCP通信流程
在开始使用socket之前,我们要知道TCP服务器端与TCP客户端进行通信的流程。
我们小熊派要实现的是图中的9右半部分,即TCP服务器端。我们一步一步的看,每一步都有对应的代码来实现。
第一步:socket()函数用来创建socket套接字,可以理解为面对对象中的创建对象,但是不等同。
第二步:bind()函数用来绑定IP和端口,即选择你的TCP服务器在哪个IP和端口提供服务。
第三步:listen()函数用来监听上一步中选择的IP和端口。
第四步:accept()函数用来等待来自客户端的连接,即进入阻塞状态。
第五步:read()函数会在有客户请求时,读取客户端发送的请求数据。
第六步:write()函数可以给客户端返回数据,该操作可选,也可以不给客户端返回任何数据。
第七步:read()和write()操作可以一直反复执行,即互相不断通信,当通信完成时,执行close函数关闭套接字。
(3) 相关函数讲解
创建socket套接字
int socket(int domain,int type,int protocol);
参数介绍
- domain:协议域,又称协议族(family),常用的协议族有 AFL INET、AF INET6、AF LOCAL(或称AF UNIX, Unix成socket) AF ROUTE 等。协议族决定了 socket 的地址类型,在通信中必须采用对应的地址,如AF INET 決定了要用 ipv4 地址 。 (32位的》与端口号(16位的)的组合,AF UNIX 決定了要用一个绝对路径名作为地址。
- type:指定 Socket 类型。常用的 socket 类型有 SOCK STREAM、SOCK DGRAM、SOCK RAW 、SOCK PACKET、 SOCK SEQPACKET 等。流式 socket (SOCK STREAM)是一种面向连接的 Socket, 针对于面向连接的 TCP 。 服务应用。数据报式 socket(SOCK DGRAM) 是一种无连接的 Socket,对应于 无连接的 UDP 服务应用。
- protocol: 指定协议。常用协议有 IPPROTO TCP、IPPROTO UDP、IPPROTO STCP、IPPROTO TIPC 等,分别对应TCP 传输协议,UDP 传输协议、STCP 传输协议、TIPC 传输协议。参数为o时,会自动选择第二个参数类型对应的默认协议。
注意:type 和protocol 不可以随意组合,如SOCK STREAM 不可以跟 1PPROTOUDP 组合。
返回值: 如果调用成功就返口新创建的套接字的描述符,如果大败就返回INVALID SOCKET(Linux 下失败返回-1)。
绑定端口函数
int bind(int socketfd,const struct sockaddr *addr,socklen_t addrlen)
参数介绍
- socketfd:—个标识己连接套接口的描述字。
- address:是个sockaddr结构指针,该结构中包含了要结合的地址和端口号。
- address_len:确定 address 缓冲区的长度。
注意:其中 sockaddr这个地址结构根据地址创建 socket 时的地址协议族的不同而不同。
返回值:如果函数执行成功,返回值为0,否则为SOCKET_ERROR。
开始监听函数
int listen(int socketfd,int backlog)
参数介绍
- socketfd:要监听的socket描述字。
- backlog:相应socket可以排队的最大连接个数。
等待连接阻塞函数
int accept(int socketfd,struct sockaddr *addr, socklen_t *addrlen);
参数介绍
- socketfd:就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,已只知道一个地址和一个端口号。
- sockaddr:结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
- len:它也是结果的参数,用来接受上述 addr 的结构的大小的,已指明 addr 结构所占有的宇节个数。同样的,它也可以被设置为NULL。
注意:accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。
返回值:成功返回客户端的文件猫述符,失败返回-1。一如果accept成功返回,则服务器与容户
己经正确建立连接了,此时服务器通过accept返口的套接字来完成与客户的通信。
五、连接函数
int connnect(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
参数介绍
- socketfd:客户端socket的描述字。
- socketaddr:服务器的socket地址。
- addrlen:socket地址的长度
发送函数
发送函数有两个:
ssize_t write(int sockfd,const void *buf,szie_t nbytes);
参数介绍
- sockfd为要写入文件的描述符。
- buf为要写入数据的缓冲区地址。
- nbytes为要写入的数据的字节书。
返回值:成功返回写入的字节数,失败返回-1。
int send(int sockfd,const void *buf,int len,int flags)
参数介绍
- sockfd为要写入文件的描述符。
- buf为要写入数据的缓冲区地址。
- len为要写入的数据的字节书。
- flags有以下选择,MSG_ DONTROUTE 为不查找路由表;MSG_OOB为接受或发送带外数据 ;MSG PEEK为查看数据,且不从系统缓冲区移走数据;MSG WAITALL为等待任何数据;0和write函数的操作一样。
返回值:成功返回写入的字节数,失败返回-1。
接受函数
接受函数同样有两个:
ssize_t read(int sockfd,const void *buf,szie_t nbytes);
参数介绍
- sockfd为要读取文件的描述符。
- buf为要读取数据的缓冲区地址。
- nbytes为要读取的数据的字节书。
返回值:成功返回读取到的字节数,失败返回-1。
int recv(int sockfd,const void *buf,int len,int flags)
参数介绍
- sockfd为要写入文件的描述符。
- buf为要写入数据的缓冲区地址。
- len为要写入的数据的字节书。
- flags有以下选择,MSG_ DONTROUTE 为不查找路由表;MSG_OOB为接受或发送带外数据 ;MSG PEEK为查看数据,且不从系统缓冲区移走数据;MSG WAITALL为等待任何数据;0和write函数的操作一样。
返回值:成功返回写入的字节数,失败返回-1。