寒假Java网络编程学习笔记

Lesson09A-1.rmvb
网络编程概述
java网络编程类:java.net包
UDP通信:DatagramSocket类
TCP通信服务器端:ServerSocket类; 还有Socket类可用于TCP通信的服务器端和客户端。

 

**************************************************************************
Lesson09A-2.rmvb
UDP网络程序:DatagramSocket类,DatagramPacket类,InetAddress类

DatagramSocket类
public DatagramSocket()
public DatagramSocket(int port)
public DatagramSocket(int port, InetAddress laddr) //拥有多个IP地址的计算机

DatagramSocket的close方法,释放资源
send(DatagramPacket p)方法,发送UDP数据包
receive(Datagrampacket p)方法,接受UDP数据包

DatagramPacket类:数据的容器(集装箱)
public DatagramPacket(byte[] buf, int length) //数据接收时使用,buf是缓冲区,未必被完全使用
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) //数据发送时使用,后两个参数指定接受方的IP和端口
getInetAddress方法和getPort方法获取发送方的IP和端口。
GetData和getLength方法:分别返回buf和实际接受到的数据长度

InetAddress类,用于表示IP地址的类
getByName静态方法:由hostname(或者IP)获得InetAddress
getHostAddress方法:返回InetAddress中包装的IP地址(以xxx.xxx.xxx.xxx的字符串返回)


这里还牵涉到byte与String的转换问题
另外,当传送的String中包含UNICODE字符(如中文字符)时,不能简单地用String.length()获得String的字节数(因为length返回的是字符数),而必须用String.getBytes().length获得其长度,作为构造DatagramPacket的参数。

实例程序:
发送:注意注释部分
import java.net.*;
public class UdpSend { 
 public static void main(String[] args) throws Exception{
 DatagramSocket ds = new DatagramSocket();
 
 String msg = "你好, guy!哈哈";  //在程序中,我们发现,如果字符串的“哈哈”二字去除,则即使在以下的msg.getBytes().length改为msg.length(),程序也能正常处理中文部分。即:只有在尾部的中文会被忽略,不知道为什么?

 DatagramPacket dp = new DatagramPacket(msg.getBytes(), msg.getBytes().length, InetAddress.getByName("127.0.0.1"),1000);
 
 ds.send(dp); 
 ds.close(); 
 } 
}

接收:
import java.net.*;
public class UdpRcv {
 public static void main(String[] args) throws Exception {    
  DatagramSocket ds = new DatagramSocket(1000);
 
  byte[] b = new byte[1024];
  DatagramPacket dp = new DatagramPacket(b, b.length);
 
  ds.receive(dp);
  
  System.out.println("received msg: " + new String(dp.getData(), 0, dp.getLength()));
  }
 }

 

 

 

 

  Lesson09A-3.rmvb
用UDP编写网络聊天程序
1.程序中必须新建一个线程用来处理接收操作
 new Thread(new Runnable(){
  public void run(){
   byte[] buf = new byte[1024];
   DatagramPacket dp = new DatagramPacket(buf, 1024);
   while(true){
    try{
     ds.receive(dp);
     //显示内容处理
    }catch(Exception e){
     if(!ds.isClosed()){
      e.printStackTrace();
     }
    }
   }
  }
 }).start();

2.本程序可以给本机发信,也可以通过广播地址,广播发信。但是不能透过网关通信。


在程序编写中遇到的问题:
1.为JList添加一个选项用哪个函数?List有add,JList没有。

 

 

程序代码:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.net.*;

/**
 * Sample application using Frame.
 *
 * @author
 * @version 1.00 06/02/04
 */
public class ChattingRFrame extends JFrame {
    List jl = new List();
   
    JPanel jp = new JPanel();
    JTextField jtf_IP = new JTextField(15);
    JTextField jtf_msg = new JTextField(20);
   
    DatagramSocket ds = null;
    /**
     * The constructor.
     */ 
     public ChattingRFrame() {
      
      try{  
       ds = new DatagramSocket(10000);
        }catch(Exception e){
         e.printStackTrace();
        }
       
      new Thread(new Runnable(){
   public void run() {
    byte[] buf = new byte[1024];
    DatagramPacket dp = new DatagramPacket(buf, 1024);
    while(true){
     try{
      ds.receive(dp);
      String msg = new String(dp.getData(), 0, dp.getLength());
      jl.add(dp.getAddress().toString() + ": " + msg, 0); //显示内容处理
     }catch(Exception e){
      if(!ds.isClosed()){
       e.printStackTrace();
      }
     }
    }
   }
  }).start();


      
      
        this.getContentPane().add(jl, "Center");
        this.getContentPane().add(jp, "South");
       
       
        jp.setLayout(new BorderLayout());
  jp.add(jtf_IP, BorderLayout.WEST);
  jp.add(jtf_msg, BorderLayout.EAST);       
               
        setSize(new Dimension(400, 400));
       
        jtf_msg.addActionListener(new ActionListener(){
         public void actionPerformed(ActionEvent e){
          jl.add("I said: " + jtf_msg.getText(), 0);
          
          try{
           byte [] buf = new byte[1024];
           DatagramPacket dp = new DatagramPacket(
            jtf_msg.getText().getBytes(), jtf_msg.getText().getBytes().length, InetAddress.getByName(jtf_IP.getText()), 10000);
           ds.send(dp);           
          }catch(Exception ex){
           ex.printStackTrace();
          }
          jtf_msg.setText("");
         }
        }
        );
       
        // Add window listener.
        this.addWindowListener
        (
            new WindowAdapter() {
                public void windowClosing(WindowEvent e) {
                 ds.close();
                    ChattingRFrame.this.windowClosed();
                }
            }
        ); 
    }
   
   
    /**
     * Shutdown procedure when run as an application.
     */
    protected void windowClosed() {
     
     // TODO: Check if it is safe to close the application
     
        // Exit application.
        System.exit(0);
    }
}

 

 

 

  Lesson09A-4.rmvb
内网私有IP通过网关代理上网的原理
内网机器的 内网机IP:内网机port 被网关机改装成 网关IP:随机port ;另外,网关机维护一张IP转发表。记录由 内网机IP:内网机port 到 网关IP:特定随机port 的映射。IP转发表是有一定时效性的,表中某条记录一定时间不被使用的话会被清除。


可以修改之前的程序,使之能在内外网通信(当然,通信必须由内网机器发起)
(怎么改????)

 

 

 

   Lesson09B-1
TCP网络程序:分为服务器程序(ServerSocket)和客户机程序(Socket)
通信流程:
1.服务器程序新建ServerSocket类,调用accept方法等待客户的连接
2.客户机程序创建一个Socket类,请求与服务器连接
3.服务器接收客户的连接请求,并创建一个新的Socket与该客户建立专线连接
4.建立了连接的两个Socket在一个单独的线程(由服务器程序创建)上对话
5.服务器开始等待新的连接请求,当新的连接请求到达时,重复2-5

ServerSocket类
public ServerSocket()  //构造后还要用bind方法,以下三个都不用bind
public ServerSocket(int port)  //最常用
public ServerSocket(int port, int backlog) //backlog指定可与服务器连接的客户机数量,默认50
public ServerSocket(int port, int backlog, InetAddress bindAddr) //计算机上有多张网卡(多个IP)

与DatagramSocket一样,需要用close方法释放连接

accept方法,等待客户端的连接请求(一旦连接完成,返回一个Socket对象);无连接请求时,accept一直保持阻塞状态

 

Socket类
public Socket(); //构造后还要用connect方法,才能被使用,适用于用一个Socket轮询多个服务器。
public Socket(String host, int port); //host指定IP地址
public Socket(InetAddress address, int port);
//以下两种构造方式适合于一台机器上有多张网卡(多IP)的情况
public Socket(String host, int port, InetAddress localAddr, int localPort);
public Socket(InetAddress address, int port, InetAddress localAddr, int localPort);
以上所有的构造函数都用于客户端;服务器端的Socket用accept方法返回

getInputStream和getOutputStream返回Socket的输入输出流对象。
InputStream.read(byte[])方法用来读取客户端发送过来的数据,返回数据的字节长度。
OutputStream.write(byte[])方法用来向客户端发送信息。
InputStream和OutputStream需要关闭。(close)

服务器端程序的close顺序
1.InputStream, OutputStream(因为这两个是在Socket的基础上获得的(getInputStream和getOutputStream))
2.Socket(因为这是在ServerSocket的基础上产生的(accept返回))
3.ServerSocket

可以用BufferedReader类包装InputStream和OutputStream,使之在字节流和字符流间方便的自由转换,如:
InputStream is = s.getInputStream() //s是一个Socket对象
BufferedReader br = new BufferedReader(new InputStreamReader(is));
然后可以用br.readLine(),br.read()等方法方便的一次读取一行等。
另外,BufferReader也需要close.(在InputStream的close之前)

对于OutputStream,可以用PrintWriter包装
如:OutputStream os = s.getOutputStream();
    PrintWriter pw = new PrintWriter(os);
PrintWriter的println函数可以向客户端发出一行字符串(并换行),并且这个换行符可以根据操作系统的不同作变化(在Linux产生的换行符为'\n',Windows下为'\n';这与PrintStream不同,PrintStream无论在哪个操作系统的换行符都是'\n').另外,在用PrintWriter时,默认的自动刷新是关闭的,需要在构造函数中指定打开自动刷新;//??而PrintStream的自动刷新是开启的。


以下是程序的代码:
import java.io.*;
import java.net.*;


public class TCPServer {
 
 public static void main(String[] args) throws Exception {
  ServerSocket ss = new ServerSocket(10000);
  
  Socket s = ss.accept();
  
  InputStream is = s.getInputStream();
  OutputStream os = s.getOutputStream();
  
  // PrintWriter pw = new PrintWriter(os/*,true*/);
  // pw.println("Welcome , haha!");
  
  PrintStream ps = new PrintStream(os);
  ps.println("Welcome , haha!");
  
  //os.write("Welcome , haha!".getBytes());
 
  /*这种写法的话,这个程序接收一个字符后就与客户机断开了。
  byte[] buf = new byte[1024];
  int length = is.read(buf);
  */
  
  //用BufferedReader包装
  BufferedReader br = new BufferedReader(new   InputStreamReader(is));
   
  
  //System.out.println(new String(buf, 0, length));
  //System.out.println(new String(buf, 0, length));
  System.out.println(br.readLine());
  //pw.println("you said: " + br.readLine());
  ps.println("you said: " + br.readLine());
 
  br.close();//br也要关闭(是不是br关闭了,is就不用close了???)
  is.close();//注意下面的关闭顺序
  os.close();
  s.close();
  ss.close();
 } 
}

 

 

 

  Lesson09B-2
编写完善的TCP服务器模型,能接受多个客户端连接,并且能独立互不干扰地完成相应要点:
1.TCP服务器程序要想能接收多个客户端连接,需要循环调用ServerSocket.accept方法。
2.服务器程序与每个客户端连接的会话过程不能相互影响,需要在独立的线程中运行(多线程)
3.一个线程服务对象与一个服务器端Socket对象相关联,共同完成与一个客户端的对话。


程序如下:
import java.net.*;
import java.io.*;
public class TCPServerThread implements Runnable {
 private Socket s = null;  
 
 public TCPServerThread(Socket s) throws Exception{
  this.s = s;
 }
 
 public void run(){
  try{
   InputStream is = s.getInputStream();
   OutputStream os = s.getOutputStream();   
    
   BufferedReader br = new BufferedReader(new InputStreamReader(is));
   PrintWriter pw = new PrintWriter(os, true);
   pw.println("Welcome to ReverseServer");
   
   while(true){ //几乎所有TCP服务器程序模型都是如这个程序,区别就在于这个循环体里的内容
    String clientStr = br.readLine();
    boolean exit = false;
    
    try{
     exit = clientStr.equalsIgnoreCase("exit");
     }catch(NullPointerException exc){
      ConverseServer.lessConnector(); //非exit退出,也需要减少连接数      
     }
    
    if(exit){
     break;
    }
    StringBuffer sb = new StringBuffer(clientStr);
    StringBuffer ServerStrBuf = sb.reverse(); //TIPS:字符串的反转可以用StringBuffer的reverse方法实现。
    pw.println(clientStr + " -> " + ServerStrBuf.toString()); 

    br.close(); //问题:br.close之后是不是is就不需要close了?
    pw.close(); //pw.close后os就不需要close了?
    s.close();    
    ConverseServer.lessConnector();
 
   }
  }catch(Exception e){
   e.printStackTrace();
  }
  
 }
 
}


import java.net.*;
import java.io.InputStream;
import java.io.OutputStream;

public class ConverseServer {
 private static int connectNum = 0;
 
 public static void addConnector(){
  connectNum++;
  System.out.println("one logon! " + ConverseServer.getConnectNum() + " connected totally");
  
 }
 
 public static void lessConnector(){
  connectNum--;
  System.out.println("one exited! " + ConverseServer.getConnectNum() + " connected totally");
 }
 
 public static int getConnectNum(){
  return connectNum;
 }
 
 public static void main(String[] args) throws Exception {
  ServerSocket ss = new ServerSocket(10000);
  
  
  boolean run = true;
  while(run){   
   Socket s = ss.accept();
   ConverseServer.addConnector();   
   new Thread(new TCPServerThread(s)).start(); 
  }
  
  ss.close();
  
 } 
 
}

 

 

 

 

 

检测解决端口冲突问题
netstat查看当前已被占用的端口

解决方法:通过一个配置参数指定TCP服务程序的端口号(命令提示行中的参数或保存一个配置文件)

 


TCP客户端程序的编写
1、创建Socket
2、getInputStream和getOutputStream
3、定义BufferedReader和PrintWriter;另外,与服务器端程序不同的是,通常需要定义键盘输入流(的包装)(BufferedReader keyBr = new BufferedReader(new InputStreamReader(System.in));)。之后用keyBr获取客户端的输入,然后用PrintWriter对象的println方法将输入内容发送到服务器。

以下是(上一个服务器端程序配套的)客户端程序实例:
import java.net.*;
import java.io.*;
public class TCPClient {
 public static void main(String[] args) throws Exception{
   
  Socket s = new Socket("127.0.0.1", 10000);
  InputStream is = s.getInputStream();
  OutputStream os = s.getOutputStream();
  
  BufferedReader br_keyboard = new BufferedReader(new InputStreamReader(System.in)); //读取客户端本地的键盘输入
  BufferedReader br_server = new BufferedReader(new InputStreamReader(is)); //读取来自服务器的信息
  
  PrintWriter pw = new PrintWriter(os, true); //用于向服务器发送信息
  
  while(true){
   System.out.println(br_server.readLine());
   String input = br_keyboard.readLine();
   pw.println(input);
   if(input.equalsIgnoreCase("exit")){
    break;
   }     
  }

  //close操作在while循环之后
  br_server.close();
  br_keyboard.close();
  pw.close();
  s.close();  
 }
}

 

在TCP网络连接上传递对象
ObjectInputStream(像BufferedReader一样包装InputStream对象即可,public ObjectInputStream(InputStream))和ObjectOutputStream(像PrintWriter一样包装OutputStream对象即可,public ObjectOutputStream(OutputStream))
!!!要传递的类必须实现Serializable接口

以下是简单的实例程序
import java.io.*;
public class Student implements Serializable{ //必须实现Serializable接口,否则会报异常
 public int stu_id;
 public String stu_name;
 
 public Student(int id, String name){
  stu_id = id;
  stu_name = name;
 }
}

import java.net.*;
import java.io.*;
public class TCPObjectClient { 
 public static void main(String[] args) throws Exception{
  Socket s = new Socket("127.0.0.1", 10000);
  
  InputStream is = s.getInputStream();  
  ObjectInputStream ois = new ObjectInputStream(is); //这是传输Object与传输String的区别之处
  
  Student stu = (Student)ois.readObject();  
  System.out.println(stu.stu_id);
  System.out.println(stu.stu_name);
 } 
}

import java.net.*;
import java.io.*;
public class TCPObject {
 
 
 public static void main(String[] args) throws Exception{
  ServerSocket ss = new ServerSocket(10000);
  
  Socket s = ss.accept();
  
  OutputStream os = s.getOutputStream();
  ObjectOutputStream oos = new ObjectOutputStream(os); ////这是传输Object与传输String的区别之处
  
  Student stu = new Student(1, "stuName1");
  oos.writeObject(stu);
  
  oos.close();
  s.close();
  ss.close(); 
  
 } 
}


 

 

 

Lession9c-1

访问Internet网络资源
java.net包中的URL,URLDecoder,URLEncoder,URLConnection,HttpURLConnection等类

URL:Uniform Resource Locator
组成:协议、主机名、段口号、资源名。(资源名大小写敏感)

URL编码规则:
空格转为+;
数字字母不变
其他字符用其在当前字符集中的十六进制表示,并在每个字节前加%。空格也可用%20表示。

URLEncoder和URLDecoder用来实现编码和解码


HTTP协议会话过程
HTTP1.0:
步骤:1.客户机发出请求建立连接2.客户机发出请求信息3.服务器发出响应信息4.服务器关闭连接
浏览器主动请求,服务器被动接受。每一次信息请求都新建一个连接,而不是多个请求共享一个连接

HTTP1.1
每一个网页中多个信息请求(如同一页面中多个图片的信息请求)和应答可以在同一个连接中完成

HTTP消息:消息头可实现有条件的信息传送
HTTP请求消息
包括:一个请求行(GET /文件名 HTTP/1.1),若干消息头(Host,Connection,Accept-Language等)以及实体内容
HTTP响应消息
包括:一个状态行,若干消息头(Server,Date,Content-Length等)以及实体内容

几个重要的HTTP消息头
Connection:请求消息头,指定处理完本次请求/响应后,客户机与服务器是否继续保持连接。设置值为Keep-Alive(默认)和close
Accept-Lanaguage:请求消息头,指定客户机期望服务器返回的文档的语言
Content-Length:响应消息头,实体内容的长度(字节数)
Range:请求消息头, 用于指定服务器只需返回文档中的部分内容及内容范围(对断电续传很有用)
 格式:1)Range:bytes=100-599
  2)Range:bytes=100-(要求服务器返回第100字节后的内容)
  3)Range:bytes=-100(返回最后100字节)
Content-Range:响应消息头,指定服务器返回的部分实体内容的位置信息
 格式:Content-Range: bytes 2543-4532/7898(共7898字节,取2543-4532字节)

 

 

 

Lession9c-2

URL类
构造函数(可引发MalformedURLException异常)
public URL(String spec)
public URL(String protocol, String host, int port, String file)
public URL(String protocol, String host, int port, String file, URLStreamHandler handler(协议处理器))
public URL(URL context, String spec)

getProtocol, getHost, getPort, getFile方法
URL类中的openConnection方法调用协议处理器(URLStreamHandler)的openConnection方法返回URLConnection,然后调用URLConnection方法可以获得网页资源中的具体内容


工厂设计模式
URL类的setURLStreamHandlerFactory(URLStreamHandlerFactory fac)静态方法,其中的参数URLStreamHandlerFatory指定协议名和协议处理器的对应关系

URLStreamHandlerFactory类的createURLStreamHandler(String protocol)方法用来建立与协议名protocol相应的协议处理器(URLStreamHandler)
其实JVM内部也维护着一个协议名和协议处理器的对照表,当程序没有用setURLStreamHandlerFactory设置URLStreamHandlerFactory,或者createURLStreamHandler(String protocol)返回null时,JVM将会用它的内部的对照表设置prococol对应的协议处理器。但当JVM内部也没有这样的protocol名时,则会报MalformedURLException异常。
当要用一个或多个自定义的协议处理器时,才需要编写实现了URLStreamHandlerFactory接口的类,并实现createURLStreamHandlerFactory方法

JVM最多只能调用一次setURLStreamHandlerFactory方法,并且应在创建任何URL实例对象前被调用

工厂类
设某类(抽象类或者接口)有多个子类(或实现),则可建立一个工厂类,通过这个工厂类的某个函数(的参数)确定生产哪个子类(或实现)

URLConnetion和HTTPURLConnection类
获取信息内容
URLConnection连接过程:在实例对象建立后,网络连接也未必建立
 因此在连接建立前,可以用它的setRequestProperty方法设置消息头。

在连接建立后,就可以用各种方法获取服务器返回的各种信息,如getHeaderFields等。连接建立后,setRequestProperty方法将被忽略。

getInputStream和getOutputStream方法
getHeaderFilder, getContentLength, getContentEncoding, getContentType等方法
一个HttpURLConnection只能用于一次会话请求(一次请求,一次回应),但一个HTTP连接可以被多个HttpURLConnection实例对象共享,调用disconnect方法可以彻底关闭底层共享网络。

 

 

 

 

  Lession9c-3

编程:用URLConnection和HttpURLConnection类访问WEB站点
要点:
1、建立URL对象
2、建立HttpURLConnection对象(URL.openConnection()方法)
3、用setRequestProperty设置HttpURLConnection对象的部分参数
4、调用HttpURLConnection对象的connect方法(如果后面有get方法,这步可以省略)
5、调用各种get方法

一个简单的程序示例,获取google首页的源代码:
import java.net.*;
import java.io.*;
public class URLConnection {
 

 public static void main(String[] args) throws Exception{
  URL u = new URL("http://www.google.com");
  
  HttpURLConnection huc = (HttpURLConnection)u.openConnection();
  
  huc.setRequestProperty("Accept-Language","zh-cn");
  
  InputStream is = huc.getInputStream();
  BufferedReader br = new BufferedReader(new InputStreamReader(is));
  
  System.out.println("content of google");
  String content = null;
  while((content = br.readLine()) != null){
   System.out.println(content);
  }
  
 } 
}

 

 

 

 

 

你可能感兴趣的:(java,编程,socket,网络协议,OS)