今天整理一下Java网络编程与邮件发送。
一、网络编程
网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节,可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。
(一)网络协议
常见的网络协议又HTTP,TCP,UDP
1、HTTP协议(HyperText Transfer Protocol,超文本传输协议)
(1)特点:
- 一个应用层协议。
- 支持客户/服务器模式。
- 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
- 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
- 无连接:限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
- 无状态:协议对于事务处理没有记忆,如果后续处理需要前面的信息,那么它必须重传,可能导致每次连接传送的数据量增大,但在服务器不需要先前信息时它的应答就较快。为此Web程序引入了Cookie机制来维护状态。
(2)Session与Cookie:
- 严格来说,Session和Cookie并不是http协议的一部分,它们都是为了解决HTTP协议无状态的缺陷问题而提出来的解决方案。
- Cookie机制采用在客户端保持状态的方案来解决THHP无状态的问题。
- Session机制采用的是在服务器端保持状态的方案。由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的,但它也有其他选择。
2、TCP协议(Transmission Control Protocol,传输控制协议)
(1)特点:
- 面向连接的、可靠的、基于字节流的传输层通信协议。
- 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的
- TCP提供可靠交付的服务。
- TCP提供全双工通信。数据在两个方向上独立的进行传输。因此,连接的每一端必须保持每个方向上的传输数据序号。
- 面向字节流。面向字节流的含义:虽然应用程序和TCP交互是一次一个数据块,但TCP把应用程序交下来的数据仅仅是一连串的无结构的字节流。
(2)三种通信方式:
- 单工:数据传输只支持数据在一个方向上传输,在同一时间只有一方能接受或发送信息,不能实现双向通信,如:电视,广播。
- 半双工:数据传输允许数据在两个方向上传输,但在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;在同一时间只可以有一方接受或发送信息,可以实现双向通信。举例:对讲机。
- 全双工:数据通信允许数据同时在两个方向上传输,因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力;在同一时间可以同时接受和发送信息,实现双向通信,举例:电话通信。
(3)TCP协议中的三次握手与四次挥手:
三次握手
- 第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
- 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
个人理解就是 :客户端要连服务端,它告诉服务端“我想连接你的服务,你同意一下呗”;服务端收到它的请求后又告诉客户端“我知道你要连我了,我同意了”;客户端收到服务后再告诉服务器一声“你放心,我收到服务了”,然后这两端都进入连接成功的状态。
四次挥手
- 第一次挥手:客户端给服务端发送FIN报文,用来关闭客户端到服务端的数据传送。将标志位FIN和ACK置为1。
- 第二次挥手:服务端收到FIN后,发回一个ACK(标志位ACK=1)。
- 第三次挥手:当服务端确定数据已发送完成,则向客户端发送FIN报文,关闭与客户端的连接。
- 第四次挥手:客户端收到服务器发送的FIN之后,发回ACK确认(标志位ACK=1)。
个人理解就是:客户端这边不给服务器端发报文了,告诉它“我没有东西给你了,我要关闭了,但你要还有东西没发没发完我就等等你”;然后服务端收到客户端要关闭的通知后告诉客户端“我知道你要关闭了,但我还有数据没传完,你等等我啊”;等服务端传完后告诉客户端“我传完了,也要关闭连接了”;客户端收到通知后告诉客户端“我知道可以关了,但我想再确认一下”,等了一会后没收到服务端回应客户端知道“这兄弟竟然不回我,看了是真关闭了,那我也关了吧”,然后它也关闭了。
(4)为什么需要四次挥手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
3、UDP(User Datagram Protocol,用户数据报协议)
(1)特点:
- UDP是传输层的协议,功能即为在IP的数据报服务之上增加了最基本的服务:复用和分用以及差错检测。
- UDP无连接,时间上不存在建立连接需要的时延。空间上,TCP需要在端系统中维护连接状态,需要一定的开销。此连接装入包括接收和发送缓存,拥塞控制参数和序号与确认号的参数。UCP不维护连接状态,也不跟踪这些参数,开销小。空间和时间上都具有优势。
- UDP没有拥塞控制,应用层能够更好的控制要发送的数据和发送时间,网络中的拥塞控制也不会影响主机的发送速率。某些实时应用要求以稳定的速度发送,能容 忍一些数据的丢失,但是不能允许有较大的时延(比如实时视频,直播等)
- UDP提供尽最大努力的交付,不保证可靠交付。
- UDP是面向报文的,对应用层交下来的报文,添加首部后直接乡下交付为IP层,既不合并,也不拆分,保留这些报文的边界。对IP层交上来UDP用户数据报,在去除首部后就原封不动地交付给上层应用进程,报文不可分割,是UDP数据报处理的最小单位。
- UDP常用一次性传输比较少量数据的网络应用
(2)TCP与UDP的区别
-
TCP协议面向连接,UDP协议面向非连接;
-
TCP协议传输速度慢,UDP协议传输速度快
-
TCP有丢包重传机制,UDP没有;
-
TCP协议保证数据正确性,UDP协议可能丢包;
(二)Socket编程
1、概述
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
2、两台计算机之间使用套接字建立TCP连接的步骤
-
服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
-
服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
-
服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
-
Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
-
在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
3、ServerSocket 类的方法
(1)ServerSocket 四个构造方法:
序号 | 方法描述 |
1 | public ServerSocket(int port) throws IOException 创建绑定到特定端口的服务器套接字。 |
2 | public ServerSocket(int port, int backlog) throws IOException 利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。 |
3 | public ServerSocket(int port, int backlog, InetAddress address) throws IOException 使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。 |
4 | public ServerSocket() throws IOException 创建非绑定服务器套接字。 |
(2)ServerSocket 常用方法:
序号 | 方法描述 |
1 | public int getLocalPort() 返回此套接字在其上侦听的端口。 |
2 | public Socket accept() throws IOException 侦听并接受到此套接字的连接。 |
3 | public void setSoTimeout(int timeout) 通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。 |
4 | public void bind(SocketAddress host, int backlog) 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。 |
4、Socket 类的方法
(1)Socket的五个构造方法:
序号 | 方法描述 |
1 | public Socket(String host, int port) throws UnknownHostException, IOException. 创建一个流套接字并将其连接到指定主机上的指定端口号。 |
2 | public Socket(InetAddress host, int port) throws IOException 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 |
3 | public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException. 创建一个套接字并将其连接到指定远程主机上的指定远程端口。 |
4 | public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException. 创建一个套接字并将其连接到指定远程地址上的指定远程端口。 |
5 | public Socket() 通过系统默认类型的 SocketImpl 创建未连接套接字 |
(1)Socket的常用方法:
序号 | 方法描述 |
1 | public void connect(SocketAddress host, int timeout) throws IOException 将此套接字连接到服务器,并指定一个超时值。 |
2 | public InetAddress getInetAddress() 返回套接字连接的地址。 |
3 | public int getPort() 返回此套接字连接到的远程端口。 |
4 | public int getLocalPort() 返回此套接字绑定到的本地端口。 |
5 | public SocketAddress getRemoteSocketAddress() 返回此套接字连接的端点的地址,如果未连接则返回 null。 |
6 | public InputStream getInputStream() throws IOException 返回此套接字的输入流。 |
7 | public OutputStream getOutputStream() throws IOException 返回此套接字的输出流。 |
8 | public void close() throws IOException 关闭此套接字。 |
5、InetAddress 类的方法
序号 | 方法描述 |
1 | static InetAddress getByAddress(byte[] addr) 在给定原始 IP 地址的情况下,返回 InetAddress 对象。 |
2 | static InetAddress getByAddress(String host, byte[] addr) 根据提供的主机名和 IP 地址创建 InetAddress。 |
3 | static InetAddress getByName(String host) 在给定主机名的情况下确定主机的 IP 地址。 |
4 | String getHostAddress() 返回 IP 地址字符串(以文本表现形式)。 |
5 | String getHostName() 获取此 IP 地址的主机名。 |
6 | static InetAddress getLocalHost() 返回本地主机。 |
7 | String toString() 将此 IP 地址转换为 String。 |
6、实例
package Socket;
import java.io.*;
import java.net.*;
//服务端
public class GreetingServer{
public static void main(String [] args)
{
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8080);
System.out.println("8080端口启动成功");
while (true){
Socket browseClient = serverSocket.accept();
System.out.println("接受到"+browseClient.getInetAddress().getHostAddress()+"地址的请求");
//新建一个线程去处理资源
new Thread(new Runnable() {
@Override
public void run() {
try{
//通过网络输入流获得用户请求信息
InputStream socketInputStream = browseClient.getInputStream();
//1-获得BufferedReader对象
BufferedReader br = new BufferedReader(new InputStreamReader(socketInputStream));
//2-使用readLine方法 读取一行
String str = br.readLine();
//3-使用空格拆分字符串
String[] strArr = str.split("[' ']+");
//4-获取数组中的第一个索引位置
String loadPath = strArr[1];
//5-截取字符串
loadPath = loadPath.substring(1);
System.out.println("访问的资源路径是:"+loadPath);
//6-使用文件输入流指向gy.jpg文件
FileInputStream fis = new FileInputStream(new File("D:/java/FSR/src/Socket/file",loadPath));
//7-使用网络输出流 写回文件信息
OutputStream socketOutputStream = browseClient.getOutputStream();
//先把响应头信息写进去 http协议的响应头
socketOutputStream.write("HTTP/1.1 200 OK \r\n".getBytes());
socketOutputStream.write("\r\n".getBytes());
//再写访问资源信息
byte[] bs = new byte[1024];
int len = -1;
while ((len=fis.read(bs))!=-1) {
socketOutputStream.write(bs,0,len);
}
//8-关闭网络输出流
browseClient.shutdownOutput();
//9-关闭文件输入流
fis.close();
//10-关闭socket对象
browseClient.close();
} catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//启动后,浏览器上访问http://localhost:8080/(D:/java/FSR/src/Socket/file目录下的任意文件)
输出结果:
8080端口启动成功
接受到0:0:0:0:0:0:0:1地址的请求
接受到0:0:0:0:0:0:0:1地址的请求
访问的资源路径是:gy.JPG
访问的资源路径是:favicon.ico
java.io.FileNotFoundException: D:\java\FSR\src\Socket\file\favicon.ico (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at Socket.GreetingServer$1.run(GreetingServer.java:36)
at java.lang.Thread.run(Thread.java:745)
说明:
这个问题可忽略,因为favicon.ico是它访问的网络图标
二、邮件发送
(一)邮件服务器
要在网络上实现邮件功能,必须要有专门的邮件服务器。这些邮件服务器类似于现实生活中的邮局,它主要负责接收用户投递过来的邮件,并把邮件投递到邮件接收者的电子邮箱中。
SMTP服务器地址:一般是 smtp.xxx.com,比如163邮箱是smtp.163.com,qq邮箱是smtp.qq.com。
(二)传输协议
1、SMTP协议
通常把处理用户smtp请求(邮件发送请求)的服务器称之为SMTP服务器(邮件发送服务器)。
2、POP3协议
通常把处理用户pop3请求(邮件接收请求)的服务器称之为POP3服务器(邮件接收服务器)。
(三)邮件发送实现步骤
- 下载一个mail.jar
- 导包
- 创建一个用于存放配置信息的对象,Properties类型
- 设置发送邮件需要的一些信息
- 创建一个session对象
- 通过session对象获取一个Transport对象
- 邮件服务器认真,获得一个认证码
- 创建映射关系
- 发送邮件
- 关闭通道
(四)获取邮箱授权码(以QQ邮箱为例)
1、登录QQ,进入设置
2、进入账户
3、下拉到这
4、点击开启服务,按步骤来
(五)示例
package test;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;
public class TestSendEmail {
//设计一个方法,创建一个邮件对象
//参数? 一个Session对象
//返回值? 一个邮件(映射) MimeMessage
private static Message createMessage(Session session) throws MessagingException {
//自己创建一个邮件对象
Message message = new MimeMessage(session);
/*设置邮件的基本信息*/
//设置发送人
message.setFrom(new InternetAddress("2669168424@qq.com"));
//设置接收人 recipient容器收纳 参数:类型(收件人To,抄送人CC,密送/暗送BCC)
message.setRecipient(Message.RecipientType.TO,new InternetAddress("2015591866@qq.com"));
/*//设置发送邮件的时间
message.setSentDate(new Date());*/
message.setSubject("测试");
//设置发送邮件正文
message.setText("这是一个小测试");
//以上所有信息要保存才能生效,因为这个message只是一个映射对象,并不真正存在,就像文件流一样,需要flush一下
message.saveChanges();
//将message对象返回
return message;
}
public static void main(String[] args) throws MessagingException {
//1、下载一个mail.jar
//2、导包
//3、创建一个用于存放配置信息的对象,Properties类型
Properties prop = new Properties();
//4、设置发送邮件需要的一些信息
//设置发送邮件的协议 smtp
prop.put("mail.transport.protocol","smtp"); //必须
//设置发送邮件的主机名
prop.put("mail.smtp.host","smtp.qq.com"); //必须
//设置发送邮件的端口,默认25,还有110,143,465
//prop.put("mail.smtp.port","xxx");
//设置发送邮件时,是否需要进行身份验证
//prop.put("mail.smtp.auth","true");
//设置是否使用ssl安全连接,默认使用
//prop.put("mail.smtp.ssl.enable","true");
//5、创建一个session对象(可以理解为是一个Socket,Java和邮箱之间建立一个连接)
Session session = Session.getDefaultInstance(prop);
//6、通过session对象获取一个Transport对象(可以理解为是一个输出流)
Transport ts = session.getTransport();
//7、想要通过邮箱发送邮件,必须得到邮件服务器的认证
ts.connect("2669168424@qq.com","zritukpvdouleafa");
//8、发送的是一封邮件(创建一个邮件的映射关系) File对象 Class对象 映射
// 发送邮件对象本身比较复杂,需要好多信息,单独写一个方法
Message message = createMessage(session);
//9、发送邮件(需要一个message对象,知道message对象中的哪些接收人)
ts.sendMessage(message,message.getAllRecipients());
//10、关闭通道
ts.close();
}
}