【Java】网络编程基础

个人主页:Hello Code.
本文专栏:Java零基础指南
更多相关内容请点击前往 Java零基础指南 查看
如有问题,欢迎指正,一起学习~~



网络编程入门

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

  • IP地址:设备在网络中的地址,是唯一的标识
  • 端口:应用程序在设备中唯一的标识
  • 协议:数据在网络中传输的规则,常见的协议有UDP和TCP协议

IP

IP:全称“互联网协议地址”,也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6

通过域名访问服务器->域名通过DNS服务器解析为IP地址传递回去->计算机再通过解析好的IP地址访问相应的服务器->服务器返回数据展示在浏览器上

IPv4

  • 32bit(4字节):1 100000000 10101000 00000001 01000010

点分十进制表示法:192.168.1.66

IPv6

  • 由于互联网的蓬勃发展,IP地址的需求量愈来愈大,而IPv4的模式下IP的总数是有限的。

采用128位地址长度,分成8组,每16位分为一组

  • 冒分十六进制表示法:2001:0DB8:0000:0023:0008:0800:200C:417A

省略前面的0:2001:DB8:0:23:8:800:200C:417A

扫描二维码关注公众号,回复: 13731560 查看本文章
  • 特殊情况:如果计算出的16进制表示形式中间有多个连续的0(FF01:0:0:0:0:0:0:1101)

采用0位压缩表示法:FF01::1101

常用命令

  • ipconfig:查看本机IP地址
  • ping IP地址:检查网络是否连通

InetAddress的使用

为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用
InetAddress:此类表示Internet协议(IP)地址

常用方法

  • static InetAddress getByName(String host):确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
  • String getHostName():获取此IP地址的主机名
  • String getHostAddress():返回文本显示中的IP地址字符串

端口

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

端口号:用两个字节表示的整数,它的取值范围是0~65535

其中0~1023之间的端口号用于一些知名的网络服务或者应用
在自己使用时使用1024以上的端口号就可以了

注意:一个端口号只能被一个应用程序使用

协议

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

UDP协议
用户数据报协议(User Datagram Protocol)

  • UDP是面向无连接通信协议

速度快,有大小限制,一次最多发送64K,数据不安全,容易丢失数据

TCP协议

  • 传输控制协议(Transmission Control Protocol)
  • TCP协议是面向连接的通信协议

速度慢,没有大小限制,数据安全


UDP通讯程序

  • 发送端:发送数据
  • 接收端:接收数据

UDP发送端

发送数据步骤

  • 创建发送端的DatagramSocket对象
  • 创建数据,并把数据打包(DatagramPacket)
  • 调用DatagramSocket对象的方法发送数据
  • 释放资源

代码实现

public class Demo{
    
    
    public static void main(String[] args) throws IOException{
    
    
        // 创建发送端对象
    	DatagramSocket ds = new DatagramSocket();
    	// 数据打包   DatagramPacket(byte[] buf, int length, InetAddress, int port)
    	String s = "需要发送的数据";
    	byte[] bytes = s.getBytes();
    	InetAddress address = InetAddress.getByName("127.0.0.1");
    	int port = 10000;
    	DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
    	// 发送数据
    	ds.send(dp);
    	// 释放资源
    	ds.close();
    }
}

UDP接收端

步骤

  • 创建接收端的DatagramSocket对象
  • 创建一个箱子,用于接收数据
  • 调用DatagramSocket的方法接收数据并将数据放入箱子中
  • 解析数据包,并把数据在控制台显示
  • 释放资源

代码

public class Demo{
    
    
    public static void main(String[] args) throws{
    
    
        // 创建接收端对象------参数表示从10000端口接收数据
        DatagramSocket ds = new DatagramSocket(10000);
        // 创建箱子
        byte[] bytes = new bytes[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
        // 接收数据
        ds.receive(dp);
        // 获取数据
        byte[] data = dp.getData();
        System.out.println(new String(data));
        // 释放资源
        ds.close();
    }
}

在这里运行时必须先运行接收端再运行发送端(否则都发送完了才运行接收端就接收不到了)

如果接收端在启动之后没有接收到数据,会阻塞

在接收数据的时候,需要调用一个getLength方法,表示接收到了多少字节

UDP练习

  • UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
  • UDP接收端:因为不知道发送端什么时候停止发送,就死循环接收
// 发送端
package UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Scanner;

public class ClientDemo {
    
    
	public static void main(String[] args) throws IOException{
    
    
		Scanner sc = new Scanner(System.in);
		DatagramSocket ds = new DatagramSocket();
		while(true){
    
    
			String s = sc.nextLine();
			if("886".equals(s)) break;
			byte[] bytes = s.getBytes();
			InetAddress address = InetAddress.getByName("127.0.0.1");
			int port = 10000;
			DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
			ds.send(dp);
		}
		ds.close();
	}
}
// 接收端
package UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class ServerDemo {
    
    

	public static void main(String[] args) throws IOException {
    
    
		DatagramSocket ds = new DatagramSocket(10000);
		while(true){
    
    
			byte[] bytes = new byte[1024];
			DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
			ds.receive(dp);
			byte[] data = dp.getData();
			int length = dp.getLength();
			System.out.println(new String(data, 0, length));
		}
		// ds.close();
	}
}

三种通信方式

单播:一个发送端通过一个路由器只发送给一个接收端(一对一)

组播:一个发送端通过一个路由器发送给一组接收端(一对多)

  • 组播地址:224.0.0.0~239.255.255.255

其中224.0.0.0~224.0.0.255为预留的组播地址,不可用

  • 组播的发送端在调用DatagramSocket对象的方法发送数据时,单播是发送给指定IP的电脑,组播是发送给组播地址
  • 组播的接收端创建的是MulticastSocket对象

在创建箱子后,还要把当前电脑添加到这一组中

// 组播的发送端
public static void main(String[] args) throws IOException{
    
    
    DatagramSocket ds = new DatagramSocket();
    String s = "Hello Word";
    byte[] bytes = s.getBytes();
    InetAddress address = InetAddress.getByName("224.0.1.0");
    int port = 10000;
    DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
    ds.send(dp);
    ds.close();
}
// 接收端
public static void main(String[] args) throws IOException{
    
    
    MulticastSocket ms = new MulticastSocket(10000);
    DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
    // 把当前计算机绑定一个组播地址
    ms.joinGroup(InetAddress.getByName("224.0.1.0"));
    
    // 接收数据
    ms.receive(dp);
    byte[] data = dp.getData();
    int length = dp.getLength();
    System.out.println(new String(data, 0, length));
    ms.close();
}

广播:一个发送端通过一个路由器发送给该局域网下所有接收端(一对所有)

  • 广播地址:255.255.255.255
// 发送端
public static void main(String[] args) throws IOException{
    
    
    DatagramSocket ds = new DatagramSocket();
    String s = "广播发送端";
    byte[] bytes = s.getBytes();
    InetAddress address = InetAddress.getByName("255.255.255.255");
    int port = 10000;
    DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
    ds.sent(dp);
    ds.close();
}
// 接收端
public class ServerDemo {
    
    

	public static void main(String[] args) throws IOException {
    
    
		DatagramSocket ds = new DatagramSocket(10000);
		while(true){
    
    
			byte[] bytes = new byte[1024];
			DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
			ds.receive(dp);
			byte[] data = dp.getData();
			int length = dp.getLength();
			System.out.println(new String(data, 0, length));
		}
		ds.close();
	}
}

TCP通讯程序

TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象

发送端:Socket 接收端:ServerSocket

通信之前要保证连接已经建立
通过Socket产生IO流来进行网络通信

发送数据

  • 创建客户端的Socket对象(Socket)与指定服务端连接
    Socket(String host, int port)
  • 获取输出流,写数据
    OutputStream getOutputStream()
  • 释放资源
    void close()

代码实现

// 客户端
public static void main(String[] args) throws IOException{
    
    
	Socket socket = new Socket("127.0.0.1", 10000);
	OutputStream os = socket.getOutputStream();
	os.write("hello".getBytes());
      
	os.close();
	socket.close();
}

接收数据

  • 创建服务端的Socket对象(ServerSocket)
    ServerSocket(int port)
  • 监听客户端连接,返回一个Socket对象
    Socket accept()
  • 获得输入流,读数据,并把数据显示在控制台
    InputStream getInputStream()
  • 释放资源
    void close()

代码实现

// 服务端
public static void main(String[] args){
    
    
	ServerSocket ss = new ServerSocket(10000);
      
	Socket accept = ss.accept();		// 没有客户端连接的话,就会死等,不执行后续代码,即阻塞
      
	InputStream is = accept.getInputStream();
	int b;
	while((b = is.read()) != -1){
    
    
		System.out.print((char) b);
	}
      
	is.close();
	ss.close();
}

原理分析

  • 不能先运行客户端(先运行客户端会没有服务端可连,就会报错)
  • 先运行服务端,代码会依次执行到accept方法,然后就阻塞,等待客户端连接
  • 运行客户端,在对象创建完毕之后,客户端与服务端的连接通道就已经建立

客户端写数据,服务端读数据

  • 客户端释放资源,服务端释放资源

客户端创建对象并连接服务器,此时是通过三次握手协议保证服务器之间的连接

针对客户端,是往外写的,所以是输出流;而服务端是往进读的,所以是输入流

read方法也是阻塞的

在关流的时候,还多了一个往服务器写结束标记的动作

最后一步断开连接,会通过四次挥手协议保证连接终止

三次握手

保证客户端与服务器之间建立连接

  • 第一次:客户端向服务器发出连接请求(等待服务器确认)
  • 第二次:服务器向客户端返回一个响应(告诉客户端收到了请求)
  • 第三次:客户端向服务端再次发出确认信息(连接建立)

四次挥手

保证客户端与服务端取消连接,成功终止连接

  • 第一次:客户端向服务器发出取消连接请求
  • 第二次:服务器向客户端返回一个响应,表示收到客户端取消请求
    服务器将最后的数据处理完毕
  • 第三次:服务器向客户端发出确认取消信息
  • 第四次:客户端再次发送确认消息(连接取消)

练习

练习一

  • 客户端:发送数据,接收服务器反馈
  • 服务器:接收数据,给出反馈
  • 代码
// 客户端
package TCP;

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


public class ClientDemo {
    
    

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

        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());
        socket.shutdownOutput();    // 仅仅关闭输出流,并写一个结束标记,对socket没有任何影响

        InputStream is = socket.getInputStream();
        int b;
        while ((b = is.read()) != -1) {
    
    
            System.out.print((char) b);
        }

        is.close();
        os.close();
        socket.close();

    }
}
// 服务端
package TCP;

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

import java.io.InputStream;

public class ServerDemo {
    
    

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

        Socket accept = ss.accept();

        InputStream is = (InputStream) accept.getInputStream();
        int b;
        while ((b = is.read()) != -1) {
    
    
            System.out.print((char) b);
        }

        OutputStream os = accept.getOutputStream();
        os.write("who?".getBytes());

        os.close();
        is.close();
        accept.close();
        ss.close();

    }
}

练习二

  • 客户端:将本地文件上传到服务器,接收服务器的反馈
  • 服务器:接收客户上传的文件,上传完毕之后给出反馈
  • 代码
// 客户端
package TCP;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

public class ClientDemo2 {
    
    

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

        // 本地的流,读取本地文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.png"));

        OutputStream os = socket.getOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(os);
        int b;
        while ((b = bis.read()) != -1) {
    
    
            bos.write(b);        // 通过网络写到服务器中
        }

        // 给服务器一个结束标记
        socket.shutdownOutput();

        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while ((line = br.readLine()) != null) {
    
    
            System.out.println(line);
        }

        socket.close();
        bis.close();
    }

}
    
// 服务器
package TCP;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo2 {
    
    

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

        Socket accept = ss.accept();
        // 网络中的流,从客户端读取数据的
        BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
        // 本地的IO流,把数据写到本地中,实现永久化存储
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\copy.png"));

        int b;
        while ((b = bis.read()) != -1) {
    
    
            bos.write(b);
        }
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("上传成功!");
        bw.newLine();
        bw.flush();

        accept.close();
        ss.close();
        bos.close();
    }

}
    

服务端优化

  • 第一个弊端:服务器只能处理一个客户端请求,接受完一个文件之后,服务器就关闭了
    改进方式:循环
  • UUID
    • UUID uuid = UUID.randomUUID(); 生成一个随机且唯一的uid
    • uuid.toString(); 转换为字符串
    • 防止多次上传文件时,后上传的文件覆盖掉先上传的文件
  • 仅仅使用while循环,无法同时处理多个客户端的请求
    采用多线程改进
  • 使用多线程虽然可以让服务器同时处理多个客户端请求,但是资源消耗太大
    采用线程池改进

推荐阅读:【Java】基础知识加强

猜你喜欢

转载自blog.csdn.net/qq_24980365/article/details/121892232
今日推荐