[Java] 网络编程

2021/4/7 3:03:49

本文主要是介绍[Java] 网络编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

结合了网络的java才有点意思

  • 第一章 网络编程入门
    • 1.1 软件结构
    • 1.2 网络通信协议
    • 1.3 协议分类
    • 1.4 网络编程三要素
      • 协议
      • IP地址
      • 端口号
      • 总结
  • 第二章 TCP通信程序
    • 2.1 概述
    • 2.2 ServerSocket类
      • 构造方法
      • 常用成员方法
    • 2.3 Socket类
      • 构造方法
      • 成员常用方法
  • 第三章 综合案例
    • 3.1 文件上传案例
      • 文件上传案例优化
    • 3.2 模拟B/S服务器(拓展知识点)

第一章 网络编程入门

1.1 软件结构

  • c/s结构: 全程为Client/Server结构,是指客户端和服务器结构。常见程序有QQ和迅雷邓

在这里插入图片描述

B/S结构: 全称为Browser/Server结构,是指浏览器和服务器结构。常见浏览器有谷歌,火狐等

在这里插入图片描述

1.2 网络通信协议

  • 网络通信协议: 通过计算机网络可以使多态计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议他对数据的传输格式、传输速率、传输步骤都做了统一规定,通信双方必须同时遵守才能完成数据交换
  • TCP/IP协议: 采用了4层的分层模型,每一层都依靠下一层所提供的服务来完成自己的需求

在这里插入图片描述

1.3 协议分类

通信的协议还是比较复杂的,java.net包中包含的类和接口,他们提供低层次的通信细节.我们可以直接使用这些类和接口,来专注于网络程序开发,而不用考虑通信的细节.

该包支持:

  • **UDP:**用户数据报协议(User Datagram Protocol). UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接.简单来说,就是发送端不会确认接收端是否存在,就会发出数据;同时,接收端也不会反馈是否收到数据

    • 由于UDP面向无连接性,不能保证数据的完整性,因此,传输重要数据的时候不建议使用这个
    • 通信效率高,可以用于在线视频,音频等情况,因为就算偶尔丢一两个包,也没所谓
    • 特点:数据包的范围被限制在64kb以内,超过了就不能发送了
  • TCP:传输控制协议(Transmission Control Protocol). TCP协议是面向连接的通信协议,传输端和接收端之间建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输.即每次通信都要进行"三次握手"

    • 三次握手”: TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠

      • 第一次握手: 客户端向服务器端发送连接请求,等待服务器确认
      • 第二次握手: 服务器端向客户端回送一个响应,通知客户端收到了连接请求
      • 第三次握手: 客户端再次向服务器段发送确认信息,确认连接

在这里插入图片描述

建立逻辑连接之后,就可以开始数据传输了,由于这种面向连接的特性,所以TCP协议可以保证传输数据的安全,所以运用十分广泛,例如: 下载文件,浏览网页等

1.4 网络编程三要素

协议

之前已经介绍过了,就不多vbb

IP地址

  • IP地址: 指互联网协议地址(Internet Protocol Address),俗称IP. IP地址用来给一个网络中的计算机设备做唯一的编号. 假如我们把"个人电脑"比作"一台电话"的话,那么"IP地址"就相当于电话号码

IP地址分类

  • IPv4: 是一个32位的二进制数,通常分为4个字节,表示成a.b.c.d的形式,例如192.168.65.100.其中a.b.c.d都是0~255之间的十进制整数,那么最多可以表示42亿个

  • IPv6: 由于互联网的蓬勃发展,IP地址的需求量越来越大,但是网络地址的资源有限,使得IP的分配越来越紧张.

    为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16字节一组,分成8组十六进制,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源不够的问题.

常用命令(控制台)

  • 查看本机ip地址
ipconfig
  • 检查网络是否连通
ping 空格 IP地址
  • 本机的ip地址: 127.0.0.1,localhost

本机的ip地址是一定可以连通的

端口号

网络的通信,本质上就是两个进程之间的通信. 每台计算机都有很多的进程,那么在网络通信时,如何区分这些进程呢?

如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程了.

  • 端口号: 用两个字节标识的整数,他的取值范围是0~65535(256*256). 其中,0~1023之间的端口号用于一些知名的网络服务和运用,普通的应用程序需要使用1024以上的端口号. 如果端口号被另外一个服务或运用所占用,会导致当前程序启动失败.
    • 端口号只是一个逻辑端口,我们无法直接看到,但是可以通过一些软件查看

在这里插入图片描述

总结

利用上面的三个组合起来,就可以标识网络中的进程了,那么进程之间的通信就可以利用这个标识与其他进程进行交互.

第二章 TCP通信程序

2.1 概述

TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server).

两端通信时步骤

  1. 服务端程序,需要事先启动,等待客户端的连接
  2. 客户端主动连接服务器端,连接成功才能通信.服务器端不可以主动连接客户端

在Java中,提供了两个类用于实现TCP通信程序:

  1. 客户端: java.net.Socket类表示.创建Socket对象,向服务器端发送连接请求,服务器端响应请求,两者连接开始通信
  2. 服务端: java.net.ServerSocket类表示.创建这个对象,相当于开启了一个服务,并等待客户端的连接

注意:

  • 两者建立的逻辑连接中包含一个对象,这个对象就是IO对象
  • IO对象进行通信,IO对象是字节流对象
  • 客户端与服务器进行一次数据的交互要用到4个IO流对象(2个InputStream,2个OutputStream)

在这里插入图片描述

明确关于服务器端的两件事情

  • 多个客户端同时和服务器进行交互,服务器必须明确和哪个客户端进行的交互
    • 在客户端中有一个方法叫做accept(),用这个方法获取客户端对象
  • 多个客户端同时和服务器进行交互,就需要使用多个流对象
    • 服务器没有IO流,而是直接使用客户端的IO流和客户端进行交互

2.2 ServerSocket类

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

服务器套接字的实际工作由 SocketImpl 类的实例执行。应用程序可以更改创建套接字实现的套接字工厂来配置它自身,从而创建适合本地防火墙的套接字。

注意: 明确一件事情: 我们必须知道是哪个客户端请求的我们这个服务器,使用accept()来获取

构造方法

ServerSocket() 
          创建非绑定服务器套接字。 
ServerSocket(int port) 
          创建绑定到特定端口的服务器套接字。 
    
ServerSocket(int port, int backlog) 
          利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。 
    
ServerSocket(int port, int backlog, InetAddress bindAddr) 
          使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。 

常用成员方法

Socket accept() 
          侦听并接受到此套接字的连接。 
    
void close() 
          关闭此套接字。 
    

实现步骤

  1. 创建ServerSockret对象,指定自己的端口号
  2. 使用ServerSocket对象中的accept方法获取到客户端的Soctet类
  3. 使用Sockey对象中的方法GetInputStream()获取网络字节输入流对象
  4. 使用InputStream对象中的read方法,读取客户端给我们发送的数据
  5. 使用该对象的getOutputStream()方法获取网络字节输出流对象(注意!! 流对象不是我们自己创建的,而是获取的)
  6. 使用网络字节输出流OutputStram对象中的Write方法,给客户端发送数据
  7. 关闭我们的服务器(一般不这么干)

实例代码

package demo07;

//这是我们的服务器端

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8888);

        //使用accept获取对象
        Socket client = ss.accept();

        //获取输入流
        InputStream  is = client.getInputStream();
        //使用read,读取客户端发送的数据
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);

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

        //获取输出流
        OutputStream os =client.getOutputStream();

        //发送给客户端
        os.write("小客户你就是弟弟".getBytes());

        //释放资源
        client.close();
        ss.close();
    }
}

2.3 Socket类

此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点(包含了IP地址和端口号的网络单位)。

套接字的实际工作由 SocketImpl 类的实例执行。应用程序通过更改创建套接字实现的套接字工厂可以配置它自身,以创建适合本地防火墙的套接字。

构造方法

Socket() 
          通过系统默认类型的 SocketImpl 创建未连接套接字 
    
Socket(InetAddress address, int port) 
          创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 
    
Socket(InetAddress address, int port, InetAddress localAddr, int localPort) 
          创建一个套接字并将其连接到指定远程地址上的指定远程端口。 
    
Socket(Proxy proxy) 
          创建一个未连接的套接字并指定代理类型(如果有),该代理不管其他设置如何都应被使用。 
    
protected  Socket(SocketImpl impl) 
          使用用户指定的 SocketImpl 创建一个未连接 Socket。 
    
Socket(String host, int port) 
          创建一个流套接字并将其连接到指定主机上的指定端口号。 如果指定的host是null,则相当于指定地址为回送地址

Socket(String host, int port, InetAddress localAddr, int localPort) 
          创建一个套接字并将其连接到指定远程主机上的指定远程端口。 

注意: 回送地址(127.X.X.X)是本机的回送地址(Loopback Address),主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回, 不进行任何网络传输.

成员常用方法

InputStream getInputStream() 
          返回此套接字的输入流 
    
OutputStream getOutputStream() 
          返回此套接字的输出流。 
    
void close() 
          关闭此套接字。 

实现步骤:

  1. 创建一个Socket对象,构造方法绑定服务器的IP地址和端口号
  2. 使用该对象的getOutputStream()方法获取网络字节输出流对象(注意!! 流对象不是我们自己创建的,而是获取的)
  3. 使用网络字节输出流OutputStram对象中的Write方法,给服务器发送数据
  4. 使用Sockey对象中的方法GetInputStream()获取网络字节输入流对象
  5. 使用InputStream对象中的read方法,读取服务器写回的数据
  6. 关闭Socket

注意

  • 客户端和服务器端交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
  • 当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3此握手建立连接通路
    • 如果服务器没有启动,那么就会报错

实例代码(服务器代码在上面)

package demo07;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Main {
    public static void main(String[] args) throws IOException {
        //创建对象
        Socket socket = new Socket("127.0.0.1",8888);

        //获取输出流对象
        OutputStream os = socket.getOutputStream();

        //输出数据到服务器
        os.write("服务器我是你爹".getBytes());


        //获取输入流并读取服务器发送来的内容
        InputStream is = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);

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

        //关闭
        socket.close();
    }
}

第三章 综合案例

3.1 文件上传案例

文件上传案例分析图解

  1. [客户端]输入流,从硬盘读取文件数据到程序中.
  2. [客户端]输出流,写出文件数据到服务端
  3. [服务端]输入流,读取文件数据到客户端程序
  4. [服务端]输出流,写出文件数据到服务器硬盘中

在这里插入图片描述

案例实现:

//客户端代码
package demo07;

import java.io.*;
import java.net.Socket;

public class Main {
    public static void main(String[] args) throws IOException {
        //创建对象
        Socket socket = new Socket("127.0.0.1",8888);

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\网络编程入门\\客户端の小秘密.txt"));

        //获取输出流对象
        OutputStream os = socket.getOutputStream();

        int length;
        byte[] temp = new byte[1024];
        //输出数据到服务器
        os.write("服务器,我告诉你一个秘密~  等会你翻一下d盘目录".getBytes());

        while ((length = bis.read(temp))!= -1){
            os.write(temp,0,length);
        }


        //获取输入流并读取服务器发送来的内容
        InputStream is = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = is.read(bytes);

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

        //关闭
        socket.close();


    }
}

//服务器端代码
package demo07;

//这是我们的服务器端

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(8888);
        //使用accept获取对象
        Socket client = ss.accept();
        //获取输入流
        InputStream  is = client.getInputStream();
        //使用read,读取客户端发送的数据
        byte[] bytes1 = new byte[1024];
        int len = is.read(bytes1);
        System.out.println(new String(bytes1,0,len));
        byte[] bytes2 = new byte[1024];
        is.read(bytes2);
        BufferedOutputStream bos  = new BufferedOutputStream(new FileOutputStream("D:\\秘密在这里.txt"));
        bos.write(bytes2);
        bos.flush();

        //获取输出流
        OutputStream os =client.getOutputStream();

        //发送给客户端
        os.write("收到了,臭弟弟!".getBytes());

        //释放资源
        client.close();
        ss.close();
    }
}

文件上传案例优化

1 文件名称写死问题

我们可以使用System.currrntTimeMills()."jpg"来命名文件

2 循环接收的问题

服务端只接收一个文件就关闭了,这不合理,所以我们用死循环来不断接收文件

while(true){
    Socket accept = serverSocket.accept();
    ...
}

3 效率问题

服务端,在接收大文件的时候,可能耗费几秒钟的时间,此时不能接收其他用户上传吗,所以,使用多线程技术优化,代码如下:

new Thread(new Runnable() {
            @Override
            public void run() {
                //里面放文件保存的代码,这样就可以在提高服务器运行的效率,里面的错误用try...catch
            }
        }).start();

4 read方法阻塞问题


while ((length = bis.read(temp))!= -1){
            os.write(temp,0,length);
        }//这个会导致服务器不会接收到结束标记,而服务器中的read方法结束的标志就是结束标记,所以说我们要在客户端的Socket类调用shutdownput()方法来手动给服务器接收一个结束标志

3.2 模拟B/S服务器(拓展知识点)

模拟网站服务器,使用浏览器访问自己编写的服务端程序,查看网页效果

实现步骤如图所示

在这里插入图片描述

实现代码:

package demo06;


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建服务器
        ServerSocket server = new ServerSocket(8888);

        //使用accept来获取浏览器对象
        Socket socket = server.accept();

        //使用Socket对象总的getInputStream()方法

        InputStream is = socket.getInputStream();

//        byte[] bytes = new byte[1024];

//        int len;
//        while ((len = is.read(bytes)) != -1){
//            System.out.println(new String(bytes,0,len));
//        }  看过这里的代码就知道要怎么写了

        //接收到客户的请求之后,给浏览器返回一个html文件让他可以看到网页
        BufferedReader br = new BufferedReader(new InputStreamReader(is));

        //把客户端请求信息的第一行读取出来
        String lin = br.readLine();

        //对读取到的信息进行切割
        String[] arr = lin.split("\\s");
        lin = arr[1].substring(1);

        //System.out.println(lin); 这里可以验证我们已经获取了正确的文件路径

        //创建一个本地字节输入流,构造方法中绑定要读取的html路径
        FileInputStream fis = new FileInputStream(lin);


        //使用socket的getOutputStream()方法来将上面的文件写入到客户端

        OutputStream os = socket.getOutputStream();

        //写进http协议响应头,固定写法
        os.write("HTTP/1.1 200 OK\r\n".getBytes());
        os.write("Content-Type:text/html\r\n".getBytes());
        //必须要写入空行,否则浏览器不解析
        os.write("\r\n".getBytes());


        //将数组传进输出流
        int len;
        byte[] bytes = new byte[1024];
        while ((len = fis.read(bytes)) != -1) {
            os.write(bytes, 0, len);
        }

        fis.close();
        br.close();
        socket.close();
        server.close();

    }
}

注意: 如果图片中有图片,那么浏览器就会单独开启一个线程,读取服务器的图片,我们必须让服务器一直处于监听状态,这样才能正确加载

所以改进一下代码:

package demo06;


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建服务器
        ServerSocket server = new ServerSocket(8888);

        while(true){
            //使用accept来获取浏览器对象
            Socket socket = server.accept();

            new Thread(() -> {
                try {
                    //使用Socket对象总的getInputStream()方法

                    InputStream is = socket.getInputStream();


                    //接收到客户的请求之后,给浏览器返回一个html文件让他可以看到网页
                    BufferedReader br = new BufferedReader(new InputStreamReader(is));

                    //把客户端请求信息的第一行读取出来
                    String lin = br.readLine();

                    //对读取到的信息进行切割
                    String[] arr = lin.split("\\s");
                    lin = arr[1].substring(1);

                    System.out.println(lin);

                    //创建一个本地字节输入流,构造方法中绑定要读取的html路径
                    FileInputStream fis = new FileInputStream(lin);


                    //使用socket的getOutputStream()方法来将上面的文件写入到客户端

                    OutputStream os = socket.getOutputStream();

                    //写进http协议响应头,固定写法
                    os.write("HTTP/1.1 200 OK\r\n".getBytes());
                    os.write("Content-Type:text/html\r\n".getBytes());
                    //必须要写入空行,否则浏览器不解析
                    os.write("\r\n".getBytes());


                    //将数组传进输出流
                    int len;
                    byte[] bytes = new byte[1024];
                    while ((len = fis.read(bytes)) != -1) {
                        //System.out.println(new String(bytes));
                        os.write(bytes, 0, len);
                    }

                    fis.close();
                    br.close();
                    socket.close();
                }catch(IOException e){
                    e.fillInStackTrace();
                }
            }).start();

        }

    }
}

行,否则浏览器不解析
os.write("\r\n".getBytes());

                //将数组传进输出流
                int len;
                byte[] bytes = new byte[1024];
                while ((len = fis.read(bytes)) != -1) {
                    //System.out.println(new String(bytes));
                    os.write(bytes, 0, len);
                }

                fis.close();
                br.close();
                socket.close();
            }catch(IOException e){
                e.fillInStackTrace();
            }
        }).start();

    }

}

}


                   

这篇关于[Java] 网络编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程