WEB后台--邮件和短信业务实现(包括Java一键实现、封装和异步)以及原理详解

本来就打算针对一些固定的特别点的业务(QQ与网易邮件、拦截设计、短信、定时器等等)来进行记录以及解析原理,这些会比较零散记录在JavaWeb的分类里面,感兴趣的童鞋可以去看下。

有人问为什么要邮件短信一起写,呃,短信的东西,非巨型公司都是用第三方的,第三方的文档支持都十分完整,短信例子的话,我一会也是引用第三方去做一个而已。主要在于原理,两个都是十分相近的业务,而且都是在TCP/IP的应用层封装,并且设计的重传方案基本相似。
重传方案,为了避免本篇过长,我会在后面一篇文章写出,结合线程或者定时器的重传方案。

文章结构:(1)邮件实现与原理;(2)短信实现与原理。

DEMO在本文最下方。

文章目录:

(1)邮件实现与原理

  • 邮件概述(邮件在互联网通信架构中的位置)
  • 一个邮件系统的组成分析
  • 邮件系统--邮件通信过程如图
  • 邮件协议详解
  • Java一键实现邮件发送
  • Java邮件封装和异步实现

(2)短信实现与原理

  • 短信的概述:(短信在互联网通信架构中的位置)
  • 短信原理
  • 短信系统--短信通信过程
  • 短信的协议
  • 一键实现短信发送
  • Java短信封装和异步实现

一、邮件实现与原理:

(1)邮件概述(邮件在互联网通信架构中的位置):

这里写图片描述
上图就是互联网的TCP/IP架构。具体详情请见我的【计算机网络系列】。

应用层定义了应用程序使用互联网的规程。电子邮件的协议就建立在这一层。

/*  
*   为什么建立在TCP/IP的应用层??原因是??
*   
*   首先确立在现今社会,企业的正式工作都是通过邮件进行。邮件对于可靠性要求非常高,所以就要求一个可靠的传输协议。就把邮件协议建立在TCP/IP的应用层基础上了。

    IP 协议的主要功能包括无连结数据报传送﹑数据报寻径以及差错处理三部分。IP协议的特点是点到点的,IP对等实体间的通信不经过中间机器,对等实体所在的机器位于同一物理网络,对等机器之间有直接的物理连接。IP层的主要功能是屏蔽下面物理层的差别,向上一层提供一致的数据格式。所有要传输的数据,被按照一定的格式分组封装层IP数据报,数据报单元通过寻径等机制进行传输,在接收方数据报进行重组,得到最初要传送的数据。由于IP协议是不可靠的数据传输协议,由于网络的拥塞而发生的数据丢失等情况是不可避免的,因此Internet 还必须有一定的控制重传机制,这就是差错与控制报文协议(ICMP)。
    但IP协议还不能解决数据分组在传输过程中可能出现的问题。(透明传输等问题)。
    因此,还需要TCP协议来提供可靠的并且无差错的通信服务。TCP协议被称作一种端对端协议。这是因为它为两台计算机之间的连接起了重要作用:当一台计算机需要与另一台远程计算机连接时,TCP协议会让它们建立一个连接、发送和接收数据以及终止连接。传输控制协议TCP协议利用重发技术和拥塞控制机制,向应用程序提供可靠的通信连接,使它能够自动适应网上的各种变化。
     IP协议只保证计算机能发送和接收分组数据,而TCP协议则可提供一个可靠的、可流控的、全双工的信息流传输服务。虽然IP和TCP这两个协议的功能不尽相同,也可以分开单独使用,但它们是在同一时期作为一个协议来设计的,并且在功能上也是互补的。只有两者的结合,才能保证 Internet 在复杂的环境下正常运行。凡是要连接到 Internet 的计算机,都必须同时安装和使用这两个协议,因此在实际中常把这两个协议统称作TCP/IP协议。 TCP/IP 协议除了TCP协议和IP协议,还包含物理接口和IP层之间的ARP/RARP协议,应用层的FTP协议﹑SMTP协议和BOOTP协议等,所用的这些协议构成Intenet 的TCP/IP 协议族。
*/

(2)一个邮件系统的组成分析:

一个邮件系统组成必须包括邮件服务器,然后是用户代理和邮件传送协议。

(一)邮件服务器。(存储用户邮箱的地方)

是一个供在网上存储邮件的空间。

一般每个邮件服务器的提供商都有自己的邮件服务器,只要你申请了他的邮箱账号,你就会在他的邮件服务器上拥有自己邮箱。像Google,腾讯都是邮件服务的提供商,他们都有自己的邮件服务器,如果你申请了Gamil邮箱,那么在Google的邮件服务器上面,你就有自己的一块存储空间了。同样,如果你申请了qq邮箱,那么在qq邮件服务器上面也有你自己的空间了,也就是你的邮箱。当你要收取信件的时候,你就需要连接到不同的服务器上面。不同的邮件服务提供商,他们的邮件服务器的地址是不一样的。后面会介绍一些常用的邮件服务器的地址。

(二) 用户代理:(用户读取邮件的地方)

就是你用来从邮件服务器上读取或者发送邮件到邮件服务器上的一个软件。

比如常用的OutLook,qq邮箱(公司呈现出来的,邮箱服务器是看不到的另一区域)等等。我们知道,我们的邮件都是存储在邮件服务器上面的,我们发送邮件的时候,去往邮件服务器上面发,我们收取邮件的时候,也需要从服务器上面读。为了方便的完成这些工作,我们就需要用户代理。

(三) 邮件传送协议:(邮箱信息发送的约定)

是指邮件在传送过程中必须遵守的约定,它规定了不同的服务器(或客户端)之间应如何交换信息。

比较常见的有邮件服务器之间的通信协议SMTP以及用户代理与邮件服务器之间的通信协议POP3。(注意:邮件服务器之间的协议是使用SMTP,用户发送邮件到邮件服务器使用的还是SMTP协议,用户从邮件服务器读取邮件用的才是POP3协议)。

(3)邮件系统--邮件通信过程如图:

图取自教材的计算机网络--谢希任
这里写图片描述

邮件通信过程(文字描述):

1)发信人调用自己的用户代理撰写、编辑邮件,并写清楚收件人的邮箱地址;

2)发信人的用户代理根据发信人编辑的信息,生成一封符合邮件格式的邮件;

3)发信人的用户代理把邮件发送到发信人的的邮件服务器上,邮件服务器上面有一个缓冲队列,发送到邮件服务器上面的邮件都会加入到缓冲队列中,等待邮件服务器上的SMTP客户端进行发送;

4) 发信人的邮件服务器的 SMTP 客户端与接收方邮件服务器的 SMTP 服务器建立 TCP 连接,发信人的邮件服务器使用SMTP协议把这封邮件发送到收件人的邮件服务器上(它会自动根据收件人的邮箱来分析出收件人的邮箱服务器);

5)收件人的邮件服务器收到邮件后,把这封邮件放到收件人在这个服务器上的邮箱中,等待收件人进行读取;

6)收件人使用他的用户代理来收取邮件。首先用户代理使用POP3协议来连接收件人所在的邮件服务器,身份验证成功后,用户代理就可以把邮件服务器上面的收件人邮箱里面的邮件读取出来,并展示给收件人。

(4)邮件协议详解:

(一)SMTP:

SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,是一种提供可靠且有效电子邮件传输的协议。SMTP是建立在FTP文件传输服务上的一种邮件服务,主要用于传输系统之间的邮件信息并提供与来信有关的通知。SMTP主要负责底层的邮件系统如何将邮件从一台机器传至另外一台机器。

SMTP提供了一种邮件传输的机制,当收件方和发件方都在一个网络上时,可以把邮件直传给对方;当双方不在同一个网络上时,需要通过一个或几个中间服务器转发。SMTP首先由发件方提出申请,要求与接收方SMTP建立双向的通信渠道,收件方可以是最终收件人也可以是中间转发的服务器。收件方服务器确认可以建立连接后,双发就可以开始通信。

(二)POP3:

是把邮件从电子邮箱服务器中传输到本地计算机客户端的协议。

POP3(Post Office Protocol 3)即邮局协议的第3个版本,它是规定个人计算机如何连接到互联网上的邮件服务器进行收发邮件的协议。它是因特网电子邮件的第一个离线协议标准,POP3协议允许用户从服务器上把邮件存储到本地主机(即自己的计算机)上,同时根据客户端的操作删除或保存在邮件服务器上的邮件,而POP3服务器则是遵循POP3协议的接收邮件服务器,用来接收电子邮件的。

(三)IMAP:

Internet Mail Access Protocol(交互式邮件存取协议)。它的主要作用是邮件客户端(例如MS Outlook Express)可以通过这种协议从邮件服务器上获取邮件的信息,下载邮件等。IMAP协议运行在TCP/IP协议之上,使用的端口是143。它与POP3协议的主要区别是用户可以不用把所有的邮件全部下载,可以通过客户端直接对服务器上的邮件进行操作。

IMAP协议比较自由的功能是用户可以维护自己在服务器上的邮件目录;可以直接抓取邮件的特定部分(例如只有文本)。

IMAP的一个与POP3的区别是:IMAP它只下载邮件的主题,并不是把所有的邮件内容都下载下来,而是你邮箱当中还保留着邮件的副本,没有把你原邮箱中的邮件删除,你用邮件客户软件阅读邮件时才下载邮件的内容。

(5)Java一键实现邮件发送:(QQ邮箱为例,一会封装用网易邮箱(很多坑))

(一)先导入库:

<!-- poi -->
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi</artifactId>
      <version>3.15</version>
    </dependency>
    <!--zdk add 2017-5-16-->
    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi-ooxml</artifactId>
      <version>3.15</version>
    </dependency>

    <!-- Java邮件操作类-->
    <dependency>
      <groupId>javax.mail</groupId>
      <artifactId>mail</artifactId>
      <version>1.4.5</version>
    </dependency>

(二)给发信的邮箱申请协议服务开通

这里写图片描述

(三)一键复制即可实现:

我们需要修改的东西:邮箱、邮箱服务器授权码


import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Date;
import java.util.Properties;
/**
 * Created by 符柱成 on 2017/6/6.
 */
public class JavaMailSendTest {
    // 发件人的 邮箱 和 密码(替换为自己的邮箱和密码)
    // PS: 某些邮箱服务器为了增加邮箱本身密码的安全性,给 SMTP 客户端设置了独立密码(有的邮箱称为“授权码”),
    //     对于开启了独立密码的邮箱, 这里的邮箱密码必需使用这个独立密码(授权码)。
    public static String myEmailAccount = "751197996@qq.com";//我们申请服务的邮箱
    public static String myEmailPassword = "";//这个就要填上我们刚刚拿到的授权码
    // 发件人邮箱的 SMTP 服务器地址, 必须准确, 不同邮件服务器地址不同, 一般(只是一般, 绝非绝对)格式为: smtp.xxx.com
    // 网易163邮箱的 SMTP 服务器地址为: smtp.163.com;qq邮箱的SMTP服务器地址:smtp.qq.com
    public static String myEmailSMTPHost = "smtp.qq.com";

    // 收件人邮箱(替换为自己知道的有效邮箱)
    public static String receiveMailAccount = "1433317518@qq.com";

    public static void main(String[] args) throws Exception {
        // 1. 创建参数配置, 用于连接邮件服务器的参数配置
        Properties props = new Properties();                    // 参数配置
        props.setProperty("mail.transport.protocol", "smtp");   // 使用的协议(JavaMail规范要求)
        props.setProperty("mail.smtp.host", myEmailSMTPHost);   // 发件人的邮箱的 SMTP 服务器地址
        props.setProperty("mail.smtp.auth", "true");            // 需要请求认证


        // PS: 某些邮箱服务器要求 SMTP 连接需要使用 SSL 安全认证 (为了提高安全性, 邮箱支持SSL连接, 也可以自己开启),
        //     如果无法连接邮件服务器, 仔细查看控制台打印的 log, 如果有有类似 “连接失败, 要求 SSL 安全连接” 等错误,
        //     打开下面 /* ... */ 之间的注释代码, 开启 SSL 安全连接。
        /*
        // SMTP 服务器的端口 (非 SSL 连接的端口一般默认为 25, 可以不添加, 如果开启了 SSL 连接,
        //                  需要改为对应邮箱的 SMTP 服务器的端口, 具体可查看对应邮箱服务的帮助,
        //                  QQ邮箱的SMTP(SLL)端口为465或587, 其他邮箱自行去查看)
        final String smtpPort = "465";
        props.setProperty("mail.smtp.port", smtpPort);
        props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.setProperty("mail.smtp.socketFactory.fallback", "false");
        props.setProperty("mail.smtp.socketFactory.port", smtpPort);
        */
        final String smtpPort = "465";
        props.setProperty("mail.smtp.port", smtpPort);
        props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        props.setProperty("mail.smtp.socketFactory.fallback", "false");
        props.setProperty("mail.smtp.socketFactory.port", smtpPort);

        // 2. 根据配置创建会话对象, 用于和邮件服务器交互
        Session session = Session.getDefaultInstance(props);
        session.setDebug(true);                                 // 设置为debug模式, 可以查看详细的发送 log

        // 3. 创建一封邮件
        MimeMessage message = createMimeMessage(session, myEmailAccount, receiveMailAccount);

        // 4. 根据 Session 获取邮件传输对象
        Transport transport = session.getTransport();

        // 5. 使用 邮箱账号 和 密码 连接邮件服务器, 这里认证的邮箱必须与 message 中的发件人邮箱一致, 否则报错
        //
        //    PS_01: 成败的判断关键在此一句, 如果连接服务器失败, 都会在控制台输出相应失败原因的 log,
        //           仔细查看失败原因, 有些邮箱服务器会返回错误码或查看错误类型的链接, 根据给出的错误
        //           类型到对应邮件服务器的帮助网站上查看具体失败原因。
        //
        //    PS_02: 连接失败的原因通常为以下几点, 仔细检查代码:
        //           (1) 邮箱没有开启 SMTP 服务;
        //           (2) 邮箱密码错误, 例如某些邮箱开启了独立密码;
        //           (3) 邮箱服务器要求必须要使用 SSL 安全连接;
        //           (4) 请求过于频繁或其他原因, 被邮件服务器拒绝服务;
        //           (5) 如果以上几点都确定无误, 到邮件服务器网站查找帮助。
        //
        //    PS_03: 仔细看log, 认真看log, 看懂log, 错误原因都在log已说明。
        transport.connect(myEmailAccount, myEmailPassword);

        // 6. 发送邮件, 发到所有的收件地址, message.getAllRecipients() 获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人
        transport.sendMessage(message, message.getAllRecipients());

        // 7. 关闭连接
        transport.close();
    }

    /**
     * 创建一封只包含文本的简单邮件
     *
     * @param session 和服务器交互的会话
     * @param sendMail 发件人邮箱
     * @param receiveMail 收件人邮箱
     * @return
     * @throws Exception
     */
    public static MimeMessage createMimeMessage(Session session, String sendMail, String receiveMail) throws Exception {
        // 1. 创建一封邮件
        MimeMessage message = new MimeMessage(session);
        // 2. From: 发件人
        message.setFrom(new InternetAddress(sendMail, "符柱成主页", "UTF-8"));
        // 3. To: 收件人(可以增加多个收件人、抄送、密送)
        message.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(receiveMail, "XX用户", "UTF-8"));
        // 4. Subject: 邮件主题
        message.setSubject("重要通知啊,老哥", "UTF-8");
        // 5. Content: 邮件正文(可以使用html标签)
        message.setContent("辅助:打声招呼而已", "text/html;charset=UTF-8");
        // 6. 设置发件时间
        message.setSentDate(new Date());
        // 7. 保存设置
        message.saveChanges();
        return message;
    }
}
这里写图片描述

(6)Java邮件封装和异步实现:(网易邮箱为例)

为什么要异步??邮件和短信都要经过复杂的网络通信,这就意味着很可能极其耗时,使用同步方式,很容易导致主线程卡死,导致极差的体验。

所以,我们应该以异步方式去执行此业务,然后直接告诉用户已经发送(发送情况失败毕竟很少见)。

(一)定接口与实现类:

public interface EmailService {
    /*
     * hisEmail收件人email
     * subject主题(标题)
     * content内容(文本)
     */
    void sendEmail(String hisEmail, String subject, String content);
    
}

记得配置好config.properties。分别是发件人邮箱(开通SMTP服务的),授权码,邮件名字

SENDER_MAILBOX=???
MAIL_PASSWPRD=???
MAIL_NAME=BSS\u7CFB\u7EDF\u90AE\u7BB1

可以看到跟使用qq邮箱有很大区别,但是必须如此配置使用,必须使用Authenticator 去验证


public class EmailServiceImpl implements EmailService {
    private static final Log log = LogFactory.getLog(EmailServiceImpl.class);

    // 发件人的 邮箱 和 密码(替换为自己的邮箱和密码)
    // 对于开启了独立密码的邮箱, 这里的邮箱密码必需使用这个独立密码(授权码)。
    public static String myEmailAccount = "";
    public static String myEmailPassword = "";
    public static String myEmailName = "";
    // qq邮箱的SMTP服务器地址:smtp.qq.com
    public static String myEmailSMTPHost = "smtp.126.com";

    static {
        //博主自己封装了一个获取本地文件的配置参数方式,大家可以参考使用。针对config.properties的,想改别的文件请大家自行修改。
        try {
            myEmailAccount = Config.getConfigValue("SENDER_MAILBOX");
            myEmailPassword = Config.getConfigValue("MAIL_PASSWPRD");
            myEmailName = Config.getConfigValue("MAIL_NAME");
            System.out.println(myEmailAccount);
            System.out.println(myEmailPassword);
            System.out.println(myEmailName);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public void sendEmail(String hisEmail, String subject, String content) {
        System.out.println("myEmailAccount  :" + myEmailAccount);
        System.out.println("myEmailPassword :" + myEmailPassword);
        try {
            // 1. 创建参数配置, 用于连接邮件服务器的参数配置
            final Properties props = new Properties(); // 参数配置
            props.put("mail.smtp.auth", "true");
            props.put("mail.smtp.host", "smtp.126.com");

            // 发件人的账号
            props.put("mail.user", myEmailAccount);
            // 发件人的密码
            props.put("mail.password", myEmailPassword);
            //网易邮箱必须这样,使用Authenticator,进行一系列的验证。不然就是给你504,验证失败或者辣鸡邮件发不出去
            Authenticator authenticator = new Authenticator() {
                protected PasswordAuthentication getPasswordAuthentication() {
                    String userName = props.getProperty("mail.user");
                    String password = props.getProperty("mail.password");
                    return new PasswordAuthentication(userName, password);
                }
            };

            // 使用环境属性和授权信息,创建邮件会话
            Session mailSession = Session.getInstance(props, authenticator);
            // 创建邮件消息
            MimeMessage message = new MimeMessage(mailSession);
            // 设置发件人
            String username = props.getProperty("mail.user");
            InternetAddress form = new InternetAddress(username);
            message.setFrom(form);

            // 设置收件人
            InternetAddress to = new InternetAddress(hisEmail);
            message.setRecipient(RecipientType.TO, to);

            // 设置邮件标题
            message.setSubject(subject);

            // 设置邮件的内容体
            message.setContent(content, "text/html;charset=UTF-8");
            // 发送邮件
            Transport.send(message);
        } catch (AddressException e) {
            e.printStackTrace();
        } catch (MessagingException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

(二)采用工厂模式:

public class EmailServiceFactory {

    public static EmailService getEmailService(){
        return new EmailServiceImpl();
    }
}

(三)使用线程异步一键调用:

public class EmailTest {
    //创建一个线程池,可装载大概3个线程任务的
    private static ExecutorService executorService = Executors.newFixedThreadPool(3);

    // 收件人邮箱(替换为自己知道的有效邮箱)1433317518
    public static String receiveMailAccount = "751197996@qq.com";

    public static void main(String[] args)  {
        //这种写法详情请去参考lambda表达式
        executorService.submit(() -> EmailServiceFactory.getEmailService().sendEmail(receiveMailAccount, "BSS系统工单", "<body><p>工单工号GGGGDADA</p></body>"));
        //调用 shutdown() 方法,ExecutorService 并不会马上关闭,而是不再接收新的任务,一但所有的线程结束执行当前任务,ExecutorServie 才会真的关闭。所有在调用 shutdown() 方法之前提交到 ExecutorService 的任务都会执行。
        //调用时机由你自己去决定
        //你希望立即关闭 ExecutorService,你可以调用 shutdownNow() 方法。这個方法会尝试马上关闭所有正在执行的任务,并且跳过所有已经提交但是还没有运行的任务。但是对于正在执行的任务,是否能够成功关闭它是无法保证的,有可能他们真的被关闭掉了,也有可能它会一直执行到任务结束。这是一個最好的尝试。
        executorService.shutdown();
    }
}

二、短信实现与原理:

(1)短信的概述:(短信在互联网通信架构中的位置)

首先短信与邮件同属于TCP/IP这一层。SMS表示短信服务。简单来讲,它是在手机之间发送文字信息或从个人计算机或手持设备向手机发送信息的一种方式。短信的“短”指的是文本信息的最大发送量:160个字符(字母、数字或拉丁字母中的符号)。至于其他字母,例如中文,一条短信的最大发送量为70个字符。

(2)短信原理:

在理解其原理前,需要先懂得手机的通信原理。

手机通信原理:

尽管您没有使用手机打电话,您的手机也在不停地发送和接收着信息。它通过被称为控制通道的通路与手机发射塔进行通信。这种通讯的目的是让手机系统了解自己所在的信号区域,以便在您移动时,手机可以切换到其他信号区域。每隔一段时间,手机和发射塔将交换数据包以确定一切工作正常。手机也使用控制通道来建立呼叫。当有人打电话给您时,手机发射塔将通过控制通道向手机发送信号,然后手机就会振铃。同时,手机发射塔为手机提供两个语音信道频率用来进行通话。

手机通信为什么这样做??

在美国的标准模拟手机系统中,手机运营商获准在整个城市使用约800个频率。运营商将城市细分成小区,每个小区面积通常约26平方公里。通常把小区看作是一个大六边形网格上的一个个六边形。
由于手机和基站使用低功率发射器,因此相同频率可以在非邻小区中重复使用。每个小区有一个基站,由一个塔和一个安装有无线电设备的小机房组成。在模拟系统中,一个小区使用七分之一可用的双工语音信道。换句话说,在由七个小区构成的六边形网格中,每个小区使用七分之一的可用信道,因此每个小区都有唯一的一组频率,彼此间不会发生冲突:手机运营商通常可以在一个城市中使用832个无线电频率。每部手机在每次通话期间使用两个频率,即一个双工信道,因此每个运营商通常有395个语音信道。其他42个频率用于控制信道。
因此,每个小区大约有56个语音信道可用。也就是在任何小区中,可以有56个人同时用手机通话。以上是第一代的蜂窝方案设计。往后的方案基于此去扩展。
基站与小区内手机之间的传输不会超出该小区太远。蜂窝方案要求无论城市大小,都需要有大量的基站。一般的大城市可能有数百个发射塔,不过由于很多人使用手机,因此按用户平均下来,成本仍能保持较低。每个运营商在各个城市还会设置一个中心局,也称为移动电话交换局(MTSO)。该局处理与普通陆地电话系统的所有电话连接,控制所辖区域的所有基站。

短信通信原理:

基于手机通信原理,控制通道也为SMS短信提供通路。当朋友给您发送SMS短信时,该条短信将以控制通道上小型数据包的形式先通过SMSC(短信业务中心),然后通过手机发射塔,再由发射塔将短信发送到手机。同理,当您发送短信时,手机将通过控制通道将短信发送到发射塔,再由发射塔传送到SMSC,最后从这个位置到达接收目标。

短信的实际数据格式包括短信的长度、时戳、目标电话号码以及格式等等。

为什么是160个字符?就是为什么叫短信??

手机短信可以提供像数字页那样的短量数据。为了避免使用多于标准的转发和回复操作而使系统过载,短信技术的开发人员一致同意使用一次160个字符的最大发送量。但是160个字符的限制并不是绝对的。字符长度限制可能会因网络、手机型号以及无线运营商的差异而不同。许多手机在达到160个字符的限制时将不允许继续键入。这样您只能在发送后才可以继续键入。但是,许多服务可以自动将你所发送的短信拆分成若干个小于等于160个字符的信息块。这样,您就可以键入并发送一长条短信,只不过它会以几条短信的方式传输。

相对于电话的优势:

短信交流要比电话交流更加私密,而且省时。

手机短信是一种存储和转发服务,这意味着,如果您向朋友发送一条短信,短信不会直接进入到您朋友的手机上。这种方法的优势在于,您朋友的手机不必开机或处于服务区内,您也可以发送短信。您发出的短信将被存储在短消息业务中心(可以根据需要存储数日),当您的朋友打开手机或进入服务区时,就会立即收到这条信息。如果不将它删除,这条短信将始终存储在您朋友的SIM卡上。

除了一人对一人的短信交流,SMS也可以用于同时将一条短信发送给很多人,包括联系人列表或是特定区域的所有用户。这种服务叫做群发,企业用它来联系各组员工或通过在线服务向订阅用户发布新闻或其他信息。

(3)短信系统--短信通信过程:

这里写图片描述

当朋友给您发送SMS短信时,该条短信将通过基站以控制通道上小型数据包的形式先通过SMSC(短信业务中心),然后通过手机发射塔,再由发射塔(基站)将短信发送到手机。同理,当您发送短信时,手机将通过控制通道将短信发送到发射塔,再由发射塔传送到SMSC,最后从这个位置到达接收目标。

(4)短信的协议

标准的短信协议是SMPP。

SMPP(ShortMessage Peer to Peer)协议是一个开放的消息转换协议;它定义了一系列操作的协议数据单元(PDUS)和当SMPP运行时ESMS应用系统与SMSC之间交换的数据格式。从而完成SMSC与ESMES(外部短消息实体)的信息交换。SMPP是基于SMSC与ESME之间的请求和响应协议数据单元的交换,每一个SMPP操作都由一个请求PDU和相应的一个响应PDU组成,这种交换一般是基于IP网络。

SMPP协议是一个应用层协议,不提供传输功能。因此,底层网络连接将提供点对点的可靠数据传输。这些传输包括加密包,窗口,流量控制和错误处理等。

但是,广大运营商门都是自己定义自己的协议。如中国电信:SGMP;中国移动:CMPP; 中国联通:SGIP。

(5)一键实现短信发送:

public class MessageTest {

//短信服务提供商。这个就百度吧,很多,真的很多。我随便找的一家提供商。
    private static String Url = "http://106.ihuyi.cn/webservice/sms.php?method=Submit";

    public static void main(String[] args) {
        /*
            方式二是一键完成短信功能的展示而已
         */
        HttpClient client = new HttpClient();
        PostMethod method = new PostMethod(Url);

        client.getParams().setContentCharset("GBK");// 在头文件中设置转码 
        method.setRequestHeader("ContentType","application/x-www-form-urlencoded;charset=GBK");

        int mobile_code = (int)((Math.random()*9+1)*100000);

        String content = new String("您的验证码是:" + mobile_code + "。请不要把验证码泄露给其他人。");

        NameValuePair[] data = {//提交短信
                new NameValuePair("account", "xxxxxx"),// 注册的用户名  
                new NameValuePair("password", "xxxxxxx"), //查看密码请登录用户中心->验证码、通知短信->帐户及签名设置->APIKEY
                //new NameValuePair("password", util.StringUtil.MD5Encode("密码")),
                new NameValuePair("mobile", "xxxxx"),//要发的手机
                new NameValuePair("content", content),//要发的内容
        };
        method.setRequestBody(data);

        try {
            client.executeMethod(method);//发送短信

        Header[] headers = method.getResponseHeaders();//短信返回信息
        int statusCode = method.getStatusCode();//状态码
        System.out.println("statusCode:" + statusCode);
        for (Header h : headers) {//响应头的打印
            System.out.println(h.toString());
        }
        String result = null;

            result = new String(method.getResponseBodyAsString().getBytes(
                    "gbk"));//打印响应体

        System.out.println(result);
        method.releaseConnection();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    }

}

实现的效果如下:

这里写图片描述

(6)Java短信封装和异步实现:

同样用工厂模式:

public class MessageServiceFactory {
    
    public static MessageService getMobileMessageService(){
        return new MessageServiceSupport(){
            public String getType() {
                return MessageServiceSupport.PHONO_MESSAGE_TYPE;
            }
        };
    }
}

接口

public interface MessageService {
    
    /**
     * 
     * @param acceptorName 消息接收者名称
     * @param acceptor 消息接收者,若是短信为手机号码,若是邮件则是邮箱地址
     * @param context 消息内容
     * @return 成功返回true, 内部采用异步机制,这里返回成功只是代表初步校验成功,比如手机号码或邮箱格式校验
     */
    public boolean send(String acceptorName, String acceptor, String context);
    
    /**
     * 
     * @param acceptorName 消息接收者名称
     * @param acceptor 消息接收者,若是短信为手机号码,若是邮件则是邮箱地址
     * @param context 消息内容
     * @return 成功返回true, 内部采用异步机制,这里返回成功只是代表初步校验成功,比如手机号码或邮箱格式校验
     */
    public boolean[] send(String acceptorName, String[] acceptor, String context);
    

    public boolean send(String acceptorName, String acceptor, String context, String title);
    
    /**
     * 
     * @param acceptorUserId 接收者的用户ID, cf_user.userid
     * @param context
     * @return
     */
    public boolean send(Long acceptorUserId, String context);
    
    /**
     * 
     * @param acceptorLoginId 接收者的登陆ID, cf_user.loginid
     * @param context
     * @return
     */
    public boolean send(String acceptorLoginId, String context);

}

短信接口的实现类:

可以基于我暴露出来的状态码设计,进行一些短信的分门别类判断。

public abstract class MessageServiceSupport implements MessageService {

    private static String Url = "http://106.ihuyi.cn/webservice/sms.php?method=Submit";

    public abstract String getType();
    
    public static String PHONO_MESSAGE_TYPE = "1";
    


    protected boolean checkAcceptor(String acceptor){
        if(acceptor == null || "".equals(acceptor)){
            return false;
        }


         if(PHONO_MESSAGE_TYPE.equals(getType())){
            // TODO 验证手机号码
        }

        return true;
    }
    


    public boolean send(String acceptorName, String acceptor, String context){
        return send(acceptorName, acceptor, context, "无标题");
    }
    
    public boolean send(String acceptorName, String acceptor, String context, String title){
        if(checkAcceptor(acceptor)){
            try {
                //如果有业务需求要保存,就用此bean保存。
                CfMessage message = new CfMessage();
                message.setType(getType());
                message.setAcceptor(acceptor);
                message.setAcceptorname(acceptorName);
                message.setTitle(title);
                message.setContext(context);
                message.setCreatetime(new Date());
                message.setRecordstatus(1);
                //前面应该加短信内容状态判断
                //TODO  短信内容判断,因为一般第三方的API会根据内容去发送
                //调用发送短信
                sendMessage(acceptor,context);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return false;
    }
    public void sendMessage(String acceptor,String content){
        HttpClient client = new HttpClient();
        PostMethod method = new PostMethod(Url);
        client.getParams().setContentCharset("GBK");
        method.setRequestHeader("ContentType","application/x-www-form-urlencoded;charset=GBK");
        NameValuePair[] data = {//提交短信
                new NameValuePair("account", "C65868831"),
                new NameValuePair("password", "fc422f9380ae002985db316ecce0ab27"), //查看密码请登录用户中心->验证码、通知短信->帐户及签名设置->APIKEY
                //new NameValuePair("password", util.StringUtil.MD5Encode("密码")),
                new NameValuePair("mobile", acceptor),
                new NameValuePair("content", content),
        };
        method.setRequestBody(data);

        try {
            client.executeMethod(method);//发送短信

            Header[] headers = method.getResponseHeaders();//短信返回信息
            int statusCode = method.getStatusCode();
            System.out.println("statusCode:" + statusCode);
            for (Header h : headers) {
                System.out.println(h.toString());
            }
            String result = null;

            result = new String(method.getResponseBodyAsString().getBytes(
                    "gbk"));

            System.out.println(result);
            method.releaseConnection();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    

    public boolean[] send(String acceptorName, String[] acceptors, String context) {
        if(acceptors == null || acceptors.length == 0){
            return null;
        }
        boolean[] result = new boolean[acceptors.length];
        int i = 0;
        for(String acceptor : acceptors){
            result[i++] = send(acceptorName, acceptor, context);
        }
        return result;
    }
    
    public boolean send(Long acceptorUserId, String context){
        if(acceptorUserId == null ){
            return false;
        }
        try {
            List<User> userList=null ;
            if(userList != null && userList.size() == 1){

            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    
    public boolean send(String acceptorLoginId, String context){
        if(acceptorLoginId == null || "".equals(acceptorLoginId)){
            return false;
        }
        try {
            List<User> userList=null ;
            if(userList != null && userList.size() == 1){
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}

至于如何异步实现,请参考上面邮件的发送。


源码下载:WEB后台--邮件和短信业务实现(包括Java一键实现、封装和异步)以及原理详解

好了,WEB后台--邮件和短信业务实现(包括Java一键实现、封装和异步)以及原理详解讲完了,这是实习时候所负责的一些功能,在这里写出来记录,这是积累的必经一步,我会继续出这个系列文章,分享经验给大家。欢迎在下面指出错误,共同学习!!你的点赞是对我最好的支持!!

更多内容,可以访问JackFrost的博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容