java基础学习笔记:一文带你搞懂网络编程

1. 网络编程学习

1.1. 扩展知识点

  1. 网络协议OSI协议:网络协议的划分学习

1.2. 网络协议那些事


五层协议模型
  1. 网络协议按照5层划分可以分为5层:物理层、数据链路层、网络层,传输层,应用层。每一层都有一定的协议标准定义。
  2. 物理层:物理层的协议是根据硬件传输数据的特性制定的协议,由于光纤,电线只能传输0和1二进制数字,物理层的协议有ISO2210,IEE802,IEE8022,这一层只传输二进制数据。
  3. 数据链路层:数据链路层是对电信号用来分组的,比如物理层传来一组数据0101010101001,我们可以把它8个分成一组,16个分成一组,刚开始时,各个公司都有自己的分组格式,后来国际上统一了标准,将一组电信号称为一个数据包,或者叫一个帧,每一个数据帧分成报头head和数据data两部分。head包含18个字节,包含==发送者(源地址,6个字节)== ,==接收者(目标地址,6个字节)== ,数据类型(6个字节)data包含最短46个字节,最长1500个字节)
  4. 数据链路层是对数据进行了简单的封装,用于方便传输数据,解析数据。但是当传输时有一个问题,当处于局域网时,也就是在同一个ip网关内的不同主机可以直接传输数据,但是如果不同网关的主机如何实现通信?==注意:在同一个网关内主机之间的通信是靠广播的方式寻找对方的。== 不在同一个网关内,广播就失效了。这就引出了网络层的协议。
  5. 网络层:网络层有IP协议,如果传输的数据不在同一个网关内,就要需要IP协议了,它是整个OSI协议的核心。只有通过配置IP才能准确获得目标计算机的Mac地址。
  6. 应用层:包含的协议就多了,包含HTTP协议,FTP协议,SMTP协议等。主要用于应用层开发。但是HTTP协议需要TCP协议的支持。

1.3. 应用之间通信

  1. 需要对方的ip地址,
  2. 需要对方应用的逻辑端口号,端口号要绑定应用。

1.4. 网络编程类

  1. InetAddress类
  • 该类构造函数属于private,不可以创建对象,但是里面有大量静态方法。
  • getlocalhost()获取主机名+ip地址。
  • getHostAddress()获取ip地址
  • getHostName()获取主机名,里面的参数可以是任意的,可以是www.baidu.com,将会获取百度服务器的ip地址和主机名。

1.5. UDP协议和TCP协议

  1. UDP协议:无连接通信协议,无论对方是否在线,都可以发送,比如qq的离线发送。不过不安全,会丢数据,例如广播产生卡顿等。好处是传播速度快。消耗资源小。效率高。比如手机发短信,qq发消息。UDP协议会对传输数据大小进行限制,只能传输64kb
  2. TCP协议:面向连接的通信协议,先建立连接,才能发送数据。安全性高,速度慢。连接必须经过三次握手。

1.6. UDP通信

  1. 实现UDP协议的发送端:
  • 实现封装数据的类:java.net.DatagramPacket;将你的数据包装起来
  • 实现数据传输的类:java.net.DatagramSocket;将你的数据包发送出去。
  1. 实现步骤:
  • 创建DatagramPacket对象,封装数据,接收的地址和端口
  • 创建DatagrampSocket对象,
  • 调用DatagrampSocket的send,发送数据包。
  • 关闭资源。
  • DatagramPacket构造方法:DatagramPacket(byte[] buf,int length,InetAddress address,int port);
  • DatagramSocket构造方法:DatagramSocket()空参。send(DatagramPacket d);发送数据。
  • 什么是套接字:==绑定了ip地址和端口号的网络对象。==
 1public static void main(String[] args) throws IOException {
 2    //创建数据包对象,封装要发送的数据,接收端Ip,端口号
 3    byte[] data="123udp".getBytes();
 4    //创建一个InetAddress对象,封装自己的Ip地址
 5    InetAddress inetAddress =InetAddress.getByName("127.0.0.1");
 6    DatagramPacket dp =new DatagramPacket(data, data.length,inetAddress,8899);
 7    //创建DatagramSocket对象,
 8    DatagramSocket datagramSocket=new DatagramSocket();
 9    datagramSocket.send(dp);
10    datagramSocket.close();
11}
  1. 实现UDP接收端
  • 实现封装数据包 java.net.DatagramPacket  将数据接收
  • 实现输出传输 java.net.DatagramSocket  接收数据包
  1. 实现步骤:
  • 创建DatagramSocket对象,绑定端口号,要与发送的端口号一致。
  • 创建字节数组,接收发来的数据。
  • 创建数据包对象DatagrampPocket接收数据。
  • 调用DatagrampSocket对象方法,receive(DatagramPacket, dp)接收数据,数据放在数据包中。
  • 拆包,包含:发送端的IP,接收到的字节个数,发送端的端口号。拆包时,可以通过DatagramPacket获取数据的长度getLength,然后打印该长度的数据。通过getAddress()获取发送端的ip地址对象InetAddress,用getPort()获取发送端的端口。发送端端口一般是操作系统指定的。
  • 关闭资源。
 1public static void main(String[] args) throws IOException {
 2    //创建数据包传输对象,并绑定端口号
 3    DatagramSocket ds =new DatagramSocket(8899);
 4    //创建字节数组
 5    byte[] buf=new byte[1024];
 6    //创建数据包对象传递字节数组
 7    DatagramPacket dp =new DatagramPacket(buf, buf.length);
 8    //调用ds的receive传递数组
 9    ds.receive(dp);
10    String ip =dp.getAddress().getHostAddress();
11    int port =dp.getPort();
12    int length=dp.getLength();
13    System.out.println(new String(buf,0,length)+"..."+ip+"..."+port);
14    ds.close();
15}
  1. 发送端实现不间断的输入发送数据
 1public static void main(String[] args) throws IOException {
 2      Scanner sc=new Scanner(System.in);
 3    DatagramSocket datagramSocket=new DatagramSocket();
 4    InetAddress inetAddress =InetAddress.getByName("127.0.0.1");
 5    while(true){
 6        String message=sc.nextLine();
 7        byte[] data=message.getBytes();
 8        DatagramPacket dp =new DatagramPacket(data, data.length,inetAddress,8899);
 9        datagramSocket.send(dp);
10    }
11}
  1. 实现永不停歇的接收端
 1public static void main(String[] args) throws IOException {
 2    //创建数据包传输对象,并绑定端口号
 3    DatagramSocket ds =new DatagramSocket(8899);
 4    //创建字节数组
 5    byte[] buf=new byte[1024];
 6    //创建数据包对象传递字节数组
 7    while(true){
 8        DatagramPacket dp =new DatagramPacket(buf, buf.length);
 9    //调用ds的receive传递数组
10        ds.receive(dp);
11           String ip =dp.getAddress().getHostAddress();
12        int port =dp.getPort();
13        int length=dp.getLength();
14            System.out.println(new String(buf,0,length)+"..."+ip+"..."+port);
15    }
16}

1.7. TCP通信

  1. TCP通信和UDP通信大致相同,分为客户端和服务端,但是TCP通信是需要先进行连接的,只有连接成功才能通信,不过TCP建立连接后,也就建立了IO流,可以通过IO流完成数据的传输。
  2. TCP客户端程序的类 java.net.Socket:构造方法有:Socket(String host,int port)用于传递服务器Ip和端口号。该构造方法只要运行就会和服务器连接,如果连接失败就会抛出异常。
  3. Socket类有两个方法,getOutputStream(),返回类型是OutputStream(),返回套接字的输出流:作用是将数据输出,输出到服务器。getInputStream(),返回套接字的输入流,返回类型是InputStream.这些方法都是从Socket中获取的,不是new出来的。
1public static void main(String[] args) throws IOException {
2    Socket socket=new Socket("120.27.60.73", 8899);
3    OutputStream outStream=socket.getOutputStream();
4    outStream.write("789456".getBytes());
5    socket.close();
6}
  1. TCPServer端:表示服务器程序的类:java.net.ServerSocket,构造方法有:ServerSocket(int port)传递端口号,相当于监听该端口号。
  2. 一个很重要的事情是,由于多个客户端向服务端传值,服务端无法辨认是哪个客户端发来的值,解决方法是:必须获取客户端的套接字对象Socket,用accept()方法获取,返回类型是Socket
 1public static void main(String[] args) throws IOException {
 2    ServerSocket serverSocket=new ServerSocket(8899);
 3    Socket socket=serverSocket.accept();
 4    InputStream inputStream=socket.getInputStream();
 5    byte[] buf=new byte[1024];
 6    int len=inputStream.read(buf);
 7    System.out.println(new String(buf,0,len));
 8    //服务器返回数据
 9    OutputStream out=socket.getOutputStream();
10    out.write("nihao".getBytes());
11    socket.close();
12    serverSocket.close();
13}

1.8. TCP通信图片上传服务器多线程解决方案

  1. TCP通信图片上传客户端代码
  • ==问题==:客户端上传图片,客户端可能有多个同时上传,每次上传图片会传递一个Socket,多个上传后会引起服务器端对Socket混乱,不知是谁的Socket对象。
  • ==解决== :多线程解决方案,每次传来一个Socket对象,我们就为他开启一个线程,这样就不会造成混乱。

Socket多线程解决方案
 1public class TCPClient {
 2    public static void main(String[] args) throws IOException{
 3        Socket socket = new Socket("127.0.0.1", 8000);
 4        //获取字节输出流,图片写到服务器
 5        OutputStream out = socket.getOutputStream();
 6        //创建字节输入流,读取本机上的数据源图片
 7        FileInputStream fis = new FileInputStream("c:\\t.jpg");
 8        //开始读写字节数组
 9        int len = 0 ;
10        byte[] bytes = new byte[1024];
11        while((len = fis.read(bytes))!=-1){
12            out.write(bytes, 0, len);
13        }
14        //给服务器写终止序列
15        socket.shutdownOutput();
16
17        //获取字节输入流,读取服务器的上传成功
18        InputStream in = socket.getInputStream();
19
20        len = in.read(bytes);
21        System.out.println(new String(bytes,0,len));
22
23        fis.close();
24        socket.close();
25    }
26}
  1. 将上传文件接收代码放到一个线程中。这样就保证一个线程接收一个Socket。
 1public class Upload implements Runnable{
 2
 3    private Socket socket;
 4
 5    public Upload(Socket socket){this.socket=socket;}
 6
 7    public void run() {
 8        try{
 9            //通过客户端连接对象,获取字节输入流,读取客户端图片
10            InputStream in = socket.getInputStream();
11            //将目的文件夹封装到File对象
12            File upload = new File("d:\\upload");
13            if(!upload.exists())
14                upload.mkdirs();
15
16            //防止文件同名被覆盖,从新定义文件名字
17            //规则:  域名+毫秒值+6位随机数
18            String filename="itcast"+System.currentTimeMillis()+new Random().nextInt(999999)+".jpg";
19            //创建字节输出流,将图片写入到目的文件夹中                         
20            FileOutputStream fos = new FileOutputStream(upload+File.separator+filename);
21            //读写字节数组
22            byte[] bytes = new byte[1024];
23            int len = 0 ;
24            while((len = in.read(bytes))!=-1){
25                fos.write(bytes, 0, len);
26            }
27            //通过客户端连接对象获取字节输出流
28            //上传成功写回客户端
29            socket.getOutputStream().write("上传成功".getBytes());
30
31            fos.close();
32            socket.close();
33        }catch(Exception ex){
34
35        }
36    }
37
38}
  1. 服务器端,调用Thread,并将Socket作为参数传递给Upload类。并开启一个新线程。并让服务端永久的监听端口。
 1public class TCPThreadServer {
 2    public static void main(String[] args) throws IOException{
 3        ServerSocket server = new ServerSocket(8000);
 4        while(true){
 5            //获取到一个客户端,必须开启新线程
 6            Socket socket = server.accept();
 7            new Thread( new Upload(socket) ).start();
 8        }
 9
10    }
11}

1.9. 一些技巧

  1. 公司域名+毫秒值+随机六位数,防止文件重名
1String fileName="xdclass"+System.currentTimeMillis()+new Ranadow.nextInt(999999)+".jpg";
  1. File.separator,跨系统生成目录分隔符。
1File myFile = new File("C:" + File.separator + "tmp" + File.separator, "test.txt");
  1. 此外还有File.pathSeparator生成字符分号;
  2. 2,3总结:File类的两个常量,一个是斜杠,一个是分号。