Core Java 8 读书笔记-Networking编程

Core Java 8 读书笔记-Networking编程

封面14 拷贝.png

作者:老九—技术大黍

原文:Core Java 8th Edition

社交:知乎

公众号:老九学堂(新人有惊喜)

特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权

前言

网络应用

我们一张图来描述网络应用的概念,参见下图所示:

image-20210427101131771.png

  • client-网络客户端
  • network packet--网络数据包
  • Internet--互联网
  • ports on server--服务器的端口号
  • server-网络服务器

telnet是大多数操作系统都预装的网络工具命令。你可以使用请求HTTP服务器如下:

  • telnet java.sun.com 80
  • 然后输入GET / HTTP/1.0
  • 最后点击回车键多次。

如果一切正常,那么你会在命令行窗体中看到html的所有标签。它得到一个web网站的完整网页内容。 下面我们使用Java的Socket技术进行编程,也可以达到相同的效果。

ShowSocket演示代码

package com.jb.arklis.net;
import static java.lang.System.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;

/**
	功能:书写一个类,用来演示怎样使用Socket技术达到与telnet命令相同的效果
	作者:技术大黍
	*/

public class ShowSocket{
	private Socket socket;
	
	public ShowSocket(){
	//	firstNetworking(); //模拟telnet命令
		showInetAddress();
	}
	
	private void showInetAddress(){
		try{
			String host = JOptionPane.showInputDialog("输入主机名称:");
			//得到主机的所有信息
			InetAddress[] addresses = InetAddress.getAllByName(host);
			//打印出来 
			for(InetAddress x : addresses){
				out.println("远程主机是:" + x);
			}
			//显示本地主机
			InetAddress localhostAddress = InetAddress.getLocalHost();
			out.println("本地主机是:" + localhostAddress);
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	private void firstNetworking(){
		try{
			socket = new Socket("time-A.timefreq.bldrdoc.gov",13);
			InputStream input = socket.getInputStream();
			//读取内存中去
			Scanner scanner = new Scanner(input);
			StringBuilder message = new StringBuilder();
			//演示出来
			while(scanner.hasNextLine()){
				String line = scanner.nextLine();
				message.append(line);
			}
			javax.swing.JOptionPane.showMessageDialog(null,message.toString());
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args){
		new ShowSocket();
	}
}

运行效果

image-20210427101615816.png

在VS Code的控制台中显示内容信息如下:

image-20210427101653875.png

上面这个例子的第一行代码是打开一个socket对象,该对象是对网络的一种抽象。我们把远程地址和端口号传送给socket构造方法,然后由该对象实现具体的细节。一旦一个socket对象打开了,那么getInputStream访求 会得到一个InputStream对象,如果我们得到这个流对象,那么就可以在控制台输出内容,直到服务器关闭连接为止。Socket类为Java程序员提供了非常方便和简单的方式进行网络编程。

读取服务器数据时如果找不主机,那么你的应用会等很长时间。为解决这种问题,我们可以使用setSoTimeout方法来间隔读取网络数据。

网络应用—服务器实现

我们上面的例子实现了从互联网获取数据,下面我们解决怎样在互联网上提供数据—书写一个简单的服务器提供数据。它的原理是通过ServerSocket类创建一个socket对象,然后对象socket对象得到输入和输出流对象。

EchoServer类演示代码

package com.jb.arklis.net;
import static java.lang.System.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;

/**
	功能:书写一个服务器类,用来演示怎样在互联网上提供一种服务
	作者:技术大黍
	*/
public class EchoServer{
	private ServerSocket server;
	private Socket incoming;
	
	public EchoServer(){
		try{
			//创建服务器端socket对象
			server = new ServerSocket(8888);
			//得到客户端的请求
			incoming = server.accept();
			
			try{
				//从客户端得到输入流对象
				InputStream inStream = incoming.getInputStream();
				OutputStream outStream = incoming.getOutputStream();
				
				//读到内存,然后在客户端显示出来
				Scanner scanner = new Scanner(inStream);
				PrintWriter output = new PrintWriter(outStream,true/*自动刷新*/);
				output.println("你好!远程主机的连接已创建。。。");//给客户端显示已经建立会话
				//在服务端响应客户端的请求
				boolean done = false;
				while(!done && scanner.hasNextLine()){
					String line = scanner.nextLine();
					output.println("响应:" + line);
					//如果客户端输入"BYE"
					if(line.trim().equals("BYE")){
						//那么在服务端标识客户端请求结束(或者会话结束),于是退出循环。
						done = true;
					}
				}
			}finally{
				//关闭与客户端的会话
				incoming.close();
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args){
		EventQueue.invokeLater(new Runnable(){
			public void run(){
				System.out.println("服务器已启动!!!");
				new EchoServer();
			}
		});
	}
}

运行效果

image-20210427102224042.png

运行服务端程序后,打印一个命令窗体,然后输入telnet localhost 8888 之后的结果如下:

image-20210427102302739.png 如果要实现一对多的服务,即服务多个客户端,那么我们需要使用多线程来完成处理动作。

ThreadedEchoServer类演示多线程服务器代码

package com.jb.arklis.net;
import static java.lang.System.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;
/**
	功能:书写一个多线程服务器类,用来演示一对多服务器的模式
	作者:技术大黍
	*/
public class ThreadedEchoServer{
	private ServerSocket server;
	private Socket incoming;
	public static int i = 1;//计算客户端连接个数
	
	public ThreadedEchoServer(){
		try{
			//创建服务器端socket对象
			server = new ServerSocket(8888);
			out.println("服务器已启动。。。");
			//处理多个客户端请求
			while(true){
				//得到客户端的请求
				incoming = server.accept();
				//声明一个多线程对象
				Runnable run = new ThreadedEchoHandler(incoming);
				Thread thread = new Thread(run);
				//启动多线程对象
				thread.start();
				out.println("当前客户端请求数:" + i);
				i++;
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args){
		new ThreadedEchoServer();
	}
}

/**
	功能:处理客户端的输入
	*/
class ThreadedEchoHandler implements Runnable{
	private Socket incoming;
	
	public ThreadedEchoHandler(Socket incoming){
		this.incoming = incoming;
	}
	
	public void run(){
		try{
			try{
				//从客户端得到输入流对象
				InputStream inStream = incoming.getInputStream();
				OutputStream outStream = incoming.getOutputStream();
				
				//读到内存,然后在客户端显示出来
				Scanner scanner = new Scanner(inStream);
				PrintWriter output = new PrintWriter(outStream,true/*自动刷新*/);
				output.println("你好!远程主机的连接已创建。。。");//给客户端显示已经建立会话
				//在服务端响应客户端的请求
				boolean done = false;
				while(!done && scanner.hasNextLine()){
					String line = scanner.nextLine();
					output.println("响应:" + line);
					//如果客户端输入"BYE"
					if(line.trim().equals("BYE")){
						//那么在服务端标识客户端请求结束(或者会话结束),于是退出循环。
						done = true;
					}
				}
			}finally{
				//关闭客户端会话
				incoming.close();
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

运行效果

image-20210427102455055.png

可中断Sockets

在实际的交互应用中,如果我们希望给用户选择是否取消socket连接时,那么我们需要使用可中断socket技术—使用SocketChannel来实现。因为,一个channel中没有关联的流,相反,它只是读或者写一个缓存的对象,所以可以实现中断的操作动作。

ShowInterruptibleSocket类演示代码

package com.jb.arklis.net;
import static java.lang.System.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;
import java.nio.channels.*;
import java.awt.event.*;


/**
	功能:书写一个应用类,用来演示中断Socket对象
	作者:技术大黍
	备注:
		在实际中,这样写可以让客户端用户中断socket连接对象
	*/
public class ShowInterruptibleSocket{
	public static void main(String[] args){
		EventQueue.invokeLater(new Runnable(){
			public void run(){
				new InterruptibleSocketFrame();
			}
		});
	}
}

/**
	功能:书写一个演示中断Socket请求的窗体
	*/
class InterruptibleSocketFrame extends JFrame{
	private Scanner scanner;
	private JButton interruptibleButton;
	private JButton blockingButton;
	private JButton cancelButton;
	private JTextArea messages;
	private TestServer server;
	private Thread connectThread;
	private static final int WIDTH = 550;
	private static final int HEIGHT = 450;
	
	/**
		功能:定义一个内部类用来演示中断客户端的请求
		*/
	class TestServer implements Runnable{
		public void run(){
			try{
				ServerSocket server = new ServerSocket(8888);
				//处理多个客户端的请求
				while(true){
					Socket incomfing = server.accept();
					Runnable run = new TestServerHandler(incomfing);
					Thread thread = new Thread(run);
					thread.start();
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
	
	/**
		功能:书写一个处理请求Socket对象
		*/
	class TestServerHandler implements Runnable{
		private Socket incoming;
		private int counter;
		
		public TestServerHandler(Socket incoming){
			this.incoming = incoming;
		}
		
		public void run(){
			try{
				OutputStream outStream = incoming.getOutputStream();
				PrintWriter output = new PrintWriter(outStream,true/*自动刷新*/);
				try{
					while(counter < 100){
						counter++;
						if(counter <= 10){
							output.println(counter);
						}
						Thread.sleep(100);
					}
				}finally{
					incoming.close();
					messages.append("关闭服务!\n");
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
	
	public InterruptibleSocketFrame(){
		setTitle("演示中断客户端的请求动作");
		setSize(WIDTH,HEIGHT);
		init();
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);
	}
	
	private void init(){
		Container container = getContentPane();
		//使用面板来布局
		JPanel northPanel = new JPanel();
		container.add(northPanel,BorderLayout.NORTH);
		
		//初始化成员变量
		messages = new JTextArea();
		container.add(new JScrollPane(messages),BorderLayout.CENTER);
		
		interruptibleButton = new JButton(" 中断 ");
		blockingButton = new JButton(" 锁定 ");
		
		northPanel.add(interruptibleButton);
		northPanel.add(blockingButton);
		
		interruptibleButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				interruptibleButton.setEnabled(false);
				blockingButton.setEnabled(false);
				cancelButton.setEnabled(true);
				//启动多线程
				connectThread = new Thread(new Runnable(){
					public void run(){
						try{
							connectInterruptibly();
						}catch(Exception e){
							e.printStackTrace();
							messages.append("connectInterruptibly()呼叫失败:" + e);
						}
					}
				});
				//启动多线程
				connectThread.start();
			}
		});
		
		//给blockingButton按钮添加事件处理方法
		blockingButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				interruptibleButton.setEnabled(false);
				blockingButton.setEnabled(false);
				cancelButton.setEnabled(true);
				connectThread = new Thread(new Runnable(){
					public void run(){
						try{
							connectBlocking();
						}catch(Exception e){
							messages.append("connectBlocking方法呼叫失败:" + e);
						}
					}
				});
				connectThread.start();
			}
		});
		
		cancelButton = new JButton("取消");
		cancelButton.setEnabled(false);
		northPanel.add(cancelButton);
		cancelButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				//关闭线程
				connectThread.interrupt();
				cancelButton.setEnabled(false);
			}
		});
		//开始一个服务端线程
		server = new TestServer();
		new Thread(server).start();
	}
	
	/**
		使用中断的方式连接测试服务器
		*/
	public void connectInterruptibly()throws IOException{
		messages.append("可中断连接服务器:\n");
		SocketChannel channel = SocketChannel.open(new InetSocketAddress("localhost",8888));
		try{
			//从管道中读取到内存
			scanner = new Scanner(channel);
			//如果当前线程没有被中止
			while(!Thread.currentThread().isInterrupted()){
				//读取网络流对象
				messages.append("正在读取中。。。\n");
				if(scanner.hasNextLine()){
					String line = scanner.nextLine();
					messages.append(line);
					messages.append("\n");
				}
			}
		}finally{
			channel.close();
			//使用多线程来异步说明读取完毕
			EventQueue.invokeLater(new Runnable(){
				public void run(){
					messages.append("管道已关闭\n");
					interruptibleButton.setEnabled(true);
					blockingButton.setEnabled(true);
				}
			});
		}
	}
	
	/**
		完成客户端的锁定动作
		*/
	public void connectBlocking()throws IOException{
		messages.append("锁定中:\n");
		Socket socket = new Socket("localhost",8888);
		try{
			scanner = new Scanner(socket.getInputStream());
			while(!Thread.currentThread().isInterrupted()){
				messages.append("客户端读取中\n");
				if(scanner.hasNextLine()){
					String line = scanner.nextLine();
					messages.append(line);
					messages.append("\n");
				}
			}
		}finally{
			socket.close();
			EventQueue.invokeLater(new Runnable(){
				public void run(){
					messages.append("Socket已关闭\n");
					interruptibleButton.setEnabled(true);
					blockingButton.setEnabled(true);
				}
			});
		}
	}
}

发送邮件

下面我们介绍使用socket编程来向远程网站发送电子邮件。如果要发送MAIL,那么需要让socket连接端口号25也就同SMTP端口号。SMTP是Simple Mail Transport Protocol的缩写,它表示E-MAIL消息的格式。我们可使用socket连接任何一个支持SMTP的服务器。但是,由于现在垃圾邮件的泛滥,所以大多数邮件服务器都内置了检查和可接收用户指定范围的邮件。

如果要使用socket发送邮件,它的步骤如下:

  • 打开一个连接主机的socket
    • Socket s = new Socket(“mail.yourserver.com”,25);
    • PrintWriter out = new PrintWriter(s.getOutputStream());
  • 发送如下的流信息

image-20210427103117762.png

ShowSendMail类演示代码

package com.jb.arklis.net;
import static java.lang.System.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;
import java.nio.channels.*;
import java.awt.event.*;


/**
	功能:书写一个应用类,用来演示Socket对象发送MAIL
	作者:技术大黍
	备注:
		不使用JavaMail API来发送EMail
	*/
public class ShowSendMail{
	
	public static void main(String[] args){
		EventQueue.invokeLater(new Runnable(){
			public void run(){
				new MailSendFrame();
			}
		});
	}
}

/**
	书写一个发送Email的窗体
	*/
class MailSendFrame extends JFrame{
	private Scanner scanner;
	private PrintWriter output;
	private JTextField from;
	private JTextField to;
	private JTextField smtpServer;
	private JTextArea message;
	private JTextArea comments;
	public static final int DEFAULT_WIDTH = 300;
	public static final int DEFAULT_HEIGHT = 300;
	
	public MailSendFrame(){
		setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
		setTitle("自定义的Outlook");
		init();
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);
	}
	
	private void init(){
		Container container = getContentPane();
		
		container.setLayout(new GridBagLayout());
		container.add(new JLabel("从(From):"), new GBC(0,0).setFill(GBC.HORIZONTAL));
		from = new JTextField(20);
		container.add(from, new GBC(1,0).setFill(GBC.HORIZONTAL).setWeight(100,0));
		
		container.add(new JLabel("到(To)"),new GBC(0,1).setFill(GBC.HORIZONTAL).setWeight(100,0));
		to = new JTextField(20);
		container.add(to, new GBC(1,1).setFill(GBC.HORIZONTAL).setWeight(100,0));
		
		container.add(new JLabel("SMTP服务器:"),new GBC(0,2).setFill(GBC.HORIZONTAL).setWeight(100,0));
		smtpServer = new JTextField(20);
		container.add(smtpServer,new GBC(1,2).setFill(GBC.HORIZONTAL).setWeight(100,0));
		
		message = new JTextArea();
		container.add(new JScrollPane(message),new GBC(0,3,2,1).setFill(GBC.BOTH).setWeight(100,100));
		
		comments = new JTextArea();
		container.add(new JScrollPane(comments),new GBC(0,4,2,1).setFill(GBC.BOTH).setWeight(100,100));
		
		JPanel buttonPanel = new JPanel();
		container.add(buttonPanel, new GBC(0,5,2,1));
		
		JButton sendButton = new JButton(" 发送 ");
		buttonPanel.add(sendButton);
		sendButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				new SwingWorker<Void, Void>(){
					protected Void doInBackground()throws Exception{
						comments.setText("");
						sendMail();
						return null;
					}
				}.execute();
			}
		});
	}
	
	/**
		通过GUI输入的信息进行发送邮件
		*/
	public void sendMail(){
		try{
			//创建一个连接smtp服务器的socket对象
			Socket socket = new Socket(smtpServer.getText(),25);
			//读取socket客户端内容
			InputStream inStream = socket.getInputStream();
			//向socket客户端写出内容
			OutputStream outStream = socket.getOutputStream();
			
			scanner = new Scanner(inStream);
			output = new PrintWriter(outStream,true/*自动刷新*/);
			
			String hostName = InetAddress.getLocalHost().getHostName();
			//先接收
			receive();
			//再发送
			send("HELO " + hostName);
			//再接收
			receive();
			send("MAIL FROM: <" + from.getText() + ">");
			//以请求响应的方式循环
			receive();
			send("RCPT TO: <" + to.getText() + ">");
			receive();
			send("DATA");
			receive();
			send(message.getText());
			send(".");
			receive();
			socket.close();//关闭与服务器的请求
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/**
		发送一个字符串到socket对象,然后响应到注释文本域
		*/
	public void send(String content)throws IOException{
		comments.append(content);
		comments.append("\n");
		//把结束符替换成Mail服务器要求的格式
		output.print(content.replaceAll("\r","\r\n"));
		output.print("\r\n");
		output.flush();
	}
	
	/**
		接收MAIL服务器的内容
		*/
	public void receive()throws IOException{
		String line = scanner.nextLine();
		comments.append(line);
		comments.append("\n");
	}
}

使用URL连接

URL和URLConnection类封装了从远程站点复杂的接收信息的方式。如果我们只是想要获取资源的内容,那么我们可以使用URL类的openStream方法来获取:

URL url = new URL(urlString);
InputStream inStream = url.openStream();
Scanner scanner = new Scanner(inStream );

在java.net包中还有URI类,它没有访问资源的方法,它用来完成解决资源时使用。相对于URL来说,URL类可以打开被请求资源的流。所以,它可以与http:, https:, ftp:,和file:, jar:等联合使用。比如:

maps.yahoo.com/py/maps.py?… ftp://username:password@ftp.yourserver.com/pub/file.txt

如果我们想得到web网站的额外信息,那么需要使用类URLConnection来达到这种目的。该类提供更多的方法来操作Web网站的资源。使用该类有五大步骤:

  • 呼叫URL的openConnection初始化该对象
  • URLConnection con = url.openConnection();
  • 设置请求属性
  • 连接远程web网站
  • 连接之后使用getter方法得到相应的内容
  • 使用getInputStream方法读取远程web服务器响应的的内容

ShowURLConnection类演示代码

package com.jb.arklis.net;
import static java.lang.System.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.EventQueue;
import java.awt.event.*;


/**
	功能:书写一个演示类用来演示URLConnection的使用
	作者:技术大黍	
	*/
public class ShowURLConnection{
	
	public ShowURLConnection(){
		showURLConnetion();
	}
	
	private void showURLConnetion(){
		try{
			String usrlName = "http://horstmann.com/corejava/AllowUserInteractionTest.html";
			//String usrlName = "http://java.sun.com";
			URL url = new URL(usrlName);
			URLConnection connection = url.openConnection(); //初始化URLConnection对象
			
			//设置用户名和密码
			String user = "";
			String password = "";
			String temp = user + ":" + password;
			//使用自定义的编码算法来实现
			String encoding = base64Encode(temp);
			//使用未说明的API进行编码
			//String encoding = new sun.misc.BASE64Encoder().encode(temp.getBytes());
			connection.setRequestProperty("Authorization","Basic " + encoding);
			//连接远程Web服务器
			connection.connect();
			
			//读头信息
			Map<String,List<String>> headers = connection.getHeaderFields();
			for(Map.Entry<String,List<String>> entry : headers.entrySet()){
				String key = entry.getKey();
				//根据键读对应的值
				for(String value : entry.getValue()){
					out.println(key + ": " + value);
				}
			}
			out.println("-----------------------------------------------");
			out.println("getContentType: " + connection.getContentType());
			out.println("getContentLength: " + connection.getContentLength());
			out.println("getContentEncoding: " + connection.getContentEncoding());
			out.println("getDate: " + connection.getDate());
			out.println("getExpiration: " + connection.getExpiration());
			out.println("getLastModified: " + connection.getLastModified());
			out.println("-----------------------------------------------");
			
			//读到内存中
			Scanner scanner = new Scanner(connection.getInputStream());
			//把内容显示屏幕上
			for(int n = 1; scanner.hasNextLine() && n <= 10; n++){
				out.println(scanner.nextLine());
			}
			if(scanner.hasNextLine()){
				out.println(". . .");
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/**
		对字符串进行加密
		*/
	public String base64Encode(String content){
		ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
		Base64OutputStream output = new Base64OutputStream(byteOut);
		try{
			output.write(content.getBytes());
			output.flush();
		}catch(Exception e){
			e.printStackTrace();
		}
		//返回加密的字符串
		return byteOut.toString();
	}
	
	public static void main(String[] args){
		EventQueue.invokeLater(new Runnable(){
			public void run(){
				new ShowURLConnection();
			}
		});
	}
}

/**
	功能:实现Base64的编码
	备注:
		  Base64编码就是把3字个字节编码成4个字符。比如|11111122|22223333|33444444|第组都是基于6位
	的影射算法。如果输入的字符不是一个3的倍数,那么4个字符的最后一组使用一个或者两个“=”来填充,
	第一输出行最多76个字符。
	*/
class Base64OutputStream extends FilterOutputStream{
	private static char[] toBase64 = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
          'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
          'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
          't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
          '+', '/' };
	private int colomn = 0;
	private int i = 0;
	private int[] inbuffers = new int[3];
	
	
	public Base64OutputStream(OutputStream output){
		super(output);
	}
	
	//进行写字符运算
	public void write(int ch)throws IOException{
		inbuffers[i] = ch;
		i++;
		//如果i == 3
		if(i == 3){//进行位与以及移位运算
		 	super.write(toBase64[(inbuffers[0] & 0xFC) >> 2]);
		 	super.write(toBase64[((inbuffers[0] & 0x03) << 4) | ((inbuffers[1] & 0xF0) >> 4)]);
            super.write(toBase64[((inbuffers[1] & 0x0F) << 2) | ((inbuffers[2] & 0xC0) >> 6)]);
            super.write(toBase64[inbuffers[2] & 0x3F]);
			//移四个字符
			colomn += 4;
			//计数器归位
			i = 0;
			//判断最大字符
			if(colomn >= 76){
				super.write('\n');
				colomn = 0;
			}
		}
	}
	
	public void flush()throws IOException{
		if(i == 1){
			super.write(toBase64[(inbuffers[0] & 0xFC) >> 2]);
            super.write(toBase64[(inbuffers[0] & 0x03) << 4]);
            super.write('=');
            super.write('=');
		}else if(i == 2){
			super.write(toBase64[(inbuffers[0] & 0xFC) >> 2]);
            super.write(toBase64[((inbuffers[0] & 0x03) << 4) | ((inbuffers[1] & 0xF0) >> 4)]);
            super.write(toBase64[(inbuffers[1] & 0x0F) << 2]);
            super.write('=');
		}
		if(colomn > 0){
			super.write('\n');
			colomn = 0;
		}
	}
}

发送表单数据

上面我们讲解了怎样从一个web服务器读取数据,下面我们来讨论怎样向一个web服务器发送数据,以便web服务器调用。向一个服务器发送数据时,需要用户填写form表单。

image-20210427104005089.png

image-20210427104103882.png

当表单数据被发送到一个web服务器之后,使用什么脚本技术来解析是不重要的。因为,客户端使用标准的格式发送数据给一个web服务器,然后web服务器会根据这些标准的格式来进行解析这些数据,然后产生相应的响应动作。其中GET和POST是常用的向web服务器发送信息的命令。 GET命令如下:

http://host/script?parameter

每个参数都有一个键/值对来表示。多个参数由“&”分隔,参数值的编码编码是使用URL编码格式。遵守的规则如下:

  • 保留A-Z, a-z,0-9和, - * _不变
  • 把所有的空白使用”+”字符替换
  • 把所有其它字符转换成UTF-8格式,每个字符使用a %跟一个两位十六进制的数值

比如,把街道S.Main编码成S%2e+Main格式,2e是一个十六进制(或者小数46)是ASCII码’.’字符。

使用POST命令埋,我们不是向URL发送参数,而是使用从URLConnection得到一个输出流对象,然后向输出流写键/值对。但是,如果是多参数时,我们仍然使用”&”字符来进行分隔。

ShowPostSendData类演示代码

package com.jb.arklis.net;
import static java.lang.System.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;
import java.nio.channels.*;
import java.awt.event.*;


/**
	功能:书写一个应用类,用来演示向web服务器发送POST方式的数据
	作者:技术大黍	
	*/

public class ShowPostSendData{
	public static void main(String[] args){
		try{
			EventQueue.invokeLater(new Runnable(){
				public void run(){
					new PostSendFrame();
				}
			});
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

/**
	功能:用来演示向web服务器发送POST方式的数据的窗体
	*/
class PostSendFrame extends JFrame{
	private JPanel northPanel;
	
	public PostSendFrame(){
		setTitle("演示POST方式向web服务器发送数据");
		setSize(450,350);
		init();
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);
	}
	
	private void init(){
		Container container = getContentPane();
		northPanel = new JPanel();
		container.add(northPanel,BorderLayout.NORTH);
		northPanel.setLayout(new GridLayout(0,2));
		northPanel.add(new JLabel("主机(Host):",SwingConstants.CENTER));
		final JTextField hostField = new JTextField();
		northPanel.add(hostField);
		northPanel.add(new JLabel("行为(Action):",SwingConstants.CENTER));
		final JTextField actionField = new JTextField();
		northPanel.add(actionField);
		
		for(int i = 1; i <= 8; i++){
			northPanel.add(new JTextField());
		}
		final JTextArea result = new JTextArea(20,40);
		container.add(new JScrollPane(result),BorderLayout.CENTER);
		
		JPanel southPanel = new JPanel();
		container.add(southPanel,BorderLayout.SOUTH);
		JButton addButton = new JButton("更多表单元素(More)");
		southPanel.add(addButton);
		addButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				northPanel.add(new JTextField());
				northPanel.add(new JTextField());
				pack();
			}
		});
		
		JButton getButton = new JButton("获取(Get)");
		southPanel.add(getButton);
		getButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				//在文本域中显示出来
				result.setText("");
				final Map<String, String> post = new HashMap<String, String>();
				//下面是关键代码,指POST向web服务器以键/值对的形式发送表单数据
				for(int i = 4; i < northPanel.getComponentCount(); i += 2){
					String name = ((JTextField)northPanel.getComponent(i)).getText();
					if(name.length() > 0){
						String value = ((JTextField)northPanel.getComponent(i + 1)).getText();
						post.put(name,value);
						out.println("当前有的表单键/值:" + name + " : " + value);
					}
				}
				new SwingWorker<Void,Void>(){
					protected Void doInBackground()throws Exception{
						try{
							String urlString = hostField.getText() + "/" + actionField.getText();
							result.setText(doPost(urlString,post));
						}catch(IOException e){
							result.setText("" + e);
						}
						return null;
					}
				}.execute();
			}
		});
		pack();
	}
	
	/**
		实现具体分析的doPost方法
		*/
	public String doPost(String urlString, Map<String, String>nameValuePaires)throws IOException{
		URL url = new URL(urlString);
		URLConnection connection = url.openConnection();
		connection.setDoOutput(true);//呼叫该方法让连接对象可以输出
		//接下来准备向web服务器发送数据
		PrintWriter output = new PrintWriter(connection.getOutputStream());
		boolean first = true;
		for(Map.Entry<String,String> pair : nameValuePaires.entrySet()){
			if(first) 
				first = false;
			else
				output.print('&');
			String name = pair.getKey();
			String value = pair.getValue();
			output.print(name);
			output.print('=');
			//编码为UTF-8格式
			output.print(URLEncoder.encode(value,"UTF-8"));
		}
		output.close();
		
		//得到web服务器的响应信息
		Scanner scanner = null;
		StringBuilder response = new StringBuilder();
		try{
			scanner = new Scanner(connection.getInputStream());
		}catch(IOException e){
			//如果不Http服务器
			if(!(connection instanceof HttpURLConnection))
				throw e; //抛出异常
			InputStream error = ((HttpURLConnection)connection).getErrorStream();
			if (error == null)
				throw e;
			scanner = new Scanner(error);
		}
		
		//演示到响应的信息在屏幕中
		while(scanner.hasNextLine()){
			response.append(scanner.nextLine());
			response.append("\n");
		}
		scanner.close();
		//响应到客户端去
		return response.toString();
	}
}

运行效果

image-20210427104832776.png

输入项是模拟浏览器中的表单数据;当我们点击“获取”按钮后,得到web服务器响应数据。

总结

很多人天天写Web应用,但是其实真的理解是什么Web应用吗?真的理解表单的概念吗?

嘿嘿,希望我们文章可以真的帮助大家。

最后

记得给大黍❤️关注+点赞+收藏+评论+转发❤️

作者:老九学堂—技术大黍

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值