欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JAVA-18-网络编程

程序员文章站 2022-07-12 08:06:52
...

网络编程(又名socket编程,套接字编程)
(1)Socket通信——网络编程三要素
  ①ip: 一个计算的标示(找到这个计算机)
    特殊的IP地址:
      127.0.0.1 本地回环地址 用来做一些本地测试
      ping IP地址 ; 用来检测本机是否可以和指定的IP地址的计算机可以进行正常通讯
      ipconfig 用来查看IP地址
      xxx.xxx.xxx.255 广播地址
  ②端口: 应用程序都会对应一个端口,用来进行通信,有效端口:0~65535,其中0~1024系统使用或保留端口。不同于物理端口——物理设备对应的端口, 网卡口
  ③协议: 总共有2种协议(TCP,UDP)
    a. UDP
      把数据打成一个数据包 , 不需要建立连接
      数据包的大小有限制不能超过64k
      因为无连接,所以属于不可靠协议(可能丢失数据)
      效率高
    b. TCP
      需要建立连接,形成连接通道
      数据可以使用连接通道直接进行传输,无大小限制
      因为有链接,所以属于可靠协议
      效率低

(2)InetAddress: IP地址的封装类
为了方便我们对IP地址的获取和操作,java提供了一个InetAddress 类供我们使用,此类表示互联网协议 (IP) 地址。
  InetAddress类的常见功能:
    public static InetAddress getByName(String host)( host: 可以是主机名,也可以是IP地址的字符串表现形式)
    public String getHostAddress()返回 IP 地址字符串(以文本表现形式)。
    public String getHostName()获取此 IP 地址的主机名。

package com.edu_01;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressDemo {
    public static void main(String[] args) throws Exception {
        InetAddress address = InetAddress.getByName("shengjie-PC");

        System.out.println(address.getHostAddress());
        System.out.println(address.getHostName());
    }
}
//      192.168.1.100
//      shengjie-PC

(3)Socket原理机制
  Socket套接字:网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
  Socket原理机制:
    通信的两端都有Socket。
    网络通信其实就是Socket间的通信。
    数据在两个Socket间通过IO传输。
(4)UDP协议讲解
UDP通信步骤:
  a. 发送端步骤:
    ①创建UDP发送数据端Socket对象
      DatagramSocket ds = new DatagramSocket();
    ②创建数据包,并给出数据,把数据打包
      DatagramPacket dp = new DatagramPacket(buf, length, address, port); 其中参数buf-要发送的数据的字节数组,参数length要发送数据的字节数组的长度,参数address要发送给的电脑的ip地址的InetAddress包装形式,参数port是要发送给的电脑上指定应用对应的端口号
    ③通过Socket对象发送数据包
      ds.send(dp);
    ④释放资源
      ds.close();
      
  b. 接收端步骤:
    ①创建UDP接收数据端Socket对象
      DatagramSocket ds = new DatagramSocket(8888);发送端与接收端定义的端口号必须一致
    ②创建一个接收数据的数据包(空包)
      byte[] buf = new byte[1024];
      int length = buf.length;
      DatagramPacket dp = new DatagramPacket(buf, length);
    ③接收数据,数据在数据包中
      ds.receive(dp);
    ④解析数据包,并把数据显示在控制台
      byte[] data = dp.getData();
      int len = dp.getLength();
      System.out.println(new String(data,0,len));
    ⑤释放资源
      ds.close();

  使用UDP通信时,客户端和服务器运行的顺序不一样将会导致结果不一样;当先运行服务器时,客户端传来的数据可以接收到,但是当先运行客户端时,客户端发出的数据无法被正常接收到,因为此时的服务器没有运行,但是这样并不会有异常抛出。
  
练习:键盘录入数据实现数据的动态发送

package com.edu_03;
//客户端UdpClient类
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;

public class UdpClient {
    public static void main(String[] args) throws Exception {
        //1.创建发送端的socket对象
        DatagramSocket ds = new DatagramSocket();

        InetAddress address = InetAddress.getByName("192.168.20.254");
        int port = 9999;

        //2.创建键盘录入对象
        Scanner sc = new Scanner(System.in);
        String line;
        while ((line=sc.nextLine())!=null) {
            //键盘录入的数据line
            byte[] buf = line.getBytes();
            int length = buf.length;
            DatagramPacket dp = new DatagramPacket(buf, length, address, port);
            //发送数据包
            ds.send(dp);
        }

        //释放资源
        ds.close(); 
    }
}
package com.edu_03;
//服务器UdpServer类
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpServer {
    public static void main(String[] args) throws Exception {
        //创建接收端的socket对象
        DatagramSocket ds = new DatagramSocket(9999);

        //接受来自客户端的数据
        while (true) {
            //创建数据包
            byte[] buf = new byte[1024];
            int length = buf.length;
            DatagramPacket dp = new DatagramPacket(buf, length);

            //接受来自客户端的数据
            ds.receive(dp);

            //解析数据包中的数据
            byte[] data = dp.getData();
            int len = dp.getLength();
            System.out.println(new String(data, 0, len));
        }
    }
}

  利用上面的Java文件我们可以在DOS窗口实现多人聊天,首先打开窗口利用javac对两个Java文件进行编译,然后运行服务器,接着打开多个客户端窗口,这样在便实现了多人聊天效果。然而这样操作很麻烦,并且一个窗口发的消息只能在本窗口和服务器窗口看见,其他客户端窗口不可见,这与实际是有差别的。可以考虑利用多线程的方式进行改进,让客户端与服务器共存,并且使用广播地址解决消息共享问题,同时获取ip以区分消息是谁发来的(获取发送信息的人的ip地址String ip = dp.getAddress().getHostAddress();)。代码如下:

package com.edu_04;
//线程UdpCilent 
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class UdpCilent implements Runnable{

    //构造传参
    DatagramSocket ds ;
    public UdpCilent(DatagramSocket ds){
        this.ds = ds;
    }

    @Override
    public void run() {
        try {
            InetAddress address = InetAddress.getByName("192.168.20.255");
            int port = 9999;

            //2.创建键盘录入对象
            Scanner sc = new Scanner(System.in);
            String line;
            while ((line=sc.nextLine())!=null) {
                //键盘录入的数据line
                byte[] buf = line.getBytes();
                int length = buf.length;
                DatagramPacket dp = new DatagramPacket(buf, length, address, port);
                //发送数据包
                ds.send(dp);
            }

            //释放资源
            ds.close();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
package com.edu_04;
//线程UdpServer 
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpServer implements Runnable{

    //构造传参
    DatagramSocket ds ;
    public UdpServer(DatagramSocket ds){
        this.ds = ds;
    }

    @Override
    public void run() {
        try {
            //接受来自客户端的数据
            while (true) {
                //创建数据包
                byte[] buf = new byte[1024];
                int length = buf.length;
                DatagramPacket dp = new DatagramPacket(buf, length);

                //接受来自客户端的数据
                ds.receive(dp);

                //获取发送信息的人的ip地址
                String ip = dp.getAddress().getHostAddress();

                //解析数据包中的数据
                byte[] data = dp.getData();
                int len = dp.getLength();
                System.out.println(ip+":"+new String(data, 0, len));

            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

(5)TCP协议讲解
  TCP通信步骤:
    a. 客户端(发送端):
      ①创建TCP协议发送端Socket对象
        Socket sk = new Socket(“192.168.20.254”, 10086);其中第一个参数是要发送给电脑的ip地址或者主机名,第二个参数是要发送给电脑的指定的端口号
      ②获取输出流,并写数据
        OutputStream outputStream = sk.getOutputStream() ;
        写数据调用输出流的write(…)方法
      ③释放资源
        sk.close() ;
        
    b. 接收端:
      ①创建TCP协议接收端Socket对象
        ServerSocket ss = new ServerSocket(10086);
        发送端与接收端定义的端口号必须一致
      ②监听客户端连接
        Socket sk = ss.accept() ;
      ③获取输入流,并读取数据,显示在控制台
        InputStream is = sk.getInputStream();
        byte[] buf = new byte[1024];
        读数据调用输入流read()方法 int len = is.read(buf);
        
        获取ip地址 InetAddress inetAddress = sk.getInetAddress() ;
        String ip = inetAddress.getHostAddress() ;
        // 输出System.out.println(ip + “发来数据是: ” + new String(bytes , 0 , len));
      ④释放资源
        sk.close() ;
        
TCP必须先运行服务器,在运行客户端。
先运行客户端的时候抛出异常:java.net.ConnectException: Connection refused: connect 因为服务器端还没有准备好跟客户端建立连接呢,所以抛出异常,tcp协议是面向连接的一个协议。

练习:用TCP协议写一个数据的发送和接收,接收端接收到数据之后给发送端一个反馈

package com.edu_06;
//TcpClient 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

public class TcpClient {
    public static void main(String[] args) throws Exception {
        //创建tcp发送端的sockt对象
        Socket sk = new Socket("192.168.20.254", 10000);

        //从通道中获取输出流
        OutputStream os = sk.getOutputStream();

        //网通道中写数据
        os.write("你是谁?".getBytes());

        //接受来自服务器端的反馈
        InputStream is = sk.getInputStream();
        //解析is
        byte[] buf = new byte[1024];
        int len = is.read(buf);
        System.out.println(new String(buf, 0, len));

        //释放资源
        sk.close();
    }
}
package com.edu_06;
//TcpServer 
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 Exception {
        //创建服务器端的socket对象
        ServerSocket ss = new ServerSocket(10000);

        //监听来自客户端的连接
        Socket sk = ss.accept();

        //从通道中获取输入流读取数据
        InputStream is = sk.getInputStream();

        //解析is
        byte[] buf = new byte[1024];
        int len = is.read(buf);
        System.out.println(new String(buf, 0, len));

        //给客户端一个反馈
        OutputStream os = sk.getOutputStream();
        os.write("你猜".getBytes());

        //释放资源
        sk.close();
    }
}

练习:上传文本文件

package com.edu_09;
//TcpClient
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
/**
 *  客户端:
 *    1.读取文本文件,一次读取一行
 *    2.将读取到的内容写入通道,一次写一行
 *    3.释放资源
 *  服务器端:   
 *    1.从通道中读取数据,一次读取一行
 *    2.输出
 */
public class TcpClient {
    public static void main(String[] args) throws Exception {
        //创建socket对象
        Socket sk = new Socket("192.168.20.254", 2000);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));

        //读取文本一次读取一行
        BufferedReader br = new BufferedReader(new FileReader("a.txt"));
        String line;
        while ((line=br.readLine())!=null) {
            //line就是我读取到的数据,我需要将这个数据写入通道,一次写一行
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        //释放资源
        br.close();
        bw.close();
        sk.close();
    }
}
package com.edu_09;
//TcpServer 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
    public static void main(String[] args) throws Exception {
        //创建服务器端的socket对象
        ServerSocket ss = new ServerSocket(2000);

        //监听来自于客户端的连接
        Socket sk = ss.accept();

        //创建BufferedReader一次读取一行数据
        BufferedReader br = new BufferedReader(new InputStreamReader(sk.getInputStream()));
        String line;
        while ((line=br.readLine())!=null) {
            System.out.println(line);
        }

        //释放资源
        br.close();
        sk.close(); 
    }
}

  当一个服务器被提起来之后可能有多个客户端与之相连,如果大家一起上传文件,那么一个一个上传时速度会很慢,那么我们就考虑利用多线程每一个人过来传文件就给他开启一个线程,这样问题迎刃而解。
  在这之前先学习一个UUDI类,
    String fileName = UUID.randomUUID().toString();返回的是一个包含若干“-”、字母和数字的随机名字
    fileName = fileName.replaceAll(“-“, “”)+”.txt”;去掉所有“-”。
利用多线程改进后的代码如下:

package com.edu_10;
//TcpClient 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class TcpClient {
    public static void main(String[] args) throws Exception {
        //创建socket对象
        Socket sk = new Socket("192.168.20.254", 2000);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));

        //读取文本一次读取一行
        BufferedReader br = new BufferedReader(new FileReader("InetAddressDemo.java"));
        String line;
        while ((line=br.readLine())!=null) {
            //line就是我读取到的数据,我需要将这个数据写入通道,一次写一行
            bw.write(line);
            bw.newLine();
            bw.flush();
        }

        //释放资源
        br.close();
        bw.close();
        sk.close();
    }
}
package com.edu_10;
//UUIDUtils类,用于给上传的文件命名,防止文件名重复无法上传
import java.util.UUID;
import org.junit.Test;

public class UUIDUtils {
    @Test
    public static String getFileName(){
        String fileName = UUID.randomUUID().toString();
        fileName = fileName.replaceAll("-", "")+".txt";
        return fileName;
    }
}
package com.edu_10;
//ServerThread线程,用于上传文件
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class ServerThread implements Runnable{
    Socket sk;
    public ServerThread(Socket sk){
        this.sk = sk;
    }

    @Override
    public void run() {
        try {
            //创建BufferedReader一次读取一行数据
            BufferedReader br = new BufferedReader(new InputStreamReader(sk.getInputStream()));
            BufferedWriter bw = new BufferedWriter(new FileWriter(UUIDUtils.getFileName()));
            String line;
            while ((line=br.readLine())!=null) {
                bw.write(line);
                bw.newLine();
                bw.flush();
                //System.out.println(line);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
package com.edu_10;
//TcpServer服务器
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
    public static void main(String[] args) throws Exception {
        //创建服务器端的socket对象
        ServerSocket ss = new ServerSocket(2000);

        while (true) {
            //监听来自于客户端的连接
            Socket sk = ss.accept();
            //启动一个子线程,去执行复制文件的动作
            new Thread(new ServerThread(sk)).start();
        }
    }
}

  学习过UDP、TCP协议等网络编程,我们可以深刻的认识到Socket间的通信,底层是通过IO流传输数据,与IO流联系紧密。在以后使用的软件当中基本都是udp和tcp混用的。