• Java基础深化和提高 ---- 网络编程


    网络编程基本概念

    计算机网络

    计算机网络是指将地理位置不同的具有独立功能的多台计算机及其 外部设备,通过通信线路连接起来,在网络操作系统,网络管理软 件及网络通信协议的管理和协调下,实现资源共享和信息传递的计 算机系统。

    从其中我们可以提取到以下内容:

    1 计算机网络的作用:资源共享和信息传递。

    2 计算机网络的组成:

        计算机硬件:计算机(大中小型服务器,台式机、笔记本等)、外部设备(路由器、交换机 等)、通信线路(双绞线、光纤等)。

       计算机软件:网络操作系统(Windows 2000 Server/Advance Server、Unix、Linux等)、 网络管理软件(WorkWin、SugarNMS等)、网络通信协议(如TCP/IP协议栈等)。 

     网络通信协议

     什么是网络通信协议

    通过计算机网络可以实现不同计算机之间的连接与通信,但是计算 机网络中实现通信必须有一些约定即通信协议,对速率、传输代 码、代码结构、传输控制步骤、出错控制等制定标准。

    国际标准化组织(ISO,即International Organization for Standardization)定义了网络通信协议的基本框架,被称为OSI (Open System Interconnect,即开放系统互联)模型。要制定通 讯规则,内容会很多,比如要考虑A电脑如何找到B电脑,A电脑在 发送信息给B电脑时是否需要B电脑进行反馈,A电脑传送给B电脑的 数据格式又是怎样的?内容太多太杂,所以OSI模型将这些通讯标准 进行层次划分,每一层次解决一个类别的问题,这样就使得标准的 制定没那么复杂。

    OSI模型制定的七层标准模型,分别是:应用层, 表示层,会话层,传输层,网络层,数据链路层,物理层。

    OSI七层协议模型:

     网络协议的分层

    虽然国际标准化组织制定了这样一个网络通信协议的模型,但是实 际上互联网通讯使用最多的网络通信协议是TCP/IP网络通信协议。 TCP/IP 模型,也是按照层次划分,共四层:应用层,传输层,网络 层,网络接口层(物理+数据链路层)。 OSI模型与TCP/IP模型的对应关系:

     数据封装与解封

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

    数据发送处理过程

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

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

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

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

    数据接收处理过程

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

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

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

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

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

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

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

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

    数据封装与解封:

        数据封装

    数据解封

    IP地址 

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

    IP地址分类: 

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

     

     IPV4,采用32位地址长度,只有大约43亿个地址,它只有4段 数字,每一段最大不超过255。随着互联网的发展,IP地址不够 用了,在2019年11月25日IPv4位地址分配完毕。 IPv6采用128位地址长度,几乎可以不受限制地提供地址。按保 守方法估算IPv6实际可分配的地址,整个地球的每平方米面积 上仍可分配1000多个地址。

    IP地址实际上是一个32位整数(称为IPv4),以字符串表示的IP地 址如 192.168.0.1 实际上是把32位整数按8位分组后的数字表示,目的是 便于阅读。 IPv6地址实际上是一个128位整数,它是目前使用的IPv4的升级版, 以字符串表示类似于 2001:0db8:85a3:0042:1000:8a2e:0370:7334 

    公有地址 

    公有地址(Public address)由Inter NIC(Internet Network Information Center互联网信息中心)负责。这些IP地址分配给注 册并向Inter NIC提出申请的组织机构。通过它直接访问互联网。

     私有地址

    私有地址(Private address)属于非注册地址,专门为组织机构内 部使用。 以下列出留用的内部私有地址

    A类 10.0.0.0--10.255.255.255

    B类 172.16.0.0--172.31.255.255

    C类 192.168.0.0--192.168.255.255

    注意事项

    1、127.0.0.1 本机地址

    2、192.168.0.0--192.168.255.255为私有地址,属于非注册地址,专门为组织机构内部使用 

    端口port 

    端口号用来识别计算机中进行通信的应用程序。因此,它也被称为 程序地址。 一台计算机上同时可以运行多个程序。传输层协议正是利用这些端 口号识别本机中正在进行通信的应用程序,并准确地进行数据传 输。

     

     总结

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

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

    端口分配 

    端口是虚拟的概念,并不是说在主机上真的有若干个端口。通过端 口,可以在一个主机上运行多个网络应用程序。 端口的表示是一个 16位的二进制整数,对应十进制的0-65535。 操作系统中一共提供了0~65535可用端口范围。 按端口号分类:

    公认端口(Well Known Ports):从0到1023,它们紧密绑定 (binding)于一些服务。通常这些端口的通讯明确表明了某种服务 的协议。例如:80端口实际上总是HTTP通讯。

    注册端口(Registered Ports):从1024到65535。它们松散地绑 定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同 样用于许多其它目的。例如:许多系统处理动态端口从1024左右开 始。

    URL 

     URL作用:

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

    Socket 

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

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

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

     TCP协议和UDP协议

    TCP协议

    TCP(Transmission Control Protocol,传输控制协议)。TCP方 式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专 门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则 客户端会自动重发该数据。

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

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

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

    n 第三步,客户端也返回一个确认报文ACK给服务器端,同样TCP序 列号被加一,到此一个TCP连接完成。然后才开始通信的第二步: 数据处理。 n 这就是所说的TCP的三次握手(Three-way Handshake)。

    UDP协议

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

    UDP是一个非连接的协议,传输数据之前源端和终端不建立连接, 当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地 把它扔到网络上。 在发送端,UDP传送数据的速度仅仅是受应用程 序生成数据的速度、 计算机的能力和传输带宽的限制; 在接收端, UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息 段。

    UDP方式就类似于发送短信,使用这种方式进行网络通讯时,不需 要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户 端无法获得。 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络 条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网 络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些 实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP 

    TCP和UDP区别 

    这两种传输方式都在实际的网络编程中使用,重要的数据一般使用 TCP方式进行数据传输,而大量的非核心数据则可以通过UDP方式 进行传递,在一些程序中甚至结合使用这两种方式进行数据传递。 由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使 用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP 稍微大一些。

     

     总结

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

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

     Java网络编程中的常用类

    Java为了跨平台,在网络应用通信时是不允许直接调用操作系统接 口的,而是由java.net包来提供网络功能。下面我们来介绍几个 java.net包中的常用的类。

    InetAddress的使用 

    作用:封装计算机的IP地址和DNS(没有端口信息)

    注:DNS是Domain Name System,域名系统。

    特点:

    这个类没有构造方法。如果要得到对象,只能通过静态方法:

    getLocalHost()、getByName()、 getAllByName()、 getAddress()、getHostName()

     获取本机信息

    获取本机信息需要使用getLocalHost方法创建InetAddress对象。 getLocalHost()方法返回一个InetAddress对象,这个对象包含了本 机的IP地址,计算机名等信息。

    1. public class InetTest {
    2. public static void main(String[] args)throws Exception {
    3. //实例化InetAddress对象
    4. InetAddress inetAddress = InetAddress.getLocalHost();
    5. //返回当前计算机的IP地址
    6. System.out.println(inetAddress.getHostAddress());
    7. //返回当前计算机名
    8. System.out.println(inetAddress.getHostName());
    9. }
    10. }

    根据域名获取计算机的信息

    根据域名获取计算机信息时需要使用getByName(“域名”)方法创建 InetAddress对象。

    1. public class InetTest2 {
    2. public static void main(String[] args)throws Exception {
    3. InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
    4. System.out.println(inetAddress.getHostAddress());
    5. System.out.println(inetAddress.getHostName());
    6. }
    7. }

    根据IP获取计算机的信息

    根据IP地址获取计算机信息时需要使用getByName(“IP”)方法创建 InetAddress对象。

    1. public class InetTest3 {
    2. public static void main(String[] args)throws Exception {
    3. InetAddress inetAddress = InetAddress.getByName("14.215.177.38");
    4. System.out.println(inetAddress.getHostAddress());
    5. System.out.println(inetAddress.getHostName());
    6. }
    7. }

     InetSocketAddress的使用

    作用:包含IP和端口信息,常用于Socket通信。此类实现 IP 套接字 地址(IP 地址 + 端口号),不依赖任何协议。 InetSocketAddress相比较InetAddress多了一个端口号,端口的作 用:一台拥有IP地址的主机可以提供许多服务,比如Web服务、 FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实 现。 那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址, 因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地 址+端口号”来区分不同的服务的。

    1. public class InetSocketTest {
    2. public static void main(String[] args) {
    3. InetSocketAddress inetSocketAddress = new InetSocketAddress("www.baidu.com",80);
    4. System.out.println(inetSocketAddress.getAddress().getHostAddress());
    5. System.out.println(inetSocketAddress.getHostName());
    6. }
    7. }

    URL的使用

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

    1. public class UrlTest {
    2. public static void main(String[] args)throws Exception {
    3. URL url = new URL("https://www.itbaizhan.com/search.html?kw=java");
    4. System.out.println("获取与此URL相关联协议的默认端口:"+url.getDefaultPort());
    5. System.out.println("访问资源:"+url.getFile());
    6. System.out.println("主机名"+url.getHost());
    7. System.out.println("访问资源路径:"+url.getPath());
    8. System.out.println("协议:"+url.getProtocol());
    9. System.out.println("参数部分:"+url.getQuery());
    10. }
    11. }

     通过URL实现最简单的网络爬虫

    1. public class UrlTest2{
    2. public static void main(String[] args)throws Exception {
    3. URL url = new URL("https://www.itbaizhan.com/");
    4. try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
    5. StringBuilder sb = new StringBuilder();
    6. String temp;
    7. /*
    8. * 这样就可以将网络内容下载到本地机器。
    9. * 然后进行数据分析,建立索引。这也是搜索引擎的第一步。
    10. */
    11. while ((temp = br.readLine()) != null) {
    12. sb.append(temp);
    13. }
    14. System.out.println(sb);
    15. } catch (Exception e) {
    16. e.printStackTrace();
    17. }
    18. }
    19. }

    TCP通信的实现和项目案例

    TCP通信实现原理

    前边我们提到TCP协议是面向的连接的,在通信时客户端与服务器 端必须建立连接。在网络通讯中,第一次主动发起通讯的程序被称 作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的 程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则 客户端和服务器端完全一样,没有本质的区别。

     “请求-响应”模式:

    Socket类:发送TCP消息。

    ServerSocket类:创建服务器。

     套接字Socket是一种进程间的数据交换机制。这些进程既可以在同 一机器上,也可以在通过网络连接的不同机器上。换句话说,套接 字起到通信端点的作用。单个套接字是一个端点,而一对套接字则 构成一个双向通信信道,使非关联进程可以在本地或通过网络进行 数据交换。一旦建立套接字连接,数据即可在相同或不同的系统中 双向或单向发送,直到其中一个端点关闭连接。套接字与主机地址 和端口地址相关联。主机地址就是客户端或服务器程序所在的主机 的IP地址。端口地址是指客户端或服务器程序使用的主机的通信端 口。 在客户端和服务器中,分别创建独立的Socket,并通过Socket的属 性,将两个Socket进行连接,这样,客户端和服务器通过套接字所 建立的连接使用输入输出流进行通信。 TCP/IP套接字是最可靠的双向流协议,使用TCP/IP可以发送任意数 量的数据。 实际上,套接字只是计算机上已编号的端口。如果发送方和接收方 计算机确定好端口,他们就可以通信了。 客户端与服务器端的通信关系图:

     

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

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

     通过Socket的编程顺序:

    1 创建服务器ServerSocket,在创建时,定义ServerSocket的监听端口(在这个端口接收客户端发来 的消息)

    2 ServerSocket调用accept()方法,使之处于阻塞状态。

    3 创建客户端Socket,并设置服务器的IP及端口。

    4 客户端发出连接请求,建立连接。

    5 分别取得服务器和客户端Socket的InputStream和OutputStream。

    6 利用Socket和ServerSocket进行数据传输。

    7 关闭流及Socket。

    TCP通信入门案例 

    创建服务端

    1. public class BasicSocketServer {
    2. public static void main(String[] args) {
    3. System.out.println("服务器启动等待监听。。。。");
    4. //创建ServerSocket
    5. try(ServerSocket ss =new ServerSocket(8888);
    6. //监听8888端口,此时线程会处于阻塞状态。
    7. Socket socket = ss.accept();
    8. //连接成功后会得到与客户端对应的 Socket对象,并解除线程阻塞。
    9. //通过客户端对应的Socket对象中的输入流对象,获取客户端发送过来的消息。
    10. BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))
    11. ){
    12. System.out.println(br.readLine());
    13. }catch(Exception e){
    14. e.printStackTrace();
    15. System.out.println("服务器启动失败。。。。");
    16. }
    17. }
    18. }

    创建客户端

    1. public class BasicSocketClient {
    2. public static void main(String[] args) {
    3. //创建Socket对象
    4. try(Socket socket =new Socket("127.0.01",8888);
    5. //创建向服务端发送消息的输出流对象。
    6. PrintWriter pw = new PrintWriter(socket.getOutputStream())){
    7. pw.println("服务端,您好!");
    8. pw.flush();
    9. }catch(Exception e){
    10. e.printStackTrace();
    11. }
    12. }
    13. }

    TCP单向通信

    单向通信是指通信双方中,一方固定为发送端,一方则固定为接收 端。

     创建服务端

    1. public class OneWaySocketServer {
    2. public static void main(String[] args) {
    3. System.out.println("服务端启动,开始监听。。。。。");
    4. try(ServerSocket serverSocket = new ServerSocket(8888);
    5. //监听8888端口,获与取客户端对应的 Socket对象
    6. Socket socket = serverSocket.accept();
    7. //通过与客户端对应的Socket对象获取输入流对象
    8. BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    9. //通过与客户端对应的Socket对象获取输出流对象
    10. PrintWriter pw = new PrintWriter(socket.getOutputStream())){
    11. System.out.println("连接成功!");
    12. while(true){
    13. //读取客户端发送的消息
    14. String str = br.readLine();
    15. System.out.println("客户端说:"+str);
    16. if("exit".equals(str)){
    17. break;
    18. }
    19. pw.println(str);
    20. pw.flush();
    21. }
    22. }catch(Exception e){
    23. e.printStackTrace();
    24. System.out.println("服务端启动失败。。。。。");
    25. }
    26. }
    27. }

    创建客户端

    1. public class OneWaySocketClient {
    2. public static void main(String[] args) {
    3. //获取与服务端对应的Socket对象
    4. try(Socket socket = new Socket("127.0.0.1",8888);
    5. //创建键盘输入对象
    6. Scanner scanner = new Scanner(System.in);
    7. //通过与服务端对应的Socket对象获取输出流对象
    8. PrintWriter pw = new PrintWriter(socket.getOutputStream());
    9. //通过与服务端对应的Socket对象获取输入流对象
    10. BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())))
    11. {
    12. while(true){
    13. //通过键盘输入获取需要向服务端发送的消息
    14. String str = scanner.nextLine();
    15. //将消息发送到服务端
    16. pw.println(str);
    17. pw.flush();
    18. if("exit".equals(str)){
    19. break;
    20. }
    21. //读取服务端返回的消息
    22. String serverInput = br.readLine();
    23. System.out.println("服务端返回的:"+serverInput);
    24. }
    25. }catch(Exception e){
    26. e.printStackTrace();
    27. }
    28. }
    29. }

    TCP双向通信

    双向通信是指通信双方中,任何一方都可为发送端,任何一方都可 为接收端。

    创建服务端

    1. public class TwoWaySocketServer {
    2. public static void main(String[] args) {
    3. System.out.println("服务端启动!监听端口8888。。。。");
    4. try(ServerSocket serverSocket = new ServerSocket(8888);
    5. Socket socket = serverSocket.accept();
    6. //创建键盘输入对象
    7. Scanner scanner = new Scanner(System.in);
    8. //通过与客户端对应的Socket对象获取输入流对象
    9. BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    10. //通过与客户单对应的Socket对象获取输出流对象
    11. PrintWriter pw = new PrintWriter(socket.getOutputStream());){
    12. while(true){
    13. //读取客户端发送的消息
    14. String str = br.readLine();
    15. System.out.println("客户端说:"+str);
    16. String keyInput = scanner.nextLine();
    17. //发送到客户端
    18. pw.println(keyInput);
    19. pw.flush();
    20. }
    21. }catch(Exception e){
    22. e.printStackTrace();
    23. }
    24. }
    25. }

    创建客户端

    1. public class TwoWaySocketClient {
    2. public static void main(String[] args) {
    3. try(Socket socket = new Socket("127.0.0.1", 8888);
    4. //创建键盘输入对象
    5. Scanner scanner = new Scanner(System.in);
    6. //通过与服务端对应的Socket对象获取输入流对象
    7. BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    8. //通过与服务端对应的Socket对象获取输出流对象
    9. PrintWriter pw = new PrintWriter(socket.getOutputStream());){
    10. while (true) {
    11. String keyInput = scanner.nextLine();
    12. pw.println(keyInput);
    13. pw.flush();
    14. String input = br.readLine();
    15. System.out.println("服务端说:" + input);
    16. }
    17. } catch (Exception e) {
    18. e.printStackTrace();
    19. }
    20. }
    21. }

    创建点对点的聊天应用

    创建服务端

    1. /**
    2. * 发送消息线程
    3. */
    4. class Send extends Thread{
    5. private Socket socket;
    6. public Send(Socket socket){
    7. this.socket = socket;
    8. }
    9. @Override
    10. public void run() {
    11. this.sendMsg();
    12. }
    13. /**
    14. * 发送消息
    15. */
    16. private void sendMsg(){
    17. //创建Scanner对象
    18. try(Scanner scanner = new Scanner(System.in);
    19. //创建向对方输出消息的流对象
    20. PrintWriter pw = new PrintWriter(this.socket.getOutputStream());)
    21. {
    22. while(true){
    23. String msg = scanner.nextLine();
    24. pw.println(msg);
    25. pw.flush();
    26. }
    27. }catch(Exception e){
    28. e.printStackTrace();
    29. }
    30. }
    31. }
    32. /**
    33. * 接收消息的线程
    34. */
    35. class Receive extends Thread{
    36. private Socket socket;
    37. public Receive(Socket socket){
    38. this.socket = socket;
    39. }
    40. @Override
    41. public void run() {
    42. this.receiveMsg();
    43. }
    44. /**
    45. * 用于接收对方消息的方法
    46. */
    47. private void receiveMsg(){
    48. //创建用于接收对方发送消息的流对象
    49. try(BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));){
    50. while(true){
    51. String msg = br.readLine();
    52. System.out.println("他说:"+msg);
    53. }
    54. }catch(Exception e){
    55. e.printStackTrace();
    56. }
    57. }
    58. }
    59. public class ChatSocketServer {
    60. public static void main(String[] args) {
    61. try(ServerSocket serverSocket = new ServerSocket(8888);){
    62. System.out.println("服务端启动,等待连接。。。。。");
    63. Socket socket = serverSocket.accept();
    64. System.out.println("连接成功!");
    65. new Send(socket).start();
    66. new Receive(socket).start();
    67. }catch(Exception e){
    68. e.printStackTrace();
    69. }
    70. }
    71. }

    创建客户端

    1. /**
    2. * 用于发送消息的线程类
    3. */
    4. class ClientSend extends Thread{
    5. private Socket socket;
    6. public ClientSend(Socket socket){
    7. this.socket = socket;
    8. }
    9. @Override
    10. public void run() {
    11. this.sendMsg();
    12. }
    13. /**
    14. * 发送消息
    15. */
    16. private void sendMsg(){
    17. //创建Scanner对象
    18. try(Scanner scanner = new Scanner(System.in);
    19. //创建向对方输出消息的流对象
    20. PrintWriter pw = new PrintWriter(this.socket.getOutputStream());)
    21. {
    22. while(true){
    23. String msg = scanner.nextLine();
    24. pw.println(msg);
    25. pw.flush();
    26. }
    27. }catch(Exception e){
    28. e.printStackTrace();
    29. }
    30. }
    31. }
    32. /**
    33. * 用于接收消息的线程类
    34. */
    35. class ClientReceive extends Thread{
    36. private Socket socket;
    37. public ClientReceive(Socket socket){
    38. this.socket = socket;
    39. }
    40. @Override
    41. public void run() {
    42. this.receiveMsg();
    43. }
    44. /**
    45. * 用于接收对方消息的方法
    46. */
    47. private void receiveMsg(){
    48. //创建用于接收对方发送消息的流对象
    49. try(BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));){
    50. while(true){
    51. String msg = br.readLine();
    52. System.out.println("他说:"+msg);
    53. }
    54. }catch(Exception e){
    55. e.printStackTrace();
    56. }
    57. }
    58. }
    59. public class ChatSocketClient {
    60. public static void main(String[] args) {
    61. try {
    62. Socket socket = new Socket("127.0.0.1", 8888);
    63. System.out.println("连接成功!");
    64. new ClientSend(socket).start();
    65. new ClientReceive(socket).start();
    66. }catch(Exception e){
    67. e.printStackTrace();
    68. }
    69. }
    70. }

    优化点对点聊天应用

    1. /**
    2. * 发送消息线程
    3. */
    4. class Send extends Thread{
    5. private Socket socket;
    6. private Scanner scanner;
    7. public Send(Socket socket,Scanner scanner){
    8. this.socket = socket;
    9. this.scanner = scanner;
    10. }
    11. @Override
    12. public void run() {
    13. this.sendMsg();
    14. }
    15. /**
    16. * 发送消息
    17. */
    18. private void sendMsg(){
    19. //创建向对方输出消息的流对象
    20. try(PrintWriter pw = new PrintWriter(this.socket.getOutputStream()))
    21. {
    22. while(true){
    23. String msg = scanner.nextLine();
    24. pw.println(msg);
    25. pw.flush();
    26. }
    27. }catch(Exception e){
    28. e.printStackTrace();
    29. }
    30. }
    31. }
    32. /**
    33. * 接收消息的线程
    34. */
    35. class Receive extends Thread{
    36. private Socket socket;
    37. public Receive(Socket socket){
    38. this.socket = socket;
    39. }
    40. @Override
    41. public void run() {
    42. this.receiveMsg();
    43. }
    44. /**
    45. * 用于接收对方消息的方法
    46. */
    47. private void receiveMsg(){
    48. //创建用于接收对方发送消息的流对象
    49. try(BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()))){
    50. while(true){
    51. String msg = br.readLine();
    52. System.out.println("他说:"+msg);
    53. }
    54. }catch(Exception e){
    55. e.printStackTrace();
    56. }
    57. }
    58. }
    59. public class GoodTCP {
    60. public static void main(String[] args)
    61. {
    62. Scanner scanner = null;
    63. ServerSocket serverSocket = null;
    64. Socket socket = null;
    65. try{
    66. scanner = new Scanner(System.in);
    67. System.out.println("请输入:server, 或者:,");
    68. String str = scanner.nextLine();
    69. String[] arr = str.split(",");
    70. if("server".equals(arr[0])){
    71. //启动服务端
    72. System.out.println("TCP Server Listen at "+arr[1]+" .....");
    73. serverSocket = new ServerSocket(Integer.parseInt(arr[1]));
    74. socket = serverSocket.accept();
    75. System.out.println("连接成功!");
    76. }else{
    77. //启动客户端
    78. socket = new Socket(arr[0],Integer.parseInt(arr[1]));
    79. System.out.println("连接成功!");
    80. }
    81. //启动发送消息的线程
    82. new Send(socket,scanner).start();
    83. //启动接收消息的线程
    84. }catch(Exception e){
    85. e.printStackTrace();
    86. }finally{
    87. if(serverSocket != null){
    88. try {
    89. serverSocket.close();
    90. } catch (IOException e) {
    91. e.printStackTrace();
    92. }
    93. }
    94. }
    95. }
    96. }

    一对多应用

    一对多应用设计

    各socket对间独立问答,互相间不需要传递信息。

     一对多应答型服务器

    1. /**
    2. * 定义消息处理线程类
    3. */
    4. class Msg extends Thread{
    5. private Socket socket;
    6. public Msg(Socket socket){
    7. this.socket = socket;
    8. }
    9. @Override
    10. public void run() {
    11. this.msg();
    12. }
    13. /**
    14. * 将从客户端读取到的消息写回给客户端
    15. */
    16. private void msg(){
    17. try(BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
    18. PrintWriter pw = new PrintWriter(this.socket.getOutputStream())){
    19. while(true){
    20. pw.println(br.readLine()+" [ok]");
    21. pw.flush();
    22. }
    23. }catch(Exception e){
    24. e.printStackTrace();
    25. System.out.println(this.socket.getInetAddress()+" 断线了!");
    26. }
    27. }
    28. }
    29. public class EchoServer {
    30. public static void main(String[] args) {
    31. try(ServerSocket serverSocket = new ServerSocket(8888)){
    32. //等待多客户端连接
    33. while(true){
    34. Socket socket = serverSocket.accept();
    35. new Msg(socket).start();
    36. }
    37. }catch(Exception e){
    38. e.printStackTrace();
    39. }
    40. }
    41. }

    一对多聊天服务器

    服务器设计

    1、服务器的连接设计

    2、服务器的线程设计

     创建一对多聊天服务应用

    1. /**
    2. * 接收客户端消息的线程类
    3. */
    4. class ChatReceive extends Thread{
    5. private Socket socket;
    6. public ChatReceive(Socket socket){
    7. this.socket =socket;
    8. }
    9. @Override
    10. public void run() {
    11. this.receiveMsg();
    12. }
    13. /**
    14. * 实现接收客户端发送的消息
    15. */
    16. private void receiveMsg(){
    17. try(BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()))){
    18. while(true){
    19. String msg = br.readLine();
    20. synchronized ("abc"){
    21. //把读取到的数据写入公共数据区
    22. ChatRoomServer.buf=" ["+this.socket.getInetAddress()+"] "+msg;
    23. //唤醒发送消息的线程对象。
    24. "abc".notifyAll();
    25. }
    26. }
    27. }catch(Exception e){
    28. e.printStackTrace();
    29. }
    30. }
    31. }
    32. /**
    33. * 向客户端发送消息的线程类
    34. */
    35. class ChatSend extends Thread{
    36. private Socket socket;
    37. public ChatSend(Socket socket){
    38. this.socket = socket;
    39. }
    40. @Override
    41. public void run() {
    42. this.sendMsg();
    43. }
    44. /**
    45. * 将公共数据区的消息发送给客户端
    46. */
    47. private void sendMsg(){
    48. try(PrintWriter pw = new PrintWriter(this.socket.getOutputStream())){
    49. while(true){
    50. synchronized ("abc"){
    51. //让发送消息的线程处于等待状态
    52. "abc".wait();
    53. //将公共数据区中的消息发送给客户端
    54. pw.println(ChatRoomServer.buf);
    55. pw.flush();
    56. }
    57. }
    58. }catch(Exception e){
    59. e.printStackTrace();
    60. }
    61. }
    62. }
    63. public class ChatRoomServer {
    64. //定义公共数据区
    65. public static String buf;
    66. public static void main(String[] args) {
    67. System.out.println("Chat Server Version 1.0");
    68. System.out.println("Listen at 8888.....");
    69. try(ServerSocket serverSocket = new ServerSocket(8888)){
    70. while(true){
    71. Socket socket = serverSocket.accept();
    72. System.out.println("连接到:"+socket.getInetAddress());
    73. new ChatReceive(socket).start();
    74. new ChatSend(socket).start();
    75. }
    76. }catch(Exception e){
    77. e.printStackTrace();
    78. }
    79. }
    80. }

    UDP通信的实现和项目案例

    UDP通信实现原理

    UDP协议与之前讲到的TCP协议不同,是面向无连接的,双方不需 要建立连接便可通信。UDP通信所发送的数据需要进行封包操作 (使用DatagramPacket类),然后才能接收或发送(使用 DatagramSocket类)。

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

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

    常用方法:

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

    当服务器要向客户端发送数据时,需要在服务器端产生一个 DatagramSocket对象,在客户端产生一个DatagramSocket对象。 服务器端的DatagramSocket将DatagramPacket发送到网络上,然 后被客户端的DatagramSocket接收。 DatagramSocket有两种常用的构造函数。一种是无需任何参数 的,常用于客户端;另一种需要指定端口,常用于服务器端。

    如下 所示:

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

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

     常用方法:

     UDP通信编程基本步骤:

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

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

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

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

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

     UDP通信入门案例

    创建服务端

    1. public class UDPServer {
    2. public static void main(String[] args) {
    3. //创建服务端接收数据的DatagramSocket对象
    4. try(DatagramSocket datagramSocket = new DatagramSocket(9999)){
    5. //创建数据缓存区
    6. byte[] b = new byte[1024];
    7. //创建数据报包对象
    8. DatagramPacket dp =new DatagramPacket(b,b.length);
    9. //等待接收客户端所发送的数据
    10. datagramSocket.receive(dp);
    11. String str = new String(dp.getData(),0,dp.getLength());
    12. System.out.println(str);
    13. }catch(Exception e){
    14. e.printStackTrace();
    15. }
    16. }
    17. }

    创建客户端

    1. public class UDPClient {
    2. public static void main(String[] args) {
    3. //创建数据发送对象 DatagramSocket,需要指定消息的发送端口
    4. try(DatagramSocket ds = new DatagramSocket(8888)) {
    5. //消息需要进行类型转换,转换成字节数据类型。
    6. byte[] b = "程序员".getBytes();
    7. //创建数据报包装对象DatagramPacket
    8. DatagramPacket dp = new DatagramPacket(b, b.length, new InetSocketAddress("127.0.0.1", 9999));
    9. //发送消息
    10. ds.send(dp);
    11. }catch(Exception e){
    12. e.printStackTrace();
    13. }
    14. }
    15. }

    传递基本数据类型

    创建服务端

    1. public class BasicTypeUDPServer {
    2. public static void main(String[] args) {
    3. try(DatagramSocket datagramSocket = new DatagramSocket(9999)){
    4. byte[] buf = new byte[1024];
    5. DatagramPacket dp = new DatagramPacket(buf,buf.length);
    6. datagramSocket.receive(dp);
    7. //实现数据类型转换
    8. try(DataInputStream dis = new DataInputStream(new ByteArrayInputStream(dp.getData()))){
    9. //通过基本数据数据流对象获取传递的数据
    10. System.out.println(dis.readLong());
    11. }
    12. }catch(Exception e){
    13. e.printStackTrace();
    14. }
    15. }
    16. }

    创建客户端

    1. public class BasicTypeClient {
    2. public static void main(String[] args) {
    3. long n = 2000L;
    4. try(DatagramSocket datagramSocket = new DatagramSocket(9000);
    5. ByteArrayOutputStream bos = new ByteArrayOutputStream();
    6. DataOutputStream dos = new DataOutputStream(bos)){
    7. dos.writeLong(n);
    8. //将基本数据类型数据转换成字节数组类型
    9. byte[] arr = bos.toByteArray();
    10. DatagramPacket dp = new DatagramPacket(arr,arr.length,new InetSocketAddress("127.0.0.1",9999));
    11. datagramSocket.send(dp);
    12. }catch (Exception e){
    13. e.printStackTrace();
    14. }
    15. }
    16. }

    传递自定义对象类型

    创建Person类

    1. /**
    2. * 当该对象需要在网络上传输时,一定要实现Serializable接口
    3. */
    4. public class Person implements Serializable
    5. {
    6. private String name;
    7. private int age;
    8. public String getName() {
    9. return name;
    10. }
    11. public void setName(String name) {
    12. this.name = name;
    13. }
    14. public int getAge() {
    15. return age;
    16. }
    17. public void setAge(int age) {
    18. this.age = age;
    19. }
    20. @Override
    21. public String toString() {
    22. return "Person{" +
    23. "name='" + name + '\'' +
    24. ", age=" + age +
    25. '}';
    26. }
    27. }

    创建服务端

    1. public class ObjectTypeServer {
    2. public static void main(String[] args) {
    3. try(DatagramSocket datagramSocket = new DatagramSocket(9999);){
    4. byte[] b = new byte[1024];
    5. DatagramPacket dp = new DatagramPacket(b,b.length);
    6. datagramSocket.receive(dp);
    7. //对接收的内容做类型转换
    8. try(ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(dp.getData()))){
    9. System.out.println(objectInputStream.readObject());
    10. }
    11. }catch(Exception e){
    12. e.printStackTrace();
    13. }
    14. }
    15. }

    创建客户端

    1. public class ObjectTypeClient {
    2. public static void main(String[] args) {
    3. try(DatagramSocket datagramSocket = new DatagramSocket(8888);
    4. ByteArrayOutputStream bos = new ByteArrayOutputStream();
    5. ObjectOutputStream oos = new ObjectOutputStream(bos)){
    6. Person p = new Person();
    7. p.setName("Oldlu");
    8. p.setAge(18);
    9. oos.writeObject(p);
    10. byte[] arr = bos.toByteArray();
    11. DatagramPacket dp = new DatagramPacket(arr,arr.length,new InetSocketAddress("127.0.0.1",9999));
    12. datagramSocket.send(dp);
    13. }catch(Exception e){
    14. e.printStackTrace();
    15. }
    16. }
    17. }

    本章总结

    端口是虚拟的概念,并不是说在主机上真的有若干个端口。

    在www上,每一信息资源都有统一且唯一的地址,该地址就叫 URL(Uniform Resource Locator),它是www的统一资源定 位符。

    TCP与UDP的区别

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

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

    Socket通信是一种基于TCP协议,建立稳定连接的点对点的通 信。

    网络编程是由java.net包来提供网络功能。

           InetAddress:封装计算机的IP地址和DNS(没有端口信息!)。

           InetSocketAddress:包含IP和端口,常用于Socket通信。

           URL:以使用它的各种方法来对URL对象进行分割、合并等处理。

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

          “请求-响应”模式:

                     Socket类:发送TCP消息。

                     ServerSocket类:创建服务器。

    UDP通讯的实现

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

                     常用方法:send()、receive()、 close()。

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

                    常用方法:构造方法、getAddrress(获取发送或接收方计算机的IP地址)、getData(获取发送 或接收的数据)、setData(设置发送的数据)。

  • 相关阅读:
    Limitの线段树题单 题解目录
    27寸2K显示器 - HKC G27H2
    夏季河湖防溺水新举措:青犀AI视频智能监控系统保障水域安全
    模板特化--类和函数
    Python数据结构:列表(list)、元组(tuple)、字典(dict)
    2022年最新四川水利水电施工安全员模拟试题题库及答案
    华为鸿蒙应用--封装通用标题栏:CommonTitleBar(鸿蒙工具)-ArkTs
    【HTML5入门指北】第一篇 初始HTML5
    【一起学Rust】Rust的Hello Rust详细解析
    Locust负载测试工具实操
  • 原文地址:https://blog.csdn.net/m0_58719994/article/details/127965647