第十节 Java 网络编程

1. 网络编程基础

1.1 网络通信协议

在计算机网络中,连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交互。
TCP/IP协议(又称TCP/IP协议簇)是一组用于实现网络互联的通信协议。
基于TCP/IP协议参考模型的网络层次结构
(1)链路层:用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、双绞线提供的驱动
(2)网络层:网络层是整个TCP/IP协议的核心,它用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络
(3)运输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议
(4)应用层:主要为互联网中的各种网络应用提供服务

1.2 IP地址和端口号

IP作用:通过这个标识来寻找计算机
port端口号作用:用来标识一个应用程序的
举个例子:
一个邮件到达你的手中,会先通过你的地址找到你这个位置找到叫张三的这个人,这个位置就类似IP地址,但是可能这个地方有很多人都叫张三,我们再通过电话号码来确定具体是哪个张三的,这里面的张三类似应用程序,这个电话号码就类似端口号。
IP 地址的分类:
(1)A类地址:由第一段的网络地址和其余三段的主机地址组成,范围是1.0.0.0到127.255.255.255
(2)B类地址:由前两段网络地址和其余两段的主机地址组成,范围是128.0.0.0到191.255.255.255
(3)B类地址:由前三段网络地址和其余两段的主机地址组成,范围是192.0.0.0到223.255.255.255
(4)D类和E类为特殊地址。
其中127.0.0.1是一个本地回环地址,就是本机,可用来测试。我们可以利用ping 127.0.0.1来测试本机的TCP/IP协议是否正常
端口号是由2个字节表示的,它的取值范围是0~65535,其中0~1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上端口号。

1.3 InetAddress

在JDK中提供了一个与IP地址有关的InetAddress类该类用于封装一个IP地址,并提供了一些列与IP地址相关的方法。
InetAdress类的常用方法:

方法声明功能描述
InetAdress getByName(String host)获取给定主机名的IP地址,host参数表示指定主机
InetAddress getLocalHost()获取本地主机地址
String getHostName()获取本地IP地址的主机名
boolean isReachable(int timeout)判断在限定的时间内指定的IP地址是否可以访问
String getHostAddress()获取字符串格式的原始IP地址

前两个方法是静态方法。
示例如下:

public class InetAdressDemo {
    public static void main(String[] args) throws IOException {
        //通过字符串获取InetAdress对象
        InetAddress remoteAdress=InetAddress.getByName("www.baidu.com");
        //获取本地InetAdress对象
        InetAddress localadress=InetAddress.getLocalHost();
        System.out.println("本机的IP地址为:"+localadress.getHostAddress());
        System.out.println("本机的主机名为:"+localadress.getHostName());
        System.out.println("判断百度在五秒内是否可以访问"+remoteAdress.isReachable(5000));
        System.out.println("百度的IP地址为:"+remoteAdress.getHostAddress());
    }
}

效果如下图:
在这里插入图片描述

1.4 UDP与TCP

(1)UDP
UDP称为用户数据报协议,是无连接通信协议,在数据进行传输时,数据的发送端和接收端不建立逻辑连接。优点:UDP协议消耗小,通信效率高,延迟小,因为这种情况即使丢失一两个数据包,也不会对接收结果影响太大。由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议用UDP
(2)TCP
TCP协议称为传输控制协议,是面向连接的通信协议,在数据传输之前要先建立逻辑连接,然后再传输数据,保证了可靠无差错的数据传输。每次建立连接都要经历“三次握手”。第一次握手,客户端向服务器端发出连接请求,等待服务器确认,第二次握手,服务器端向客户端返回一个回应,通知客户端收到了连接请求;第三次握手,客户端再次向服务器端发送确认信息,确认连接。因此TCP传输速度慢,但是传输的数据比较可靠。
在这里插入图片描述

2. UDP 通信

JDK中提供了一个DatagramPacket类,该类的实例就类似集装箱,用于封装UDP通信中发送或接收的数据。DatagramSocket类,这个类的作用类似于码头,使用这个类的实例对象可以发送和接收DatagramPacket数据报。

2.1 DatagramPacket

在接收端构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。
(1)DatagramPacket(byte[ ] buf,int length)
使用该构造方法在创建DatagramPacket对象时,指定了封装数据的字节数组和数据的大小,没有指定IP地址和端口号,很显然它只能用于接收端。
(2)DatagramPacket(byte[ ] buf,int offset,int length)
该构造方法与第一个构造方法类似也只用于接收端,多的offset参数用于指定一个数组中发送数据的偏移量,即从offset位置开始
(3)DatagramPacket(byte[ ] buf ,int length,InetAddress addr,int port)
使用该构造方法创建DatagramPacket对象时,指明了要发送的数据及数据大小,同时也指明了目标ip地址和端口号。该对象通常用在发送端。
(4)DatagramPacket(byte[ ] buf ,int offset,int length,InetAddress addr,int port)
与第三个类似,不过多了个offset,用来表示从offset位置开始发送数据
DatagramPacket类中常用方法:

方法声明功能描述
InetAddress getAddress()该方法用于返回发送端或者接收端的IP地址,如果是发送端的DatagramPacket对象,就返回接收端的IP地址;反之,返回发送端的IP地址
int getPort()该方法用于返回发送端或者接收端的端口号,如果是发送端的DatagramPacket对象,就返回接收端的端口号;反之,返回发送端的端口号
byte[ ] getData()该方法用于返回将要接收或者将要发送的数据,如果是发送端的DatagramPacket对象,就返回将要发送的数据;反之,返回接收到的数据
int getLength()该方法用于返回将要接收或者将要发送的数据的长度,如果是发送端的DatagramPacket对象,就返回将要发送的数据的长度;反之,返回接收到的数据的长度

2.2 DatagramSocket

用于创建发送端和接收端对象。
构造方法如下:
(1)DatagramSocket()
用于创建发送端的DatagramSocket对象,这里并没有指定端口号,系统会分配一个没有被其他网络程序所使用的端口号
(2)DatagramSocket(int port)
用于创建接收端DatagramSocket对象,也可以创建发送端的DatagramSocket对象,在创建接收端的时候必须要有端口号,用于监听指定端口
(3)DatagramSocket(int port,InetAddress addr)
不仅指定了端口号,还指定了ip地址,这个构造方法一般用于计算机有多块网卡的情况。

DatagramSocket类中的常用方法:

方法声明功能描述
void receive(DatagramPacket p)该方法用于接收DatagramPacket数据报,在接收到数据之前会一直处于阻塞状态,如果发送消息的长度比数据报长,则消息会被截取
void send(DatagramPacket p)该方法用于发送DatagramPacket数据报,发送的数据报中包含将要发送的数据、数据的长度、远程主机的IP地址和端口号
void close()关闭当前的Socket,通知驱动程序释放这个Socket保留的资源

UDP网络程序:

//接收端
public class UDPReceiver {
    public static void main(String[] args) throws IOException {
        //定义一个指定端口号为8900的接收端DatagramSocket对象
        DatagramSocket server=new DatagramSocket(8900);
        byte[] buf=new byte[1024];
        //定义一个DatagramPacket数据报对象
        DatagramPacket packet=new DatagramPacket(buf,buf.length);
        System.out.println("等待接收数据.....");
        while (true){
            //等待接收数据
            server.receive(packet);
            String str=new String(packet.getData(),0,packet.getLength());
            System.out.println(packet.getAddress()+":"+packet.getPort()+"发送消息"+str);
        }
    }
}
//发送端
public class UDPsend {
    public static void main(String[] args) throws IOException {
        //定义一个指定端口号为3000的发送端对象
        DatagramSocket client=new DatagramSocket(3000);
        //定义要发送的数据
        String str="hello world";
        //定义一个DatagramPacket数据报对象,封装发送端信息以及发送地址
        DatagramPacket packet=new DatagramPacket(str.getBytes(),str.getBytes().length,InetAddress.getByName("localhost"),8900);
        System.out.println("开始发送信息.....");
        client.send(packet);
        client.close();
    }
}

效果如下:
在这里插入图片描述
在这里插入图片描述
在这里的DatagramPacket所能接收的数据大小是有限的,理论最大65507字节,但是实际要比这少的多,在许多平台最大时8K。
我们 可以使用netstat -anb 这个命令来查看端口号占用情况
小案例:聊天程序:

public class ChatRoom {
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        System.out.print("请输入聊天服务当前启动端口号:");
        int serverPort=sc.nextInt();
        System.out.print("请输入聊天服务发送信息对象的目标端口号:");
        int targetPort=sc.nextInt();
        System.out.println("聊天系统初始化完成并启动!!!");
        try{
            //创建聊天程序收发平台DatagramSocket对象
            DatagramSocket socket=new DatagramSocket(serverPort);
            //分别启动信息接收端和发送端程序
            new Thread(new ChatReceiver(socket),"接收服务").start();
            new Thread(new ChatSend(socket,targetPort),"发送服务").start();
        }catch (SocketException e){
            e.printStackTrace();
        }
    }
}
public class ChatReceiver implements Runnable{
    private DatagramSocket server;
    public ChatReceiver(DatagramSocket server){
        this.server=server;
    }

    @Override
    public void run() {
        try {
            //创建DatagramPacket数据包接收对象
            byte[] buf=new byte[1024];
            DatagramPacket packet=new DatagramPacket(buf,buf.length);
            while (true){
                server.receive(packet);
                //显示并打印聊天信息
                String str=new String(packet.getData(),0,packet.getLength());
                System.out.println("收到"+packet.getAddress()+":"+packet.getPort()+"发送的数据:"+str);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
public class ChatSend implements Runnable{
    //聊天程序信息发送平台DatagramSocket对象
    private DatagramSocket client;
    private int targetPort;
    public ChatSend(DatagramSocket client,int targetPort){
        this.client=client;
        this.targetPort=targetPort;
    }

    @Override
    public void run() {
        try {
            //获取键盘输入对象
            Scanner sc=new Scanner(System.in);
            while (true){
                String data=sc.nextLine();
                //封装数据到DatagramPacket数据包发送对象中
                byte[] buf=data.getBytes();
                DatagramPacket packet=new DatagramPacket(buf,buf.length, InetAddress.getByName("127.0.0.255"),targetPort);
                client.send(packet);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

3. TCP 通信

严格区分客户端和服务器端。在JDK 中提供两个用于实现TCP程序的类,一个是ServerSocket类,用于表示服务器端;另一个是Socket类用于表示客户端。必须保证服务器端先开开。

3.1 ServerSocket

JDK的java.net包中提供了一个ServerSocket类,该类的实例对象可以实现一个服务器端的程序。
ServerSocket的构造方法:
(1)ServerSocket()
使用该构造方法在创建ServerSocket对象时,并没有指定端口号,因此该对象不监听任何端口,不能直接使用,使用时还需要调用bind(SocketAddress endpoint)方法将其绑定到指定的端口号上
(2)ServerSocket(int port)
使用该构造方法在创建ServerSocket对象时,可以将其绑定到指定的端口号上。如果port参数值为0,此时系统就会分配一个未被其他程序占用的端口号。
(3)ServerSocket(int port,int backlog)
该构造方法中的backlog参数是用于指定在服务器忙时,可以与之保持连接请求的等待客户端数量,如果没有指定这个参数,默认为50.
(4)ServerSocket(int port,int backlog, InetAddress bindAddr)
适用于计算机有多个网卡。
ServerSocket的常用方法

方法声明功能描述
Socket accept()该方法用于等待客户端的连接,在客户端连接之前一直处于阻塞状态,如果有客户端连接就会返回一个与之对应的Socket对象
InetAddress getInetAddress()该方法用于返回一个InetAddress对象,该对象中封装了ServerSocket绑定的IP地址
boolean isClosed()该方法用于判断ServerSocket对象是否为关闭状态,如果是关闭状态则返回true,反之返回false
void bind(SocketAddress endpoint)该方法用于将ServerSocket对象绑定到指定的IP地址和端口号,其中参数endpoint封装了IP地址和端口号

3.2 Socket

JDK提供了一个Socket类,用于实现TCP客户端程序
构造方法:
(1)Socket()
使用这个构造方法没有指定IP地址和端口号,就说明只是创建了客户端对象,没有连接任何服务器。通过该构造方法创建对象后还需要调用connect(SocketAddress endpoint)方法,才能完成与指定服务器端的连接。
(2)Socket(String host,int port)
该构造方法会根据参数去连接指定地址和端口上运行的服务器程序
(3)Socket(InetAddress address,int port)
address用于接收一个InetAddress类型的对象。与第二个构造方法类似

Socket常用方法:

方法声明功能描述
int getPort()返回此Socket连接的远程服务端的端口号
InetAddress getLocalAddress()用于获取Socket对象绑定的本地IP地址,并将IP地址封装成InetAddress类型的对象返回
void close()该方法用于关闭Socket连接,结束本次通信。在关闭Socket之前,应将与Socket相关的所有输出输入流全部关闭,这是因为一个良好的程序应该在执行完毕时释放所有资源
InputStream getInputStream()该方法返回一个InputStream类型的输入流对象。如果该对象是由服务器端的Socket返回,就用于读取客户端发送的数据;反之,用于读取服务器端发送的数据
OutputStream getOutputStream()该方法返回一个OutputStream类型的输出流对象。如果该对象是由服务器端的Socket返回,就用于向客户端发送数据;反之,用于向服务器端发送数据。

简单的TCP网络程序

public class TCPServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        ServerSocket serverSocket=new ServerSocket(7788);
        while (true){
            //调用ServerSocket的accept()方法开始接收数据
            Socket client=serverSocket.accept();
            System.out.println("与客户端连接成功,开始进行数据交互!");
            //获取客户端的输出流
            OutputStream os=client.getOutputStream();
            //当客户端连接到服务器端,向客户端输出数据
            os.write(("服务器端向客户端做出响应!".getBytes()));
            //模拟与客户端交互耗时
            Thread.sleep(5000);
            //关闭流和Socket连接
            os.close();
            client.close();
        }
    }
}

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //创建一个Socket并连接到指定服务器
        Socket client=new Socket(InetAddress.getLocalHost(),7788);
        //获取服务器端返回的输入流并打印
        InputStream is=client.getInputStream();
        byte[] buf=new byte[1024];
        int len=is.read(buf);
        while (len!=-1){
            System.out.println(new String(buf,0,len));
            len=is.read(buf);
        }
        //关闭流
        is.close();
        client.close();
    }
}

多线程的TCP网络程序

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //创建一个Socket并连接到指定服务器
        Socket client=new Socket(InetAddress.getLocalHost(),7788);
        //获取服务器端返回的输入流并打印
        InputStream is=client.getInputStream();
        byte[] buf=new byte[1024];
        int len=is.read(buf);
        while (len!=-1){
            System.out.println(new String(buf,0,len));
            len=is.read(buf);
        }
        //关闭流
        is.close();
        client.close();
    }
}

public class MultithreadingTCPServer {
        public static void main(String[] args) throws IOException, InterruptedException {
            ServerSocket serverSocket=new ServerSocket(7788);
            while (true){
                //调用ServerSocket的accept()方法开始接收数据
                Socket client=serverSocket.accept();
                Thread thread=new Thread(()->{
                    try {
                        //获取当前连接客户端的端口号
                        int port=client.getPort();
                        System.out.println("与端口号为"+port+"的客户端连接成功,开始进行数据交互!");
                        //获取客户端的输出流
                        OutputStream os=client.getOutputStream();
                        //当客户端连接到服务器端,向客户端输出数据
                        os.write(("服务器端向客户端做出响应!".getBytes()));
                        //模拟与客户端交互耗时
                        Thread.sleep(5000);
                        System.out.println("结束与客户端数据交互");
                        //关闭流和Socket连接
                        os.close();
                        client.close();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                });
               thread.start();
            }
        }
}

效果如下:
在这里插入图片描述
TCP实现文件的上传

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

    @Override
    public void run() {
        //1.处理客户端请求,进行上传文件保存
        String ip=socket.getInetAddress().getHostAddress();
        int count=1;
        try{
            //创建图片上传保存目录
            File parentFile=new File("E:\\upload\\");
            if (!parentFile.exists()){
                parentFile.mkdir();
            }
            //把客户端的IP地址作为文件上传的文件名
            File file=new File(parentFile,ip+"("+count+").jpg");
            while (file.exists()){
                file=new  File(parentFile,ip+"("+count++ +").jpg");
            }
            //通过客户端输入流读取上传图片写入到指定目录
            InputStream in=socket.getInputStream();
            FileOutputStream fos=new FileOutputStream(file);
            byte[] buf=new byte[1024];
            int len=0;
            while ((len=in.read(buf))!=-1){
                fos.write(buf,0,len);
            }
            //服务器端向客户端做出响应
            OutputStream out=socket.getOutputStream();
            out.write("上传成功".getBytes());
            //关闭流和Socket连接
            in.close();
            fos.close();
            socket.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

public class UploadTCPServer {
    public static void main(String[] args) throws IOException {
        //创建指定端口号为10001的服务端ServerSocket对象
        ServerSocket serverSocket=new ServerSocket(10001);
        while (true){
            //调用accept()方法持续接收客户端请求
            Socket client=serverSocket.accept();
            new Thread(new ServerThread(client)).start();
        }
    }
}

public class UploadTCPClient {
    public static void main(String[] args) throws IOException {
        Socket client=new Socket(InetAddress.getLocalHost(),10001);
        //客户端向服务器端上传文件
        OutputStream out=client.getOutputStream();
        FileInputStream fis=new FileInputStream("E:\\image\\bg1.jpg");
        byte[] buf=new byte[1024];
        int len=0;
        System.out.println("连接到服务器端,开始文件上传!");
        while((len=fis.read(buf))!=-1){
            out.write(buf,0,len);
        }
        //整个图片上传完成后,要及时关闭客户端输出流
        client.shutdownOutput();
        //客户端接收服务端的响应
        InputStream is=client.getInputStream();
        byte[] bufMsg=new byte[1024];
        int len2=is.read(bufMsg);
        while (len2!=-1){
            System.out.println(new String(bufMsg,0,len2));
            len2=is.read(bufMsg);
        }
        //关闭流和Socket连接
        out.close();
        is.close();
        fis.close();
        client.close();
    }
}

这里需要特别注意:
shutDownOutput()方法,在这里非常的重要,因为服务器端程序在while循环中读写客户端发送的数据,当读取到-1时才会结束,如果在客户端不调用shutDownOutput()方法,服务器端就不会读到-1,而会一直执行循环,同时客户端读取服务器端数据的read(byte[ ])方法也是一个阻塞方法,这样服务器端和客户端就进入了死锁状态。

4. URL 编程

在这里插入图片描述
使用示例:

public class Demo2 {

	public static void main(String[] args) throws IOException {
		//关键使用步骤:
		//1. 	先准备一个URL类的对象 u
		URL url = new URL("http://www.4399.com");
		//2. 	打开服务器连接,得到连接对象 conn
		URLConnection conn = url.openConnection();
		//3. 	获取加载数据的字节输入流 is
		InputStream is = conn.getInputStream();
		//4.	将is装饰为能一次读取一行的字符输入流 br
		BufferedReader br = new BufferedReader(new InputStreamReader(is));
		//5.	加载一行数据
		String text = br.readLine();
		//6.	显示
		System.out.println(text);
		//5.	加载一行数据
		String text2 = br.readLine();
		//6.	显示
		System.out.println(text2);
		//5.	加载一行数据
		String text3 = br.readLine();
		//6.	显示
		System.out.println(text3);
		//5.	加载一行数据
		String text4 = br.readLine();
		//6.	显示
		System.out.println(text4);
		//5.	加载一行数据
		String text5 = br.readLine();
		//6.	显示
		System.out.println(text5);
		//5.	加载一行数据
		String text6 = br.readLine();
		//6.	显示
		System.out.println(text6);
		//7.	释放资源
		br.close();

	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值