Java 网络编程:(六)TCP网络编程 一、TCP协议概述 二、Socket 类 三、ServerSocket 类 四、基于 TCP 协议的网络通信 五、简单的 TCP网络程序 六、TCP案例一 七、TCP案例二 八、TCP案例三 九、文件上传案例 十、小结

  TCP(Transmission Control Protocol,传输控制协议)被称作一种端对端协议。是一种面向连接的、可靠的、基于字节流的传输层的通信协议,可以连续传输大量的数据。

  这是因为它为当一台计算机需要与另一台远程计算机连接时,TCP协议会采用“三次握手”方式让它们建立一个连接,用于发送和接收数据的虚拟链路。数据传输完毕TCP协议会采用“四次挥手”方式断开连接。

  TCP协议负责收集这些信息包,并将其按适当的次序放好传送,在接收端收到后再将其正确的还原。TCP协议保证了数据包在传送中准确无误。TCP协议使用重发机制,当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体确认信息,如果没有收到另一个通信实体确认信息,则会再次重复刚才发送的消息。

  TCP 通信能实现两台计算机之间的数据交互,通信的两端,要严格区分为客户端(Client)与服务端(Server)。

  

  两端通信时步骤:

    1、服务端程序,需要事先启动,等待客户端的连接;

    2、客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。

  在 Java 中,提供了两个类用于实现 TCP 通信程序:

    1、客户端:java.net.Socket 类表示。创建 Socket 对象,向服务端发出连接请求,服务器响应请求,两者建立连接开始通信。

    2、服务端:java.net.ServerSocket 类表示。创建 ServerSocket 对象,相当于开启一个服务,并等待客户端的连接。

二、Socket 类

  1、Socket 概述

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

  2、常用构造方法

public Socket(InetAddress address,int port)创建一个流套接字并将其连接到指定 IP 地址的指定端口号。

public Socket(String host, int port) :创建套接字对象并将其连接到指定主机上的指定端口号。如果指定的host是null ,则相当于指定地址为回送地址。

    Tips:回送地址(127.x.x.x)是本机回送地址(Loopback Address),主要用于网络软件测试以及本地进程间通信,无论什么程序,一旦使用回送地址发送数据,立即返回,不进行任何网络传输。

  3、常用方法

public InputStream getInputStream() : 返回此套接字的输入流。

       ① 如果此 Socket 具有相关联的通信,则生成的 InputStream 的所有操作也关联该通道。

       ② 关闭生成的 InputStream 也将关闭相关的 Socket。

public OutputStream getOutputStream() : 返回此套接字的输出流

     ① 如果此 Socket 具有相关联的通道,则生成的 OutputStream 的所有操作也关联该通道。

     ② 关闭生成的 OutputStream 也将关闭相关的 Socket。

public void close() :关闭此套接字

    ① 一旦一个 Socket 被关闭,它不可再使用。

    ② 关闭此 Socket 也将关闭相关的 InputStream 和 OutputStream。

public void shutdownOutput() : 禁用此套接字的输出流

    任何先前写出的数据将被发送,随后终止输出流。

三、ServerSocket 类

  1、Socket 概述

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

  2、常用构造方法

public ServerSocket(int port) :使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上,参数port就是端口号。

    port 为服务器端监听的端口号。

  3、常用方法

public Socket accept() :侦听并接受连接,返回一个新的Socket对象,用于和客户端实现通信。该方法会一直阻塞直到建立连接。

  

四、基于 TCP 协议的网络通信

  1、通信模型

    Java 语言的基于套接字 TCP 编程分为服务端编程和客户端编程,其通信模型如图所示:

    Java 网络编程:(六)TCP网络编程
一、TCP协议概述
二、Socket 类
三、ServerSocket 类
四、基于 TCP 协议的网络通信
五、简单的 TCP网络程序
六、TCP案例一
七、TCP案例二
八、TCP案例三
九、文件上传案例
十、小结

  2、客户端

    (1)客户端Socket的工作过程包含以下四个基本的步骤:

      ① 创建 Socket: 根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。

      ② 打开连接到 Socket 的输入/出流: 使用 getInputStream()方法获得输入流,使用getOutputStream()方法获得输出流,进行数据传输

      ③ 按照一定的协议对 Socket 进行读/写操作: 通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。

      ④ 关闭 Socket: 断开客户端到服务器的连接,释放线路;

    (2)客户端创建 Socket 对象

      客户端程序可以使用Socket类创建对象, 创建的同时会自动向服务器方发起连接Socket的构造器是:

Socket(String host,int port)throws UnknownHostException,IOException: 向服务器(域名是host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。

Socket(InetAddress address,int port)throws IOException: 根据InetAddress对象所表示的IP地址以及端口号port发起连接。

  

      客户端建立socketAtClient对象的过程就是向服务器发出套接字连接请求

      Java 网络编程:(六)TCP网络编程
一、TCP协议概述
二、Socket 类
三、ServerSocket 类
四、基于 TCP 协议的网络通信
五、简单的 TCP网络程序
六、TCP案例一
七、TCP案例二
八、TCP案例三
九、文件上传案例
十、小结

  3、服务器端

    (1)服务器程序的工作过程包含以下五个基本的步骤

      ① 使用 ServerSocket(int port) :创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求;

      ② 调用 accept()方法:监听连接请求,如果客户端请求连接,则接受连接,创建与该客户端的通信套接字对象。否则该方法将一直处于等待;

      ③ 调用 该Socket对象的 getOutputStream() 和 getInputStream ():获取输出流和输入流,开始网络数据的发送和接收;

      ④ 关闭Socket对象:某客户端访问结束,关闭与之通信的套接字;

      ⑤ 关闭ServerSocket:如果不再接收任何客户端的连接的话,调用close()进行关闭;

    (2)服务器建立 ServerSocket 对象

      ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员。也就是说, 服务器必须事先建立一个等待客户请求建立套接字连接的ServerSocket对象。

      所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象:

      Java 网络编程:(六)TCP网络编程
一、TCP协议概述
二、Socket 类
三、ServerSocket 类
四、基于 TCP 协议的网络通信
五、简单的 TCP网络程序
六、TCP案例一
七、TCP案例二
八、TCP案例三
九、文件上传案例
十、小结

  4、注意

    客户端和服务器端在获取输入流和输出流时要对应,否则容易死锁。例如:客户端先获取字节输出流(即先写),那么服务器端就先获取字节输入流(即先读);反过来客户端先获取字节输入流(即先读),那么服务器端就先获取字节输出流(即先写)。

  5、

五、简单的 TCP网络程序

  TCP 通信分析图解

   1、【服务端】启动,创建 ServerSocket 对象,等待连接。

   2、【客户端】启动,创建 Socket 对象,请求连接。

   3、【服务端】接收连接,调用 accept 方法,并返回一个 Socket 对象

   4、【客户端】Socket 对象,获取 OutputStream ,向服务端写出数据

   5、【服务端】Socket对象,获取 InputStream,读取客户端发送的数据。

    到此,客户端向服务端发送数据成功。

  Java 网络编程:(六)TCP网络编程
一、TCP协议概述
二、Socket 类
三、ServerSocket 类
四、基于 TCP 协议的网络通信
五、简单的 TCP网络程序
六、TCP案例一
七、TCP案例二
八、TCP案例三
九、文件上传案例
十、小结

     自此,服务端向客户端回写数据。

   6、【服务端】Socket对象,获取 OutputStream,向客户端回写数据。

   7、【客户端】Socket对象,获取 InputStream,解析回写数据。

   8、【客户端】释放资源,断开连接。

六、TCP案例一

  客户端发送信息给服务端,服务端将数据显示在控制台上

  客户端:

 1     @Test
 2     public void client()  {
 3         Socket socket = null;
 4         OutputStream os = null;
 5         try {
 6             //1.创建Socket对象,指明服务器端的ip和端口号
 7             InetAddress inet = InetAddress.getByName("192.168.14.100");
 8             socket = new Socket(inet,8899);
 9             //2.获取一个输出流,用于输出数据
10             os = socket.getOutputStream();
11             //3.写出数据的操作
12             os.write("你好,我是客户端mm".getBytes());
13         } catch (IOException e) {
14             e.printStackTrace();
15         } finally {
16             //4.资源的关闭
17             if(os != null){
18                 try {
19                     os.close();
20                 } catch (IOException e) {
21                     e.printStackTrace();
22                 }
23 
24             }
25             if(socket != null){
26                 try {
27                     socket.close();
28                 } catch (IOException e) {
29                     e.printStackTrace();
30                 }
31 
32             }
33         }
34     }

  服务端:

 1     @Test
 2     public void server()  {
 3 
 4         ServerSocket ss = null;
 5         Socket socket = null;
 6         InputStream is = null;
 7         ByteArrayOutputStream baos = null;
 8         try {
 9             //1.创建服务器端的ServerSocket,指明自己的端口号
10             ss = new ServerSocket(8899);
11             //2.调用accept()表示接收来自于客户端的socket
12             socket = ss.accept();
13             //3.获取输入流
14             is = socket.getInputStream();
15 
16             //不建议这样写,可能会有乱码
17 //        byte[] buffer = new byte[1024];
18 //        int len;
19 //        while((len = is.read(buffer)) != -1){
20 //            String str = new String(buffer,0,len);
21 //            System.out.print(str);
22 //        }
23             //4.读取输入流中的数据
24             baos = new ByteArrayOutputStream();
25             byte[] buffer = new byte[5];
26             int len;
27             while((len = is.read(buffer)) != -1){
28                 baos.write(buffer,0,len);
29             }
30 
31             System.out.println(baos.toString());
32 
33             System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
34 
35         } catch (IOException e) {
36             e.printStackTrace();
37         } finally {
38             if(baos != null){
39                 //5.关闭资源
40                 try {
41                     baos.close();
42                 } catch (IOException e) {
43                     e.printStackTrace();
44                 }
45             }
46             if(is != null){
47                 try {
48                     is.close();
49                 } catch (IOException e) {
50                     e.printStackTrace();
51                 }
52             }
53             if(socket != null){
54                 try {
55                     socket.close();
56                 } catch (IOException e) {
57                     e.printStackTrace();
58                 }
59             }
60             if(ss != null){
61                 try {
62                     ss.close();
63                 } catch (IOException e) {
64                     e.printStackTrace();
65                 }
66             }
67 
68         }
69     }

七、TCP案例二

  客户端发送文件给服务端,服务端将文件保存在本地。

  客户端:

 1     @Test
 2     public void client() throws IOException {
 3         //1.创建Socket对象,指明服务器端的ip和端口号
 4         Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
 5         //2.获取一个输出流,用于输出数据
 6         OutputStream os = socket.getOutputStream();
 7         //3.创建本地文件读取流
 8         FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
 9         //4.读取文件流,并写入到网络中
10         byte[] buffer = new byte[1024];
11         int len;
12         while((len = fis.read(buffer)) != -1){
13             os.write(buffer,0,len);
14         }
15         //5.关闭资源
16         fis.close();
17         os.close();
18         socket.close();
19     }

  服务端:

 1     @Test
 2     public void server() throws IOException {
 3         //1.创建服务器端的ServerSocket,指明自己的端口号
 4         ServerSocket ss = new ServerSocket(9090);
 5         //2.调用accept()表示接收来自于客户端的socket
 6         Socket socket = ss.accept();
 7         //3.获取输入流
 8         InputStream is = socket.getInputStream();
 9         //4.本地文件输出流
10         FileOutputStream fos = new FileOutputStream(new File("beauty1.jpg"));
11         //5.写入到文件中
12         byte[] buffer = new byte[1024];
13         int len;
14         while((len = is.read(buffer)) != -1){
15             fos.write(buffer,0,len);
16         }
17         //6.关闭资源
18         fos.close();
19         is.close();
20         socket.close();
21         ss.close();
22 
23     }

八、TCP案例三

  从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端,并关闭相应的连接。

  客户端:

 1     @Test
 2     public void client() throws IOException {
 3         //1.创建Socket对象,指明服务器端的ip和端口号
 4         Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
 5         //2.获取一个输出流,用于输出数据
 6         OutputStream os = socket.getOutputStream();
 7         //3.创建本地文件读取流
 8         FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
 9         //4.读取文件,并写入到网络中
10         byte[] buffer = new byte[1024];
11         int len;
12         while((len = fis.read(buffer)) != -1){
13             os.write(buffer,0,len);
14         }
15         //关闭数据的输出
16         socket.shutdownOutput();
17 
18         //5.接收来自于服务器端的数据,并显示到控制台上
19         InputStream is = socket.getInputStream();
20         ByteArrayOutputStream baos = new ByteArrayOutputStream();
21         byte[] bufferr = new byte[20];
22         int len1;
23         while((len1 = is.read(buffer)) != -1){
24             baos.write(buffer,0,len1);
25         }
26 
27         System.out.println(baos.toString());
28 
29         //6.关闭资源
30         fis.close();
31         os.close();
32         socket.close();
33         baos.close();
34     }

  服务端:

 1     @Test
 2     public void server() throws IOException {
 3         //1.创建服务器端的ServerSocket,指明自己的端口号
 4         ServerSocket ss = new ServerSocket(9090);
 5         //2.调用accept()表示接收来自于客户端的socket
 6         Socket socket = ss.accept();
 7         //3.获取输入流
 8         InputStream is = socket.getInputStream();
 9         //4.要保存的本地文件
10         FileOutputStream fos = new FileOutputStream(new File("beauty2.jpg"));
11         //5.读取文件,并保存到本地
12         byte[] buffer = new byte[1024];
13         int len;
14         while((len = is.read(buffer)) != -1){
15             fos.write(buffer,0,len);
16         }
17 
18         System.out.println("图片传输完成");
19 
20         //6.服务器端给予客户端反馈
21         OutputStream os = socket.getOutputStream();
22         os.write("你好,照片我已收到,非常漂亮!".getBytes());
23 
24         //7.关闭资源
25         fos.close();
26         is.close();
27         socket.close();
28         ss.close();
29         os.close();
30 
31     }

  注意:这里的客户端的 shutdownOutput() 很重要,这是告诉服务器自己已经关闭输出流,即自己不再发送数据了,否则服务器没有收到这个信息,就会一直等待阻塞在读的过程,不会把返回给客户端。

九、文件上传案例

 文件上传分析图解

  1.【客户端】输入流,从硬盘读取文件数据到程序中。

  2.【客户端】输出流,写出文件数据到服务端。

  3.【服务端】输入流,读取文件数据到服务程序。

  4.【服务端】输出流,写出文件数据到服务器硬盘中。

Java 网络编程:(六)TCP网络编程
一、TCP协议概述
二、Socket 类
三、ServerSocket 类
四、基于 TCP 协议的网络通信
五、简单的 TCP网络程序
六、TCP案例一
七、TCP案例二
八、TCP案例三
九、文件上传案例
十、小结

   基本实现

  服务端实现:

 1 public class FileUpload_Server {
 2   public static void main(String[] args) throws IOException {
 3     System.out.println("服务器 启动..... ");
 4     // 1. 创建服务端ServerSocket
 5     ServerSocket serverSocket = new ServerSocket(6666);
 6     // 2. 建立连接
 7     Socket accept = serverSocket.accept();
 8     // 3. 创建流对象
 9     // 3.1 获取输入流,读取文件数据
10     BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
11     // 3.2 创建输出流,保存到本地 .
12     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.jpg"));
13     // 4. 读写数据
14     byte[] b = new byte[1024 * 8];
15     int len;
16     while ((len = bis.read(b)) != ‐1) {
17       bos.write(b, 0, len);
18     } 
19     //5. 关闭 资源
20     bos.close();
21     bis.close();
22     accept.close();
23     System.out.println("文件上传已保存");
24   }
25 }

  客户端实现:

 1 public class FileUPload_Client {
 2   public static void main(String[] args) throws IOException {
 3     // 1.创建流对象
 4     // 1.1 创建输入流,读取本地文件
 5     BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
 6     // 1.2 创建输出流,写到服务端
 7     Socket socket = new Socket("localhost", 6666);
 8     BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
 9     //2.写出数据.
10     byte[] b = new byte[1024 * 8 ];
11     int len ;
12     while (( len = bis.read(b))!=‐1) {
13       bos.write(b, 0, len);
14       bos.flush();
15     } 
16     System.out.println("文件发送完毕");
17     // 3.释放资源
18     bos.close();
19     socket.close();
20     bis.close();
21     System.out.println("文件上传完毕 ");
22   }
23 }

  文件上传优化分析:

  1、文件名称写死的问题

    服务端,保存文件的名称如果写死,那么最终导致服务器硬盘,只会保留一个文件,建议使用系统时间优化,保证文件名唯一性。

    代码实现:

FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") // 文件名称
BufferedOutputStream bos = new BufferedOutputStream(fis);

  2、循环接收的问题

    服务端,指保存一个文件就关闭了,之后的用户无法再上传,这是不符合实际的,使用循环改进,可以不断的接收不同用户的文件。

    代码实现:

// 每次接收新的连接,创建一个Socket
while(true){
Socket accept = serverSocket.accept();
......
}

  3、效率问题

    服务端,在接收大文件时,可能消耗几秒钟的时间,此时不能接收其他用户上传,所以,使用多线程技术优化。

    代码实现:

while(true){
Socket accept = serverSocket.accept();
// accept 交给子线程处理.
new Thread(() ‐> {
......
InputStream bis = accept.getInputStream();
......
}).start();
}

  优化实现:

 1 public class FileUpload_Server {
 2   public static void main(String[] args) throws IOException {
 3     System.out.println("服务器 启动..... ");
 4     // 1. 创建服务端ServerSocket
 5     ServerSocket serverSocket = new ServerSocket(6666);
 6     // 2. 循环接收,建立连接
 7     while (true) {
 8       Socket accept = serverSocket.accept();
 9       /*
10       3. socket对象交给子线程处理,进行读写操作
11       Runnable接口中,只有一个run方法,使用lambda表达式简化格式
12       */
13       new Thread(() ‐> {
14         try (
15           //3.1 获取输入流对象
16           BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
17           //3.2 创建输出流对象, 保存到本地 .
18           FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() +19 ".jpg");
20           BufferedOutputStream bos = new BufferedOutputStream(fis);) {
21           // 3.3 读写数据
22           byte[] b = new byte[1024 * 8];
23           int len;
24           while ((len = bis.read(b)) != ‐1) {
25             bos.write(b, 0, len);
26           } 
27           //4. 关闭 资源
28           bos.close();
29           bis.close();
30           accept.close();
31           System.out.println("文件上传已保存");
32         } catch (IOException e) {
33           e.printStackTrace();
34         }
35       }).start();
36     }
37   }
38 }

  信息回写分析图解

   前四部与基本文件上传一致。

   5.【服务端】获取输出流,回写数据。

   6.【客户端】获取输入流,解析回写数据。

  Java 网络编程:(六)TCP网络编程
一、TCP协议概述
二、Socket 类
三、ServerSocket 类
四、基于 TCP 协议的网络通信
五、简单的 TCP网络程序
六、TCP案例一
七、TCP案例二
八、TCP案例三
九、文件上传案例
十、小结

   回写实现:

    服务端实现:

 1 public class FileUpload_Server {
 2   public static void main(String[] args) throws IOException {
 3     System.out.println("服务器 启动..... ");
 4     // 1. 创建服务端ServerSocket
 5     ServerSocket serverSocket = new ServerSocket(6666);
 6     // 2. 循环接收,建立连接
 7     while (true) {
 8       Socket accept = serverSocket.accept();
 9       /*
10       3. socket对象交给子线程处理,进行读写操作
11       Runnable接口中,只有一个run方法,使用lambda表达式简化格式
12       */
13       new Thread(() ‐> {
14         try (
15           //3.1 获取输入流对象
16           BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
17           //3.2 创建输出流对象, 保存到本地 .
18           FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() +".jpg");
19           BufferedOutputStream bos = new BufferedOutputStream(fis);
20          ) {
21           // 3.3 读写数据
22           byte[] b = new byte[1024 * 8];
23           int len;
24           while ((len = bis.read(b)) != ‐1) {
25             bos.write(b, 0, len);
26           } 
27           // 4.=======信息回写===========================
28           System.out.println("back ........");
29           OutputStream out = accept.getOutputStream();
30           out.write("上传成功".getBytes());
31           out.close();
32           //================================
33           //5. 关闭 资源
34           bos.close();
35           bis.close();
36           accept.close();
37           System.out.println("文件上传已保存");
38          } catch (IOException e) {
39             e.printStackTrace();
40          }
41         }).start();
42      }
43   }
44 }

    客户端实现:

 1 public class FileUpload_Client {
 2   public static void main(String[] args) throws IOException {
 3     // 1.创建流对象
 4     // 1.1 创建输入流,读取本地文件
 5     BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
 6     // 1.2 创建输出流,写到服务端
 7     Socket socket = new Socket("localhost", 6666);
 8     BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
 9     //2.写出数据.
10     byte[] b = new byte[1024 * 8 ];
11     int len ;
12     while (( len = bis.read(b))!=‐1) {
13       bos.write(b, 0, len);
14     } 
15     // 关闭输出流,通知服务端,写出数据完毕
16     socket.shutdownOutput();
17     System.out.println("文件发送完毕");
18     // 3. =====解析回写============
19     InputStream in = socket.getInputStream();
20     byte[] back = new byte[20];
21     in.read(back);
22     System.out.println(new String(back));
23     in.close();
24     // ============================
25     // 4.释放资源
26     socket.close();
27     bis.close();
28   }
29 }

十、小结

Java 网络编程:(六)TCP网络编程
一、TCP协议概述
二、Socket 类
三、ServerSocket 类
四、基于 TCP 协议的网络通信
五、简单的 TCP网络程序
六、TCP案例一
七、TCP案例二
八、TCP案例三
九、文件上传案例
十、小结