Java语言基础-网络编程

网络模型

OSI(Open System Interconnection 开放系统互连)参考模型
——(底层到上层)物理层,数据链路层,网络层,传输层,会话层,表示层,应用层
TCP(传输控制协议):传输效率低,可靠性强。用于传输的可靠性要求高,数据量大的数据。
UDP(用户数据报协议):与TCP特性相反,用于传输可靠性要求不高,数据量小的数据。
TCP/IP参考模型
    ——主机至网络,网络层,传输层,应用层

网络通讯要素
IP地址(InteAddress)
    网络中设备的标识
不易记忆,可用主机名
本机回环地址:127.0.0.1 主机名:localhost
端口号
    用于标识进程的逻辑地址,不同进程的标识
    有效端口:0~65535,其中0~1024系统是使用或保留端口
传输协议
    通讯的协议
    常见协议:TCP、UDP
UDP
    将数据及源和目的封装到数据包中,不需要建立连接;
    每个数据包的大小限制在64K以内;
    因无连接,是不可靠协议;
    不需要建立连接,速度快。
TCP
    建立连接,形成传输数据的通道;
    在连接中进行大量数据传输;
    通过三次握手完成连接,是可靠协议;
    必须建立连接,效率稍低。

public class InetAddress
extends Object
implements Serializable
此类表示互联网协议 (IP) 地址。

static InetAddress getLocalHost() ;//返回本地主机。

InetAddress方法示例:

package cn.itcast.net.p1.ip;



import java.net.InetAddress;

import java.net.UnknownHostException;



public class IPDemo {



    /**

     * @param args

     * @throws UnknownHostException

     */

    public static void main(String[] args) throws UnknownHostException {



        // 获取本地主机IP地址对象

        InetAddress ip = InetAddress.getLocalHost();



        // 获取其他主机的IP地址对象

        // ip=InetAddress.getByName("192.168.0.101");

        ip = InetAddress.getByName("www.baidu.com");



        System.out.println(ip.getHostAddress());

        System.out.println(ip.getHostName());

    }



}




Socket
Socket就是为网络服务提供的一种机制;
通信的两端都有Socket;
网络通信其实就是Socket间的通信;
数据在两个Socket间通过IO传输。

UDP传输
DatagramSocket和DatagramPacket

UDP传输示例:

package cn.itcast.net.p2.udp;



import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;



public class UDPSendDemo {



    /**

     * @param args

     * @throws IOException 

     */

    public static void main(String[] args) throws IOException {



        System.out.println("发送端启动......");

        /*

         * 创建UDP传输的发送端

         * 思路:

         * 1.建立UDP的Socket服务;

         * 2.将要发送的数据封装到数据包中;

         * 3.通过UDP的Socket服务,将数据包发送出去;

         * 4.关闭Socket服务。

         */

        // 1.UDP的Scoket服务,使用DatagramSocket对象.

        DatagramSocket ds = new DatagramSocket(8888);



        // 2.将要发送的数据封装到数据包中。

        String str = "UDP传输演示...";

        // 使用DatagramPacket将对象封装到包中

        byte[] buf = str.getBytes();



        DatagramPacket dp = new DatagramPacket(buf, buf.length,

                InetAddress.getByName("192.168.0.101"), 10000);

        

        //3.通过UDP的Socket服务将数据包发送出去。使用send方法

        ds.send(dp);

        

        //4.关闭资源

        ds.close();

        

    }



}



package cn.itcast.net.p2.udp;



import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;



public class UDPReceiveDemo {



    /**

     * @param args

     * @throws IOException 

     */

    public static void main(String[] args) throws IOException {

        

        System.out.println("接收端启动......");

        /*

         * 建立UDP接收端的思路:

         * 1.建立UDP的Socket服务;因为要接收数据,必须明确端口号;

         * 2.创建数据包,用于存储接收到的数据,方便用数据包对象的方法解析数据;

         * 3.使用Socket服务的receive方法将接收到的方法存储到数据包中;

         * 4.通过数据包的方法解析数据包中的数据;

         * 5.关闭资源。

         */

        //1.建立UDP的Socket服务

        DatagramSocket ds=new DatagramSocket(10000);

        

        //2.创建数据包

        byte[] buf=new byte[1024];

        DatagramPacket dp=new DatagramPacket(buf, buf.length);

        

        //3.使用接收方法见数据存储到数据包中

        ds.receive(dp);//阻塞式

        

        //4.通过数据包对象的方法,解析其中的数据:地址、端口、数据内容等;

        String ip=dp.getAddress().getHostAddress();

        int port=dp.getPort();

        String text=new String (dp.getData(),0,dp.getLength());

        

        System.out.println(ip+":"+port+":"+text);

        ds.close();

    }



}




基于多线程的聊天程序:

package cn.itcast.net.p3.chat;



import java.io.IOException;

import java.net.DatagramSocket;



public class ChatDemo {



    /**

     * @param args

     * @throws IOException 

     */

    public static void main(String[] args) throws IOException {

        

        DatagramSocket send=new DatagramSocket();

        

        DatagramSocket rece=new DatagramSocket(10001);

        new Thread(new Send(send)).start();

        new Thread(new Rece(rece)).start();

    }



}



package cn.itcast.net.p3.chat;



import java.io.BufferedReader;

import java.io.InputStreamReader;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;



/**

 * 发送端

 * @author chenchong

 *

 */

public class Send implements Runnable {



    private DatagramSocket ds;



    public Send(DatagramSocket ds) {

        this.ds = ds;

    }



    @Override

    public void run() {

        

        try {

            BufferedReader bufr = new BufferedReader(new InputStreamReader(

                    System.in));

            String line = null;

            

            while ((line = bufr.readLine()) != null) {

                byte[] buf = line.getBytes();



                DatagramPacket dp = new DatagramPacket(buf, buf.length,

                        InetAddress.getByName("192.168.0.255"), 10001);//IP地址末尾为255,则发送广播

                ds.send(dp);

                if ("886".equals(line))

                    break;

                }

                ds.close();

        } catch (Exception e) {

        }

    }



}



package cn.itcast.net.p3.chat;



import java.net.DatagramPacket;

import java.net.DatagramSocket;





/**

 * 接收端

 * @author chenchong

 *

 */

public class Rece implements Runnable {



    private DatagramSocket ds;



    public Rece(DatagramSocket ds) {

        this.ds = ds;

    }



    @Override

    public void run() {

        try {

            while (true) {



                byte[] buf = new byte[1024];

                DatagramPacket dp = new DatagramPacket(buf, buf.length);



                ds.receive(dp);// 阻塞式



                String ip = dp.getAddress().getHostAddress();

                int port = dp.getPort();

                String text = new String(dp.getData(), 0, dp.getLength());



                System.out.println(ip + ":" + port + ":" + text);



                if (text.equals("886")) {

                    System.out.println(ip + ".....退出聊天室");

                }

                // ds.close();//此处如果关闭资源,则只接收一次

            }

        } catch (Exception e) {

        }

        ds.close();

    }



}



TCP传输
建立客户端和服务器端;
建立连接后,通过Socket中的IO流进行数据的传输;
关闭Socket。
客户端与服务器端是两个独立的应用程序。

public class Socket extends Object
此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
public class ServerSocket extends Object
此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果

TCP示例:

package cn.itcast.net.p4.tcp;



import java.io.IOException;

import java.io.InputStream;

import java.net.ServerSocket;

import java.net.Socket;



public class ServerDemo {



    /**

     * @param args

     * @throws IOException 

     */

    public static void main(String[] args) throws IOException {

        

        /*

         *服务端接收客户端发送过来的数据,并打印在控制台上 

         * 

         * 建立TCP服务端:

         * 1.创建服务端Socket服务,通过ServerSocket对象;

         * 2.服务端必须对外提供一个端口,否则客户端无法连接;

         * 3.获取连接过来的客户端对象;

         * 4.通过客户端对象获取Socket流读取客户端发送的数据,

         *     并打印在控制台上;

         * 5.关闭资源:关客户端;关服务端。

         */

        

        //1.创建服务端对象

        ServerSocket ss=new ServerSocket(10002);

        

        //2.获取连接过来的服务端对象

        Socket s=ss.accept();

        

        //3.通过Socket对象获取输入流,读取客户端发来的数据

        InputStream in=s.getInputStream();

        

        String ip=s.getInetAddress().getHostAddress();

        

        //4.获取客户端发来的数据

        byte[] buf=new byte[1024];

        

        int len=in.read(buf);

        String text=new String(buf,0,len);

        System.out.println(ip+":"+text);

        

        //5.关闭资源

        s.close();

        ss.close();

        

        

    }



}



package cn.itcast.net.p4.tcp;



import java.io.IOException;

import java.io.OutputStream;

import java.net.Socket;

import java.net.UnknownHostException;



public class ClientDemo {



    /**

     * @param args

     * @throws IOException

     * @throws UnknownHostException

     */

    public static void main(String[] args) throws UnknownHostException,

            IOException {

        /*

         * 需求:客户端发送数据到服务端

         * 

         * TCP传输,客户端的建立

         * 1.创建TCP客户端Socket服务,使用的是Socket对象,

         * 建议该对象一创建就明确目的地(要连接的主机);

         * 2.如果连接建立成功,说明数据传输通道已建立;

         * 通道就是Socket流,是底层建立的,这里既有输入又有输出。

         * 想要输入或者输出流对象,可以找Socket流获取;

         * 可以通过getOutputStream()和getInputStream()来获取两个字节流;

         * 3.使用输出流,将数据写出;

         * 4.关闭资源。

         */



        // 创建客户端Socket服务

        Socket socket = new Socket("192.168.0.101", 10002);



        // 获取Socket流中的输出流

        OutputStream out = socket.getOutputStream();



        // 使用输出流将指定的数据写出去

        out.write("TCP演示:这里是客户端......".getBytes());



        // 关闭资源(将连接断开)

        socket.close();

    }



}



服务端、客户端交互

package cn.itcast.net.p4.tcp;



import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.ServerSocket;

import java.net.Socket;



public class ServerDemo2 {



    /**

     * @param args

     * @throws IOException 

     */

    public static void main(String[] args) throws IOException {

        ServerSocket ss=new ServerSocket(10002);

        

        Socket s=ss.accept();//阻塞式

        

        InputStream in=s.getInputStream();

        

        String ip=s.getInetAddress().getHostAddress();

        

        byte[] buf=new byte[1024];

        

        int len=in.read(buf);

        String text=new String(buf,0,len);

        System.out.println(ip+":"+text);

        

        //使用客户端Socke对象的输出流给客户端返回数据

        OutputStream out=s.getOutputStream();

        out.write("server收到".getBytes());

        

        //5.关闭资源

        s.close();

        ss.close();

        

        

    }



}



package cn.itcast.net.p4.tcp;



import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.Socket;

import java.net.UnknownHostException;



public class ClientDemo2 {



    /**

     * @param args

     * @throws IOException

     * @throws UnknownHostException

     */

    public static void main(String[] args) throws UnknownHostException,

            IOException {



        Socket socket = new Socket("192.168.0.101", 10002);



        OutputStream out = socket.getOutputStream();



        out.write("TCP演示:这里是客户端......".getBytes());



        // 读取服务端返回的数据使用Socket读取流

        InputStream in = socket.getInputStream();



        byte[] buf = new byte[1024];

        int len = in.read(buf);

        String text = new String(buf, 0, len);

        System.out.println(text);



        // 关闭资源(将连接断开)

        socket.close();

    }



}




文本转换客户端

package cn.itcast.net.p5.tcptest;



import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;



public class TransServer {



    /**

     * @param args

     * @throws IOException

     */

    public static void main(String[] args) throws IOException {



        /*

         * 转换服务端:

         * 分析:

         * 1.ServerSocket服务;

         * 2.获取Socket对象;

         * 3.源:Socket,读取客户端发过来需要转换的数据;

         * 4.目的:显示在服务端控制台上;

         * 5.将数据转成大写发回客户端;

         * 6.关闭资源

         */



        // 1.ServerSocket

        ServerSocket ss = new ServerSocket(10004);



        // 2.获取Socket对象

        Socket s = ss.accept();



        // 获取ip

        String ip = s.getInetAddress().getHostAddress();

        System.out.println(ip + ".....connected.");



        // 3.获取Socket读取流,装饰

        BufferedReader bufIn = new BufferedReader(new InputStreamReader(

                s.getInputStream()));



        // 4.获取Socket输出流并装饰

        PrintWriter out = new PrintWriter(s.getOutputStream(), true);



        String line = null;

        while ((line = bufIn.readLine()) != null) {// 读取到数据,如果没有读到换行标记,则认为没有读取完毕

            System.out.println(line);

            out.println(line.toUpperCase());

            

            // out.print(line.toUpperCase());//没有换行标记

            // out.flush();//在不设置自动刷新时,需要调用flush()方法

        }

        s.close();

        ss.close();

    }



}



package cn.itcast.net.p5.tcptest;



import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.Socket;



public class TransClient {



    /**

     * @param args

     * @throws IOException

     */

    public static void main(String[] args) throws IOException {



        /*

         * 思路:

         * 客户端:

         * 1.需要Socket端点;

         * 2.客户端的数据源:键盘;

         * 3.客户端的目的:Socket;

         * 4.接收服务端的数据,源:socket;

         * 5.将数据打印出来,目的:控制台;

         * 6.在这些流中操作的数据,都是文本数据。

         * 

         * 转换客户端:

         * 1.创建Socket客户端对象;

         * 2.获取键盘录入;

         * 3.将录入的信息发送给Socket输出流;

         */



        // 1.创建Socket对象

        Socket socket = new Socket("192.168.0.101", 10004);



        // 2.获取键盘录入

        BufferedReader bufr = new BufferedReader(new InputStreamReader(

                System.in));



        // 3.目的是Socket输出流

        // new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);// 自动刷新



        // 4.Socket输入流,读取服务端返回的大写数据

        BufferedReader bufIn = new BufferedReader(new InputStreamReader(

                socket.getInputStream()));



        String line = null;

        while ((line = bufr.readLine()) != null) {// 读取到数据,如果没有读到换行标记,则认为没有读取完毕

            if ("over".equals(line))

                break;

            out.println(line);// 写入到PrintWriter中,需要刷新动作,才输出到Socket流中



            // out.print(line);//没有换行标记

            // out.flush();//在不设置自动刷新时,需要调用flush()方法



            // 读取服务端发回的一行大写数据

            String upperStr = bufIn.readLine();

            System.out.println(upperStr);

        }

        socket.close();// 导致服务端结束 

    }



}



 

如果不刷新,则数据暂存在PrintWriter中,不输出到Socket输出流,需要使用flush()方法;
如果readLine()没有读到换行标记,则任务数据没有读取完毕,继续等待;


上传文本文件

package cn.itcast.net.p6.uploadtext;



import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.FileWriter;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;



public class UploadServer {



    /**

     * @param args

     * @throws IOException 

     */

    public static void main(String[] args) throws IOException {

        

        //1.创建ServerSocket服务端

        ServerSocket ss=new ServerSocket(10005);

        

        //2.获取Socket对象

        Socket s=ss.accept();

        System.out.println(s.getInetAddress().getHostAddress()+"......connected.");

        

        //3.获取客户端读取流

        BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

        

        //4.创建目的文件对象

        BufferedWriter bufw=new BufferedWriter(new FileWriter("c:\\server.txt"));

        

        String line=null;

        while((line=bufIn.readLine())!=null){

            if("over".equals(line))

                break;

            bufw.write(line);

            bufw.newLine();

            bufw.flush();

        }

        

        PrintWriter out=new PrintWriter(s.getOutputStream(),true);

        out.println("上传成功");



        bufw.close();

        s.close();

        ss.close();

        

    }



}



package cn.itcast.net.p6.uploadtext;



import java.io.BufferedReader;

import java.io.FileReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.Socket;

import java.net.UnknownHostException;



public class UploadClient {



    /**

     * @param args

     * @throws IOException

     * @throws UnknownHostException

     */

    public static void main(String[] args) throws UnknownHostException,

            IOException {



        // 1.创建Socket对象

        Socket s = new Socket("192.168.0.101", 10005);



        // 2.创建源文件对象,源是文本文件

        BufferedReader bufr = new BufferedReader(new FileReader(

                "c:\\client.txt"));

        



        // 3.目的是Socket输出流

        PrintWriter out = new PrintWriter(s.getOutputStream(), true);

        String line = null;

        while ((line = bufr.readLine()) != null) {

            out.println(line);

            out.flush();

        }

        // out.println("over");

        // 告诉服务端,客户端数据已经写完了

        s.shutdownOutput();



        // 4.读取Socket流,

        BufferedReader bufIn = new BufferedReader(new InputStreamReader(

                s.getInputStream()));



        String str = bufIn.readLine();

        System.out.println(str);



        bufr.close();

        s.close();



    }



}



服务端多线程示例:

package cn.itcast.net.p1.uploadpic;



import java.io.IOException;

import java.net.ServerSocket;

import java.net.Socket;



public class UploadPicServer {



    /**

     * @param args

     * @throws IOException

     */

    public static void main(String[] args) throws IOException {



        // 1.创建TCP的Socket服务端

        ServerSocket ss = new ServerSocket(10006);



        while (true) {

            // 2.获取客户端

            Socket s = ss.accept();



            new Thread(new UploadTask(s)).start();

        }



        // ss.close();



    }



}



package cn.itcast.net.p1.uploadpic;



import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.Socket;



public class UploadTask implements Runnable {



    private Socket s;



    public UploadTask(Socket s) {

        this.s = s;

    }



    @Override

    public void run() {



        int count=0;

        String ip = s.getInetAddress().getHostAddress();

        System.out.println(ip + "......connected.");



        try {

            // 3.读取客户端发来的数据

            InputStream in = s.getInputStream();



            // 4.将读取到的数据存储到一个文件中

            File dir = new File("c:\\pic");

            if (!(dir.exists())) {

                dir.mkdirs();

            }

            File file = new File(dir, ip + ".jpg");

            

            //如果文件已经存在于服务器端

            while(file.exists()){

                file =new File(dir,ip+"("+(++count)+").jpg");

            }

            

            FileOutputStream fos = new FileOutputStream(file);



            byte[] buf = new byte[1024];

            int len = 0;

            while ((len = in.read(buf)) != -1) {

                fos.write(buf, 0, len);

            }



            // 给客户端发回:上传成功

            OutputStream out = s.getOutputStream();

            out.write("上传成功".getBytes());



            fos.close();

            s.close();

        } catch (IOException e) {

        }

    }



}



package cn.itcast.net.p1.uploadpic;



import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.Socket;

import java.net.UnknownHostException;



public class UploadPicClient {



    /**

     * @param args

     * @throws IOException

     * @throws UnknownHostException

     */

    public static void main(String[] args) throws UnknownHostException,

            IOException {



        // 1.创建客户端

        Socket s = new Socket("192.168.0.101", 10006);



        // 2.读取客户端要上传的图片文件

        FileInputStream fis = new FileInputStream("c:\\0.jpg");



        // 3.获取Socket输出流,将读到的图片数据发送给服务端

        OutputStream out = s.getOutputStream();



        byte[] buf = new byte[1024];



        int len = 0;

        while ((len = fis.read(buf)) != -1) {

            out.write(buf, 0, len);

        }

        // 告诉服务端,数据发送完毕

        s.shutdownOutput();



        // 读取服务端发回的内容

        InputStream in = s.getInputStream();

        byte[] bufIn = new byte[1024];

        int lenIn = in.read(bufIn);

        String text = new String(bufIn, 0, lenIn);

        System.out.println(text);



        fis.close();

        s.close();

    }



}




常见客户端和服务器
最常见的客户端:
    浏览器:IE
最常见的服务端:
    服务器:Tomcat

客户端和服务器端原理
1.自定义服务端,使用已有的客户端IE,了解客户端给服务器端发送了什么请求。

GET / HTTP/1.1            请求行:请求方式        /myweb/index.html 请求的资源路径 http协议版本
请求消息头,属性名:属性值
Accept: text/html, application/xhtml+xml, */*       
Accept-Language: zh-CN
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: 192.168.0.101:9090
Connection: Keep-Alive
//请求头和请求体之间有一个空行,此行为空行
//请求体

模拟浏览器
//服务器发回的应答消息
HTTP/1.1 200 OK    //应答行    http的协议版本,应答状态码,应答状态描述信息

//应答消息头。属性名:属性值
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"1807-1345210068813"
Last-Modified: Fri, 17 Aug 2012 13:27:48 GMT
Content-Type: text/html
Content-Length: 1807
Date: Fri, 17 Aug 2012 13:28:20 GMT
Connection: close
//空行
//应答体
<font color='red' size='7'>欢迎光临</font>


public final class URL extends Objectimplements Serializable
类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。

InputStream openStream()
打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。
打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。此方法是下面方法的缩写:
     openConnection().getInputStream()

package cn.itcast.net.p2.ie_server;



import java.io.IOException;

import java.io.InputStream;

import java.net.MalformedURLException;

import java.net.URL;

import java.net.URLConnection;



public class URLDemo {



    /**

     * @param args

     * @throws IOException

     */

    public static void main(String[] args) throws IOException {



        String str_url = "http://192.168.0.101:8080/myweb/2.html?name=lisi";



        URL url = new URL(str_url);



        // System.out.println("getProtocol:"+url.getProtocol());

        // System.out.println("getHost:"+url.getHost());

        // System.out.println("getPath:"+url.getPath());

        // System.out.println("getFile:"+url.getFile());

        // System.out.println("getPath:"+url.getPath());

        // System.out.println("getQuery:"+url.getQuery());



        // 获取url对象的Url连接器对象,将连接封装成了对象:java中内置的可以解析具体协议的对象+socket

        URLConnection conn = url.openConnection();

        

        String value=conn.getHeaderField("Content-Type");//text/html



        System.out.println(value);

        

//        System.out.println(conn);

        // 输出:sun.net.www.protocol.http.HttpURLConnection:http://192.168.0.101:8080/myweb/2.html?name=lisi



        

        // InputStream in=url.openStream();//打开到此 URL 的连接并返回一个用于从该连接读入的InputStream

        // 打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。此方法是下面方法的缩写:openConnection().getInputStream()

        InputStream in=conn.getInputStream();

        

         byte[] buf=new byte[1024];

         int len=in.read(buf);

        

         String text=new String(buf,0,len);

        

         System.out.println(text);//只有应答体,没有消息头

        

         in.close();



    }



}




网络架构
1.C/S    Client/Server   
        缺点:
        该结构的软件,客户端和服务端都需要编写;
        开发成本较高,维护较为麻烦;
        优点:
        客户端在本地,可以分担一部分运算;

2.B/S    Browser/Server   
        缺点:
        所有运算都在服务器端完成;
        优点:
        该结构的软件,只开发服务器端,不开发客户端;因为客户端直接由浏览器取代;
        开发成本相对较低,维护更为简单;

你可能感兴趣的:(java语言)