Java高级编程-----网络编程

网络通信协议

通过计算机网络可以实现多台计算机连接,但是不同计算机的操作系统和硬件体系结构不同,为了提供通信支持,位于同一个网络中的计算机在进行连接和通信时必须要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交互。
网络通信协议有很多种,目前应用最广泛的是TCP/IP协议(Transmission Control Protocol/Internet Protocol,传输控制协议/英特网互联协议)、UDP协议(User Datagram Protocol,用户数据报协议)、ICMP协议(Internet Control Message Protocol,Internet 控制报文协议)和其他一些协议的协议组。
在学习具体的内容之前,首先来了解一下TCP/IP 协议。TCP/IP(又称TCP/IP协议簇)是一组用于实现网络互连的通信协议,其名称来源于该协议簇中两个重要的协议(TCP协议和IP协议)。

基于TCP/IP的参考模型将协议分成四个层次。
在这里插入图片描述

CP/IP协议中的四层分别是链路层、网络层、传输层和应用层,每层分别负责不同的通信功能,接下来针对这四层进行详细地讲解。

  • 链路层:
    也称为网络接口层,该层负责监视数据在主机和网络之间的交互。事实上,TCP/IP本身并未定义该层的协议,而由参与互连的各网络使用自己的物理层和数据链路层协议与TCP/IP的网络互联层进行连接。(传出的数据基本上是0101的二进制数据)
  • 网络层:
    也称网络互联层,是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。
    (IP协议、ICMP协议等)
  • 传输层:
    主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。(Java网络编程就是基于该层进行数据传输)
  • 应用层:
    主要负责应用程序的协议,如HTTP协议(浏览器)、FTP协议(文件)等。

IP地址和端口号

要想使网络中的计算机能够进行通信,必须为每台计算机指定一个标识号,通过这个标识号指定接收数据的计算机或者发送数据的计算机。在TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机。目前,IP地址广泛使用的版本是IPv4,它由4个字节大小的二进制数来表示,如:00001010000000000000000000000001。由于二进制形式表示的IP地址非常不便记忆和处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表示,数字间用符号“.”分开,如 “10.0.0.1”。(常被称为点分十进制)

随着计算机网络规模的不断扩大,对IP地址的需求也越来越多,IPv4这种用4个字节表示的IP地址将面临使用枯竭的局面(2的32次方)。为解决此问题,IPv6 便应运而生。IPv6使用16个字节表示IP地址,它所拥有的地址容量约是IPv4的8×1028倍,达到2128个(算上全零的),这样就解决了网络地址资源数量不足的问题。

IP地址由两部分组成,即“网络地址.主机地址”的形式,其中网络部分表示其属于互联网的哪一个网络,是网络的地址编码,主机部分表示其属于该网络中的哪一台主机,是网络中一个主机的地址编码,二者是主从关系。

接下来通过一个图例描述IP地址和端口号的作用。
在这里插入图片描述

InetAddress

在Java中,提供了一个与IP地址相关的InetAddress类,该类用于封装一个IP地址,并提供了一系列与IP地址相关的方法,InetAddress类的一些常用方法如下表。
在这里插入图片描述
上表列举了InetAddress的五个常用方法。其中,前两个方法用于获得该类的实例对象,第一个方法用于获得表示指定主机的InetAddress对象,第二个方法用于获得表示本地的InetAddress对象。通过InetAddress对象便可获取指定主机名,IP地址等。
接下来通过一个案例来演示InetAddress常用方法的使用。

1 import java.net.InetAddress;
2 public class Example01 {
3  public static void main(String[] args) throws Exception {
4   InetAddress localAddress = InetAddress.getLocalHost();
5   InetAddress remoteAddress = InetAddress. getByName("www.itcast.cn");
6   System.out.println("本机的IP地址:" + localAddress.getHostAddress());
7   System.out.println("itcast的IP地址:" + remoteAddress.getHostAddress());
8   System.out.println("3秒是否可达:" + remoteAddress.isReachable(3000));
9   System.out.println("itcast的主机名为:" + remoteAddress.getHostName());
10	}
11 }

上述代码中,第4行代码获取本机的IP地址并打印,第5~6行代码获取主机名为“www.itcast.cn的IP”地址,第7行代码获取itcast的主机地址。第8行代码判断3秒是否可到达主机。第9行代码用于获取itcast的主机名。

UDP与TCP协议

协议是定义的通信规则,一般有TCP协议和UDP协议。通过TCP/IP结构,我们知道传输层的两个重要的高级协议分别是UDP和TCP,其中,UDP是User Datagram Protocol的简称,称为用户数据报协议;TCP是Transmission Control Protocol的简称,称为传输控制协议

UDP

UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输,例如视频会议使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。

UDP的交互过程如下图
在这里插入图片描述

TCP

TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务器端发出连接请求,每次连接的创建都需要经过“三次握手”。第一次握手,客户端向服务器端发出连接请求,等待服务器确认;第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求;第三次握手,客户端再次向服务器端发送确认信息,确认连接。

TCP连接的整个交互过程如下图:
在这里插入图片描述
由于TCP协议的面向连接特性,它可以保证传输数据的安全性,是一个被广泛采用的协议。例如,在下载文件时,如果数据接收不完整,将会导致文件数据丢失而不能被打开,因此,下载文件时必须采用TCP协议

UDP通信

前面介绍了UDP是一种面向无连接的协议,因此,在通信时发送端和接收端不用建立连接。UDP通信的过程就像是货运公司在两个码头间发送货物一样,在码头发送和接收货物时都需要使用集装箱来装载货物。UDP通信也是一样,发送和接收的数据也需要使用“集装箱”进行打包,为此Java提供了一个DatagramPacket类(封装数据)。然而运输货物只有“集装箱”是不够的,还需要有“码头”。同理,在程序中,要实现通信只有DatagramPacket数据包也是不行的,它也需要一个“码头”。为此,Java还提供了一个DatagramSocket类(发送数据/接收数据)。

通过DatagramPacket类和DatagramSocket类发送数据的过程如下图。
在这里插入图片描述

DatagramPacket

DatagramPacket类用于封装UDP通信中发送或者接收的数据。想要创建一个DatagramPacket对象,首先需要了解一下它的构造方法。在创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。
DatagramPacket类用于封装UDP通信中发送或者接收的数据。想要创建一个DatagramPacket对象

  • DatagramPacket(byte[] buf,int length)
    使用该构造方法在创建DatagramPacket对象时,指定了封装数据的字节数组和数据的大小,没有指定IP地址和端口号。很明显,这样的对象只能用于接收端,不能用于发送端。因为发送端一定要明确指出数据的目的地(IP地址和端口号),而接收端不需要明确知道数据的来源,只需要接收到数据即可。

  • DatagramPacket(byte[] buf,int length,InetAddress addr,int port)
    使用该构造方法在创建DatagramPacket对象时,不仅指定了封装数据的字节数组和数据的大小,还指定了数据包的目标IP地址(addr)和端口号(port)。该对象通常用于发送端,因为在发送数据时必须指定接收端的IP地址和端口号,就好像发送货物的集装箱上面必须标明接收人的地址一样。

  • DatagramPacket(byte[] buf,int offset,int length)
    该构造方法与第一个构造方法类似,同样用于接收端,只不过在第一个构造方法的基础上,增加了一个offset参数(偏移量),该参数用于指定接收到的数据在放入buf缓冲数组时是从offset处开始的。

  • DatagramPacket(byte[] buf,int offset,int length,InetAddress addr,int port)
    该构造方法与第二个构造方法类似,同样用于发送端,只不过在第二个构造方法的基础上,增加了一个offset参数,该参数用于指定一个数组中发送数据的偏移量为offset,即从offset位置开始发送数据。

上面已经讲解了DatagramPacket的构造方法,接下来对DatagramPacket类中的常用方法进行详细的讲解。
在这里插入图片描述

UDP网络程序

使用DatagramSocket类的实例对象就可以发送和接收DatagramPacket数据包在创建发送端和接收端的DatagramSocket对象时,使用的构造方法也有所不同,下面对DatagramSocket类中常用的构造方法进行讲解

  • DatagramSocket()
    该构造方法用于创建发送端的DatagramSocket对象,在创建DatagramSocket对象时,并没有指定端口号,此时,系统会分配一个没有被其他网络程序所使用的端口号。
  • DatagramSocket(int port)
    该构造方法既可用于创建接收端的DatagramSocket对象,又可以创建发送端的DatagramSocket对象,在创建接收端的DatagramSocket对象时,必须要指定一个端口号,这样就可以监听指定的端口。
  • DatagramSocket(int port,InetAddress addr)
    使用该构造方法在创建DatagramSocket对象时,不仅指定了端口号,还指定了相关的IP地址。该对象的使用适用于计算机上有多块网卡的情况,在使用时可以明确规定数据通过哪块网卡向外发送和接收哪块网卡的数据。由于计算机中针对不同的网卡会分配不同的IP,因此在创建DatagramSocket对象时需要通过指定IP地址确定使用哪块网卡进行通信。

上面我们讲解了DatagramSocket的常用构造方法,接下来对DatagramSocket类中的常用方法进行详细的讲解
在这里插入图片描述
前面两个小节讲解了DatagramPacket和DatagramSocket的相关知识,接下来通过一个案例来学习一下它们在程序中的具体用法。要实现UDP通信需要创建一个发送端程序和一个接收端程序。很明显,在通信时只有接收端程序先运行,才能避免发送端发送数据时找不到接收端,而造成数据丢失的问题。因此,首先需要完成接收端程序的编写。接收端程序如下

1 import java.net.*;
2 // 接收端程序
3 public class Receiver {
4	public static void main(String[] args) throws Exception {
5		byte[] buf = new byte[1024]; // 创建一个字节数组,用于接收数据
6        // 定义一个DatagramSocket对象,监听的端口号为8954
7		DatagramSocket ds = new DatagramSocket(8954);
8         // 定义一个DatagramPacket对象,用于接收数据
9		DatagramPacket dp = new DatagramPacket(buf, buf.length);
10		System.out.println("等待接收数据");
11		ds.receive(dp); // 等待接收数据,如果没有数据则会阻塞
12 // 调用DatagramPacket的方法获得接收到的信息
13         //包括数据的内容、长度、发送的IP地址和端口号
14		String str = new String(dp.getData(), 0, dp.getLength()) +
15         "from "+ dp.getAddress().getHostAddress() + ":" + dp.getPort();
16		System.out.println(str); // 打印接收到的信息
17		ds.close();// 释放资源
18	}
19 }

创建了一个接收端程序,用来接收数据。其中,第6行代码创建了一个DatagramSocket对象,并指定其监听的端口号为8954,这样发送端就能通过这个端口号与接收端程序进行通信。第9行代码在创建DatagramPacket对象时传入一个大小为1024个字节的数组用来接收数据,第11行代码调用DatagramPacket对象的receive()方法接收到数据以后,数据会填充到DatagramPacket中,第14~15行代码是通过DatagramPacket的相关方法可以获取接收到的数据的内容、长度、发送的IP地址和端口号等信息,第17行代码是释放资源。

从运行结果可以看到,程序运行后,程序一直处于停滞状态,这是因为DatagramSocket的receive()方法在运行时会发生阻塞,只有接收到发送端程序发送的数据时,该方法才会结束这种阻塞状态,程序才能继续向下执行。
实现了接收端程序之后,接下来还需要编写一个发送端的程序。

1 import java.net.*;
2 //发送端程序
3 public class Sender {
4	public static void main(String[] args) throws Exception {
5		// 创建一个DatagramSocket对象
6		DatagramSocket ds = new DatagramSocket(3000);
7		String str = "hello world"; // 要发送的数据
8		byte[] arr = str.getBytes(); //将定义的字符串转为字节数组
9 //创建一个要发送的数据包,数据包包括发送的数据,
10         //数据的长度,接收端的IP地址以及端口号
11	     DatagramPacket dp = new DatagramPacket(arr, arr.length,
12         InetAddress.getByName("localhost"), 8954);
13		System.out.println("发送信息");
14		ds.send(dp); // 发送数据
15		ds.close(); // 释放资源
16	}
17 }

上述代码中,创建了一个发送端程序,用来发送数据。在创建DatagramPacket对象时需要指定目标IP地址和端口号,而且端口号必须要和接收端指定的端口号一致,这样调用DatagramSocket的send()方法才能将数据发送到对应的接收端。

脚下留心: UDP程序所使用的端口号被占用时运行异常

需要注意的是,运行接收端程序有时会出现一种异常,如下图。
在这里插入图片描述
上图所示异常是因为在一台计算机中,一个端口号上只能运行一个程序,而我们编写的UDP程序所使用的端口号已经被其他的程序占用。遇到这种情况时,可以在命令行窗口输入“netstat -ano”命令来查看当前计算机端口占用情况,netstat命令运行结果如下图。
在这里插入图片描述

上图显示了所有正在运行的应用程序及它们所占用的端口号。想要解决端口号占用的问题,只需关掉占用端口号的应用程序或者使用一个未被占用的端口号重新运行程序即可。

多线程的UDP网络程序

在上一节中,分别实现了发送端程序和接收端程序,当接收端程序阻塞的状态下,运行发送端程序,接收端程序就会收到发送端发送的数据而结束阻塞状态,完成程序运行。实际上,发送端可以无限发送数据,接收端也可以一直接收数据,例如,聊天程序发送端可以一直发消息,接收端也可以一直接收消息,因此发送端和客户端都是多线程的.
接下来通过一个案例演示使用UDP通信方式实现多线程的UDP网络程序。

1 import java.io.IOException;
2 import java.net.*;
3 import java.util.Scanner;
4 public class Example04 {
5    public static void main(String[] args) {
6        new Receive().start();
7        new Send().start();
8    }
9 }
10 class Receive extends Thread {
11    public void run() {
12        try {
13            //创建socket相当于创建码头
14            DatagramSocket socket = new DatagramSocket(6666);	
15           //创建packet相当于创建集装箱		
16            DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
17            while(true) {
18                socket.receive(packet);//接收货物
19                byte[] arr = packet.getData();
20               int len = packet.getLength();
21                String ip = packet.getAddress().getHostAddress();
22                System.out.println(ip + ":" + new String(arr,0,len));
23            }
24 } catch (IOException e) {
25            e.printStackTrace();
26        }
27    }
28 }
29 class Send extends Thread {
30    public void run() {
31        try {
32            //创建socket相当于创建码头
33            DatagramSocket socket = new DatagramSocket();		
34            Scanner sc = new Scanner(System.in);
35            while(true) {
36                String str = sc.nextLine();
37 if("quit".equals(str))
38                    break;
39                DatagramPacket packet = new DatagramPacket(str.getBytes(),
40                str.getBytes().length, InetAddress.getByName
41               ("127.0.0.1"), 6666);
42                socket.send(packet);//发货
43            }
44            socket.close();
45        }  catch (IOException e) {
46            e.printStackTrace();
47        }
48    }
49 }

上述代码中,第10-28行代码使用多线程的方法创建了一个接收端程序,第17-23行代码通过在接收端的while循环中调用receive()方法,不停地接收发送端发送的请求,当与发送端建立连接后,就会开启一个新的线程,该线程会去处理发送端发送的数据,而主线程仍处于继续等待状态;第29~49行代码使用多线程的方法创建的一个发送端程序,第35-43行代码通过在发送端的while循环中调用的send()方法,不停的发送数据。

TCP通信

TCP通信同UDP通信一样,也能实现两台计算机之间的通信,但TCP通信的两端需要创建socket对象。UDP通信与TCP通信的区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据;而TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接
Java提供了两个用于实现TCP程序的类,一个是ServerSocket类,用于表示服务器端;一个是Socket类,用于表示客户端。通信时,首先要创建代表服务器端的ServerSocket对象,创建该对象相当于开启一个服务,此服务会等待客户端的连接;然后创建代表客户端的Socket对象,使用该对象向服务器端发出连接请求,服务器端响应请求后,两者才建立连接,开始通信。
整个通信过程如下图.
在这里插入图片描述

ServerSocket

在开发TCP程序时,首先需要创建服务器端程序。java.net包提供了一个ServerSocket类,该类的实例对象可以实现一个服务器端的程序。通过查阅API文档可知,ServerSocket类提供了多种构造方法。

  • ServerSocket()
    ServerSocket有一个不带参数的默认构造方法。通过该方法创建的ServerSocket对象不与任何端口绑定,这样的ServerSocket对象创建的服务器端没有监听任何端口,不能直接使用,还需要继续调用bind(SocketAddress endpoint)方法将其绑定到指定的端口号上,才可以正常使用。
  • ServerSocket(int port)
    使用该构造方法在创建ServerSocket对象时,可以将其绑定到一个指定的端口号上(参数port就是端口号)。端口号可以指定为0,此时系统就会分配一个还没有被其他网络程序所使用的端口号。由于客户端需要根据指定的端口号来访问服务器端程序,因此端口号随机分配的情况并不常用,通常都会让服务器端程序监听一个指定的端口号。
  • ServerSocket(int port, int backlog)
    该构造方法就是在第二个构造方法的基础上,增加了一个backlog参数。该参数用于指定在服务器忙时,可以与之保持连接请求的等待客户数量,如果没有指定这个参数,默认为50。
  • ServerSocket(int port, int backlog, InetAddress bindAddr)
    该构造方法就是在第三个构造方法的基础上,增加了一个bindAddr参数,该参数用于指定相关的IP地址。该构造方法的使用适用于计算机上有多块网卡和多个IP的情况,使用时可以明确规定ServerSocket在哪块网卡或IP地址上等待客户的连接请求。显然,对于一般只有一块网卡的情况,就不用专门的指定了。
    除了构造方法,ServerSocket还提供了其他常用方法,如下表。
    在这里插入图片描述
    ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收来自客户端的请求。当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求时,accept()方法才会返回一个Socket对象用于和客户端实现通信,程序才能继续向下执行。

Socket

ServerSocket对象可以实现服务器端程序,但只实现服务器端程序还不能完成通信,此时还需要一个客户端程序与之交互,为此Java提供了一个Socket类,用于实现TCP客户端程序。通过查阅API文档可知,Socket类同样提供了多种构造方法

  • Socket()
    使用该构造方法在创建Socket对象时,并没有指定IP地址和端口号,也就意味着只创建了客户端对象,并没有去连接任何服务器。通过该构造方法创建对象后还需调用connect(SocketAddress endpoint)方法,才能完成与指定服务器端的连接,其中参数endpoint用于封装IP地址和端口号。
  • Socket(String host, int port)
    使用该构造方法在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host接收的是一个字符串类型的IP地址。
  • Socket(InetAddress address, int port)
    该构造方法在使用上与第二个构造方法类似,参数address用于接收一个InetAddress类型的对象,该对象用于封装一个IP地址。

除了构造方法,Socket还提供了很多其他方法,如下表。
在这里插入图片描述
接下来通过一张图描述服务器端和客户端的数据传输。
在这里插入图片描述

简单的TCP网络程序

接下来通过一个TCP通信的案例来进一步学习ServerSocket、Socket类的用法。要实现TCP通信需要创建一个服务器端程序和一个客户端程序,为了保证数据传输的安全性,首先需要实现服务器端程序。服务器端程序实现如下。

1 import java.io.*;
2 import java.net.*;
3 public class Server {
4	public static void main(String[] args) throws Exception {
5		new TCPServer().listen(); // 创建TCPServer对象,并调用listen()方法
6	}
7 }
8 // TCP服务器端
9 class TCPServer {
10	private static final int PORT = 7788;   // 定义一个端口号
11	public void listen() throws Exception { // 定义一个listen()方法,抛出异常
12		ServerSocket serverSocket = new ServerSocket(PORT);
13 // 调用ServerSocket的accept()方法接收数据
14		Socket client = serverSocket.accept(); 
15		OutputStream os = client.getOutputStream();// 获取客户端的输出流
16		System.out.println("开始与客户端交互数据");
17         // 当客户端连接到服务器端时,向客户端输出数据
18		os.write(("传智播客欢迎你!").getBytes());   
19		Thread.sleep(5000);// 模拟执行其他功能占用的时间
20		System.out.println("结束与客户端交互数据");
21		os.close();
22		client.close();
23	}
24 }
 

上述代码中,第9-24行代码封装了一个TCP服务端的方法,第12行代码创建ServerSocket对象时指定了端口号(7788),第14代码调用ServerSocket对象的accept()方法用于接收数据,第15行代码使用OutputStream获取客户端的输出流,第19行代码使用线程的sleep()方法使线程休眠5000毫秒,用于模拟执行其他功能占用的时间,最后在第21~22行代码中分别使用OutputStream与Socket的close()方法关闭了OutputStream与Socket。

从运行结果可以看出,控制台中的光标一直在闪动,这是因为accept()方法发生阻塞***,程序暂时停止运行,直到有客户端来访问时才会结束这种阻塞状态***。这时该方法会返回一个Socket类型的对象用于表示客户端,通过该对象获取与客户端关联的输出流并向客户端发送信息,同时执行Thread.sleep(5000)语句模拟服务器执行其他功能占用的时间。最后,调用Socket对象的close()方法将通信结束。

客户端程序如下。

1 import java.io.*;
2 import java.net.*;
3 public class Client {
4	public static void main(String[] args) throws Exception {
5		new TCPClient().connect();// 创建TCPClient对象,并调用connect()方法
6	}
7 }
8 //TCP客户端
9 class TCPClient {
10	private static final int PORT = 7788; // 服务器端的端口号
11	public void connect() throws Exception {
12		//创建一个Socket并连接到给出地址和端口号的计算机
13		Socket client = new Socket(InetAddress.getLocalHost(), PORT);
14		InputStream is = client.getInputStream();   // 得到接收数据的流
15		byte[] buf = new byte[1024];   // 定义1024个字节数组的缓冲区
16		int len = is.read(buf);                        // 将数据读到缓冲区中
17		System.out.println(new String(buf, 0, len));// 将缓冲区中的数据输出
18		client.close();   // 关闭Socket对象,释放资源
19	}
20 }

上述代码中,第9~20行代码封装了一个TCP客户端的方法,第13行代码创建了一个Socket并连接到给出地址给端口号的计算机,第14行代码使用InputStream接收得到的数据流,第15行代码定义1024个字节数组的缓冲区,第16行代码将InputStream接收到的数据读到缓冲区中,最后在第18行代码中使用Socket的close()方法关闭Socket。

在客户端创建的Socket对象与服务器端建立连接后,通过Socket对象获得输入流读取服务器端发来的数据,并打印出如上图所示的结果。
同时服务器端程序会结束阻塞状态,并在控制台中打印出“开始与客户端交互数据”,然后向客户端发出数据“传智播客欢迎你!”,在休眠5秒钟后会在控制台打印出“结束与客户端交互数据”,此时,本次通信才结束。

多线程的TCP网络程序

实际上,很多服务器端程序都是允许被多个应用程序访问的,例如,门户网站可以被多个用户同时访问,因此服务器都是多线程的。下面就通过一个图例来表示多个用户访问同一个服务器。在这里插入图片描述
在上图中,服务器端为每个客户端创建一个对应的Socket,并且开启一个新的线程使两个Socket建立专线进行通信。

1 import java.io.*;
2 import java.net.*;
3 public class Server {
4	public static void main(String[] args) throws Exception {
5		new TCPServer().listen();   // 创建TCPServer对象,并调用listen()方法
6	}
7 }
8 // TCP服务器端
9 class TCPServer {
10	private static final int PORT = 7788; // 定义一个静态常量作为端口号
11	public void listen() throws Exception {
12          // 创建ServerSocket对象,监听指定的端口
13		ServerSocket serverSocket = new ServerSocket(PORT); 
14         // 使用while循环不停的接收客户端发送的请求
15		while (true) {
16              // 调用ServerSocket的accept()方法与客户端建立连接
17			final Socket client = serverSocket.accept();
18 // 下面的代码用来开启一个新的线程
19			new Thread() {              
20				public void run() {
21					OutputStream os;  // 定义一个输出流对象
22					try {
23						os = client.getOutputStream();// 获取客户端的输出流
24						System.out.println("开始与客户端交互数据");
25						os.write(("传智播客欢迎你!").getBytes());
26						Thread.sleep(5000);  // 使线程休眠5000毫秒
27						System.out.println("结束与客户端交互数据");
28						os.close();     // 关闭输出流
29						client.close(); // 关闭Socket对象
30					} catch (Exception e) {
31						e.printStackTrace();
32					}
33				};
34			}.start();
35		}
36	}
37 }

在上述代码中,第9-37行代码使用多线程的方式创建了一个服务器端程序。第15~35行代码通过在while循环中调用accept()方法,不停地接收客户端发送的请求,当与客户端建立连接后,就会开启一个新的线程,该线程会去处理客户端发送的数据,而主线程仍处于继续等待状态。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/171473.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SMART PLC向导PID和一阶低通滤波器组合编程应用(恒压控制)

一阶滞后滤波器算法和代码详细介绍请参考下面的文章链接: 【精选】PLC信号处理系列之一阶低通(RC)滤波器算法_数字rc滤波-CSDN博客文章浏览阅读3.6k次。1、先看看RC滤波的优缺点 优点:采用数字滤波算法来实现动态的RC滤波,则能很好的克服模拟滤波器的缺点; 1、在模拟常数要…

【python FastAPI】fastapi中如何限制输入参数,如何让docs更好看,如何自定义错误码json返回

原则: 输入输出都基于BaseModel依靠JSONResponse制定返回错误的json信息依靠装饰器中app.post制定responses字典从而让docs文档更丰富 import uvicorn from pydantic import BaseModel, Field from fastapi import FastAPI, HTTPException from fastapi.middleware…

单片机和FreeRTOS上跑机器人ROS的应用

机器人的应用越来越广泛了,大家熟知的稚晖君直接创业搞机器人,可想而至,接下来的十年,机器人绝对是热门的行业。 目前市面上很多机器人都是基于一套叫做ROS的系统开发的,今天就给大家分享一个跑在MCU上,基…

红葡萄酒和白葡萄酒哪个好?哪个更适合你?

云仓酒庄的品牌雷盛红酒分享接触葡萄酒不久的小伙伴不知道红葡萄和白葡萄酒哪个更好,更适合自己。其实,任何葡萄酒不论价位、风格、颜色,没有哪个更好,只有哪个更适合品饮者。 红葡萄酒之所以呈现出浓艳的颜色,是在酿造…

网站监控的重要性及实施策略

随着互联网的快速发展,网站已经成为企业和个人不可或缺的在线服务平台。然而,网站的安全性和稳定性一直是企业及个人非常关注的问题。一旦网站出现故障或者被攻击,将会给企业和个人带来严重的损失。因此,实施有效的网站监控策略对…

我了解的3D游戏引擎和图形开发框架

如果你像我一样,没有什么比编写或设计软件更让人兴奋的了。 当我编写代码时,我所获得的巨大快乐促使我开发了跨越许多软件领域的项目。 这些领域之一是为本机应用程序、桌面展示或 Web 创建 3D 图形。 我从未创建过任何 3D 游戏,但很多时候我…

基于JAVA+SSM+VUE+微信小程序的前后端分离的生活日用品交易平台的设计与实现

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背景介绍: 随着互联网的快速发展…

Python Web框架的三强之争:Flask、Django和FastAPI

JetBrains 公布 2022 Python 开发者调查结果。 完整报告地址:https://lp.jetbrains.com/zh-cn/python-developers-survey-2022/ 这是由 Python 软件基金会 (PSF) 和 JetBrains 共同开展的第六次官方年度 Python 开发者调查,回复于 2022 年 10 月至 12 …

国外客户要求免费样品?我来教你如何应对

这一次的问题对外贸业务员来说是非常重要。无论你是做什么行业,无论你是做什么产品我相信这个问题对你来说超级有用。 关于发样品给客户我有四个方案来跟大家分享,我希望你能够喜欢希望你很认真的思考一下: 方法一【样品费及运费一起收】&am…

【网络编程】简述TCP通信程序,三次握手,四次挥手

文章目录 🎄TCP通信程序⭐打印字符串✨中文乱码问题🎈解决方法 🌺TCP三次握手🌺TCP四次挥手🛸其他 🎊专栏【网络编程】 🍔喜欢的诗句:更喜岷山千里雪 三军过后尽开颜。 &#x1f386…

腾讯云标准型S5云主机性能评测_CPU内存_带宽系统盘测评

腾讯云服务器CVM标准型S5实例具有稳定的计算性能,CVM 2核2G S5活动优惠价格280.8元一年自带1M带宽,15个月313.2元、2核4G配置748.2元15个月,CPU内存配置还可以选择4核8G、8核16G等配置,公网带宽可选1M、3M、5M或10M,腾…

buildAdmin 后端控制器的代码分析

buildAdmin的代码生成&#xff0c;很像是 fastadmin 的生成模式&#xff0c;当我们利用数据库生成了一个控制器的时候&#xff0c;我们可以看到&#xff0c; 它的生成代码很简洁 <?phpnamespace app\admin\controller\askanswer;use app\common\controller\Backend;/*** 回…

2023.11.19使用flask制作一个文件夹生成器

2023.11.19使用flask制作一个文件夹生成器 实现功能&#xff1a; &#xff08;1&#xff09;在指定路径上建立文件夹 &#xff08;2&#xff09;返回文件夹的路径和建立成功与否的提示 main.py import os from flask import Flask, request, jsonify, render_templateapp F…

archery修改为不能自提自审核上线SQL

目录 背景修改代码效果参考 背景 我和同事都可以提交上线SQL&#xff0c;但是不能自己提交的SQL自己去审核通过。目前的情况是可以自提自审。 修改代码 找到/opt/archery/sql/utils/workflow_audit.py文件 ...省略...# 判断用户当前是否是可审核staticmethoddef can_revie…

VScode调试没有反应

点击调试按钮后没反应 有可能是vscode中安装的python插件版本问题 可以通过重新安装比较旧一点的python尝试解决此问题 步骤如下&#xff1a; 然后从中选择比当前版本更低的版本即可 安装完成后需重启vscode

【从入门到起飞】JavaSE—多线程(1)(实现方式,成员方法)

&#x1f38a;专栏【JavaSE】 &#x1f354;喜欢的诗句&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f33a;进程&#x1f33a;线程&#x1f384;并发&am…

线性表但是是Java中数组实用使用

线性表定义&#xff1a; 由n (n≥0)个数据特性相同的元素构成的有限序列称为线性表&#xff0c;(n0)的时候被称为空表。 线性表的顺序表示 线性表的顺序存储又被称为顺序表 优点 无需为表示表中元素之间的逻辑关系而增加额外的存储空间可以随意读取任意位置的元素 缺点 插入…

商品购物管理与推荐系统Python+Django网页界面+协同过滤推荐算法

一、介绍 商品管理与推荐系统。本系统使用Python作为主要开发语言&#xff0c;前端采用HTML、CSS、BootStrap等技术搭建显示界面&#xff0c;后端采用Django框架处理用户的请求响应。 创新点&#xff1a;使用协同过滤算法&#xff0c;以用户对商品的评分作为依据&#xff0c;在…

[C国演义] 第二十二章

第二十二章 不同的子序列交错字符串 不同的子序列 力扣链接 两个数组的dp问题 (子序列 && 子数组(子串)) ⇒ 分区间来讨论 ⇒ dp[i][j] -- 在s数组的[0, i]区间内, 去寻找t数组在[0, j]这段子串的个数 状态转移方程 遍历顺序 初始化 需要使用左上角的情况 ⇒ dp…

筒仓料位监测|敢不敢对“精度”下狠手!您家筒仓料位测得准吗?

您家是不是还在人工敲仓估算&#xff1f; 您能精确知道料位和库存吗&#xff1f; 您能实时看到库存盈亏吗&#xff1f; 筒仓里装了什么&#xff1f;用了多少&#xff1f; 什么时候进料最划算&#xff1f; 您家的筒仓管理方式可靠吗&#xff1f; 上海思伟筒仓料位监测方案 看…
最新文章