第26 天网络编程

网络编程

  1. 回顾

Lock接口:

    它代替的同步,将过去的隐式锁的操作修改为由程序员手动获取和释放。

Condition接口:

    它代替的Object类中的等待和唤醒功能。

 

网络编程:

    1、OSI模型:它将网络分成7层

    2、网络三要素:

        IP:连接在网络中终端设备的唯一标识。

        端口:运行在设备中的软件的唯一标识。

        协议:通信双方需要遵守的通信规则。

            UDP:

            TCP:

    3、UDP编程:

        IP对象:InetAddress类。

        DatagramSocket:它是使用UDP协议通信的端点对象,可以接收或发送数据包

        DatagarmPacket:它是打包或者拆包对象。

  1. 网络编程

    1. TCP编程

      1. 回顾IO流

File:它是用来操作文件或文件夹,但不能读写文件中的数据。

字节流:

    字节输入流:

        InputStream:它中定义了读取字节的方法 read

            FileInputStream:它主要负责从文件中读取字节数据

        字节输入流模版代码:

            一次读取一个字节:

            一次读取多个字节:

    字节输出流:

        OutputStream:它中定义基本写字节的方法 write

            FileOutputStream:它主要负责是给文件中写数据

        

字符流:

    字符输入流:

        Reader:它主要负责读取字符数据。它的底层使用字节流+编码表

            InputStreamReader:字节转字符的输入转换流,它可以指定编码表

                FileReader:它是便捷的读取文件的字符输入流,使用的本地默认的编码表和字节缓冲区

            BufferedReader : 字符输入流的缓冲区,readLine可以读取一行数据

                

    字符输出流:

            Writer:它是主要负责写字符数据,底层字节流+编码表

                OutputStreamWriter:字符转字节的输出转换流,它可以指定编码表

                    FileWriter:它使用的本地默认的编码表写字符数据

                BufferedWriter:它字符输出流的缓冲区,newLine方法可以写换行

    

    编码表:

        ASCII:

        ISO-8859-1:

        GB2312:

        GBK:

        UNICODE:

        UTF-8:

    

    编码:

    解码:

    乱码:

  1. TCP编程模型介绍(理解)

  1. TCP客户端(理解)

 

使用下面的构造方法创建客户端,并且需要指定连接的服务器的ip和端口号。

    注意:当这个Socket对象创建出来之后,JVM的底层就会自动的帮我们完成和服务器的三次握手建立通道这个过程。

    

    

    在客户端对象中调用上面的getOutputstream方法,获取输出流,给服务器发送数据。

    

    

    在客户端对象中调用上面的getInputstream方法,获取输入流,读取服务器发给客户端的数据。

 

  1. TCP服务端(理解)

此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。

 

ServerSocket:它是基于TCP协议的服务器实现。它被动等待客户端的连接。

请求:request。客户端向服务器发送数据这个过程,被称为客户端请求服务器。

响应:response。服务器基于客户端的请求,给客户端发送的数据的过程,称为服务器在响应客户端。

 

使用上面的构造方法,可以创建一个基于TCP协议的服务器对象,创建的时候需要指定服务器的端口号。

 

    服务器中使用accept方法,获取连接到服务器的那个客户端对象,并在服务器内部得到这个客户端对象。

 

  1. TCP客户端实现(重点+编码)

/*

* 演示基于TCP协议的客户端实现

*/

public class TcpClientDemo {

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

        

        // 创建客户端对象

        Socket s = new Socket("192.168.49.74",9191);

        

        // 获取输出流对象,给服务器发送数据

        OutputStream out = s.getOutputStream();

        

        // 调用写的功能,发送数据

        out.write("TCP练习!!!".getBytes());

        

        // 关闭客户端

        s.close();

    }

}

 

上面的代码,运行可能发生下面的异常:

 

异常的原因:

    是客户端连接服务器的时候,而服务器不存在,或者是服务器根本就没有开启。

  1. TCP服务端实现(重点+编码)

 

/*

* 基于TCP协议的服务器实现

*/

public class TcpServerDemo {

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

        

        // 创建服务器对象

        ServerSocket ss = new ServerSocket(9191);

        

        // 在服务器调用accept方法获取连接到服务器的那个客户端对象

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

        

        /*

         * 在服务器中获取到连接到服务器的那个客户端对象中之后,

         * 这时就需要在服务器内部根据获取到的这个客户端对象开始获取流对象

         * 读取这个客户端发给服务器的数据,或者给当前这个客户端发送数据

         */

        InputStream in = s.getInputStream();

        // 使用字节输入流的模版代码读取数据

        byte[] buf = new byte[1024];

        int len = 0;

        // 这里先不使用循环读取,仅仅只读取一次数据

        len = in.read(buf);

        System.out.println( new String( buf , 0 , len));

        

        // 关闭客户端

        s.close();

        // 关闭服务器

        ss.close();

    }

}

  1. TCP练习

    1. 需求:

  1. 客户端实现

 

/*

* 客户端键盘录入数据,将数据发送给服务器,等待服务器返回的数据

*/

public class TcpClientTest {

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

        

        // 创建客户端对象

        Socket s = new Socket("192.168.49.74",9292);

        

        // 创建键盘录入的对象

        Scanner sc = new Scanner( System.in );

        

        System.out.println("请输入需要发表的说说:");

        String data = sc.nextLine();

        // 获取输出流

        OutputStream out = s.getOutputStream();

        out.write(data.getBytes());

        //////////////////上面的代码已经将数据发送给服务器,需要等待服务器发来的数据///////////////////////////

        // 获取输入流

        InputStream in = s.getInputStream();

        byte[] buf = new byte[1024];

        int len = in.read(buf);

        System.out.println(new String( buf , 0 , len ));

        

        // 关闭客户端

        s.close();

    }

}

  1. 服务端实现

/*

* 服务器获取客户端发来的数据,需要将数据保存到文件中,然后给客户端发送数据

*/

public class TcpServerTest {

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

        

        // 创建服务器对象

        ServerSocket ss = new ServerSocket(9292);

        

        // 获取连接到服务器的客户端对象

        Socket s = ss.accept();

        // 获取客户端的ip

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

        // 获取输入流,读取客户端发给服务器的数据

        InputStream in = s.getInputStream();

        // 需要将数据写到文件中,需要定义输出流,给文件中写数据

        FileOutputStream fos = new FileOutputStream( "e:/"+ip+".txt" );

        // 模版代码读写数据

        byte[] buf = new byte[1024];

        int len = 0;

        len = in.read(buf);

        // 将数据写到文件中

        fos.write(buf, 0, len);

        fos.close();

        ///////////////////服务器已经将数据保存到文件中,需要给客户端发送数据/////////////////////////

        // 获取输出流,给客户端发送数据

        OutputStream out = s.getOutputStream();

        out.write("说说发送成功!!!".getBytes());

        // 关闭客户端

        s.close();

        // 关闭服务器

        ss.close();

    }

}

 

 

 

  1. 服务端开启多线程

服务器它是不会关闭的,服务器一旦开启之后,它就会给任何连接到服务器上的客户端提供服务。

保证服务器可以为任何一个客户端提供服务:

    当有一个客户端连接到服务器之后,服务器就启动一个线程为这个客户端提供服务,然后服务器继续等待下一个客户端连接。

    

/*

* 服务器获取客户端发来的数据,需要将数据保存到文件中,然后给客户端发送数据

*

* 服务器开启多线程为每个连接到服务器的客户端提供服务

*/

 

class Task implements Runnable {

    // 定义成员变量,记录当前线程需要操作的任务中的客户端对象

    private Socket s;

    public Task( Socket s ){

        this.s = s;

    }

 

    public void run() {

        try{

            // 获取客户端的ip

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

            System.out.println(Thread.currentThread().getName()+"正在给"+ip+"服务");

            // 获取输入流,读取客户端发给服务器的数据

            InputStream in = s.getInputStream();

            // 需要将数据写到文件中,需要定义输出流,给文件中写数据

            FileOutputStream fos = new FileOutputStream("e:/" + ip + ".txt");

            // 模版代码读写数据

            byte[] buf = new byte[1024];

            int len = 0;

            len = in.read(buf);

            // 将数据写到文件中

            fos.write(buf, 0, len);

            fos.close();

            /////////////////// 服务器已经将数据保存到文件中,需要给客户端发送数据/////////////////////////

            // 获取输出流,给客户端发送数据

            OutputStream out = s.getOutputStream();

            out.write("说说发送成功!!!".getBytes());

            // 关闭客户端

            s.close();

        }catch(Exception e){

            e.printStackTrace();

        }

    }

}

 

public class TcpServerTest2 {

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

 

        // 创建服务器对象

        ServerSocket ss = new ServerSocket(9292);

 

        while( true ){

            // 获取连接到服务器的客户端对象

            Socket s = ss.accept(); // accept方法获取不到连接的客户端,程序就会停留在获取客户端地方

            // 创建线程的任务

            Task task = new Task( s );

            // 开启线程,开启的这个线程,就会为当前连接到的客户端提供服务

            Thread t = new Thread(task);

            t.start();

        }

        // 关闭服务器

        //ss.close();

    }

}

  1. 文件上传(重点+编码)

    1. 文件上传分析

文件上传:在实际应用中非常的多。

例如:QQ空间的照片上传、云盘文件传输、论坛上传资料等。

 

  1. 客户端实现

/*

* 文件上传客户端实现

*/

public class UploadClientTest {

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

        

        // 创建客户端对象

        Socket s = new Socket("192.168.49.74",9393);

        System.out.println("客户端准备开始读取图片数据.....");

        // 创建字节输入流对象,读取图片文件中的数据

        FileInputStream fis = new FileInputStream("e:/1.jpg");

        // 获取输出流,给服务器发数据

        OutputStream out = s.getOutputStream();

        // 模版代码读写数据

        byte[] buf = new byte[1024];

        int len = 0;

        /*

         * 客户端在使用FileInputStream读取图片数据

         * 在使用循环读取的时候( len = fis.read(buf) ) != -1

         * 判断循环的结束。

         * 而我们知道只要输入流读取文件末尾,就会返回-1,读取文件的循环

         * 就一定会结束。

         * 客户端在循环结束的时候,read读取到-1,-1赋值给len变量,

         * 循环就会停止运行,循环停止之后,客户端就不会再给服务器发送任何的数据

         * 在循环结束的时候,没有告诉服务器图片数据已经发送完成,

         * 那么服务器的循环就一直在等待客户端发送数据

         */

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

            out.write(buf, 0, len);

        }

        /*

         * 在客户端将图片有效数据发给服务器之后,由于不能发送结束信息

         * 因此在客户端循环读写图片数据结束之后,需要我们人为手动的

         * 通知服务器数据发送结束,需要使用Socket类中的

         * shutdownOutput方法

         */

        s.shutdownOutput(); // 告诉接收方已经发送结束,不要在等待接收数据

        /////////////////循环结束,图片数据已经发送给服务器//////////////////////

        System.out.println("客户端已经图片发送给服务器...");

        // 关流

        fis.close();

        

        // 读取服务器发给客户端的数据

        InputStream in = s.getInputStream();

        byte[] b = new byte[1024];

        /*

         * 客户端在使用in.read读取服务器发给客户端的数据,

         * 如果客户端读取不到任何的数据,这时客户端就停留在此。

         * 现在程序卡在读取数据服务器发来数据的地方,

         * 就说明服务器根本没有将数据发给客户端

         */

        int l = in.read(b);

        System.out.println( new String( b , 0 , l ));

        // 关闭客户端

        s.close();

    }

}

  1. 服务端实现

/*

* 演示 上传文件的服务器实现

*/

public class UploadServerTest {

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

        

        // 创建服务器对象

        ServerSocket ss = new ServerSocket( 9393 );

        

        // 获取客户端对象

        Socket s = ss.accept();

        // 获取ip

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

        System.out.println("服务器开始读取"+ip+"发来的图片数据....");

        // 获取输入流读取上传的文件数据

        InputStream in = s.getInputStream();

        // 创建字节输出流,给服务器本地磁盘中写文件

        FileOutputStream fos = new FileOutputStream("d:/upload/"+ip+".jpg");

        // 模版代码读写数据

        byte[] buf = new byte[1024];

        int len = 0;

        /*

         * 服务器在使用循环读取数据,发现服务器的这个循环根本就没有停止

         * ( len = in.read( buf ) ) != -1 它在读取数据的时候,只要读取到结尾

         * 就会返回-1,肯定可以结束,既然没有结束,就说明read没有读取到结尾

         */

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

            // 写数据

            fos.write(buf, 0, len);

        }

        System.out.println("服务器将图片保存完成........");

        // 关流

        fos.close();

        //////////////给客户端发送数据//////////////////

        s.getOutputStream().write("图片上传成功!!!".getBytes());

        

        // 关闭客户端

        s.close();

        // 关闭服务器

        ss.close();

    }

}

  1. 服务端开启线程和解决文件重名

服务器在接收到一个客户端之后,需要开启线程,处理客户端上传图片的所有任务。

 

文件重名解决:

  1. 在文件名的后面使用 (数字) 的方式解决,这种情况经常用在下载文件中。

2、在后期开发中,一般会使用Java中提供的唯一标识符的类充当上传的文件名。

    

/*

* 演示 上传文件的服务器实现

* 开启多线程,为多个客户端服务

*/

class Task implements Runnable{

    

    private Socket s ;

    public Task( Socket s ){

        this.s = s;

    }

    public void run(){

        try{

            // 获取ip

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

            System.out.println("服务器开始读取"+ip+"发来的图片数据....");

            // 获取输入流读取上传的文件数据

            InputStream in = s.getInputStream();

            

            // 使用UUID获取唯一的文件名

            String filename = UUID.randomUUID().toString().replaceAll("-", "");

            

            // 创建字节输出流,给服务器本地磁盘中写文件

            FileOutputStream fos = new FileOutputStream("d:/upload/"+filename+".jpg");

            // 模版代码读写数据

            byte[] buf = new byte[1024];

            int len = 0;

            /*

             * 服务器在使用循环读取数据,发现服务器的这个循环根本就没有停止

             * ( len = in.read( buf ) ) != -1 它在读取数据的时候,只要读取到结尾

             * 就会返回-1,肯定可以结束,既然没有结束,就说明read没有读取到结尾

             */

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

                // 写数据

                fos.write(buf, 0, len);

            }

            System.out.println("服务器将图片保存完成........");

            // 关流

            fos.close();

            //////////////给客户端发送数据//////////////////

            s.getOutputStream().write("图片上传成功!!!".getBytes());

            

            // 关闭客户端

            s.close();

        }catch(Exception e){

            e.printStackTrace();

        }

    }

}

public class UploadServerTest2 {

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

        

        // 创建服务器对象

        ServerSocket ss = new ServerSocket( 9393 );

        while(true){

            // 获取客户端对象

            Socket s = ss.accept();

            // 创建线程的任务

            Task task = new Task(s);

            // 创建线程

            Thread t = new Thread( task );

            // 开启新线程

            t.start();

        }

        // 关闭服务器

        //ss.close();

    }

}

 

  1. 操作系统目录文件过多解决方案

一般在操作系统中,一个目录下的文件不能太多,如果文件非常的多,会导致打开这个文件夹的时候,出现打开缓慢,甚至完全打不开。

 

针对上述操作系统的问题:

    建议在上传文件的时候,根据上传文件的一些特点,然后计算这个文件在磁盘上保存的目录层级。

        

        上图的这个解决文件保存目录的方案:被称为文件目录打散。

    

    目录打散常用的方案:

        根据当前文件名计算对应的数字,然后将数组进行位运算,得到多个数字,每个数字作为当前文件保存的每级目录。

        1、根据文件名计算数字:由于文件名肯定是字符串,这时可以调用hashCode方法得到一个int值。

            

            

        2、使用学过的位运算,取出当前int值中的某些二进制数位,作为目录存在。

            /*

* DirUtils类它的主要功能就是根据传递的文件名,计算出文件保存的多个目录

*/

public class DirUtils {

    

    // 定义静态方法获取多级目录

    public static String getDir( String filename ){

        

        // 根据字符串调用hashCode方法得到一个int值

        int code = filename.hashCode();

        /*

         * 我们需要每次从int值中取出4个二进制数位,作为文件保存的一个目录

         *

         *     1010 1110 1100 1010 1110 1100 1011 0101

         * &0000 0000 0000 0000 0000 0000 0000 1111

         * -------------------------------------------

         * 0000 0000 0000 0000 0000 0000 0000 0101

         */

        // 第一级目录名称

        int x = code & 15 ;

        // 第二级目录

        int y = (code >>> 4) & 15;

        return "/" + x + "/" + y + "/";

    }

    

    public static void main(String[] args) {

        System.out.println(getDir("12abc.jpg"));

    }

}

 

将目录打散结合到程序中:

  1. 软件设计架构介绍(了解)

    1. 设计架构介绍

企业中项目开发的流程:

 

软件架构:

    在拿到需求之后,开发软件之前研究当前的软件采用哪种架构开发。

    B/S 架构

        Browser / Server 浏览器 和 服务器 架构

        BS架构它是目前和未来主流的软件架构方式。大家就业班和企业开发中就采用BS架构。

        BS架构只需要开发服务器, 不用关心客户端,因为用户使用浏览器就可以访问服务器。

        BS架构开发维护成本降低,但是需要将所有的运算都放在服务器中。

 

    C/S 架构

            Client / Server 客户端 和 服务器 架构

            CS架构的软件在研发的过程中需要开发两套软件:

                客户端软件:主要是给普通用户使用的软件。例如:QQ客户端、网游客户端、迅雷、各种音视频播放器

                服务器软件:主要是甲方公司使用的软件。例如:使用QQ客户端登录的时候,访问的网络中的设备其实就是腾讯的QQ服务器。

            CS架构:开发成本高,维护成本更高。唯一的好处是可以将部分的运算转嫁给客户端电脑。        

  1. 常见客户端和服务端介绍

客户端和服务器都分为软件和硬件:

    客户端硬件:个人电脑、手机、其他智能设备。

    客户端软件:在硬件上安装的软件程序。

        

    服务器硬件:大型电脑。提供商:IBM、HP、思科、DELL、华为、浪潮、曙光等

        

    服务器软件:它主要是运行服务器硬件上, 可以对外提供高性能服务的软件程序。

        JavaEE后期使用的服务器:

            apache公司 : apache服务器 、 tomcat 免费、支持JavaEE部分技术

            oracle公司:weblogic 服务器 商用 收费 支持JavaEE所有技术

            IBM公司:webshpere服务器 商用 收费 支持JavaEE所有技术

            jboss公司 : jboss服务器 使用免费,技术支持收费。

  1. 网络编程中其他类

    1. URL介绍

URL:统一资源定位符。

平时我们通过浏览器访问某个公司服务器上的数据(网页),都需要在浏览器地址栏中输入网址。而这个网址一定是指网络中具体的某个服务器中一个网页数据。

浏览器地址栏中的信息:http://www.baidu.com:80/index.html

 

浏览器地址栏中输入的数据,它其实代表的是网络中具体一个服务器中指定端口下的一个固定数据。

    http://192.168.49.74:9898/aaa/1.txt

    Java 就将浏览器地址栏中这类特殊的数据,使用URL类进行封装和描述。

    

    

    当我们获取到一个URL对象,其实就相当于拿到了浏览器地址栏中输入的数据。

    URL对象中(浏览器地址栏)的数据它主要包含下面几部分内容:

     协议 :// 主机(域名或ip地址) : 端口 / 主机中运行在端口下的软件中的文件信息

    http://192.168.49.74:9898/aaa/1.txt

    http: 浏览器和服务器之间交互的协议,这个协议属于应用层的协议,底层依然在使用TCP协议,http协议默认端口是80

    192.168.49.74 : 主机(域名或ip),浏览器需要连接的服务器

    9898:服务器中运行的软件的端口号

    aaa/1.txt 运行的软件中的资源数据

    

    

    
    /*

* 演示URL类的使用

*/

public class URLDemo {

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

        

        URL url = new URL("http://www.sina.com.cn:80/index.html?username=zs&age=23");

        

        // 获取方法

        System.out.println("getDefaultPort="+url.getDefaultPort());

        System.out.println("getPort="+url.getPort());

        System.out.println("getHost="+url.getHost());

        System.out.println("getProtocol="+url.getProtocol());

        System.out.println("getFile="+url.getFile());

        System.out.println("getQuery="+url.getQuery());

        

        

    }

}

  1. URLConnection介绍

 

/*

* 演示URLConnection类

* 下载京东网站上的图片

*/

public class URLConnectionDemo {

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

        

        // 封装URL对象

        URL url = new URL("http://img11.360buyimg.com/n1/s350x449_jfs/t2776/103/828087002/76556/d174c65/57281526N4aed00d6.jpg!cc_350x449.jpg");

        

        // 需要通过URL打开和服务器之间的连接通道

        URLConnection conn = url.openConnection();

        

        // 获取输入流读取数据

        InputStream in = conn.getInputStream();

        // 创建输出流,保存数据

        FileOutputStream fos = new FileOutputStream("e:/jd.jpg");

        byte[] buf = new byte[1024];

        int len = 0;

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

            fos.write(buf, 0, len);

        }

        // 关流

        fos.close();

        

    }

}

  1. HTTP1.0和HTTP1.1介绍

HTTP:它是浏览器和服务器之间交互的应用层的协议,底层使用TCP协议。

    超文本传输协议。

HTTP协议的存在:

    主要是用来限制浏览器应该如何给服务器发送什么样格式的数据。

    限制服务器给浏览器发送什么格式的数据。

 

浏览器访问服务器:客户端请求服务器。

服务器给浏览器发送数据:服务器基于客户端的请求给客户端响应数据。

 

HTTP协议其实就是在规范:请求和响应的数据格式。

    

    请求的格式:

        请求行:

        请求头:

        请求体:

 

    响应格式:

        响应行:

        响应头:

        响应体:

    HTTP协议从诞生到现在就两个版本:

        http1.0:客户端和服务器一次连接,只能对应一组请求和响应。

        http1.1:客户端和服务器一次连接,可以对应多组请求和响应。

  1. 其他工具类

    1. UUID类介绍

 

UUID类,它的主要功能就是获取一个拥有36个字符的字符串数据,并且这个字符串数据不重复。

使用其中的静态方法获取UUID类的对象:

调用其中的toString方法将对象中的数据转成字符串:

    

    // 演示UUID类的使用

public class UUIDDemo {

    public static void main(String[] args) {

        

        // 使用静态方法获取UUID类的对象

        UUID uuid = UUID.randomUUID();

        // 调用toString方法 得到字符串数据

        String s = uuid.toString();

        // 建议将字符串中的 - 字符替换掉

        s = s.replaceAll("-", "");

        

        System.out.println(s);

        

    }

}

 

  1. MD5加密技术

 

 

 

 

 

 

 

posted @ 2017-01-10 21:18  beyondcj  阅读(233)  评论(0编辑  收藏  举报