网络编程:UDP和TCP发送和接收数据、TCP实现客户端文件上传到服务器

1. 网络编程三要素

网络编程是指在网络通信协议下,不同计算机上运行的程序,可以进行数据传输

1.1 IP地址

IP地址:是网络中设备的唯一标识

IP地址分为两大类:

IPv4:是给每个连接在网络上的主机分配一个32bit地址。 例如一个采用二进制形式的IP地址是“11000000 10101000 00000001 01000010”, 为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。

IPv6:由于互联网的蓬勃发展,为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16位一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题。

特殊IP地址:127.0.0.1 是回送地址,可以代表本机地址,一般用来测试使用

InetAddress:此类表示Internet协议(IP)地址

 相关方法:

方法名 说明
static InetAddress getByName(String host) 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
String getHostName() 获取此IP地址的主机名
String getHostAddress() 返回文本显示中的IP地址字符串
import java.net.InetAddress;
import java.net.UnknownHostException;

public class Demo01 {
    public static void main(String[] args) throws UnknownHostException {
        //获取本机InetAddress对象
        InetAddress ia = InetAddress.getByName("172.20.0.15");
        //得到本机IP地址的字符串形式
        String hostAddress = ia.getHostAddress();
        //得到本机的名称
        String hostName = ia.getHostName();
        
        System.out.println(hostAddress);
        System.out.println(hostName);
    }
}

1.2 端口和协议

端口:设备上应用程序的唯一标识

端口号:用两个字节表示的整数,它的取值范围是0~65535。其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败

协议:计算机网络中,连接和通信的规则被称为网络通信协议

UDP协议:用户数据报协议(User Datagram Protocol)是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。

TCP协议:传输控制协议 (Transmission Control Protocol) 是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。

2. UDP通信程序

2.1 UDP发送数据

Java中的UDP通信

  • UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
  • Java提供了DatagramSocket类作为基于UDP协议的Socket

构造方法:

方法名 说明
DatagramSocket() 创建数据报套接字并将其绑定到本机地址上的任何可用端口
DatagramPacket(byte[] buf,int len,InetAddress add,int port) 创建数据包,发送长度为len的数据包到指定主机的指定端口

发送数据的步骤

  • 创建发送端的Socket对象(DatagramSocket)
  • 创建数据,并把数据打包
  • 调用DatagramSocket对象的send方法发送数据
  • 关闭发送端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UdpSend {
    public static void main(String[] args) {
        DatagramSocket ds = null;
        try {
            //创建DatagramSocket对象,用于发送数据
            ds = new DatagramSocket();
            String str = "udp传输";
            byte[] bytes = str.getBytes();
            int len = bytes.length;
            InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
            //将需要发送的数据使用DatagramPacket对象打包
            DatagramPacket dp = new DatagramPacket(bytes, len, inetAddress, 10000);
            //发送数据,参数为打包数据
            ds.send(dp);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ds !=null){
                //关闭发送端
                ds.close();
            }
        }

    }
}

2.2 UDP接收数据

接收数据的步骤

  • 创建接收端的Socket对象(DatagramSocket)
  • 创建一个数据包,用于接收数据
  • 调用DatagramSocket对象的方法接收数据
  • 解析数据包,并把数据在控制台显示
  • 关闭接收端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UdpRecive {
    public static void main(String[] args) {
        DatagramSocket ds = null;
        try {
            //DatagramSocket对象用于接收数据,需要指定端口
            ds = new DatagramSocket(10000);
            byte[] bytes = new byte[1024];
            int len = bytes.length;
            //创建一个空包用于接收数据
            DatagramPacket dp = new DatagramPacket(bytes, len);
            //将包作为参数接收数据
            ds.receive(dp);
            //解析数据包
            byte[] data = dp.getData();
            int length = dp.getLength();
            System.out.println(new String(data, 0, length));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ds != null){
                ds.close();
            }
        }
    }
}

2.3 UDP的三种通信方式

  • 单播:单播用于两个主机之间的端对端通信
  • 组播:组播用于对一组特定的主机进行通信
  • 广播:广播用于一个主机对整个局域网上所有主机上的数据通信

组播:

// 发送端
public class ClinetDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建发送端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
        String s = "hello 组播";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("224.0.1.0");
        int port = 10000;
        // 2. 创建数据,并把数据打包(DatagramPacket)
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        // 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
        ds.send(dp);
        // 4. 释放资源
        ds.close();
    }
}
// 接收端
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建接收端Socket对象(MulticastSocket)
        MulticastSocket ms = new MulticastSocket(10000);
        // 2. 创建一个箱子,用于接收数据
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
        ms.joinGroup(InetAddress.getByName("224.0.1.0"));
        // 4. 将数据接收到箱子中
        ms.receive(dp);
        // 5. 解析数据包,并打印数据
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        // 6. 释放资源
        ms.close();
    }
}

广播:

// 发送端
public class ClientDemo {
    public static void main(String[] args) throws IOException {
      	// 1. 创建发送端Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket();
		// 2. 创建存储数据的箱子,将广播地址封装进去
        String s = "广播 hello";
        byte[] bytes = s.getBytes();
        InetAddress address = InetAddress.getByName("255.255.255.255");
        int port = 10000;
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
		// 3. 发送数据
        ds.send(dp);
		// 4. 释放资源
        ds.close();
    }
}
// 接收端
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 1. 创建接收端的Socket对象(DatagramSocket)
        DatagramSocket ds = new DatagramSocket(10000);
        // 2. 创建一个数据包,用于接收数据
        DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
        // 3. 调用DatagramSocket对象的方法接收数据
        ds.receive(dp);
        // 4. 解析数据包,并把数据在控制台显示
        byte[] data = dp.getData();
        int length = dp.getLength();
        System.out.println(new String(data,0,length));
        // 5. 关闭接收端
        ds.close();
    }
}

3. TCP通信程序

Java中的TCP通信

  • Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。
  • Java为客户端提供了Socket类,为服务器端提供了ServerSocket类

发送:

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //创建客户端的Socket对象(Socket)
        //Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
        Socket s = new Socket("127.0.0.1",10000);

        //获取输出流,写数据
        //OutputStream getOutputStream() 返回此套接字的输出流
        OutputStream os = s.getOutputStream();
        os.write("hello,tcp,我来了".getBytes());

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

接收:

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //创建服务器端的Socket对象(ServerSocket)
        //ServerSocket(int port) 创建绑定到指定端口的服务器套接字
        ServerSocket ss = new ServerSocket(10000);

        //Socket accept() 侦听要连接到此套接字并接受它
        Socket s = ss.accept();

        //获取输入流,读数据,并把数据显示在控制台
        InputStream is = s.getInputStream();
        byte[] bys = new byte[1024];
        int len = is.read(bys);
        String data = new String(bys,0,len);
        System.out.println("数据是:" + data);

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

注意事项:

  • accept方法是阻塞的,作用就是等待客户端连接
  • 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
  • 针对客户端来讲,是往外写的,所以是输出流;针对服务器来讲,是往里读的,所以是输入流
  • read方法也是阻塞的
  • 客户端在关流的时候,还多了一个往服务器写结束标记的动作,如果没有结束标记,服务器读取不会停止
  • 最后一步断开连接,通过四次挥手协议保证连接终止

4. TCP实现文件上传

案例需求

客户端:数据来自于本地文件,接收服务器反馈

服务器:接收到的数据写入本地文件,给出反馈

客户端:

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

public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 10086);

        //bis为本地文件的字节缓冲输入流,用于读取需要上传的文件
        FileInputStream fs = new FileInputStream("day11_exercise\\05.jpg");
        BufferedInputStream bis = new BufferedInputStream(fs);
        //os为网络中的输出流,向服务器传文件
        OutputStream os = socket.getOutputStream();
        //上传文件
        int b;
        while ((b = bis.read()) != -1) {
            os.write(b);
        }
        socket.shutdownOutput();//关闭socket的输出流,并向流中写一个结束标志

        //接收服务器的反馈为中文,需要使用转换流,传入socket获取的流和指定编码格式
        InputStream socketInputStream = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(socketInputStream,"gbk");
        BufferedReader br = new BufferedReader(isr);
        String str;
        while ((str = br.readLine())!=null){
            System.out.print(str);
        }
        br.close();
        socket.close();
        bis.close();
    }
}

服务器:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
//服务器将接收文件并保存到本地的功能使用多线程实现,可以同时存储多个客户端上传的文件
//这里使用的是重写Runnable中的run方法,也可以使用线程池减少开辟多个线程的开销
public class Service {
    public static void main(String[] args) throws IOException {
        //接收网络流的文件
        ServerSocket ss = new ServerSocket(10086);
        while (true) {
            Socket accept = ss.accept();
            SocketThread st = new SocketThread(accept);
            Thread thread = new Thread(st);
            thread.start();
        }
//        ss.close();
    }
}
import java.io.*;
import java.net.Socket;
import java.util.UUID;

public class SocketThread implements Runnable {
    private Socket accept;

    public SocketThread(Socket accept) {
        this.accept = accept;
    }

    @Override
    public void run() {
        BufferedOutputStream bos = null;
        BufferedWriter bw = null;

        try {
            //得到网络流,读入
            InputStream is = accept.getInputStream();
            //本地的输出流写到本地
            FileOutputStream fs = new FileOutputStream("day15_exercise\\" + UUID.randomUUID().toString() + ".jpg");
            bos = new BufferedOutputStream(fs);

            //写到服务器本地
            int b;
            while ((b = is.read()) != -1) {
                bos.write(b);
            }

            System.out.println("向客户反馈信息");
            OutputStream socketos = accept.getOutputStream();
            bw = new BufferedWriter(new OutputStreamWriter(socketos,"gbk"));
            bw.write("上传成功!");
            bw.newLine();
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bw != null){
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 

如有错误欢迎留言评论,及时更正。2021年6月18日 羽露风

 

你可能感兴趣的:(基础,java,socket,多线程)