尚学堂Java300集:网络编程

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

文字部分参考尚学堂[https://www.sxt.cn/Java_jQuery_in_action/ten-objectstream.html] 第十二章 网络编程,代码为自测代码

1 基本概念

1.1 什么是计算机网络?

  • 计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
    • 计算机网络的作用:资源共享和信息传递。
    • 计算机网络的组成:
      • 计算机硬件:计算机(大中小型服务器,台式机、笔记本等)、外部设备(路由器、交换机等)、通信线路(双绞线、光纤等)。
      • 计算机软件:网络操作系统(Windows 2000 Server/Advance Server、Unix、Linux等)、网络管理软件(WorkWin、SugarNMS等)、网络通信协议(如TCP/IP协议栈等)。
    • 计算机网络的多台计算机是具有独立功能的,而不是脱离网络就无法存在的。

1.2 什么是网络通信协议?

  • 通过计算机网络可以实现不同计算机之间的连接与通信,但是计算机网络中实现通信必须有一些约定即通信协议对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。就像两个人想要顺利沟通就必须使用同一种语言一样,如果一个人只懂英语而另外一个人只懂中文,这样就会造成没有共同语言而无法沟通。
  • 国际标准化组织(ISO,即International Organization for Standardization)定义了网络通信协议的基本框架,被称为OSI(Open System Interconnect,即开放系统互联)模型。要制定通讯规则,内容会很多,比如要考虑A电脑如何找到B电脑,A电脑在发送信息给B电脑时是否需要B电脑进行反馈,A电脑传送给B电脑的数据格式又是怎样的?内容太多太杂,所以OSI模型将这些通讯标准进行层次划分,每一层次解决一个类别的问题,这样就使得标准的制定没那么复杂。OSI模型制定的七层标准模型,分别是:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层。
  • OSI七层协议模型如图所示:
    在这里插入图片描述
  • 虽然国际标准化组织制定了这样一个网络通信协议的模型,但是实际上互联网通讯使用最多的网络通信协议是TCP/IP网络通信协议。
  • TCP/IP 是一个协议族,也是按照层次划分,共四层:应用层,传输层,互连网络层,网络接口层(物理+数据链路层)。
  • 那么TCP/IP协议和OSI模型有什么区别呢?OSI网络通信协议模型,是一个参考模型,而TCP/IP协议是事实上的标准。TCP/IP协议参考了OSI 模型,但是并没有严格按照OSI规定的七层标准去划分,而只划分了四层,这样会更简单点,当划分太多层次时,你很难区分某个协议是属于哪个层次的。TCP/IP协议和OSI模型也并不冲突,TCP/IP协议中的应用层协议,就对应于OSI中的应用层,表示层,会话层。就像以前有工业部和信息产业部,现在实行大部制后只有工业和信息化部一个部门,但是这个部门还是要做以前两个部门一样多的事情,本质上没有多大的差别。TCP/IP中有两个重要的协议,传输层的TCP协议和互连网络层的IP协议,因此就拿这两个协议做代表,来命名整个协议族了,再说TCP/IP协议时,是指整个协议族。

1.3 网络协议的分层

  • 由于网络结点之间联系很复杂,在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再下一层不发生关系。
  • 把用户应用程序作为最高层,把物理通信线路作为最低层,将其间的协议处理分为若干层,规定每层处理的任务,也规定每层的接口标准。
  • ISO模型与TCP/IP模型的对应关系如图12-2所示。
    在这里插入图片描述

1.4 数据封装与解封:

  • 由于用户传输的数据一般都比较大,有的可以达到MB字节,一次性发送出去十分困难,于是就需要把数据分成许多片段,再按照一定的次序发送出去。这个过程就需要对数据进行封装。

  • 数据封装(Data Encapsulation)是指将协议数据单元(PDU)封装在一组协议头和协议尾中的过程。在OSI七层参考模型中,每层主要负责与其它机器上的对等层进行通信。该过程是在协议数据单元(PDU)中实现的,其中每层的PDU一般由本层的协议头、协议尾和数据封装构成。

  • 1.数据发送处理过程

    • 应用层将数据交给传输层,传输层添加上TCP的控制信息(称为TCP头部),这个数据单元称为段(Segment),加入控制信息的过程称为封装。然后,将段交给网络层。

    • 网络层接收到段,再添加上IP头部,这个数据单元称为包(Packet)。然后,将包交给数据链路层。

    • 数据链路层接收到包,再添加上MAC头部和尾部,这个数据单元称为帧(Frame)。然后,将帧交给物理层。

    • 物理层将接收到的数据转化为比特流,然后在网线中传送。

  • 2.数据接收处理过程

    • (1)物理层接收到比特流,经过处理后将数据交给数据链路层。

    • (2)数据链路层将接收到的数据转化为数据帧,再除去MAC头部和尾部,这个除去控制信息的过程称为解封,然后将包交给网络层。

    • (3)网络层接收到包,再除去IP头部,然后将段交给传输层。

    • (4)传输层接收到段,再除去TCP头部,然后将数据交给应用层。

  • 从以上传输过程中,可以总结出以下规则:

    • 发送方数据处理的方式是从高层到底层,逐层进行数据封装。

    • 接收方数据处理的方式是从底层到高层,逐层进行数据解封装。

    • 接收方的每一层只把对该层有意义的数据拿走,或者说每一层只能处理发送方同等层的数据,然后把其余的部分传递给上一层,这就是对等层通信的概念。

  • 数据封装示意图如下:
    在这里插入图片描述

  • 数据解封示意图如下:
    在这里插入图片描述

1.5 IP地址:

  • 用来标识网络中的一个通信实体的地址。通信实体可以是计算机、路由器等。 比如互联网的每个服务器都要有自己的IP地址,而每个局域网的计算机要通信也要配置IP地址。路由器是连接两个或多个网络的网络设备。

  • 目前主流使用的IP地址是IPV4,但是随着网络规模的不断扩大,IPV4面临着枯竭的危险,所以推出了IPV6。

    • IPV4:32位地址,并以8位为一个单位,分成四部分,以点分十进制表示,如192.168.0.1。因为8位二进制的计数范围是00000000—11111111,对应十进制的0-255,所以-4.278.4.1是错误的IPV4地址。
    • IPV6:128位。16位为一组,分8组,每组用(:)分隔。每一组16位再以4位一划分,分成4个4位,每个4位用一个16进制数表示,所以每组可以表示为4个16进制数。所以整体可以表示为如下:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
  • 注意事项

    • 127.0.0.1 本机地址

    • 192.168.0.0–192.168.255.255为私有地址,属于非注册地址,专门为组织机构内部使用。

1.6 端口:

  • IP地址用来标识一台计算机,但是一台计算机上可能提供多种网络应用程序,如何来区分这些不同的程序呢?这就要用到端口。

  • 端口是虚拟的概念,并不是说在主机上真的有若干个端口。通过端口,可以在一个主机上运行多个网络应用程序。 端口的表示是一个16位的二进制整数,对应十进制的0-65535。

  • Oracle、MySQL、Tomcat、QQ、msn、迅雷、电驴、360等网络程序都有自己的端口。

  • 总结

    • IP地址好比每个人的地址(门牌号),端口好比是房间号。必须同时指定IP地址和端口号才能够正确的发送数据。

    • IP地址好比为电话号码,而端口号就好比为分机号。

1.7 URL:

  • 在www上,每一信息资源都有统一且唯一的地址,该地址就叫URL(Uniform Resource Locator),它是www的统一资源定位符。URL由4部分组成:协议 、存放资源的主机域名、资源文件名和端口号。如果未指定该端口号,则使用协议默认的端口。例如http 协议的默认端口为 80。 在浏览器中访问网页时,地址栏显示的地址就是URL。
  • 在java.net包中提供了URL类,该类封装了大量复杂的涉及从远程站点获取信息的细节。

1.8 Socket:

  • 我们开发的网络应用程序位于应用层,TCP和UDP属于传输层协议,在应用层如何使用传输层的服务呢?在应用层和传输层之间,则是使用套接Socket来进行分离。

  • 套接字就像是传输层为应用层开的一个小口,应用程序通过这个小口向远程发送数据,或者接收远程发来的数据;而这个小口以内,也就是数据进入这个口之后,或者数据从这个口出来之前,是不知道也不需要知道的,也不会关心它如何传输,这属于网络其它层次工作。

  • Socket实际是传输层供给应用层的编程接口。Socket就是应用层与传输层之间的桥梁。使用Socket编程可以开发客户机和服务器应用程序,可以在本地网络上进行通信,也可通过Internet在全球范围内通信。
    在这里插入图片描述

2 TCP协议和UDP协议

2.1 TCP协议和UDP协议的联系和区别

  • TCP协议和UDP协议是传输层的两种协议。Socket是传输层供给应用层的编程接口,所以Socket编程就分为TCP编程和UDP编程两类。

  • 在网络通讯中,TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据。而UDP方式就类似于发送短信,使用这种方式进行网络通讯时,不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。

  • 这两种传输方式都在实际的网络编程中使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则可以通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据传递。

  • 由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些。

  • 总结

    • TCP是面向连接的,传输数据安全,稳定,效率相对较低。

    • UDP是面向无连接的,传输数据不安全,效率较高。

2.2 TCP协议

  • TCP(Transfer Control Protocol)是面向连接的,所谓面向连接,就是当计算机双方通信时必需经过先建立连接,然后传送数据,最后拆除连接三个过程。

  • TCP在建立连接时又分三步走:

    • 第一步,是请求端(客户端)发送一个包含SYN即同步(Synchronize)标志的TCP报文,SYN同步报文会指明客户端使用的端口以及TCP连接的初始序号。

    • 第二步,服务器在收到客户端的SYN报文后,将返回一个SYN+ACK的报文,表示客户端的请求被接受,同时TCP序号被加一,ACK即确认(Acknowledgement)。

    • 第三步,客户端也返回一个确认报文ACK给服务器端,同样TCP序列号被加一,到此一个TCP连接完成。然后才开始通信的第二步:数据处理。

      这就是所说的TCP的三次握手(Three-way Handshake)。

2.3 UDP协议

  • 基于TCP协议可以建立稳定连接的点对点的通信。这种通信方式实时、快速、安全性高,但是很占用系统的资源。
  • 在网络传输方式上,还有另一种基于UDP协议的通信方式,称为数据报通信方式。在这种方式中,每个数据发送单元被统一封装成数据报包的方式,发送方将数据报包发送到网络中,数据报包在网络中去寻找它的目的地。

3 Java网络编程

  • Java为了可移植性,不允许直接调用操作系统,而是由java.net包来提供网络功能。Java虚拟机负责提供与操作系统的实际连接。下面我们来介绍几个java.net包中的常用的类。

3.1 InetAddress

  • 作用:封装计算机的IP地址和DNS(没有端口信息)。
    • 注:DNS是Domain Name System,域名系统。
  • 特点:这个类没有构造方法。如果要得到对象,只能通过静态方法:getLocalHost()、getByName()、 getAllByName()、 getAddress()、getHostName()。

3.1.1 使用getLocalHost方法创建InetAddress对象

import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test1 {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress addr = InetAddress.getLocalHost();
        System.out.println(addr.getHostAddress()); 
        System.out.println(addr.getHostName());     
    }
}

3.1.2 根据域名得到InetAddress对象

import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test2 {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress addr = InetAddress.getByName("www.sxt.cn");
        // 返回 sxt服务器的IP:59.110.14.7
        System.out.println(addr.getHostAddress());
        // 输出:www.sxt.cn
        System.out.println(addr.getHostName());
    }
}

3.1.3 根据IP得到InetAddress对象


import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test3 {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress addr = InetAddress.getByName("59.110.14.7");
        // 返回sxt服务器的IP:59.110.14.7
        System.out.println(addr.getHostAddress());
        /*
         * 输出ip而不是域名。如果这个IP地址不存在或DNS服务器不允许进行IP地址
         * 和域名的映射,getHostName方法就直接返回这个IP地址。
         */
        System.out.println(addr.getHostName());
    }
}

3.2 InetSocketAddress

3.2.1 作用:

  • 包含IP和端口信息,常用于Socket通信。此类实现 IP 套接字地址(IP 地址 + 端口号),不依赖任何协议。

3.2.2 InetSocketAddress的使用

import java.net.InetSocketAddress;
public class Test4 {
    public static void main(String[] args) {
        InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8080);
        InetSocketAddress socketAddress2 = new InetSocketAddress("localhost", 9000);
        System.out.println(socketAddress.getHostName());
        System.out.println(socketAddress2.getAddress());
    }
}

3.3 URL类

  • IP地址唯一标识了Internet上的计算机,而URL则标识了这些计算机上的资源。类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。
  • 为了方便程序员编程,JDK中提供了URL类,该类的全名是java.net.URL,有了这样一个类,就可以使用它的各种方法来对URL对象进行分割、合并等处理。

3.3.1 URL类的使用

    public static void test1() throws MalformedURLException {
        URL url = new URL("https://baike.baidu.com/item/%E7%8C%AB/22261?fr=aladdin");
        //443
        System.out.println("获取与此url关联的协议的默认端口:" + url.getDefaultPort());
        //端口号后面的内容
        //  /item/%E7%8C%AB/22261?fr=aladdin
        System.out.println("getFile:" + url.getFile());
        //baike.baidu.com
        System.out.println("主机名:" + url.getHost());
        //端口号后,参数前的内容
        //  /item/%E7%8C%AB/22261
        System.out.println("路径:" + url.getPath());
        //如果www.google.cn:80则返回80.否则返回-1
        //-1
        System.out.println("端口:" + url.getPort());
        //https
        System.out.println("协议:" + url.getProtocol());
        //fr=aladdin
        System.out.println("参数部分:" + url.getQuery());
        //null
        System.out.println("锚点:" + url.getRef());

        URL u1 = new URL("http://www.abc.com/aa/");
        URL u2 = new URL(url, "XXXXX"); // 相对路径构建url对象,把原路径最后一段替换成了字符串的内容
        System.out.println(u2.toString()); // https://baike.baidu.com/item/%E7%8C%AB/XXXXX
    }

3.3.2 最简单的网络爬虫

public static void test() throws IOException {
        URL url = new URL("https://baike.baidu.com/tashuo/browse/content?id=7aed6f673b7453137a033b59");
        InputStream inputStream = url.openStream();
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        try {
            inputStreamReader = new InputStreamReader(inputStream);
            bufferedReader = new BufferedReader(inputStreamReader);
            String tempString="";

            while ((tempString=bufferedReader.readLine())!=null){
                System.out.println(tempString);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(bufferedReader != null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    inputStreamReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3.4 基于TCP协议的Socket编程和通信

  • 在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质的区别。

3.4.1 “请求-响应”模式:

  • Socket类:发送TCP消息。

  • ServerSocket类:创建服务器。

  • 套接字是一种进程间的数据交换机制。这些进程既可以在同一机器上,也可以在通过网络连接的不同机器上。换句话说,套接字起到通信端点的作用。单个套接字是一个端点,而一对套接字则构成一个双向通信信道,使非关联进程可以在本地或通过网络进行数据交换。一旦建立套接字连接,数据即可在相同或不同的系统中双向或单向发送,直到其中一个端点关闭连接。套接字与主机地址和端口地址相关联。主机地址就是客户端或服务器程序所在的主机的IP地址。端口地址是指客户端或服务器程序使用的主机的通信端口。

  • 在客户端和服务器中,分别创建独立的Socket,并通过Socket的属性,将两个Socket进行连接,这样,客户端和服务器通过套接字所建立的连接使用输入输出流进行通信。

  • TCP/IP套接字是最可靠的双向流协议,使用TCP/IP可以发送任意数量的数据。

  • 实际上,套接字只是计算机上已编号的端口。如果发送方和接收方计算机确定好端口,他们就可以通信了。

  • 客户端与服务器端的通信关系图如下:
    在这里插入图片描述

3.4.2 TCP/IP通信连接的简单过程

  • 位于A计算机上的TCP/IP软件向B计算机发送包含端口号的消息,B计算机的TCP/IP软件接收该消息,并进行检查,查看是否有它知道的程序正在该端口上接收消息。如果有,他就将该消息交给这个程序。
  • 要使程序有效地运行,就必须有一个客户端和一个服务器。

3.4.3 通过Socket的编程顺序

  • 创建服务器ServerSocket,在创建时,定义ServerSocket的监听端口(在这个端口接收客户端发来的消息)。
  • ServerSocket调用accept()方法,使之处于阻塞状态。
  • 创建客户端Socket,并设置服务器的IP及端口。
  • 客户端发出连接请求,建立连接。
  • 分别取得服务器和客户端Socket的InputStream和OutputStream。
  • 利用Socket和ServerSocket进行数据传输。
  • 关闭流及Socket。

3.4.4 TCP:双向通信Socket

  • 服务端代码
package com.kuang.net.shangxuetang;


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

public class TestTcpServerSocket {
    public static void main(String[] args) throws IOException {
        serverWork();
    }

    public static void serverWork() throws IOException {
        ServerSocket serverSocket = null;
        Socket accept = null;
        BufferedReader bin = null;
        BufferedWriter bout = null;
        BufferedReader bsysin = null;

        try {
            serverSocket = new ServerSocket(2000);
            accept = serverSocket.accept();
            bin = new BufferedReader(new InputStreamReader(accept.getInputStream()));
            bout = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            bsysin = new BufferedReader(new InputStreamReader(System.in));
            while (true){
                String s = bin.readLine();
                System.out.println("客户端说:"+s);
                if("bye".equals(s)){
                    break;
                }

                bout.write(bsysin.readLine()+"\n");
                bout.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(bsysin != null){
                try {
                    bsysin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bout != null){
                try {
                    bout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bin != null){
                try {
                    bin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(accept != null){
                try {
                    accept.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(serverSocket != null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

  • 客户端代码
package com.kuang.net.shangxuetang;

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

public class TestTcpClientSocket {
    public static void main(String[] args) throws IOException {
        clientWork();

    }

    public static void clientWork() throws IOException {
        Socket socket = null;
        BufferedReader bin = null;
        BufferedWriter bout = null;
        BufferedReader binSys = null;

        try {
            socket = new Socket(InetAddress.getByName("127.0.0.1"),2000);
            bout = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            binSys = new BufferedReader(new InputStreamReader(System.in));
            bin = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (true){
                String s = binSys.readLine();
                bout.write(s+"\n");
                bout.flush();
                if("bye".equals(s)){
                    break;
                }

                System.out.println("服务端说:"+bin.readLine());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(binSys != null){
                try {
                    binSys.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bout != null){
                try {
                    bout.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(bin != null){
                try {
                    bin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  • 注意
    • 运行时,要先启动服务器端,再启动客户端,才能得到正常的运行效果。
    • 上面这个程序,必须按照安排好的顺序,服务器和客户端一问一答!不够灵活!!可以使用多线程实现更加灵活的双向通讯!!

3.4.5 TCP聊天室

  • 下面程序可以实现客户端或服务端连续说,也可以实现服务端先说bye,客户端再说bye。
  • 客户端聊天者的类:
package com.kuang.net.shangxuetang.chat;

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

public class ClientChatter {
    private String ip;
    private int toPort;
    ServerSocket serverSocket;
    Socket socket = null;
    InputStream inputStream = null;
    OutputStream outputStream = null;


    public ClientChatter(String ip,  int toPort) throws IOException {
        this.ip = ip;
        this.toPort = toPort;
        socket = new Socket(InetAddress.getByName(ip), toPort);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public OutputStream getOutputStream() {
        return outputStream;
    }

    public Socket getSocket() {
        return socket;
    }

    public void speak() throws IOException {
        OutputStream outputStream = socket.getOutputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        try {
            while (true){
                String s = bufferedReader.readLine();
                outputStream.write(s.getBytes());
                if("bye".equals(s)){
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
/*           if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }*/
            if(bufferedReader!=null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }



    }
    
    public void listen() throws IOException {
        byte[] bytes = new byte[1024];
        int temp=0;
        String content="";
        InputStream inputStream = socket.getInputStream();
        try {
            while (true){
                if((temp=inputStream.read(bytes))!=-1){
                    content=new String(bytes,0,temp);
                    System.out.println("服务端说:"+content);
                }
                if("bye".equals(content)){
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
/*            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }*/
        }
    }
}

  • 服务端聊天者的类:
package com.kuang.net.shangxuetang.chat;

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

public class ServerChatter {
    private int listenPort;
    ServerSocket serverSocket;
    Socket accept = null;
    InputStream inputStream = null;
    OutputStream outputStream = null;

    public ServerChatter(int listenPort) throws IOException {
        this.listenPort = listenPort;
        serverSocket = new ServerSocket(listenPort);
        accept = serverSocket.accept();
        inputStream = accept.getInputStream();
        outputStream = accept.getOutputStream();
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public OutputStream getOutputStream() {
        return outputStream;
    }

    public void speak() throws IOException {
        OutputStream outputStream = accept.getOutputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        try {
            while (true){
                String s = bufferedReader.readLine();
                outputStream.write(s.getBytes());
                if("bye".equals(s)){
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
/*            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }*/
            if(bufferedReader!=null){
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }



    }
    
    public void listen() throws IOException {
        byte[] bytes = new byte[1024];
        int temp=0;
        String content="";
        InputStream inputStream = accept.getInputStream();
        try {
            while (true){
                if((temp=inputStream.read(bytes))!=-1){
                    content=new String(bytes,0,temp);
                    System.out.println("客户端说:"+content);
                }
                if("bye".equals(content)){
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
/*            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }*/
        }
    }
}

  • 创建客户端聊天者对象,其两个线程,一个听一个说:
package com.kuang.net.shangxuetang.chat;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;

public class TestChatterClient {
    public static void main(String[] args) throws IOException {
        ClientChatter clientChatter = new ClientChatter("127.0.0.1",1000);
        CountDownLatch countDownLatch = new CountDownLatch(2);

        try {
            new Thread(()->{
                try {
                    clientChatter.speak();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            }).start();

            new Thread(()->{
                try {
                    clientChatter.listen();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            }).start();

            try {
                //等待,直到计数器归零后再向下执行
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            OutputStream outputStream = clientChatter.getOutputStream();
            InputStream inputStream = clientChatter.getInputStream();
            Socket socket = clientChatter.getSocket();
            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }



        }
    }
}

  • 创建服务端聊天者对象,其两个线程,一个听一个说:
package com.kuang.net.shangxuetang.chat;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.CountDownLatch;

public class TestChatterServer {
    public static void main(String[] args) throws IOException {
        ServerChatter serverChatter = new ServerChatter(1000);
        CountDownLatch countDownLatch = new CountDownLatch(2);

        try {
            new Thread(()->{
                try {
                    serverChatter.speak();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            }).start();

            new Thread(()->{
                try {
                    serverChatter.listen();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            }).start();

            try {
                //等待,直到计数器归零后再向下执行
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            InputStream inputStream = serverChatter.getInputStream();
            OutputStream outputStream = serverChatter.getOutputStream();

            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

    }
}

3.5 UDP通讯的实现

3.5.1 DatagramSocket:用于发送或接收数据报包

  • 当服务器要向客户端发送数据时,需要在服务器端产生一个DatagramSocket对象,在客户端产生一个DatagramSocket对象。服务器端的DatagramSocket将DatagramPacket发送到网络上,然后被客户端的DatagramSocket接收。

  • DatagramSocket有两种常用的构造函数。一种是无需任何参数的,常用于客户端;另一种需要指定端口,常用于服务器端。如下所示:

  • DatagramSocket() :构造数据报套接字并将其绑定到本地主机上任何可用的端口。

  • DatagramSocket(int port) :创建数据报套接字并将其绑定到本地主机上的指定端口。

  • 常用方法:

    • Ø send(DatagramPacket p) :从此套接字发送数据报包。

    • Ø receive(DatagramPacket p) :从此套接字接收数据报包。

    • Ø close() :关闭此数据报套接字。

3.5.2 DatagramPacket:数据容器(封包)的作用

  • 此类表示数据报包。 数据报包用来实现封包的功能。

  • 常用方法:

    • Ø DatagramPacket(byte[] buf, int length) :构造数据报包,用来接收长度为 length 的数据包。

    • Ø DatagramPacket(byte[] buf, int length, InetAddress address, int port) :构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。

    • Ø getAddress() :获取发送或接收方计算机的IP地址,此数据报将要发往该机器或者是从该机器接收到的。

    • Ø getData() :获取发送或接收的数据。

    • Ø setData(byte[] buf) :设置发送的数据。

3.5.3 UDP通信编程基本步骤:

  1. 创建客户端的DatagramSocket,创建时,定义客户端的监听端口。

  2. 创建服务器端的DatagramSocket,创建时,定义服务器端的监听端口。

  3. 在服务器端定义DatagramPacket对象,封装待发送的数据包。

  4. 客户端将数据报包发送出去。

  5. 服务器端接收数据报包。

3.5.4 UDP单项通信

  • 服务端代码
package com.kuang.net.shangxuetang.udp;


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

public class TestServerUDP {
    public static void main(String[] args) throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket(1001);
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes,0,bytes.length);
        datagramSocket.receive(datagramPacket);
        String string = new String(datagramPacket.getData(), 0, datagramPacket.getLength());
        System.out.println(string);
    }
}

  • 客户端代码
package com.kuang.net.shangxuetang.udp;

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

public class TestClientUDP {
    public static void main(String[] args) throws IOException {
        //创建数据报套接字:指定发送信息的端口
        DatagramSocket datagramSocket = new DatagramSocket();
        byte[] bytes = "大笨狗最爱他的小猫咪".getBytes();
        //必须告诉数据报包要发到哪台计算机的哪个端口,发送的数据以及数据的长度
        DatagramPacket datagramPacket = new DatagramPacket(bytes,0,bytes.length,
                InetAddress.getByName("127.0.0.1"),1001);
        //发送数据报包
        datagramSocket.send(datagramPacket);
        //关闭资源s
        datagramSocket.close();
    }
}

3.5.5 UDP聊天

  • 代码见 狂神说Java:网络编程 10

3.5.6 UDP:基本数据类型的传递

  • 发送端代码
package com.kuang.net.shangxuetang.udp;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class TestSimpleDataClientUDP {
    public static void main(String[] args) throws IOException {
        //将基本数据类型转换为byte数组
        int i=1000;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
        //注意:写的是int类型的话,这里方法必须是writeInt,否则接收端读出来的结果不对。
        dataOutputStream.writeInt(i);
        byte[] bytes = byteArrayOutputStream.toByteArray();
        //创建数据报套接字:指定发送信息的端口
        DatagramSocket datagramSocket = new DatagramSocket();
        //必须告诉数据报包要发到哪台计算机的哪个端口,发送的数据以及数据的长度
        DatagramPacket datagramPacket = new DatagramPacket(bytes,0,bytes.length,
                InetAddress.getByName("127.0.0.1"),1001);
        //发送数据报包
        datagramSocket.send(datagramPacket);
        //关闭资源s
        datagramSocket.close();
    }
}

  • 接收端代码
package com.kuang.net.shangxuetang.udp;


import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class TestSimpleDataServerUDP {
    public static void main(String[] args) throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket(1001);
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes,0,bytes.length);
        datagramSocket.receive(datagramPacket);
        DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(datagramPacket.getData()));
        System.out.println(dataInputStream.readInt());

    }
}

3.5.7 UDP:对象的传递之Person类

  • 类的代码:注意要序列化与反序列化(对象流的类必须要序列化与反序列化)
package com.kuang.net.shangxuetang.udp;

import java.io.Serializable;

public class Cat  implements Serializable {
    private String name;

    public Cat(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}

  • 发送端代码
package com.kuang.net.shangxuetang.udp;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class TestObjectClientUDP {
    public static void main(String[] args) throws IOException {
        //将对象类型转换为byte数组
        Cat cat = new Cat("花花");
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(cat);
        byte[] bytes = byteArrayOutputStream.toByteArray();
        //创建数据报套接字:指定发送信息的端口
        DatagramSocket datagramSocket = new DatagramSocket();
        //必须告诉数据报包要发到哪台计算机的哪个端口,发送的数据以及数据的长度
        DatagramPacket datagramPacket = new DatagramPacket(bytes,0,bytes.length,
                InetAddress.getByName("127.0.0.1"),1001);
        //发送数据报包
        datagramSocket.send(datagramPacket);
        //关闭资源s
        datagramSocket.close();
    }
}

  • 接收端代码
package com.kuang.net.shangxuetang.udp;


import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class TestObjectServerUDP {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        DatagramSocket datagramSocket = new DatagramSocket(1001);
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes,0,bytes.length);
        datagramSocket.receive(datagramPacket);
        ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(datagramPacket.getData()));
        System.out.println(objectInputStream.readObject());

    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值