网络编程

来源:互联网 发布:java登陆功能实现 编辑:程序博客网 时间:2024/06/03 16:53
---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------

23,网络编程

23.1网络通信的要素

ip地址、端口、协议

Java网络编程中使用到的对象都在java.net包中。

Java网络编程中使用到的协议是:UDPTCP协议。

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 URLURLConnection

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要访问的资源到IEIE解析应答消息行,应答消息头,再对应答消息体进行解析获取到服务器端发来的资源。完成资源的访问。如果IE要访问的网络资源不存在,应答消息头是这样的:HTTP/1.1 404 Not Found,此时网页提示资源不存在:404错误。

客户端服务端通信示意图:

  

软件开发中常见的网络架构(开发方式):

1,            c/s(client/Server)

早起较多如这些软件编写的:VC++delphiVB等。

特点:该架构的软件,客户端和服务端都要编写,开发成本较高,维护相对麻烦。但是,客户端在本地进行一部分运算,可以减轻服务端负担。

2,            b/s(browser/server)

特点:这种架构的软件,浏览器作为客户端,只需要开发服务端。开发成本低,维护简单。但是,服务端的负担较重,因为运算全部在服务端完成。如:网页游戏。 

 

 

---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
原创粉丝点击