JAVA之网络编程

概念了解

计算机网络是通过传输介质、通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来,实现资源共享和数据传输的系统。网络编程就就是编写程序使联网的两个(或多个)设备(例如计算机)之间进行数据传输。Java语言对网络编程提供了良好的支持,通过其提供的接口我们可以很方便地进行网络编程。下面先对网络编程的一些基础知识进行介绍,最后给出使用Java语言进行网络编程的实例。

网络层次模型
  • 物理层

物理层处于OSI的最底层,是整个开放系统的基础。物理层涉及通信信道上传输的原始比特流(bits),它的功能主要是为数据端设备提供传送数据的通路以及传输数据。

  • 数据链路层

数据链路层的主要任务是实现计算机网络中相邻节点之间的可靠传输,把原始的、有差错的物理传输线路加上数据链路协议以后,构成逻辑上可靠的数据链路。需要完成的功能有链路管理、成帧、差错控制以及流量控制等。其中成帧是对物理层的原始比特流进行界定,数据链路层也能够对帧的丢失进行处理。

  • 网络层

网络层涉及源主机节点到目的主机节点之间可靠的网络传输,它需要完成的功能主要包括路由选择、网络寻址、流量控制、拥塞控制、网络互连等。

  • 传输层

传输层起着承上启下的作用,涉及源端节点到目的端节点之间可靠的信息传输。传输层需要解决跨越网络连接的建立和释放,对底层不可靠的网络,建立连接时需要三次握手,释放连接时需要四次挥手。

  • 会话层和表示层

会话层的主要功能是负责应用程序之间建立、维持和中断会话,同时也提供对设备和结点之间的会话控制,协调系统和服务之间的交流,并通过提供单工、半双工和全双工3种不同的通信方式,使系统和服务之间有序地进行通信。

表示层关心所传输数据信息的格式定义,其主要功能是把应用层提供的信息变换为能够共同理解的形式,提供字符代码、数据格式、控制信息格式、加密等的统一表示。

  • 应用层

应用层为OSI的最高层,是直接为应用进程提供服务的。其作用是在实现多个系统应用进程相互通信的同时,完成一系列业务处理所需的服务。

TCP/IP + 网络模型

在这里插入图片描述

TCP/IP 模型层次功能
  • 网络接口层

TCP/IP协议对网络接口层没有给出具体的描述,网络接口层对应着物理层和数据链路层。

  • 互联网层 ( IP层 )

互联网层是整个TCP/IP协议栈的核心。它的功能是把分组发往目标网络或主机。同时,为了尽快地发送分组,可能需要沿不同的路径同时进行分组传递。因此,分组到达的顺序和发送的顺序可能不同,这就需要上层必须对分组进行排序。互联网层除了需要完成路由的功能外,也可以完成将不同类型的网络(异构网)互连的任务。除此之外,互联网层还需要完成拥塞控制的功能。

  • 传输层 ( TCP层 )

TCP层负责在应用进程之间建立端到端的连接和可靠通信,它只存在与端节点中。TCP层涉及两个协议,TCP和UDP。其中,TCP协议提供面向连接的服务,提供按字节流的有序、可靠传输,可以实现连接管理、差错控制、流量控制、拥塞控制等。UDP协议提供无连接的服务,用于不需要或无法实现面向连接的网络应用中。

  • 应用层

应用层为Internet中的各种网络应用提供服务。

TCP/IP协议族

​ TCP/IP是Transmission Control Protocol/Internet Protocol的简称。它是用于计算机网络通信的协议集,即协议族。该协议族是Internet最基本的协议,它不依赖于任何特定的计算机硬件或操作系统,提供开放的协议标准。目前,绝大多数的网络操作系统都提供对TCP/IP协议族的支持,它己经成为Internet的标准协议。TCP/IP协议族包括IP协议、TCP协议、UDP协议和ARP协议等诸多协议,其核心协议是IP协议和TCP协议,所以有时也将TCP/IP协议族简称为TCP/IP协议。

TCP协议

​ TCP是Transmission Control Protocol的简称,中文名称为传输控制协议。TCP是种面向连接的、可靠的、基于字节流的传输层通信协议。TCP要求通信双方必须建立连接之后才开始通信,通信双方可以同时进行数据传输,它是全双工的,从而保证了数据的正确传送。

TCP是面向连接的协议,因此每个TCP连接都有3个阶段:连接建立、数据传送和连接释放。连接建立经历三个步骤,通常称为“三次握手”。

三次握手

在这里插入图片描述

第一次握手

客户机发送连接请求报文段到服务器,并进入SYN_SENT状态,等待服务器确认。(SYN = 1,seq=x)

第二次握手

服务器收到连接请求报文,如果同意建立连接,向客户机发回确认报文段,并为该TCP连接分配TCP缓存和变量。(SYN=1,ACK=1,seq=y,ack=x+1)。

第三次握手

客户机收到服务器的确认报文段后,向服务器给出确认报文段,并且也要给该连接分配缓存和变量。此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。(ACK=1,seq=x+1,ack=y+1)。

四次挥手

在这里插入图片描述

介绍

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

第一次

TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。

第二次

服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。

第三次

服务器关闭客户端的连接,发送一个FIN给客户端。

第四次

客户端发回ACK报文确认,并将确认序号设置为收到序号加1。

通俗理解
  • 三次握手
    A:我要过来了!B:我知道你要过来了!A:我现在过来!
  • 四次挥手
    A:我们分手吧!B:真的分手吗?B:真的真的要分手吗?A:是的!

由此,可以可靠地进行连接和断开。

UDP协议

用户数据报协议,英文全称是User Datagram Protocol,它是TCP/IP协议簇中无连接的运输层协议。

特点
  • 数据报(Datagram):网络传输的基本单位
  • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机
  • 发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
  • 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响
  • 但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时,不建议使用UDP协议。
  • 数据被限制在64kb以内,超出这个范围就不能发送了。
端口
简述
  • IP地址用来标识一台计算机,但是一台计算机上可能提供多种网络应用程序,如何来区分这些不同的程序呢?这就要用到端口。端口号是用来区分一台机器上不同的应用程序的。
  • 我们使用IP地址加端口号,就可以保证数据准确无误地发送到对方计算机的指定软件上了。
  • 端口是虚拟的概念,是一个逻辑端口。
  • 当我们使用网络软件一打开,那么操作系统就会为网络软件分配一个随机的端口号,或者打开的时候向系统要指定的端口号。
    通过端口,可以在一个主机上运行多个网络应用程序。端口的表示是一个16位的二进制整数,2个字节,对应十进制的0~65535。
  • 端口号在计算机内部是占2个字节。一台机器上最多有65536个端口号。一个应用程序可以占用多个端口号。端口号如果被一个应用程序占用了,那么其他的应用程序就无法再使用这个端口号了。
  • 记住一点,我们编写的程序要占用端口号的话占用1024以上的端口号,1024以下的端口号不要去占用,因为系统有可能会随时征用。端口号本身又分为TCP端口和UDP端口,TCP的8888端口和UDP的8888端口是完全不同的两个端口。TCP端口和UDP端口都有65536个
分类
公有端口
  • 0-1023
  • HTTP:80
  • HTTPS:443
  • FTP:21
程序注册端口
  • 1024-49151
  • Tomcat:8080
  • MySql:3306
  • Oracle:1521
动态、私有端口
  • 49152~65535
Dos命令查看
  • 查看所有端口:netstat -ano
  • 查看指定端口:netstat -ano|findstr "端口号"
  • 查看指定端口的进程:tasklist|findstr "端口号"
InetSocketAddress类
  • 构造方法摘要

    • InetSocketAddress(InetAddress addr, int port)
      根据 IP 地址和端口号创建套接字地址。
    • InetSocketAddress(int port)
      创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值。
    • InetSocketAddress(String hostname, int port)
      根据主机名和端口号创建套接字地址。
  • 常用方法摘要

    • InetAddress getAddress()
      获取 InetAddress。
    • String getHostName()
      获取 hostname。
    • int getPort()
      获取端口号。

Java 网络编程

Socket编程
简述

套接字,就是两台主机之间逻辑连接的端点。TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。Socket,本质上就是一组接口,是对TCP/IP协议的封装和应用(程序员层面上)。

Socket 通信模型

在这里插入图片描述

注意
  • UDP常用于客户咨询 和 聊天
  • TCP 常用于数据传输 文件什么的
  • TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。
  • 两端通信步骤
    • 服务端程序,需要事先启动,等待客户端的连接。
    • 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。
  • 实现TCP 通信的类
    • 客户端:java.net.Socket 类表示。创建Socket对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
    • 服务端:java.net.ServerSocket 类表示。创建ServerSocket对象,相当于开启一个服务,并等待客户端的连接。
Socket类

该类实现客户端套接字,套接字指的是两台设备之间通讯的端点。

构造方法
  • public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。【127.0.0.1】
常用方法
  • public InputStream getInputStream() : 返回此套接字的输入流。
    • 如果此Scoket具有相关联的通道,则生成的InputStream 的所有操作也关联该通道。
    • 关闭生成的InputStream也将关闭相关的Socket。
  • public OutputStream getOutputStream() : 返回此套接字的输出流。
    • 如果此Socket具有相关联的通道,则生成的OutputStream 的所有操作也关联该通道。
    • 关闭生成的OutputStream也将关闭相关的Socket。
  • public void close() :关闭此套接字。
    • 一旦一个socket被关闭,它不可再使用。
    • 关闭此socket也将关闭相关的InputStream和OutputStream 。
  • public void shutdownOutput() : 禁用此套接字的输出流。
    • 任何先前写出的数据将被发送,随后终止输出流。
ServerSocket

这个类实现了服务器套接字,该对象等待通过网络的请求。

构造方法
  • public ServerSocket(int port) :使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。
常用方法
  • public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。
实例:
多线程登录【对象】
服务器端
package com.bigdata.practice1015;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

public class Server01 {
    // 保存账户信息 模拟数据库
    private static HashMap<String ,String> userMap = new HashMap<>();

    public static void main(String[] args) {
        // 添加账户信息
        userMap.put("gu","123");
        userMap.put("zz","123");
        // 创建服务器端套接字
        ServerSocket serverSocket = null;
        // 创建socket包装服务器端套接字
        Socket socket = null;
        try {
            serverSocket = new ServerSocket(9800);
            Thread.sleep(500);
            // 一直监听
            while (true) {
                socket = serverSocket.accept();
                LoginThread loginThread = new LoginThread(socket);
                loginThread.start();

            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        } finally {
		    try {
				if (socket != null) {
					socket.close();
                }
				if (serverSocket != null) {
					serverSocket.close();
                }
			} catch (IOException e) {
				e.printStackTrace();
			}
        }
    }
    static class LoginThread extends Thread {
        private Socket socket;
        InputStream is = null;
        OutputStream os = null;
        ObjectInputStream ois = null;
        BufferedReader reader = null;

        public LoginThread(Socket socket) {
            this.socket = socket;
        }
        @Override
        public void run(){
            try {
                is = socket.getInputStream();
                os = socket.getOutputStream();
                // 这是一个输入流 传进来的对象信息
                ois = new ObjectInputStream(is);
                // 反序列化对象信息
                UserModel userModel = (UserModel) ois.readObject();
                String reply;
                if (userMap.containsKey(userModel.getUser())) {
                    if (userMap.get(userModel.getPass()) == userModel.getUser()) {
                        // 获取并输出到控制台
                        if (userModel != null) {
                            System.out.println("我是服务器端,客户端登录的信息\n" + "用户名为:" + userModel.getUser() + "," + "密码为:" + userModel.getPass());
                        }
                        // 给客户端一个响应 输出流
                        reply = "欢迎用户" + userModel.getUser() + "登录成功!";
                        os.write(reply.getBytes());
                    } else {
                        reply = "用户密码不合适!请重新登录!";
                        os.write(reply.getBytes());
                    }
                } else {
                    reply = "用户不存在!请重新输入";
                    os.write(reply.getBytes());
                }
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (os != null ) {
                        os.close();
                    }
                    if (ois != null) {
                        ois.close();
                    }
                    if (reader != null) {
                        reader.close();
                    }
                    if (is != null) {
                        is.close();
                    }
                    if (socket != null) {
                        socket.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
客户端
package com.bigdata.practice1015;

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

public class Client01 {
    public static void main(String[] args) {
        Socket client = null;
        InputStream is = null;
        OutputStream os = null;
        ObjectOutputStream oos = null;
        BufferedReader reader = null;
        Scanner scanner = new Scanner(System.in);
        try {
            // 客户端连接服务器
            client = new Socket("127.0.0.1", 9800);
            // 打开输入输出流
            is = client.getInputStream();
            os = client.getOutputStream();
            // 创建对象输出流
            oos = new ObjectOutputStream(os);
            // 键盘输入数据 并封装到对象
            System.out.println("请输入你要登录的用户名:");
            String user = scanner.next();
            System.out.println("请输入对应的密码:");
            String pass = scanner.next();
            UserModel userModel = new UserModel(user, pass);
            // 对象输出流写入数据
            oos.writeObject(userModel);
            // 客户端发送
            client.shutdownOutput();
            // 接收服务器端的响应
            reader = new BufferedReader(new InputStreamReader(is));
            String reply = "";
            while ((reply = reader.readLine()) != null) {
                System.out.println("服务器端的响应是:" + reply);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null ) {
                    os.close();
                }
                if (oos != null) {
                    oos.close();
                }
                if (reader != null) {
                    reader.close();
                }
                if (is != null) {
                    is.close();
                }
                if (client != null) {
                    client.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
传输文件
服务器端
package com.bigdata.practice1015;

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

public class FileServer {

	public static void main(String[] args) {
		ServerSocket serverSocket = null;
		Socket server = null;
		InputStream is = null;
		OutputStream os = null;
		FileOutputStream fos = null;
		File file = null;
		try {
			// 创建服务器 套接字对象
			serverSocket = new ServerSocket(8888);
			// socket 包装 接收客户端请求
			server = serverSocket.accept();
			// 用于获取传递过来的数据
			is = server.getInputStream();
			// 文件输出流 用于将读取到的文件输出
			file = new File("D:"+File.separator+"kaifamiao"+File.separator+"java se"+File.separator+ "1.pdf");
			fos = new FileOutputStream(file);
			// 字节来接受数据
			byte[] bytes = new byte[1024];
			int len = 0;
			// 读数据 并输出
			while ((len = is.read(bytes)) != -1) {
				fos.write(bytes,0,len); // 写出文件
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(is!=null){
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(fos!=null){
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(server!=null){
				try {
					server.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(serverSocket!=null){
				try {
					serverSocket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
客户端
package com.bigdata.practice1015;

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

public class FileClient {
	public static void main(String[] args) {
		Socket client = null;
		InputStream is = null;
		OutputStream os = null;
		File file = null;
		FileInputStream fis = null;

		InetAddress dstAddress;
		InetAddress inet  = null;
		try {
			// 创建客户端套接字对象
			inet = InetAddress.getByName("127.0.0.1");
			client = new Socket(inet, 8888);
			// 创建一个文件对象
			file = new File("D:"+File.separator+"kaifamiao"+File.separator+"java se"+File.separator+"阿里巴巴Java开发手册v1.2.0.pdf");
			// 创建一个文件输入流  用于上传对象
			fis = new FileInputStream(file);
			// 创建一个输出流 用于输出数据[发送]
			os = client.getOutputStream();
			// 转为字节数组
			byte[] bytes = new byte[1024];
			int len = 0;
			// 读取数据并写出
			while ((len = fis.read(bytes)) != -1) {
				os.write(bytes, 0, len);
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(client!=null){
				try {
					client.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(fis!=null){
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(os!=null) {
				try {
					os.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
UDP
package com.bigdata.practice1015;

import java.io.IOException;
import java.net.*;

/**
 * UDP 一般用两个类 
 * DatagramSocket 来发送 接收 数据包 不会验证 只管一个发送 一个接收
 *   DatagramSocket([intt port])
 *   void connections(InetAddress add, int port); void close(); void disConnect(); int getLocalPort(); void send/receive(DatagramPacket p);
 *
 * DatagramPackt 来封装报文
 *  DatagramPacket(byte[] data, int size[, InetAddress add, int port])
 *  byte[] getData(); int getLength(); InetAddress getAddress(); int getPort();
 *
 * 地址类:InetAddress
 *  static InetAddress getLocalHost(); 本地主机的InetAddress 对象
 *  static InetAddress getByName(String hostname); 返回主机名为hostname 的InetAddress对象
 *  static InetADDRESS[] getAllName(String hostname); 返回主机名为hostname 的所有可能的 InetAddress 对象
 */
public class UdpSocket01 {
    public static void main(String[] args) {
        DatagramSocket socket = null;
        DatagramPacket packet = null;
        InetAddress address = null;
        String message = "你好,我想咨询一个问题!";
        try {
            // 获取本机地址
            address = InetAddress.getByName("127.0.0.1");
            // 创建DatagramPacket 对象封装数据
            packet = new DatagramPacket(message.getBytes(), message.getBytes().length, address, 9800 );
            // 创建DatagramPacket 对象 去发送数据
            socket = new DatagramSocket();
            // 发送数据
            socket.send(packet);

        } catch (UnknownHostException | SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                socket.close();
            }
        }
    }
}
package com.bigdata.practice1015;

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

public class UdpSocket02 {
    public static void main(String[] args) {
        DatagramSocket socket = null;
        DatagramPacket packet = null;
        byte[] data = new byte[1024];

        packet = new DatagramPacket(data, 1024);
        try {
            socket = new DatagramSocket(9800);
            socket.receive(packet);
            String message = new String(packet.getData(), 0, packet.getLength());
            // 显示接收到的信息
            System.out.println(packet.getAddress().getHostAddress() + "说:" + message);
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                socket.close();
            }
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值