1、连接到服务
1.1 使用telnet
telnet是一种用于网络编程的非常强大的调试工具,可以在命令shell中输入telnet来启动它。
(在Windows中,需要激活telnet)
1.2 用Java连接到服务器
public class Demo01 {
public static void main(String[] args) throws IOException {
//打开一个套接字(负责启动该程序内部与外部之间的通信,我们将远程地址和端口号传递给套接字的构造器)
//一旦套接字被打开,socket.getInputStream()就会返回一个InputStream对象
//该程序只适用于非常简单的服务
Socket socket = new Socket("time-a.nist.gov", 13);
Scanner in = new Scanner(socket.getInputStream(), "UTF-8");
while (in.hasNext()) {
String line = in.nextLine();
System.out.println(line);
}
}
}
1.3 套接字超时
从套接字读取信息时,在有数据可供访问之前,读操作将会被阻塞。如果此时主机不可达,那么应用将要等待很长的时间,并且因为受底层操作系统的限制而最终会导致超时。调用setSoTimeout方法设置这个超时值(单位:毫秒)
Socket socket = new Socket("time-a.nist.gov", 13);
socket.setSoTimeout(10000);
另外还有一个超时问题是必须解决的。下面这个构造器:Socket(String host, int port);
会一直无限期地阻塞下去,直到建立了到达主机的初始连接为止。
可以通过先构造一个无连接的套接字,然后再使用一个超时来进行连接的方式解决这个问题。
Socket socket1 = new Socket();
socket1.connect(new InetSocketAddress(host, port), 10000);
1.4 因特网地址
public class Demo02 {
public static void main(String[] args) throws UnknownHostException {
//静态的getByName可以返回代表某个主机的InetAddress对象
InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress);// www.baidu.com/220.181.38.149
byte[] address = inetAddress.getAddress();
System.out.println(Arrays.toString(address));//[-36, -75, 38, -106]
//获取所有主机
InetAddress[] allInetAddress = InetAddress.getAllByName("google.com");
System.out.println(Arrays.toString(allInetAddress));//[google.com/216.58.200.238]
//获取本地主机地址
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
}
}
2、实现服务器
2.1 服务器套接字
public class Demo11 {
public static void main(String[] args) throws IOException {
//ServerSocket 用于建立套接字(建立一个用于监控端口8189的服务器)
ServerSocket serverSocket = new ServerSocket(8189);
//告诉程序不停的等待,直到有客户端连接到这个端口。
// 一旦有人通过网络发送了正确的连接请求,并以此连接到了端口上,该方法就会返回一个表示连接已经建立的Socket对象。
Socket accept = serverSocket.accept();
//可以使用这个对象的到输入流和输出流
InputStream inputStream = accept.getInputStream();
OutputStream outputStream = accept.getOutputStream();
}
}
服务器发送给服务器输出流的所有信息都会成为客户端程序的输入,同时来自客户端程序的所有输出都会被包含在服务器输入流中。
public class Demo12 {
public static void main(String[] args) throws IOException {
//建立一个用于监控端口8189的服务器
ServerSocket serverSocket = new ServerSocket(8189);
//一直等待...直到连接 返回accept
Socket accept = serverSocket.accept();
//拿到输入、输出流
InputStream inputStream = accept.getInputStream();
OutputStream outputStream = accept.getOutputStream();
//服务器的输入是对客户端的输出,服务器的输出是对客户端的输入
Scanner in = new Scanner(inputStream, StandardCharsets.UTF_8.name());
PrintWriter out = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8), true);
out.println("Hello! Enter BYE to exit.");
boolean done = false;
while (!done && in.hasNextLine()){
String line = in.nextLine();
out.println("Echo: " + line);
//如果输入"BYE",则结束..
if ("BYE".equals(line)){
done = true;
}
}
}
}
测试:
2.2 为多个客户端服务
public class SocketHandler implements Runnable {
private Socket accept;
public SocketHandler(Socket accept) {
this.accept = accept;
}
@Override
public void run() {
try {
InputStream inputStream = accept.getInputStream();
OutputStream outputStream = accept.getOutputStream();
Scanner in = new Scanner(inputStream, StandardCharsets.UTF_8.name());
PrintWriter out = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8), true);
out.println("Hello! Enter BYE to exit.");
boolean flag = false;
while (!flag && in.hasNextLine()){
String line = in.nextLine();
out.println("Echo: " + line);
if ("BYE".equals(line)){
flag = true;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class SocketService {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8189);
int i = 1;
while (true){
Socket accept = serverSocket.accept();
System.out.println("Spawning: " + i);
Runnable runnable = new SocketHandler(accept);
Thread t = new Thread(runnable);
t.start();
i++;
}
}
}
测试结果:
2.3 半关闭
半关闭提供了这样一种能力:套接字连接的一端可以终止其输出,同时仍旧可以接收来自另一端的数据。
可以通过关闭一个套接字的输出流来表示发送给服务的请求数据已经结束,但是必须保持输入流处于打开状态。
public class Demo13 {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8189);
Scanner in = new Scanner(socket.getInputStream(), StandardCharsets.UTF_8.name());
PrintWriter out = new PrintWriter(socket.getOutputStream());
//发送请求数据...
out.println("data...");
out.flush();
socket.shutdownOutput();
//此时 Socket处于半关闭状态
//读取响应数据...
while (in.hasNextLine()){
String line = in.nextLine();
// ...
}
}
}
服务器端将读取输入信息,直至到达输入流的结尾,然后它再发送响应。
当然,该协议只适用于一站式的服务,例如HTTP服务,在这种服务中心,客户端连接服务器,发送一个请求,捕获响应信息,然后断开连接。
3、可中断套接字
当连接到一个套接字时,当前线程将会被阻塞直到建立连接或产生超时为止。同样的,当通过套接字读写数据时,当前线程也会被阻塞直到操作成功或产生超时为止。
为了中断套接字操作,可以使用SocketChannel类:SocketChannel channel = SocketChannel.open(new InetSocketAddress("www.baidu.com", 80));
通道并没有与之相关联的流,但是它所拥有的read和write方法都是通过Buffer对象来实现的,ReadableByteChannel接口和WritableByteChannel接口都声明了这两个方法。
如果不想处理缓冲区,可以使用Scanner类从SocketChannel中读取信息,因为Scanner有一个带ReadableByteChannel参数的构造器:Scanner in = new Scanner(channek, "UTF-8");
通过调用静态方法Channels.newOutputStream,可以将通道转换成输出流。OutputStream outStream = Channels.newOutputStream(channel);
上述操作就是所有要做的事情。当线程正在执行打开、读写或写入操作时,如果线程发生中断,那么这些操作将不会陷入阻塞,而是以抛出异常的方式结束。
4、获取Web数
4.1 URL和URI
URI是个纯粹的语法结构,包含用来指定Web资源的字符串的各种组成部分。URL是URI的一个特例,它包含了用于定位Web资源的足够信息。其他URI,比如:mailto:cay@horstmann.com 则不属于定位符,因为根据该标识符我们无法定位任何数据。像这样的URI我们称之为URN(统一资源名称)。
public class Demo21 {
public static void main(String[] args) throws Exception {
//可以通过一个字符串构建一个URL对象
URL url = new URL("www.baidu.com");
//如果想要获取该资源的内容
//1.通过url.openStream()生成一个InputStream对象
InputStream inputStream = url.openStream();
//2.用一般的用法来使用这个流对象,如构建一个Scanner对象
Scanner in = new Scanner(inputStream, "UTF-8");
}
}
URI类的作用之一是解析标识符并将它分解成各种不同的组成部分。你可以用以下方法读取它们:
getScheme
getSchemeSpecificPart
getAuthority
getUserInfo
getHost
getPort
getPath
getQuery
getFragment
URI类的另一个作用是处理绝对标识符和相对标识符。如果存在一个绝对的URI和一个相对的URI,那么可以用它们组合出一个绝对URI。这个过程称为解析相对URL。
4.2 使用URLConnection获取信息
1.调用URL类中的openConnection方法获得URLConnection对象:URLConnection connection = url.openConnection();
2.使用以下方法来设置任意的请求属性:
setDoInput
setDoOutput
setIfModifiedSince 用于告诉连接你只对自某个特定日期依赖被修改过的数据感兴趣。
setUseCaches 只作用于Applet,用于命令浏览器首先检查它的缓存。
setAllowUserInteraction 只作用于Applet,用于在访问有密码保护的资源时弹出对话框,以便查询用户名和口令。
setRequestProperty 可以用来设置对特定协议起作用的任何“名-值(name/value)对”。
setConnectTimeout
setReadTimeout
3.调用connect方法连接远程资源:connection.connect();
4.与服务器建立连接后,你可以查询头信息。getHeaderFieldKey和getHeaderField这两个方法枚举了消息头的所有字段。getHeaderFields方法返回一个包含了消息头中所有字段的标准Map对象。为了方便使用,以下方法可以查询各标准字段:
getContentType
getContentLength
getContentEncoding
getDate
getExpiration
getLastModified
5.最后,访问资源数据。使用getInputStream方法获取一个输入流用以读取信息。
在默认情况下,建立的连接只产生从服务器读取信息的输入流,并不产生任何执行写操作的输出流。如果想获得输出流(例如,用于向一个Web服务器提交数据),那么你需要调用:connection.setDoOutput(true);
public class Demo22 {
public static void main(String[] args) throws IOException {
String urlName;
if (args.length > 0){
urlName = args[0];
}else {
urlName = "http://horstmann.com";
}
URL url = new URL(urlName);
URLConnection connection = url.openConnection();
if (args.length > 2){
String username = args[1];
String password = args[2];
String input = username + ": " + password;
Base64.Encoder encoder = Base64.getEncoder();
String encoding = encoder.encodeToString(input.getBytes(StandardCharsets.UTF_8));
connection.setRequestProperty("Authorization" , "Basic" + encoding);
}
connection.connect();
Map<String, List<String>> headerFields = connection.getHeaderFields();
for (Map.Entry<String, List<String>> entry : headerFields.entrySet()){
String key = entry.getKey();
for (String value : entry.getValue()){
System.out.println(key + ": " + value);
}
}
System.out.println("---------------------------");
System.out.println("getContentType: " + connection.getContentType());
System.out.println("getContentEncoding: " + connection.getContentEncoding());
System.out.println("getContentLength: " + connection.getContentLength());
System.out.println("getDate: " + connection.getDate());
System.out.println("getExpiration: " + connection.getExpiration());
System.out.println("getLastModified: " + connection.getLastModified());
System.out.println("---------------------------");
String encoding = connection.getContentEncoding();
if (encoding == null){
encoding = "UTF-8";
}
try (Scanner in = new Scanner(connection.getInputStream(), encoding)) {
for (int i = 1; in.hasNextLine() && i <= 10; i++){
System.out.println(in.nextLine());
}
if (in.hasNextLine()){
System.out.println("...");
}
}
}
}
运行结果:
Keep-Alive: timeout=2, max=100
null: HTTP/1.1 302 Found
Server: Apache
Connection: Keep-Alive
Content-Length: 206
Date: Mon, 27 Jul 2020 06:26:53 GMT
Content-Type: text/html; charset=iso-8859-1
Location: https://horstmann.com/
---------------------------
getContentType: text/html; charset=iso-8859-1
getContentEncoding: null
getContentLength: 206
getDate: 1595831213000
getExpiration: 0
getLastModified: 0
---------------------------
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="https://horstmann.com/">here</a>.</p>
</body></html>
4.3 提交表单数据
执行服务器端脚本过程中的数据流:
在提交数据给服务器端程序之前,首先需要创建一个URLConnection对象。
Url url = new URL("Http://host/path");
URLConnection connection = url.openConnection();
然后,调用setDoOutput方法简历一个用于输出的连接。connection.setDoOutput(true);
接着,调用getOutputStream方法获得一个流,可以通过这个流向服务器发送数据。如果要向服务器发送文本信息,那么可以非常方便地将流包装在PrintWriter对象中。PrintWriter out = new PrintWriter(connection.getOutputStream(), "UTF-8");
现在,可以向服务器发送数据了。
out.print(name1 + "=" + URLEncoder.encode(value1, "UTF-8") + "&");
out.print(name2 + "=" + URLEncoder.encode(value2, "UTF-8"));
之后,关闭输出流。out.close();
最后,调用getInputStream方法获取服务器的响应。
public class Demo23 {
public static void main(String[] args) throws IOException {
String popsFilename = args.length > 0 ? args[0] : "post.properties";
Properties props = new Properties();
try (InputStream in = Files.newInputStream(Paths.get(popsFilename))) {
props.load(in);
}
String urlString = props.remove("url").toString();
Object userAgent = props.remove("User-Agent");
Object redirects = props.remove("redirects");
CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
String result = doPost(new URL(urlString), props, userAgent == null ? null : userAgent.toString(), redirects == null ? -1 : Integer.parseInt(redirects.toString()));
System.out.println(result);
}
public static String doPost(URL url, Map<Object, Object> nameValuePairs, String userAgent, int redirects) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
if (userAgent != null){
connection.setRequestProperty("User-Agent", userAgent);
}
if (redirects >= 0){
connection.setInstanceFollowRedirects(false);
}
connection.setDoOutput(true);
try (PrintWriter out = new PrintWriter(connection.getOutputStream())) {
boolean first = true;
for (Map.Entry<Object, Object> entry : nameValuePairs.entrySet()){
if (first){
first = false;
}else {
out.print('&');
}
String name = entry.getKey().toString();
String value = entry.getValue().toString();
out.print(name);
out.print('=');
out.print(URLEncoder.encode(value, "UTF-8"));
}
}
String encoding = connection.getContentEncoding();
if (encoding == null){
encoding = "UTF-8";
}
if (redirects > 0){
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP
|| responseCode == HttpURLConnection.HTTP_SEE_OTHER){
String location = connection.getHeaderField("Location");
if (location != null){
URL base = connection.getURL();
connection.disconnect();
return doPost(new URL(base, location), nameValuePairs, userAgent, redirects - 1);
}
}
}else if (redirects == 0){
throw new IOException("Too many redirects");
}
StringBuffer response = new StringBuffer();
try (Scanner in = new Scanner(connection.getInputStream(), encoding)) {
while (in.hasNextLine()){
response.append(in.nextLine());
response.append("\n");
}
} catch (IOException e){
InputStream errorStream = connection.getErrorStream();
if (errorStream == null){
throw e;
}
try (Scanner in = new Scanner(errorStream)) {
response.append(in.nextLine());
response.append("\n");
}
}
return response.toString();
}
}
5、发送E-mail
导入maven:
<!-- https://mvnrepository.com/artifact/javax.mail/mail -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
public class Demo24 {
public static void main(String[] args) throws IOException, MessagingException {
Properties properties = new Properties();
try (InputStream in = Files.newInputStream(Paths.get("mail", "mail.properties"))) {
properties.load(in);
}
List<String> lines = Files.readAllLines(Paths.get(args[0]), StandardCharsets.UTF_8);
String from = lines.get(0);
String to = lines.get(1);
String subject = lines.get(2);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 3; i< lines.size(); i++){
stringBuilder.append(lines.get(i));
stringBuilder.append("\n");
}
Console console = System.console();
String password = new String(console.readPassword("Password: "));
Session mailSession = Session.getDefaultInstance(properties);
MimeMessage message = new MimeMessage(mailSession);
message.setFrom(new InternetAddress(from));
message.addRecipients(MimeMessage.RecipientType.TO, String.valueOf(new InternetAddress(to)));
message.setSubject(subject);
message.setText(stringBuilder.toString());
Transport transport = mailSession.getTransport();
try {
transport.connect(null, password);
transport.sendMessage(message, message.getAllRecipients());
} finally {
transport.close();
}
}
}