Java网络编程


一、网络基本概念

2.1、URL统一资源定位符

URL(Uniform Resource Locator)

  • 统一资源定位符,由4部分组成:协议、存放资源的主机域名、端口号和资源文件名
  • URL是指向互联网“资源”的指针

2.2、Socket套接字

  • Socket套接字是传输层供给应用层的编程接口,实质就是应用层与传输层之间的桥梁
  • 传输层在网络层的基础上提供了进程到进程间的逻辑通道,应用层的进程利用传输层向另一台主机的某一个进程通信

在这里插入图片描述

  • 案例:写信

在这里插入图片描述

2.3、相关类

  • InetAddress类——只包括IP
public class TestInetAddress {
    public static void main(String[] args) throws UnknownHostException {
        //获取本机的IP地址
        InetAddress i1 = InetAddress.getLocalHost();
        System.out.println(i1.toString());
        //HostName 主机名
        System.out.println("i1.getHostName() = " + i1.getHostName());
        //HostAddress 主机地址
        System.out.println("i1.getHostAddress() = " + i1.getHostAddress());
        //Address 地址,字节数组的形式
        System.out.println("i1.getAddress() = " + Arrays.toString(i1.getAddress()));
        
        //获取百度的IP地址
        InetAddress i2 = InetAddress.getByName("www.baidu.com");
        System.out.println(i2.toString());
        System.out.println("i2.getHostName() = " + i2.getHostName());
        System.out.println("i2.getHostAddress() = " + i2.getHostAddress());
        System.out.println("i2.getAddress() = " + Arrays.toString(i2.getAddress()));
    }
}
  • InetSocketAddress类——包括IP+Socket
public class TestInetSocketAddress {
    public static void main(String[] args) throws UnknownHostException {
        //第一种构造方式
//        InetAddress b1 = InetAddress.getByName("www.baidu.com");
//        InetSocketAddress is2 = new InetSocketAddress(b1, 8907);
//        System.out.println("is2 = " + is2);
        //第二种构造方式
        InetSocketAddress is1 = new InetSocketAddress("www.baidu.com",8907);
        //网址/IP地址:端口号
        System.out.println("is1 = " + is1);
        //网址/IP地址
        System.out.println("is1.getAddress() = " + is1.getAddress());
        //网址
        System.out.println("is1.getHostName() = " + is1.getHostName());
        //端口号
        System.out.println("is1.getPort() = " + is1.getPort());
    }
}
  • URL类
public class TestURL {
    public static void main(String[] args) throws MalformedURLException {
        URL url = new URL("https://www.baidu.com:12345/s?wd=nihao&inputT=1849#cloths");
        //url
        System.out.println("url = " + url);
        //协议
        System.out.println("url.getProtocol() = " + url.getProtocol());
        //主机/域名
        System.out.println("url.getHost() = " + url.getHost());
        //默认的端口号
        System.out.println("url.getDefaultPort() = " + url.getDefaultPort());
        //设置的端口号
        System.out.println("url.getPort() = " + url.getPort());
        //域名后,请求参数前的部分
        System.out.println("url.getPath() = " + url.getPath());
        //域名后的部分
        System.out.println("url.getFile() = " + url.getFile());
        //cloths
        System.out.println("url.getRef() = " + url.getRef());
        //请求参数的部分
        System.out.println("url.getQuery() = " + url.getQuery());
    }
}
/*
url = https://www.baidu.com:12345/s?wd=nihao&inputT=1849#cloths
url.getProtocol() = https
url.getHost() = www.baidu.com
url.getDefaultPort() = 443
url.getPort() = 12345
url.getPath() = /s
url.getFile() = /s?wd=nihao&inputT=1849
url.getRef() = cloths
url.getQuery() = wd=nihao&inputT=1849

二、TCP编程

2.1、TCP编程——一次单向通信

功能:实现类似QQ、微信、邮箱、商城的网络登录功能,可以多个用户同时登录。为了便于理解,进行功能分解和迭代,分为一次单向通信、一次双向通信、传输对象、引入多线程来实现

在这里插入图片描述

//服务端
//迭代1:客户端向服务器发起请求,服务器端获取请求,获取请求数据并在服务器输出
public class LoginServer {
    public static void main(String[] args) throws IOException {
        //1.创建一个ServerSocket,负责监听客户端请求
        ServerSocket serverSocket = new ServerSocket(8888);
        //2.使用ServerSocket进行监听,如果请求没有收到,代码就会阻塞
        Socket socket = serverSocket.accept();

        //3.处理用户的请求
        InputStream is = socket.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        String str = dis.readUTF();
        System.out.println("str = " + str);

        //4.关闭资源
        dis.close();
        socket.close();
        System.out.println("serverSocket.isClosed() = " + serverSocket.isClosed());
        serverSocket.close();
        System.out.println("serverSocket.isClosed() = " + serverSocket.isClosed());
    }
}

//客户端
public class LoginClient {
    public static void main(String[] args) throws IOException {
        //1.创建一个Socket,指明服务器端的IP地址和端口号
        //Socket socket = new Socket(InetAddress.getLocalHost(),8888);
        //Socket socket = new Socket(InetAddress.getByName("localhost"),8888);
        Socket socket = new Socket(InetAddress.getByName("192.168.140.1"),8888);

        //2.向服务器端发起一个新的请求
        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeUTF("username=doublew2w&password=doublew2w");

        3.关闭资源
        System.out.println("socket.isClosed() = " + socket.isClosed());
        dos.close();
        System.out.println("socket.isClosed() = " + socket.isClosed());
    }
}

注意事项:

  • 服务器端的要手动关闭
  • 客户端在关闭IO流的时候,就顺便把客户端关闭了。

2.2、TCP编程-一次双向通信

双向通信相当于服务器端返回响应信息给客户端。之所以能正确地发送客户端,那是因为IP地址和端口号等信息都封装在Socket socket = serverSocket.accept();Socket socket = new Socket();

//服务器端
//迭代2:客户端向服务器发起请求,服务器端获取请求,获取请求数据并在服务器输出,服务器端返回响应信息给客户端
public class LoginServer2 {
    public static void main(String[] args) throws IOException {
        //1.创建一个ServerSocket,负责监听客户端请求
        ServerSocket serverSocket = new ServerSocket(8888);

        //2.使用ServerSocket进行监听,如果请求没有收到,代码就会阻塞
        Socket socket = serverSocket.accept();
//        InputStream is = socket.getInputStream();
        DataInputStream dis = new DataInputStream(socket.getInputStream());
//        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());

        //3.处理用户的请求
        String str = dis.readUTF();
        System.out.println("str = " + str);

        //4.给客户端返回响应信息
        dos.writeUTF("用户登录成功,欢迎你");

        //5.关闭资源
        dis.close();
        dos.close();
        serverSocket.close();
    }
}


public class LoginClient2 {
    public static void main(String[] args) throws IOException {
        //1.创建一个Socket,指明服务器端的IP地址和端口号
        //Socket socket = new Socket(InetAddress.getLocalHost(),8888);
        //Socket socket = new Socket(InetAddress.getByName("localhost"),8888);
        Socket socket = new Socket(InetAddress.getByName("192.168.140.1"),8888);
//        OutputStream os = socket.getOutputStream();
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
//        InputStream is = socket.getInputStream();
        DataInputStream dis = new DataInputStream(socket.getInputStream());

        //2.向服务器端发起一个新的请求
        dos.writeUTF("username=doublew2w&password=doublew2w");

        //3.接收服务器端发送过来的响应信息
        String str = dis.readUTF();
        System.out.println("str = " + str);

        //4.关闭资源
        dos.close();
        dis.close();
    }
}

2.3、TCP编程-传输对象

在一次双向通信基础上进步优化,在客户端输入用户名和密码,并封装到User对象中。如何在TCP编程中传输对象呢?

User类

public class User implements Serializable {

    private String userId;
    private String password;

    public User() {
    }
    public User(String userId, String password){
        this.userId = userId;
        this.password = password;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId='" + userId + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

客户端

public class LoginClient3 {
    public static void main(String[] args) throws IOException {
        //1.创建一个Socket,指明服务器端的IP地址和端口号
        //Socket socket = new Socket(InetAddress.getLocalHost(),8888);
        //Socket socket = new Socket(InetAddress.getByName("localhost"),8888);
        Socket socket = new Socket(InetAddress.getByName("192.168.140.1"),8888);
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        DataInputStream dis = new DataInputStream(socket.getInputStream());

        //2.向服务器端发起一个新的请求
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名");
        String userId = scanner.next();
        System.out.println("请输入密码");
        String password = scanner.next();
        User user = new User(userId, password);
        oos.writeObject(user);

        //3.接收服务器端发送过来的响应信息
        String str = dis.readUTF();
        System.out.println("这里是客户端:" + str);

        //4.关闭资源
        oos.close();
        dis.close();
    }
}

服务器端

public class LoginServer3 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1.创建一个ServerSocket,负责监听客户端请求
        ServerSocket serverSocket = new ServerSocket(8888);

        //2.使用ServerSocket进行监听,如果请求没有收到,代码就会阻塞
        Socket socket = serverSocket.accept();
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());

        //3.处理用户的请求
        User user = (User)ois.readObject();
        System.out.println("这里是服务器端:"+user);

        //4.给客户端返回响应信息
        if("doublew2w".equals(user.getUserId())){
            dos.writeUTF("您好,登录成功,欢迎您");
        }else{
            dos.writeUTF("您好,登录失败,请重新输入");
        }


        //5.关闭资源
        ois.close();
        dos.close();
        serverSocket.close();//手动关闭
    }
}

使用TCP实现登录功能总结:

  • 服务器创建 ServerSocket ,在指定端口监听并并处理请求;
  • ServletSocket通过 accept() 接收用户请求并返回对应的Socket,否则一种处于监听等待状态,线程也被阻塞
  • 客户端创建 Socket ,需要指定服务器的ip和端口号,向服务器发送和接收响应
  • 客户端发送数据需输出流(写),客户端获取反馈数据需输入流(读)
  • 服务端反馈数据需输出流(写),服务端获取请求数据需输入流(读)
  • 一旦使用ServerSocket和Socket建立了网络连接后,网络通信和普通IO流操作并没有太大区别
  • 网络通信输出流建议使用 DataOutputStreamObjectOutputStream ,与平台无关,输入流相应使用 DataIntputStreamObjectInputStream
  • 如果是字符串通信也可以使用 BufferedReaderPrintWriter ,简单方便

2.5、TCP编程——引入多线程

  • 如何让服务器端一直处于运行状态,并且会出现多个用户同时登陆的情况,需要服务器端进行处理
  • 方法1:只引入while ,不可以
    •  此时服务器端只做两件事: 接受请求(1秒)+ 处理请求(9秒)
      
    •  必须处理完一个请求后,才能处理下一个请求——串行
      
  • 方法2:引入while+多线程
    • 接收用户的请求 + 每个请求创建一个线程来处理请求
      

LoginThread类——多线程处理请求

public class LoginThread extends Thread{
    private Socket socket;

    public LoginThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try(
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            ){
            //3.处理用户的请求
            User user = (User)ois.readObject();
            System.out.println("这里是服务器端:"+user);
            //4.给客户端返回响应信息
            if("doublew2w".equals(user.getUserId())){
                dos.writeUTF("您好,登录成功,欢迎您");
            }else{
                dos.writeUTF("您好,登录失败,请重新输入");
            }
        }catch(ClassNotFoundException | IOException e){
            e.printStackTrace();
        }
    }
}

客户端(不改变)——

public class LoginClient4 {
    public static void main(String[] args) throws IOException {
        //1.创建一个Socket,指明服务器端的IP地址和端口号
        //Socket socket = new Socket(InetAddress.getLocalHost(),8888);
        //Socket socket = new Socket(InetAddress.getByName("localhost"),8888);
        Socket socket = new Socket(InetAddress.getByName("192.168.140.1"),8888);
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        DataInputStream dis = new DataInputStream(socket.getInputStream());

        //2.向服务器端发起一个新的请求
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名");
        String userId = scanner.next();
        System.out.println("请输入密码");
        String password = scanner.next();
        User user = new User(userId, password);
        oos.writeObject(user);

        //3.接收服务器端发送过来的响应信息
        String str = dis.readUTF();
        System.out.println("这里是客户端:" + str);

        //4.关闭资源
        oos.close();
        dis.close();
    }
}

服务器端

public class LoginServer4 {
    public static void main(String[] args) throws Exception {
        //1.创建一个ServerSocket,负责监听客户端请求
        ServerSocket serverSocket = new ServerSocket(8888);
        int i=1;
        while(true){
            //2.使用ServerSocket进行监听,如果请求没有收到,代码就会阻塞
            Socket socket = serverSocket.accept();
            //3.创建一个线程处理请求
            new LoginThread(socket).start();
            //4.输出请求数量与客户端的IP地址和端口号
            System.out.println("这是第"+ (i++)+"个请求" +
                    "对方的IP地址是"+socket.getInetAddress()+
                    "对方的端口号是"+socket.getPort());
        }
    }
}

三、UDP编程

需求:完成在线咨询功能

分析:

  • 使用基于UDP协议的Socket网络编程实现
  • 不需要利用IO流实现数据的传输
  • 每个数据发送单元被统一封装成数据包的方式,发送方将数据包发送到网络中,数据包在网络中去寻找他的目的地

UDP基本概念

  • DatagramSocket:用于发送或接收数据包
  • DatagramPacket:数据包

3.1、UDP编程——一次单向通信

服务器端,被动接受请求

public class AskServer {
    public static void main(String[] args) throws IOException {
        //1.创建一个DatagramSocket,可以指定接受数据的端口(也可以不指定,会自动分配)
        DatagramSocket socket = new DatagramSocket(8888);
        //2.接受数据包
        byte[] bytes = new byte[100];
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
        System.out.println(packet);
        socket.receive(packet);
        System.out.println(packet);
        System.out.println("packet.getData() = " + Arrays.toString(packet.getData()));
        System.out.println("new String(packet.getData()) = " + new String(packet.getData(),0, packet.getLength()));
        System.out.println("packet.getLength() = " + packet.getLength());
        System.out.println("packet.getAddress() = " + packet.getAddress());
        System.out.println("packet.getPort() = " + packet.getPort());
//        socket.receive(packet);
        //3.关闭资源
        socket.close();
    }
}
/**
java.net.DatagramPacket@677327b6
java.net.DatagramPacket@677327b6
packet.getData() = [-28, -70, -78, -17, -68, -116, -27, -100, -88, -27, -112, -105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
new String(packet.getData()) = 亲,在吗
packet.getLength() = 12
packet.getAddress() = /127.0.0.1
packet.getPort() = 9999

客户端——主动发送请求

public class AskClient {
    public static void main(String[] args) throws IOException {
        //1.创建一个DatagramSocket,可以指定接受数据的端口(也可以不指定,会自动分配)
        //同一台电脑 同一个端口不能又发送又接受
        DatagramSocket socket = new DatagramSocket(9999);
        //2.发送数据包
        String str = "亲,在吗";
        byte[] buf = str.getBytes();
        int length = buf.length;
        InetAddress address = InetAddress.getByName("localhost");
        int port = 8888;
        DatagramPacket packet = new DatagramPacket(buf, length, address, port);
        socket.send(packet);
        //3.关闭资源
        socket.close();
    }
}

3.2、UDP编程-一次双向通信

服务器端

public class AskServer {
    public static void main(String[] args) throws IOException {
        //1.创建一个DatagramSocket,可以指定接受数据的端口(也可以不指定,会自动分配)
        DatagramSocket socket = new DatagramSocket(8888);
        //2.接受数据包
        byte[] bytes = new byte[100];
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
        socket.receive(packet);
        System.out.println("这里是服务器端:" + new String(packet.getData(),0, packet.getLength()));
        //3.给出客户端响应
        String str = "亲,在的";
        byte[] bytes1 = str.getBytes();
        InetAddress address = InetAddress.getByName("localhost");
        int port = 9999;
        DatagramPacket packetSend = new DatagramPacket(bytes1, 0, bytes1.length, address, port);
        socket.send(packetSend);
        //4.关闭资源
        socket.close();
    }
}

客户端

public class AskClient {
    public static void main(String[] args) throws IOException {
        //1.创建一个DatagramSocket,可以指定接受数据的端口(也可以不指定,会自动分配)
        //同一台电脑 同一个端口不能又发送又接受
        DatagramSocket socket = new DatagramSocket(9999);
        //2.发送数据包
        String str = "亲,在吗";
        byte[] buf = str.getBytes();
        int length = buf.length;
        InetAddress address = InetAddress.getByName("localhost");
        int port = 8888;
        DatagramPacket packet = new DatagramPacket(buf, length, address, port);
        socket.send(packet);
        //3.接受服务端响应
        byte[] bytes = new byte[1024];
        DatagramPacket packetReceive = new DatagramPacket(bytes, bytes.length);
        socket.receive(packetReceive);
        System.out.println(" 这里是客户端:" + new String(packetReceive.getData(),0, packet.getLength()));

        //4.关闭资源
        socket.close();
    }
}

总结:

  • 服务器端——接收请求
    • DatagramPacket packet(字节数组,字节数组长度)
    • socket.receive(packet);
    • new String(packet.getData(),0, packet.getLength()) 转换数据
  • 服务器端——发送响应
    • byte[] bytes1 = str.getBytes();
    • DatagramPacket(字节数组, 0, bytes1.length, address, port);
    • socket.send(packetSend);

3.3、UDP编程-多次双向通信

服务器端

public class AskServer {
    public static void main(String[] args) throws IOException {
        //1.创建一个Socket,用来发送和接收数据包
        DatagramSocket socket = new DatagramSocket(8888);
        Scanner input = new Scanner(System.in);
        while(true){
            //2.使用socket接收一个数据包并输出
            byte [] buf = new byte[128];
            DatagramPacket packet = new DatagramPacket(buf, buf.length);
            socket.receive(packet);
            String info = new String(packet.getData(),0,packet.getLength());
            System.out.println(info);
            if("bye".equals(info)){
                break;
            }

            //3.使用socket发送一个响应
            String str =input.nextLine();
            byte[] buf2 = str.getBytes();
            DatagramPacket packet2
                    = new DatagramPacket(buf2,buf2.length, packet.getAddress(), packet.getPort());
            socket.send(packet2);
        }
        //3.关闭socket
        socket.close();
    }
}

客户端

public class AskClient {
    public static void main(String[] args) throws IOException {
        //1.创建一个Socket,用来发送和接收数据包
        //服务器返回数据给客户端,
        DatagramSocket socket = new DatagramSocket(9999);
        Scanner input = new Scanner(System.in);
        while(true ){
            //2.使用socket发送一个数据包
            String str = input.nextLine();
            byte [] buf =  str.getBytes();
            InetAddress address = InetAddress.getLocalHost();
            int port = 8888;
            DatagramPacket packet = new DatagramPacket(buf,buf.length ,address ,port );
            socket.send(packet);
            if("bye".equals(str)){
                break;
            }

            //3.接收服务器端的反馈
            byte [] buf2 = new byte[128];
            DatagramPacket packet2 = new DatagramPacket(buf2, buf2.length);
            socket.receive(packet2);
            System.out.println(new String(packet2.getData(),0,packet2.getLength()));
        }
        //4.关闭socket
        socket.close();
    }

四、TCP编程实现文件上传功能

  • 思路:进行两次文件复制
  • 客户端将文件从本地复制到网络;服务端将文件从网络复制到本地

在这里插入图片描述

客户端实现把源文件赋值到服务器端的监听端口

public class UploadClient {
    public static void main(String[] args) throws IOException {
        //源文件是本地
        String sourceFilename = "e:/readme.txt";
        //目的文件是服务器的监听端口
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);

        //创建输入流和输出流
        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFilename));
            BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
            ){
            //使用输入流和输出流完成文件赋值
            //2.1 准备一个中转站(一个字节)
            by】;te [] buf = new byte[1024];
            int len;
            while((len = bis.read(buf)) != -1) { // ==-1,读到了文件的末尾
                //写字节数组到文件
                bos.write(buf, 0, len);
                len = bis.read(buf);
            }
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }
    }
}

服务器端实现把端口中的文件复制到某个具体位置

public class UploadServer {
    public static void main(String[] args) throws IOException {
        //源文件是服务器端的端口
        ServerSocket serverSocket = new ServerSocket(8888);
        Socket socket = serverSocket.accept();
        //目的文件是服务器
        String destFilename = "e:/readme2.txt";

        //创建输入流和输出流
        try(BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilename));
        ){
            //使用输入流和输出流完成文件赋值
            //2.1 准备一个中转站(一个字节)
            byte [] buf = new byte[1024];
            int len;
            while((len = bis.read(buf)) != -1) { // ==-1,读到了文件的末尾
                //写字节数组到文件
                bos.write(buf, 0, len);
                len = bis.read(buf);
            }
        }catch(FileNotFoundException e){
            e.printStackTrace();
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值