当前位置: 首页 > news >正文

鸿蒙网络编程系列54-仓颉版实现Smtp邮件发送客户端

1. SMTP邮件发送客户端

在本系列的第4篇文章《鸿蒙网络编程系列4-实现SMTP邮件发送客户端》中,基于ArkTS语言在API9环境下使用TCPSocket对象演示了SMTP客户端的实现,并且通过腾讯邮件服务器执行了实际的邮件发送。不过,在2024年末,腾讯发了一个通知,从2024年11月20日开始,停用以明文非加密方式登录的第三方邮件客户端,必需启用SSL/TLS加密方式。不过,除了腾讯邮件发送服务器,还有很多其他邮件服务器支持使用明文登录,其中比较知名的有搜狐邮箱,可以通过如下的方式启用:

保存的时候,搜狐邮箱会自动生成独立密码,将来可以使用这个密码执行登录。

本文将使用仓颉语言在API17环境下实现SMTP邮件发送客户端,具体的邮件发送将通过搜狐邮箱实现,关于SMTP协议的相关基础知识,可以参考本系列第4篇文章的第一部分,这里不再赘述。

2. 邮件发送客户端示例演示

本示例运行后的页面如图所示:

输入SMTP服务器地址和端口(这里输入的是搜狐邮箱发送服务器的地址),再输入邮箱用户名和登录密码,此时就可以单击“登录”按钮执行登录了,如图所示:

登录成功后,输入收件人、发件人邮箱地址以及邮件的标题和内容,再单击下面的“发送邮件”按钮,既可以执行邮件发送,过程如下所示:

发送成功后,登录收件人的邮箱,就可以查看发送的邮件了,邮件内容如下所示:

3. 邮件发送客户端示例编写

下面详细介绍创建该示例的步骤(确保DevEco Studio已安装仓颉插件)。

步骤1:创建[Cangjie]Empty Ability项目。

步骤2:在module.json5配置文件加上对权限的声明:

"requestPermissions": [{"name": "ohos.permission.INTERNET"}]

这里添加了访问互联网的权限。

步骤3:在build-profile.json5配置文件加上仓颉编译架构:

"cangjieOptions": {"path": "./src/main/cangjie/cjpm.toml","abiFilters": ["arm64-v8a", "x86_64"]}

步骤4:在index.cj文件里添加如下的代码:

package ohos_app_cangjie_entryimport ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*
import std.collection.HashMap
import std.convert.*
import std.net.*
import std.socket.*
import encoding.base64.toBase64String@Entry
@Component
class EntryView {@Statevar title: String = 'SMTP邮件发送客户端示例';//连接、通讯历史记录@Statevar msgHistory: String = ''//服务器是否响应(发送数据到客户端)var isServerResponse: Bool = false//服务端地址,smtp.sohu.com的ip地址为116.130.217.16@Statevar serverAddr: String = "116.130.217.16"//服务端端口,smtp.sohu.com的端口为25,不同的smtp服务器端口可能不一样@Statevar serverPort: UInt16 = 25//用户名@Statevar userName: String = "youmail@sohu.com"//密码,对于搜狐邮箱,这里是独立密码@Statevar passwd: String = "youpassword"//收件人邮箱列表(如果多个使用逗号分隔)@Statevar rcptList: String = "*****@sohu.com,****@qq.com"//发件人邮箱@Statevar mailFrom: String = "youmail@sohu.com"//邮件标题@Statevar mailTitle: String = "测试邮件标题"//邮件内容@Statevar mailContent: String = "这是来自鸿蒙的问候!"//是否正在登录@Statevar isLogin: Bool = false//是否可以发送邮件@Statevar canSend: Bool = false//TCP客户端var tcpClient: ?TcpSocket = Nonelet scroller: Scroller = Scroller()func build() {Row {Column {Text(title).fontSize(14).fontWeight(FontWeight.Bold).width(100.percent).textAlign(TextAlign.Center).padding(10)Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text("SMTP服务器地址:").fontSize(14)TextInput(text: serverAddr).onChange({value => serverAddr = value}).width(100).fontSize(11).flexGrow(1)Text(":").fontSize(14)TextInput(text: serverPort.toString()).onChange({value => serverPort = UInt16.parse(value)}).setType(InputType.Number).width(80).fontSize(11)}.width(100.percent).padding(5)Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text("邮箱用户名:").fontSize(14).width(100).flexGrow(0)TextInput(text: userName).onChange({value => userName = value}).width(110).fontSize(12).flexGrow(1)}.width(100.percent).padding(5)Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text("登录密码:").fontSize(14).width(100).flexGrow(0)TextInput(text: passwd).onChange({value => passwd = value}).setType(InputType.Password).width(110).fontSize(12).flexGrow(1)Button("登录").onClick {evt => login()}.enabled(!isLogin && userName != "" && passwd != "").width(70).fontSize(14)}.width(100.percent).padding(5)Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text("收件人邮箱:").fontSize(14).width(100).flexGrow(0)TextArea(placeholder: "多个收件人使用逗号分隔", text: rcptList).onChange({value => rcptList = value}).width(110).fontSize(12).flexGrow(1)}.width(100.percent).padding(5)Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text("发件人邮箱:").fontSize(14).width(100).flexGrow(0)TextInput(text: mailFrom).onChange({value => mailFrom = value}).width(110).fontSize(12).flexGrow(1)}.width(100.percent).padding(5)Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text("邮件标题:").fontSize(14).width(100).flexGrow(0)TextInput(text: mailTitle).onChange({value => mailTitle = value}).width(110).fontSize(12).flexGrow(1)}.width(100.percent).padding(5)Flex(FlexParams(direction: FlexDirection.Column, justifyContent: FlexAlign.Start,alignItems: ItemAlign.Center)) {Text("邮件内容:").fontSize(14).width(100.percent)TextArea(placeholder: "请输入要发送的邮件内容", text: mailContent).onChange({value => mailContent = value}).width(100.percent).height(80).fontSize(12)Row() {Button("发送邮件").onClick {evt => sendMail()}.enabled(canSend).width(100).fontSize(14)}.width(100.percent).justifyContent(FlexAlign.End)Scroll(scroller) {Text(msgHistory).textAlign(TextAlign.Start).padding(10).width(100.percent).backgroundColor(0xeeeeee)}.align(Alignment.Top).backgroundColor(0xeeeeee).height(200).flexGrow(1).scrollable(ScrollDirection.Vertical).scrollBar(BarState.On).scrollBarWidth(20)}.width(100.percent).padding(5).flexGrow(1).height(300)}.width(100.percent).height(100.percent)}.height(100.percent)}//发送命令到服务器func sendCmd2ServerWithCRLF(cmd: String) {let fullCmd: String = cmd + "\r\n"tcpClient?.write(fullCmd.toArray())msgHistory += "C:${cmd}\r\n"}//从服务器读取消息func readMsgFromServer() {let buffer = Array<UInt8>(1024, item: 0)//从socket读取数据var readCount = tcpClient?.read(buffer)//把接收到的数据转换为字符串let content = String.fromUtf8(buffer[0..readCount.getOrThrow()])msgHistory += "S:${content}"return content}//登录func login() {tcpClient = TcpSocket(serverAddr, serverPort)isLogin = true//启动一个线程执行登录spawn {try {tcpClient?.connect()msgHistory += "C:连接成功!\r\n"} catch (err: Exception) {msgHistory += "C:连接失败${err.message}!\r\n"isLogin = falsereturn}try {sendCmd2ServerWithCRLF("ehlo anyname")var content = readMsgFromServer()sendCmd2ServerWithCRLF("auth login")content = readMsgFromServer()sendCmd2ServerWithCRLF(toBase64String(userName.toArray()))content = readMsgFromServer()sendCmd2ServerWithCRLF(toBase64String(passwd.toArray()))content = readMsgFromServer()canSend = true} catch (exp: Exception) {msgHistory += "从Socket读取数据错误:${exp}\r\n"}isLogin = false}}func sendMail() {//启动一个线程执行发送spawn {try {sendCmd2ServerWithCRLF("mail from:<${mailFrom}>")var content = readMsgFromServer()for (rcpt in rcptList.split(",")) {sendCmd2ServerWithCRLF("rcpt to:<${rcpt}>")content = readMsgFromServer()}//准备发送邮件内容sendCmd2ServerWithCRLF("data")content = readMsgFromServer()let mailBody = "Subject: ${mailTitle} \r\nFrom: ${mailFrom}\r\n\r\n${mailContent}\r\n."sendCmd2ServerWithCRLF(mailBody)content = readMsgFromServer()sendCmd2ServerWithCRLF("quit")content = readMsgFromServer()} catch (exp: Exception) {msgHistory += "从套接字读取数据错误:${exp}\r\n"}}}
}

步骤5:编译运行,可以使用模拟器或者真机。

步骤6:按照本文第2部分“邮件发送客户端示例演示”操作即可。

4. 代码分析

本文的核心代码主要是两个函数,第一个是发送命令到服务器的函数sendCmd2ServerWithCRLF,该函数在发送命令给服务器时,会在命令后面添加回车换行符号,然后调用tcpClient的write函数执行实际的发送。第二个是从服务器读取消息的函数readMsgFromServer,该函数会从套接字读取数据并写入到缓冲区buffer中,然后把数据转换为字符串。

需要特别注意的是,为了简化开发,第二个函数假设可以一次性读取服务器的完整回复,并且服务器的回复不超过1024字节,这个假设一般是成立的,不过,在一些特殊情况下,比如网络不太好,或者网络数据“粘包”,可能会出现接收问题。这时候,可以通过更复杂的代码来解决,这里就不展开了,可以参考本系列相关的“TCP粘包”文章。

(本文作者原创,除非明确授权禁止转载)

本文源码地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/tcp/SmtpClient4Cj

本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples

http://www.lryc.cn/news/571770.html

相关文章:

  • LVS +Keepalived 高可用群集
  • 51c大模型~合集141
  • maven编译报错java: Compilation failed: internal java compiler error
  • 基于C++实现(控制台)机械提取词频
  • Hive的分区表(静态分区、动态分区)、分桶表、四种排序方式和数据加载方式
  • Linux操作系统之进程(六):进程的控制(上)
  • 鼎捷T100开发语言-Genero FGL 终极技术手册
  • Linux软件管理包-yum和基础开发工具-vim
  • 6.18 redis面试题 日志 缓存淘汰过期删除 集群
  • 【Leetcode】每日一题 —— No.2966
  • milvus和attu的搭建
  • 八种常见的神经网络介绍
  • Ubuntu 使用kubeadm部署k8s系统组件反复重启的问题
  • LVS +Keepalived高可用群集
  • 物联网控制技术期末复习 知识点总结 第六章 物联网控制算法(PID算法 PWM算法)
  • vscode连接不上服务器问题修复
  • 如何运用 AI 工具运营海外社媒账号
  • 借助AI学习编程,走向架构师之路
  • class对象【C#】2025复习
  • 排序算法专题
  • 【文本大模型】从0开始 - 本地部署一个ChatGLM对话模型(基于WebUI)
  • MySQL 索引和查询优化
  • linux虚拟机yum命令报错解决方案
  • 学习大模型---需要掌握的数学知识
  • 【Python编程】__all__ = [] 的作用
  • PROFIBUS转EtherCAT网关:市政再生水厂的智能连接枢纽
  • 二分查找算法题
  • 鸿蒙Next仓颉语言开发实战教程:懒加载
  • Neo4j常见语句-delete
  • 华为云Flexus+DeepSeek征文|一键部署华为云CCE容器高可用Dify平台的实践经验与思考