网络编程
来源:互联网 发布:java登陆功能实现 编辑:程序博客网 时间:2024/06/03 16:53
23,网络编程
23.1网络通信的要素
ip地址、端口、协议
Java网络编程中使用到的对象都在java.net包中。
Java网络编程中使用到的协议是:UDP和TCP协议。
Java中封装ip地址的对象是InetAddress。
23.2 InetAddress类
此类中封装了对ip地址的操作,InetAddress类没有构造函数,通过其静态方法,如:getByName(“主机名或IP或网址”),getLocalHost()就可以获取此类的对象。
如:
import java.net.InetAddress;import java.net.UnknownHostException;public class InetAddressDemo { public static void main(String[] args)throws UnknownHostException { //调用其静态方法,获取本类对象。 InetAddress ip=InetAddress.getLocalHost(); //通过对象,获取本地ip和主机名 System.out.println(ip.getHostAddress()); System.out.println(ip.getHostName()); } }}/* * 127.0.0.1 WIN-EK36MHANPIR */
23.3 UDP协议
UDP协议特点:通信息两端无需建立连接,传输的数据包比较小,在64K以内,因无连接,所以可靠性低,容易丢失数据,但速度快。
如:QQ等。
UDP数据包的发送和接收都是由DatagramSocket对象来完成的。而数据包的封装是由DatagramPacket对象完成的。
1,DatagramSocket对象
DatagramSocket对象负责数据包的发送和接收,使用的方法是:send(DatagramPacket dp);receive(DatagramPacket dp);
2,DatagramPacket对象
DatagramSocket对象是用来封装数据包的,但是此对象,既能封装被发送的数据,也可以封装接收到的数据,且可以将接收到的数据进行解析,如:发送端的IP,端口号,发送的信息等。
注意:网络通信两端都必须要有Socket服务,网络通信就是Socket通信,网络数据的传输通过IO传输,UDP使用的Socket服务就是DatagramSocket对象,其次DatagramPacket对象用来封装发送端数据时,必须要明确发送到的目标ip和目标端口。否则数据无法发送。
如:DatagramPacket
(byte[] buf, int length,InetAddress address, int port)
UDP发送端建立过程:
import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;import java.net.SocketException;import java.net.UnknownHostException;public class UDPDemo { public static void main(String[] args)throws IOException { udpSend(); } public static void udpSend()throws IOException { //1,创建DatagramSocket服务对象(可以指定发送数据的端口,也可能不指定) DatagramSocket ds=new DatagramSocket(); //2,对要发送的数据进行封装。 byte[] data="你好".getBytes(); DatagramPacket dp=new DatagramPacket(data,data.length,InetAddress.getByName("127.0.0.1"),12000); //3,发送数据,调用DatagramSocket对象的send()方法。 ds.send(dp); //4,关闭Socket服务 。 ds.close(); }}
UDP接收端建立过程:
import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.SocketException;public class UDPDemo2 { public static void main(String[] args)throws IOException { udpRece(); } public static void udpRece()throws IOException { //1,创建接收端DatagramSocket服务对象,这里必须要明确接收数据的端口,并且与发送端DatagramPacket封装的端口一致。 DatagramSocket ds=new DatagramSocket(12000); //2,创建接收端数据包对象,用于存放接收到的数据,只需要明确数组,和数组长度。 byte[]buf=newbyte[1024]; DatagramPacket dp=new DatagramPacket(buf,buf.length); //3,接收数据,调用receive()方法。 ds.receive(dp); //4,对数据进行解析。 byte[] data=dp.getData(); String text=new String(data,0,dp.getLength()); String ip=dp.getAddress().getHostAddress(); int port=dp.getPort(); System.out.println(ip+":"+port+" "+text); //5,关闭Socket服务 ds.close(); }}
UDP发送端和服务端因为不需要建立连接,所以先开启哪一端都行,但在这里为了接收数据,需要先开启接收端。注意,接收端的receive()方法是阻塞式方法。没收接收到数据,就会一直等待。
以上代码简单了实现了,发送“你好”,但是因为发送数据不是固定的,所以考虑可以让发送端使用键盘录入的方法,将要发送的数据封装到数据包再进行发送。因为发送端和服务端同时进行,所以可以使用多线程,且服务端一直处于接收状态。
UDP聊天程序。
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;import java.net.SocketException;public class UDPChat { public static void main(String[] args) { new Thread(new UDPRece()).start();//启动接收端线程 new Thread(new UDPSend()).start();//启动发送端线程 }}class UDPSend implements Runnable { @Override public void run() { DatagramSocket ds = null; try { ds = new DatagramSocket((int) (Math.ceil(Math.random() * 5000)));//这里随机使用0-5O00端口。 // 创建字符读取流,读取键键盘录入的数据 BufferedReader br = new BufferedReader(new InputStreamReader( System.in)); DatagramPacket dp = null; String line = null; // 循环读取 while ((line = br.readLine()) !=null) { byte[] data = line.getBytes(); dp = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"), 12000); //发送数据包。 ds.send(dp); } } catch (IOException e) { e.printStackTrace(); } finally { ds.close(); } }}class UDPRece implements Runnable { @Override public void run() { try { DatagramSocket ds = new DatagramSocket(12000);//注意这一句代码不能放在循环中。 // 因为接收端一直处于接收状态,所以使用无限循环 while (true) { // 创建数据包对象,用于封装数据包。 byte[] buf =newbyte[1024]; DatagramPacket dp = new DatagramPacket(buf, buf.length); // 接收数据。 ds.receive(dp); // 解析数据 byte[] data = dp.getData(); String text = new String(data, 0, dp.getLength()); if(text.equals("886")){ break; } String ip = dp.getAddress().getHostAddress(); int port = dp.getPort(); System.out.println(ip +":" + port + " " + text); } } catch (IOException e) { e.printStackTrace(); } }}
以下是自己完成的图形界面的聊天程序,使用到了多线程。
/*思路:1,程序一开启就会开启一条线程,执行接收端任务,接收端一直处于接收状态。2,在IP地址输入要发送的目标IP。3,在下面的发送信息窗口中输入要发送的信息,然后点击“发送”就会开启另一条线程,执行发送端任务,将信息发送到目标IP,并显示在,上面的信息显示窗口,发送信息的端口是通过Math.random()随机获取,防止与接收端端口冲突。4,服务端对发送的信息进行判断,如果是“886”,就停止接收发送端信息,并在显示信息窗口中显示:ip+port…退出聊天室,发送端要再次发送信息,需要重新开启程序。5,点击“关闭”程序退出。*/import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.KeyAdapter;import java.awt.event.KeyEvent;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.StringReader;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;import java.net.SocketException; import javax.swing.JButton;import javax.swing.JLabel;import javax.swing.JScrollPane;import javax.swing.JTextArea;import javax.swing.JTextField;import javax.swing.SwingUtilities;import javax.swing.WindowConstants; import com.cloudgarden.layout.AnchorConstraint;public class ChatDemo extends javax.swing.JFrame { private JScrollPane jScrollPane1; private JScrollPane jScrollPane2; private JButton jButton1; private JButton jButton2; private JLabel jLabel1; public static final JTextField jTextField1=new JTextField(); public static final JTextArea jTextArea2=new JTextArea(); public static final JTextArea jTextArea1=new JTextArea(); /** * Auto-generated main method to display this JFrame * @throws SocketException */ public static void main(String[] args) throws SocketException { SwingUtilities.invokeLater(new Runnable() { public void run() { ChatDemo inst = new ChatDemo(); inst.setLocationRelativeTo(null); inst.setVisible(true); } }); new Thread(new ReceService(new DatagramSocket(12000))).start();//这里就是接收端线程,在主函数中,程序一运行,就执行。 } public ChatDemo() { super(); initGUI(); } private void initGUI() { try { getContentPane().setLayout(null); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); this.setTitle("\u5c40\u57df\u7f51\u804a\u5929\u7a0b\u5e8f"); { jScrollPane1 = new JScrollPane(); getContentPane().add(jScrollPane1, new AnchorConstraint(1, 1001, 1001, 1, AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL, AnchorConstraint.ANCHOR_REL)); jScrollPane1.setBounds(12, 4, 463, 208); { //jTextArea1 = new JTextArea(); jScrollPane1.setViewportView(jTextArea1); } } { jScrollPane2 = new JScrollPane(); getContentPane().add(jScrollPane2); jScrollPane2.setBounds(12, 222, 463, 117); { //jTextArea2 = new JTextArea(); jScrollPane2.setViewportView(jTextArea2); jTextArea2.setPreferredSize(new java.awt.Dimension(460, 114)); jTextArea2.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent evt) { jTextArea2KeyPressed(evt); } }); } } { //jTextField1 = new JTextField(); getContentPane().add(jTextField1); jTextField1.setBounds(12, 352, 169, 24); } { jLabel1 = new JLabel(); getContentPane().add(jLabel1); jLabel1.setText("\u5c40\u57df\u7f51\u76ee\u6807IP\u5730\u5740\u6216\u5e7f\u64ad\u5730\u5740"); jLabel1.setBounds(12, 382, 342, 17); } { jButton1 = new JButton(); getContentPane().add(jButton1); jButton1.setText("\u53d1\u9001"); jButton1.setBounds(300, 352, 72, 24); jButton1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { jButton1ActionPerformed(evt); } }); } { jButton2 = new JButton(); getContentPane().add(jButton2); jButton2.setText("\u5173\u95ed"); jButton2.setBounds(404, 352, 72, 24); jButton2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { jButton2ActionPerformed(evt); } }); } pack(); this.setSize(503, 442); } catch (Exception e) { //add your error handling code here e.printStackTrace(); } } private void jButton2ActionPerformed(ActionEvent evt) { System.exit(0); } //“发送”按钮的事件处理,就是执行send()方法,send()方法中将发送端的线程开启进行封装,方便调用。 private void jButton1ActionPerformed(ActionEvent evt) { send(); } public void send() { try {//发送端端口随机获取。 new Thread(new SendService(new DatagramSocket((int)Math.ceil((Math.random()*10000))))).start(); } catch (SocketException e) { e.printStackTrace();} } //发送信息文本域中,加入了事件监听,事件处理的方式,是:当按下ctrl+Enter时,也执行send()方法。 private void jTextArea2KeyPressed(KeyEvent evt) { if(evt.getKeyCode()==evt.VK_ENTER&&evt.isControlDown()){ send(); } }}class SendService implements Runnable{//创建发送端多线程任务 private static final String LINE_SEPARATOR = System.getProperty("line.separator"); private DatagramSocket ds; SendService(DatagramSocket ds){ this.ds=ds; } @Override public void run() { //获取发送信息文本域中的文本内容 String sendstr=ChatDemo.jTextArea2.getText(); //因为是字符串数据,这里使用了字符串读取流。 BufferedReader br=new BufferedReader(new StringReader(sendstr)); DatagramPacket dp=null; //获取IP地址栏中的IP String jtextfield_str=ChatDemo.jTextField1.getText(); String line; try { //循环读取数据 while((line=br.readLine())!=null){ byte[]data=line.getBytes(); //将数据封装到数据包中,IP地址就是获取地址栏的IP地址,端口号:12000 dp=new DatagramPacket(data,0,data.length,InetAddress.getByName(jtextfield_str),12000); ds.send(dp); } } catch (IOException e) { e.printStackTrace(); } }}class ReceService implements Runnable{//创建接收端多线程任务 private static final String LINE_SEPARATOR = System.getProperty("line.separator"); private DatagramSocket ds; public ReceService(DatagramSocket ds) { super(); this.ds = ds; } @Override public void run() { while(true){//无限循环,一直处于接收状态。 byte[] buf=new byte[1024000];//创建临时缓冲区。 DatagramPacket dp=new DatagramPacket(buf,buf.length);//创建接收数据的数据包对象。 try { //接收数据 ds.receive(dp); //将字节数据,转成字符串数据。 String str=new String(dp.getData(),0,dp.getLength()); //将字符串数据,添加到信息显示文本域 ChatDemo.jTextArea1.append(dp.getAddress().getHostAddress()+"::"+dp.getPort()+" "+str+LINE_SEPARATOR); //判断标记,如果为886就停止接收发送端数据,需要重新开启程序,才能发送信息。 if(str.equals("886")){ ChatDemo.jTextArea1.append(dp.getAddress().getHostAddress()+"退出聊天室"+LINE_SEPARATOR); break; } } catch (IOException e) { e.printStackTrace(); } } } }
窗口图片:
23.4 TCP协议
TCP协议特点:必须要建立连接,否则无法完成数据传输,可用于数据量较大的传输,可靠性高。
TCP协议的两端要进行通信,与UDP相同必须要有Socket,但是TCP使用的Socket对象与UDP有所不同,客户端使用的是Socket对象,服务端使用的是ServerSocket对象。客户端Socket一建立,要明确目标IP和端口号,服务端ServerSocket一建立也要明确端口号,且与客户端Socket端口号一致,才能完成数据传输.TCP客户端和服务端之间的数据传输是通过IO流完成的,IO流的对象是通过Socket来获取的,也称Socket流。
注意:这里必须要先开启服务端。
1,客户端建立过程
import java.net.Socket;importjava.net.UnknownHostException;import java.io.IOException;import java.io.OutputStream;public class ClientDemo { public static void main(String[] args)throws IOException, IOException { //1,创建客户端Socket对象 Socket s=new Socket("127.0.0.1",13000); //2,通过Socket对象,获取流对象完成数据的发送。 OutputStream out=s.getOutputStream(); //3,通过IO流发送数据。 out.write("你好".getBytes()); //4,关闭客户端Socket服务。 s.close(); }}
2.服务端建立过程
import java.io.IOException;import java.io.InputStream;import java.net.ServerSocket;import java.net.Socket; public class ServerDemo { public static void main(String[] args)throws IOException { //1,创建服务端Socket对象,明确端口与客户端端口一致。 ServerSocket ss=new ServerSocket(13000); //2,获取连接到此服务端的客户端Socket对象 Socket s=ss.accept();//此方法是阻塞式方法。 //3,通过获取到的客户端Socket对象,获取流对象,读取客户端发来的数据。 InputStream in=s.getInputStream(); byte[]buf=newbyte[1024]; int len=0; while((len=in.read(buf))!=-1){ String str=new String(buf,0,len); System.out.println(str); } //4,关闭Socket服务。 s.close(); ss.close(); } }
如果传输数据为其他数据,如:键盘录入,文件等。这时客户端就需要使用IO流的输入流读取源数据。比如:将一个文件发送到服务端。
需求:客户端读取一个文本文件,将文件发送给服务端,服务获取文件数据后,将数据写入另一个文本文件中。
客户端:
import java.net.Socket;importjava.net.UnknownHostException;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileReader;import java.io.IOException;importjava.io.OutputStream;import java.io.OutputStreamWriter;public class ClientDemo { public static void main(String[] args)throws IOException, IOException { clientDemo(); } public static void clientDemo()throws IOException { //首先创建客户端Socket对象 Socket s=new Socket("127.0.0.1",13000); //将源文件封装在File对象中,并创建一个输入流对象,关联源文件对象,因为是文本文件所以使用字符流。 File file=new File("d:\\a.txt"); BufferedReader br=new BufferedReader(new FileReader(file)); //通过客户端Socket获取字节输出流。 BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line=null; //循环读取文件,并写入目的地。 while((line=br.readLine())!=null){ bw.write(line); bw.newLine();//readLine()一次读取一行,所以这里也要加入换行标记。 bw.flush();//字符流要刷新 } //告诉服务端数据已经发送完毕。如果这里不调用此方法,服务端会一直等待读取数据。 s.shutdownOutput(); //关闭Socket服务 s.close(); }}
服务端:
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.net.ServerSocket;import java.net.Socket;public class ServerDemo { public static void main(String[] args)throws IOException { serverDemo(); } public static void serverDemo()throws IOException { //创建服务端ServerSocket对象,明确端口号与客户端端口号一致。 ServerSocket ss=new ServerSocket(13000); //通过服务端ServerSocket对象,获取连接到此服务端的客户端Socket对象。 Socket s=ss.accept();//此方法是阻塞方法。 //通过获取到的客户端Socket对象,获取流对象。 BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream())); //创建输出流对象和目标文件对象,将读取到的数据写入另一个文件中。 File file=new File("e:\\b.txt"); BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); String line=null; while((line=br.readLine())!=null){ bw.write(line); bw.newLine(); bw.flush(); } //关闭资源 br.close(); s.close(); ss.close(); } }
注意:上面客户端和服务端之间传输的是文本数据,如果数据不是纯文本数据,就需要用到字节流。
上面代码中,客户端和服务端之间传输数据完成后,没有任何的反馈信息,如何让数据传输完成后,客户端能接收到服务端发来的反馈信息,说明数据传输成功呢,这就需要建立客户端和服务端和信息发馈通道。
如图:
客户端:
import java.net.Socket;importjava.net.UnknownHostException;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;public class ClientDemo { public static void main(String[] args)throws IOException, IOException { clientDemo(); } public static void clientDemo()throws IOException { //首先创建客户端Socket对象 Socket s=new Socket("127.0.0.1",13000); //将源文件封装在File对象中,并创建一个输入流对象,关联源文件对象,因为是文本文件所以使用字符流。 File file=new File("d:\\a.txt"); BufferedReader br=new BufferedReader(new FileReader(file)); //通过客户端Socket获取字节输出流。 BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line=null; //循环读取文件,并写入目的地。 while((line=br.readLine())!=null){ bw.write(line); bw.newLine();//readLine()一次读取一行,所以这里也要加入换行标记。 bw.flush();//字符流要刷新 } //告诉服务端数据已经发送完毕。如果这里不调用此方法,服务端会一直等待读取数据。 s.shutdownOutput(); //创建读取流,读取客户端发来的反馈信息。 BufferedReader bufr=new BufferedReader(new InputStreamReader(s.getInputStream())); String text=bufr.readLine();//因为反馈信息少,这里只读一行。 System.out.println(text);//将服务端发来的信息,显示到控制台。 //关闭Socket服务 s.close(); }}
服务端:
import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;public class ServerDemo { public static void main(String[] args)throws IOException { serverDemo(); } public static void serverDemo()throws IOException { //创建服务端ServerSocket对象,明确端口号与客户端端口号一致。 ServerSocket ss=new ServerSocket(13000); //通过服务端ServerSocket对象,获取连接到此服务端的客户端Socket对象。 Socket s=ss.accept();//此方法是阻塞方法。 //通过获取到的客户端Socket对象,获取流对象。 BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream())); //创建输出流对象和目标文件对象,将读取到的数据写入另一个文件中。 File file=new File("e:\\b.txt"); BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); String line=null; while((line=br.readLine())!=null){ bw.write(line); bw.newLine(); bw.flush(); } //使用字符打印流,发送反馈信息给客户端。 PrintWriter pw=new PrintWriter(s.getOutputStream(),true); pw.println("文件上传成功"); pw.close(); //关闭资源 br.close(); s.close(); ss.close(); } }
注意:如果想让服务端一直处于接收的状态,可以使用循环,但是要注意,创建服务端 ServerSocket对象的代码,不能够放到循环中,因为服务端ServerSocket对象只有一个。
publi cclass ServerDemo { public static void main(String[] args)throws IOException { serverDemo(); } public static void serverDemo()throws IOException { //创建服务端ServerSocket对象,明确端口号与客户端端口号一致。 ServerSocket ss=new ServerSocket(13000); while(true){//这样,服务端就一直处于开启状态。 //通过服务端ServerSocket对象,获取连接到此服务端的客户端Socket对象。 Socket s=ss.accept();//此方法是阻塞方法。 //通过获取到的客户端Socket对象,获取流对象。 BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream())); //创建输出流对象和目标文件对象,将读取到的数据写入另一个文件中。 File file=new File("e:\\b.txt"); BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); String line=null; while((line=br.readLine())!=null){ bw.write(line); bw.newLine(); bw.flush(); } //使用字符打印流,发送反馈信息给客户端。 PrintWriter pw=new PrintWriter(s.getOutputStream(),true); pw.println("文件上传成功"); pw.close(); } } }
上面的客户端和服务端间的通信,服务端在同一时间内只能连接一个客户端,如果有其他客户端要连接服务端,必须要等到前一个客户端和服务端的连接关闭扣,其他客户端才有机会连接上服务端,也就是多个客户端是在排队等待连接服务端,如何才能多个客户端都可以同时连接上服务端?这就需要使用多线程。
要想让多个客户端同时连接上服务端,那么可以将每个连接到服务端的客户端封装在一个单独的线程中,每连接一个客户端,服务端就对这个客户端进行线程的封装,这个就可以实现多个客户端同时能连接一个服务端。
多线程的服务端:
import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;public class ServerDemo { public static void main(String[] args)throws IOException { ServerSocket ss=new ServerSocket(13000); while(true){ new Thread(new Task(ss.accept())).start();//为每一个连接到此服务端的客户端都分配一个线程。 } }}class Task implements Runnable{ private Socket s; Task(Socket s){ this.s=s; } @Override public void run() { BufferedInputStream bis=null; BufferedOutputStream bos=null; File dir=new File("d:\\upload"); if(!dir.exists()){ dir.mkdir(); } try { bis=new BufferedInputStream(s.getInputStream()); String ip=s.getInetAddress().getHostAddress(); bos=new BufferedOutputStream(new FileOutputStream(new File(dir,ip+".txt"))); byte[]buf=newbyte[1024]; int len=0; while((len=bis.read(buf))!=-1){ bos.write(buf,0,len); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ if(bis!=null) try { bis.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(bos!=null) try { bos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }}
由此可见,多线程服务端解决了多客户端同时连接服务端的问题。
23.5 URL和URLConnection
URL:统一资源定位符,也就是网址。
格式:<URL访问方式>://<主机IP或域名>:<端口>/<资源路径>
访问方式:
1, ftp:文件转输协议。
2, http:超文本传输协议。
等等……
在java中网址封装成了对象。就是java.net包中的 URL
URL的构造函数,可以接收字符串表示的URL链接资源,并可以访问资源。
如:
import java.io.IOException;import java.io.InputStream;import java.net.URL;import java.net.URLConnection;public class URLDemo { public static void main(String[] args) throws IOException { //将域名封装在URL对象中。 URL url=new URL("http://www.baidu.com"); //通过URL对象的openConnection()方法,可以获取到URLConnection对象。 URLConnection urlconn=url.openConnection(); //通过URL连接器对象,能够获取到字节输入流。完成对资源的访问。 InputStream in=urlconn.getInputStream(); byte[]buf=newbyte[1024]; int len=0; while((len=in.read(buf))!=-1){ System.out.println(new String(buf,0,len)); //通过URLConnection对象的getHeaderField()方法,可以获取指定属性名的应答信息值。 System.out.println(urlconn.getHeaderField("content-type")); System.out.println(urlconn.getHeaderField("Server")); System.out.println(urlconn.getHeaderField("Content-length")); System.out.println(urlconn.getHeaderField("date")); } }}
URLConnection:此对象可以解析服务端发回的应答消息。通过此对象的getInputStream()和getOutputStream()方法,可以获取到字节输入流和字节输出流对象。
由此可URL对象,也可以实现连接到服务端。相比较Socket而言,URL对象可以直接使用域名连接到服务端。
23.6客户端和服务端通信原理
为了搞清楚客户端和服务端之间通信的原理,是通过什么形式完成访问的。我们把IE作为客户端,TomCat作为服务端。
下面,我们首先模拟一个服务端,看看IE在访问服务端时发送了什么信息。
模拟服务端:
import java.io.BufferedInputStream;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;public class ServerSocketDemo { public static void main(String[] args)throws IOException{ ServerSocket ss=new ServerSocket(8000); Socket s=ss.accept(); BufferedInputStream br=new BufferedInputStream(s.getInputStream()); byte[]buf=newbyte[1024]; int len=0; while((len=br.read(buf))!=-1){ System.out.println(new String(buf,0,len)); } }}
通过在IE浏览器地址栏输入:http://127.0.0.1:8000/
服务端读取到IE发来的请求信息:
GET / HTTP/1.1 //请求行包含:请求方式 /请求资源 请求协议/协议版本
Accept: text/html, application/xhtml+xml, */*//以下是请求头:都是属性名:属性值的形式。
Accept-Language: zh-CN,en-GB;q=0.7,zh-Hant;q=0.3
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: 127.0.0.1:8000
Connection: Keep-Alive
//空行
//请求体
注意:请求头和请求体之间有一行空行。
IE浏览器发送请求信息给服务端,告诉服务端自已要访问的资源和自已所支持的一些功能,服务端收到信息后会反回一个应答信息给客户端。
以上就是IE客户端,发给浏览器的信息。
既然知道了IE发送给服务端的信息,我们就可以模拟一个IE浏览器,并读取TomCat发给IE的应答信息。看看服务端给客户端发回的是什么信息。
模拟IE浏览器:
下面我们将IE发送的信息,依次发送给TomCat就可以模拟出一个IE浏览器在访问服务端。
首先,要启动Tomcat服务器,TomCat默认端口是:8080。
其次,执行模拟IE的代码:
import java.io.IOException;import java.io.InputStream;import java.io.PrintWriter;import java.net.Socket;public class IEDemo { public static void main(String[] args)throws IOException{ Socket s=new Socket("127.0.0.1",8080); PrintWriter pw=new PrintWriter(s.getOutputStream(),true); //将请求信息打印到TomCat服务器 pw.println("GET /1.html HTTP/1.1");//打印请求行 pw.println("Accept: text/html, application/xhtml+xml, */*");//以下打印的是请求头。 pw.println("Accept-Language: zh-CN,en-GB;q=0.7,zh-Hant;q=0.3"); pw.println("User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"); pw.println("Accept-Encoding: gzip, deflate"); pw.println("Host: 127.0.0.1:8000"); pw.println("Connection: Keep-Alive"); pw.println();//请求头和请求体间的空行 //这里没有请求体。 //获取输入流对象,读取服务器发回的信息。 InputStream in=s.getInputStream(); byte[]buf=newbyte[1024]; int len=0; while((len=in.read(buf))!=-1){ System.out.println(new String(buf,0,len)); } }}
服务端发回的应答消息:
HTTP/1.1 200 OK//应答消息行包含:协议/协议版本 应答消息状态码 应答状态描述(当访问的资源不存在时:HTTP/1.1 404 Not Found)
Server: Apache-Coyote/1.1//以下是应答消息头,属性名:属性值。
Accept-Ranges: bytes
ETag: W/"42-1383403700689"
Last-Modified: Sat, 02 Nov 2013 14:48:20 GMT
Content-Type: text/html
Content-Length: 42
Date: Sat, 02 Nov 2013 14:48:34 GMT
//空行
<font color='red' size='6'>欢迎光临</font>//这里是应答消息体。就是IE要访问的资源。
从上面可以看出,IE在访问服务端资源时,首要把请求信息发送至服务端,服务端进行资源搜索,然后发送应答信息包含应答消息体就是IE要访问的资源到IE,IE解析应答消息行,应答消息头,再对应答消息体进行解析获取到服务器端发来的资源。完成资源的访问。如果IE要访问的网络资源不存在,应答消息头是这样的:HTTP/1.1 404 Not Found,此时网页提示资源不存在:404错误。
客户端服务端通信示意图:
软件开发中常见的网络架构(开发方式):
1, c/s(client/Server)
早起较多如这些软件编写的:VC++、delphi、VB等。
特点:该架构的软件,客户端和服务端都要编写,开发成本较高,维护相对麻烦。但是,客户端在本地进行一部分运算,可以减轻服务端负担。
2, b/s(browser/server)
特点:这种架构的软件,浏览器作为客户端,只需要开发服务端。开发成本低,维护简单。但是,服务端的负担较重,因为运算全部在服务端完成。如:网页游戏。
---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 网络编程
- 阿里忙收购,李彦宏在忙…“深度学习”
- 简单理解计算-算法时间复杂度
- 设计模式之抽象工厂方法
- 初始化类中的静态数组
- 交换排序
- 网络编程
- 第四届蓝桥杯--买不到的数目
- stm32的三种编程下载方式
- 数组和指针参数在函数形参中的表示
- 设计模式之代理模式
- dos中的for命令简单使用
- Ubuntu查看IP,网关,DNS
- JDBC读取新插入Oracle数据库Sequence值的5种方法
- C/C++<string.h>字符串函数