Java基础—网络编程(二)

@[toc]
网络编程(二)

一、TCP协议通信

1.上传图片练习
1)将图片上传到服务器端保存。

示例代码:

package com.heisejiuhuche.socket;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;

public class PicUploadClient {
    public static void main(String[] args) throws Exception {
        upload();
    }

    private static void upload() throws Exception {
        /* 创建Socket对象,指定主机和端口 */
        Socket s = new Socket("localhost", 10007);
        /* 创建File对象,关联文件 */
        File file = new File("C://Users//jeremy//Documents//javaTmp//me.jpg");
        /* 创建输入输出流对象 */
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
        int b = 0;
        /* 上传文件 */
        while((b = bis.read()) != -1) {
            bos.write(b);
            bos.flush();
        }
        /* 发送结束符 */
        s.shutdownOutput();
        /* 获取反馈信息 */
        InputStream in = s.getInputStream();
        byte[] buf = new byte[1024];
        int msg = in.read(buf);
        System.out.println(new String(buf, 0, msg));
        bis.close();
        s.close();
    }
}

package com.heisejiuhuche.socket;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class PicUploadServer {
    public static void main(String[] args) throws Exception {
        recv();
    }

    private static void recv() throws Exception {
        ServerSocket ss = new ServerSocket(10007);
        Socket s = ss.accept();
        /* 创建输入输出流对象 */
        BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
        File file = new File("C://Users//jeremy//Documents//me.jpg");
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
        int b = 0;
        /* 保存文件 */
        while((b = bis.read()) != -1) {
            bos.write(b);
            bos.flush();
        }
        /*发送反馈信息 */
        OutputStream out = s.getOutputStream();
        out.write("上传成功".getBytes());
        bos.close();
        s.close();
        ss.close();
    }
}

程序运行结果:上传成功

2)TCP客户端并发上传图片
这个服务端有局限性。当一个客户端连接服务器之后,因为服务器是单线程,那么其他客户端如果想连接服务器,只能带前一个客户完成所有任务,才能连接。要同时处理多个客户端请求,应该明确客户端要在服务器端要执行的代码,然后将每个客户端封装到一个线程当中,同步执行。

示例代码:

package com.heisejiuhuche.socket;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.Socket;

public class UploadPicByThreadClient {
    public static void main(String[] args) throws Exception {
        /* 限制文件格式,大小等条件 */
        if(args.length != 1) {
            System.out.println("请指定一个jpg文件上传...");
            return;
        }
        File file = new File(args[0]);
        if(!(file.exists() && file.isFile())) {
            System.out.println("文件不存在或者不是文件类型...");
            return;
        }

        if(!(file.getName().endsWith(".jpg"))) {
            System.out.println("格式不正确...");
            return;
        }

        if(file.length() > 1024 * 1024 * 5) {
            System.out.println("文件过大!");
            return;
        }

        /* 创建Socket对象,指定主机和端口 */
        Socket s = new Socket("localhost", 10007);
        /* 创建File对象,关联文件 */
//        File file = new File("C://Users//jeremy//Documents//javaTmp//me.jpg");
        /* 创建输入输出流对象 */
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
        int b = 0;
        /* 上传文件 */
        while((b = bis.read()) != -1) {
            bos.write(b);
            bos.flush();
        }
        /* 发送结束符 */
        s.shutdownOutput();
        /* 获取反馈信息 */
        InputStream in = s.getInputStream();
        byte[] buf = new byte[1024];
        int msg = in.read(buf);
        System.out.println(new String(buf, 0, msg));
        bis.close();
        s.close();
    }
}

package com.heisejiuhuche.socket;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class UploadPicByThreadServer {
    public static void main(String[] args) throws Exception {
        ServerSocket ss = new ServerSocket(10007);
        while(true) {
            Socket s = ss.accept();
            new Thread(new PicServerThread(s)).start();
        }
    }
}

/* 将客户端上传的代码封装成线程 */
class PicServerThread implements Runnable {
    private Socket s;

    PicServerThread(Socket s) {
        this.s = s;
    }

    public void run() {
        String ip = s.getInetAddress().getHostAddress();
        int count = 1;
        try {
            System.out.println(ip + " connected..");
            /* 避免文件名重复 */
            File file = new File("C://Users//jeremy//Documents//" + ip + "(" + count + ")" + ".jpg");
            /* 循环判断,如果文件存在,让count自增 */
            while(file.exists())
                file = new File("C://Users//jeremy//Documents//" + ip + "(" + count++ + ")" + ".jpg");
            /* 读取文件,写入服务端 */
            BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
            int b = 0;
            while((b = bis.read()) != -1) {
                bos.write(b);
                bos.flush();
            }
            OutputStream out = s.getOutputStream();
            out.write("上传成功".getBytes());
            bos.close();
            s.close();
        } catch(IOException e) {
            throw new RuntimeException(ip + " 上传失败...");
        }
    }
}

2.检测用户名练习
客户端通过键盘录入用户名,如果该用户存在,在服务器端显示xxx,已登录;在客户端显示xxx,欢迎光临。如果用户不存在,在服务端显示xxx,尝试登录;在客户端显示xxx,该用户不存在。

示例代码:

package com.heisejiuhuche.socket;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class LoginClient {
    public static void main(String[] args) throws Exception {
        Socket s = new Socket("localhost", 10009);
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
        BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
        for(int x = 0; x < 3; x++) {
            String line = bufr.readLine();
            /* 如果输入为空,直接结束循环 */
            if(line == null)
                break;
            /* 发送名字到服务器端 */
            pw.println(line);
            /* 读取回馈信息 */
            String feedback = bufIn.readLine();
            System.out.println(feedback);
            /* 如果回馈信息有欢迎字样,结束循环 */
            if(feedback.contains("欢迎"))
                break;
        }
        bufr.close();
        s.close();
    }
}

package com.heisejiuhuche.socket;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class LoginServer {
    public static void main(String[] args) throws Exception {
        ServerSocket ss = new ServerSocket(10009);
        while(true) {
            Socket s = ss.accept();
            new Thread(new UserThread(s)).start();
        }
    }
}

class UserThread implements Runnable {
    private Socket s;

    UserThread(Socket s) {
        this.s = s;
    }

    public void run() {
        String ip = s.getInetAddress().getHostAddress();
        System.out.println(ip + " connected...");
        try {
            for(int x = 0; x < 3; x++) {
                BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
                String name = bufIn.readLine();
                if(name == null)
                    break;
                /* 这一行代码一定放在for循环里面,不然readLine的指针回不到文件开端,读不到名字 */
                BufferedReader bufr = new BufferedReader(new FileReader("C://Users//jeremy//Documents//javaTmp//user.txt"));
                PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
                String line = null;
                Boolean flag = false;
                /* 根据flag的结果判断要进行的操作  */
                while((line = bufr.readLine()) != null) {
                    if(name.equals(line)) {
                        flag = true;
                        break;
                    }
                }
                if(flag) {
                    System.out.println(name + "已登录!");
                    pw.println(name + ", 欢迎登录!");
                    break;
                } else {
                    System.out.println(ip + "正在尝试登录...");
                    pw.println(name + "不存在...");
                }
                bufr.close();
            }
            s.close();
        } catch(IOException e) {
            throw new RuntimeException(ip + "校验失败...");
        }
    }
}

3.自定义浏览器
1)HTTP请求消息头
请求头信息中包括了发送该请求的主机地址,端口,路径,可以接受的应用程序类型,所使用语言,封装压缩形式等信息。请求头信息示例如下:

GET / HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko
Accept-Encoding: gzip, deflate
Host: 127.0.0.1:11000
DNT: 1
Connection: Keep-Alive

模拟一个浏览器,想服务器发送请求头信息,获取页面。

示例代码:

package com.heisejiuhuche.socket;

import java.io.InputStream;
import java.io.PrintWriter;
import java.net.Socket;

public class BrowserClient {
    public static void main(String[] args) throws Exception {
        /* 访问本机的Tomcat服务器,端口8080 */
        Socket s = new Socket("localhost", 8080);
        PrintWriter out = new PrintWriter(s.getOutputStream(), true);
        /* 发送HTTP请求头 */
        out.println("GET / HTTP/1.1");
        out.println("Accept: */*");
        out.println("Accept-Language: en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3");
        out.println("Accept-Encoding: gzip, deflate");
        out.println("Connection: closed");
        /* 用空行分割请求头和请求体 */
        out.println();
        out.println();
        /* 获取服务器返回的信息 */
        InputStream in = s.getInputStream();
        byte[] buf = new byte[1024];
        int len = in.read(buf);
        System.out.println(new String(buf, 0, len));

        s.close();
    }
}

二、URL类

1.概述
URL(Uniform Resource Locator)类代表一个同一资源定位符。该类是指向互联网资源的指针。

2.常用方法
-String getFile():获取此URL的文件名
-String getHost():获取此URL的主机名
-String getPath():获取此URL的路径部分
-int getPort():获取此URL的端口号
-String getProtocol():获取此URL的协议名称
-String getQuery():获取此URL的查询部

3.常用方法演示

示例代码:

package com.heisejiuhuche.socket;

import java.net.MalformedURLException;
import java.net.URL;


public class URLDemo {
    public static void main(String[] args) throws MalformedURLException {
        URL url = new URL("http://192.168.0.100:8080/myweb/index.html/?name=hsjhc&age=25");
        /* 演示常用方法 */
        System.out.println("getProtocol: " + url.getProtocol());
        System.out.println("getHost: " + url.getHost());
        System.out.println("getPort: " + url.getPort());
        System.out.println("getPath: " + url.getPath());
        System.out.println("getFile: " + url.getFile());
        System.out.println("getQuery: " + url.getQuery());
    }
}

程序运行结果:

getProtocol: http
getHost: 192.168.0.100
getPort: 8080
getPath: /myweb/index.html/
getFile: /myweb/index.html/?name=hsjhc&age=25
getQuery: name=hsjhc&age=25

注意:
没有指定端口,返回-1;开发时要进行判断,如果port的值为-1,要给port赋值为80

三、URLConnection类

1.概述
URLConnection类对象是URL对象调用openConnect()方法之后的返回值,就能得到这个URL的连接对象,也就是目标主机对象;获取URLConnection对象可以方便获取Socket输入流对象

2.URLConnection类演示

示例代码:

package com.heisejiuhuche.socket;

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

public class URLConnectionDemo {
    public static void main(String[] args) throws Exception {
        /* 封装URL对象 */
        URL url = new URL("http://192.168.0.100:8080/myweb/indetml/?name=hsjhc&age=25");
        /* 调用url对象的openConnection方法,得到URLConnection对象 */
        URLConnection conn = url.openConnection();
        System.out.println(conn);

        /* 因为URLConnection对象中封装了Socket对象
         * 所以可以调用getInputStream()方法获取输入流来读取服务器端数据 */
        InputStream in = conn.getInputStream();
        byte[] buf = new byte[1024];
        int len = in.read(buf);
        System.out.println(new String(buf, 0, len));
    }
}

注意:
url.openConnection().getInputStream()这行代码,可以简写成url.openStream();该方法是URLConnection类中获取InputStream的便捷方法

四、扩展知识

1.Socket空参数构造函数
通过调用connect()方法连接主机;connect()方法可以接收一个SocketAddress对象作为参数;SocketAddress类的子类InetSocketAddress类封装IP地址端口;那么以前在创建Socket对象的时候分开写主机IP和端口,现在也可以调用connect方法,传递一个InetSocketAddress对象即可。

2.ServerSocket类接收的backlog参数
backlog表示队列的最大长度。它代表能同时连接到服务器的最大客户端个数。在服务器端开发时,要指定这个参数,避免服务器因连接数过多而死机。

五、域名解析

1.概述
IP地址不容易记忆,通常人们在访问网站的时候,在地址栏输入的都是类似于www.baidu.com这样的域名。但是要访问到网络上对应这个域名的那个主机,需要将这个域名解析成IP地址。这个过程,需要DNS域名解析服务器来完成。DNS服务器中记录的是每个域名对应的IP地址映射表。

2.基本原理

这里写图片描述

PC在访问网站的时候,要走这4步流程。在浏览器输入域名之后,第1步,到本机C盘``(C:\windows\Systems\drivers\etc\)下的hosts文件,寻找这个域名是否在本地有已经配置的IP地址,如果有,直接访问该IP,否则,进入第2步;第2步,访问公网DNS服务器,找到DNS服务器上的域名——IP映射关系;第3步,DNS服务器将对应的IP地址返回给PC;第4步,PC端访问该IP地址,找到公网上的指定主机。

注意:
127.0.0.1与localhost之间的映射关系在本机上C盘下的hosts文件中。该文件可以用于屏蔽恶意网站。