乐趣区

网络编程之TCP协议

tcp 协议图解

第二章 TCP 通信程序

2.1 概述

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

两端通信时步骤:

  1. 服务端程序,需要事先启动,等待客户端的连接。
  2. 客户端主动连接服务器端,连接成功才能通信。服务端不可以主动连接客户端。

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

  1. 客户端:java.net.Socket 类表示。创建 Socket 对象,向服务端发出连接请求,服务端响应请求,两者建立连接开始通信。
  2. 服务端:java.net.ServerSocket 类表示。创建 ServerSocket 对象,相当于开启一个服务,并等待客户端的连接。

2.2 Socket 类

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

构造方法

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

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

构造举例,代码如下:

Socket client = new Socket("127.0.0.1", 6666);

成员方法

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

    • 如果此 Scoket 具有相关联的通道,则生成的 InputStream 的所有操作也关联该通道。
    • 关闭生成的 InputStream 也将关闭相关的 Socket。
  • public OutputStream getOutputStream():返回此套接字的输出流。

    • 如果此 Scoket 具有相关联的通道,则生成的 OutputStream 的所有操作也关联该通道。
    • 关闭生成的 OutputStream 也将关闭相关的 Socket。
  • public void close():关闭此套接字。

    • 一旦一个 socket 被关闭,它不可再使用。
    • 关闭此 socket 也将关闭相关的 InputStream 和 OutputStream。
  • public void shutdownOutput():禁用此套接字的输出流。

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

## 2.3 ServerSocket 类

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

构造方法

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

构造举例,代码如下:

ServerSocket server = new ServerSocket(6666);

成员方法

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

2.4 简单的 TCP 网络程序

TCP 通信分析图解

  1. 【服务端】启动, 创建 ServerSocket 对象,等待连接。
  2. 【客户端】启动, 创建 Socket 对象,请求连接。
  3. 【服务端】接收连接, 调用 accept 方法,并返回一个 Socket 对象。
  4. 【客户端】Socket 对象,获取 OutputStream,向服务端写出数据。
  5. 【服务端】Scoket 对象,获取 InputStream,读取客户端发送的数据。

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

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

  1. 【服务端】Socket 对象,获取 OutputStream,向客户端回写数据。
  2. 【客户端】Scoket 对象,获取 InputStream,解析回写数据。
  3. 【客户端】释放资源,断开连接。

客户端向服务器发送数据

服务端实现:

public class ServerTCP {public static void main(String[] args) throws IOException {System.out.println("服务端启动 , 等待连接 ....");
        // 1. 创建 ServerSocket 对象,绑定端口,开始等待连接
        ServerSocket ss = new ServerSocket(6666);
        // 2. 接收连接 accept 方法, 返回 socket 对象.
        Socket server = ss.accept();
        // 3. 通过 socket 获取输入流
        InputStream is = server.getInputStream();
        // 4. 一次性读取数据
          // 4.1 创建字节数组
        byte[] b = new byte[1024];
          // 4.2 据读取到字节数组中.
        int len = is.read(b);// 4.3 解析数组, 打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
        //5. 关闭资源.
        is.close();
        server.close();}
}

客户端实现:

public class ClientTCP {public static void main(String[] args) throws Exception {System.out.println("客户端 发送数据");
        // 1. 创建 Socket (ip , port) , 确定连接到哪里.
        Socket client = new Socket("localhost", 6666);
        // 2. 获取流对象 . 输出流
        OutputStream os = client.getOutputStream();
        // 3. 写出数据.
        os.write("你好么? tcp , 我来了".getBytes());
        // 4. 关闭资源 .
        os.close();
        client.close();}
}

服务器向客户端回写数据

服务端实现:

public class ServerTCP {public static void main(String[] args) throws IOException {System.out.println("服务端启动 , 等待连接 ....");
        // 1. 创建 ServerSocket 对象,绑定端口,开始等待连接
        ServerSocket ss = new ServerSocket(6666);
        // 2. 接收连接 accept 方法, 返回 socket 对象.
        Socket server = ss.accept();
        // 3. 通过 socket 获取输入流
        InputStream is = server.getInputStream();
        // 4. 一次性读取数据
          // 4.1 创建字节数组
        byte[] b = new byte[1024];
          // 4.2 据读取到字节数组中.
        int len = is.read(b);// 4.3 解析数组, 打印字符串信息
        String msg = new String(b, 0, len);
        System.out.println(msg);
          // ================= 回写数据 =======================
          // 5. 通过 socket 获取输出流
           OutputStream out = server.getOutputStream();
          // 6. 回写数据
           out.write("我很好, 谢谢你".getBytes());
          // 7. 关闭资源.
          out.close();
        is.close();
        server.close();}
}

客户端实现:

public class ClientTCP {public static void main(String[] args) throws Exception {System.out.println("客户端 发送数据");
        // 1. 创建 Socket (ip , port) , 确定连接到哪里.
        Socket client = new Socket("localhost", 6666);
        // 2. 通过 Scoket, 获取输出流对象 
        OutputStream os = client.getOutputStream();
        // 3. 写出数据.
        os.write("你好么? tcp , 我来了".getBytes());
          // ============== 解析回写 =========================
          // 4. 通过 Scoket, 获取 输入流对象
          InputStream in = client.getInputStream();
          // 5. 读取数据数据
          byte[] b = new byte[100];
          int len = in.read(b);
          System.out.println(new String(b, 0, len));
        // 6. 关闭资源 .
          in.close();
        os.close();
        client.close();}
}

以下是实际代码
package com.itheima.demo01.TCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*

TCP 通信的服务器端: 接收客户端的请求, 读取客户端发送的数据, 给客户端回写数据
表示服务器的类:
    java.net.ServerSocket: 此类实现服务器套接字。构造方法:
    ServerSocket(int port) 创建绑定到特定端口的服务器套接字。服务器端必须明确一件事情, 必须的知道是哪个客户端请求的服务器
所以可以使用 accept 方法获取到请求的客户端对象 Socket
成员方法:
    Socket accept() 侦听并接受到此套接字的连接。服务器的实现步骤:
    1. 创建服务器 ServerSocket 对象和系统要指定的端口号
    2. 使用 ServerSocket 对象中的方法 accept, 获取到请求的客户端对象 Socket
    3. 使用 Socket 对象中的方法 getInputStream()获取网络字节输入流 InputStream 对象
    4. 使用网络字节输入流 InputStream 对象中的方法 read, 读取客户端发送的数据
    5. 使用 Socket 对象中的方法 getOutputStream()获取网络字节输出流 OutputStream 对象
    6. 使用网络字节输出流 OutputStream 对象中的方法 write, 给客户端回写数据
    7. 释放资源(Socket,ServerSocket)

*/
public class TCPServer {

public static void main(String[] args) throws IOException {
    //1. 创建服务器 ServerSocket 对象和系统要指定的端口号
    ServerSocket server = new ServerSocket(8888);
    //2. 使用 ServerSocket 对象中的方法 accept, 获取到请求的客户端对象 Socket
    Socket socket = server.accept();
    //3. 使用 Socket 对象中的方法 getInputStream()获取网络字节输入流 InputStream 对象
    InputStream is = socket.getInputStream();
    //4. 使用网络字节输入流 InputStream 对象中的方法 read, 读取客户端发送的数据
    byte[] bytes = new byte[1024];
    int len = is.read(bytes);
    System.out.println(new String(bytes,0,len));
    //5. 使用 Socket 对象中的方法 getOutputStream()获取网络字节输出流 OutputStream 对象
    OutputStream os = socket.getOutputStream();
    //6. 使用网络字节输出流 OutputStream 对象中的方法 write, 给客户端回写数据
    os.write("收到谢谢".getBytes());
    //7. 释放资源(Socket,ServerSocket)
    socket.close();
    server.close();}

}
package com.itheima.demo01.TCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/*

TCP 通信的客户端: 向服务器发送连接请求, 给服务器发送数据, 读取服务器回写的数据
表示客户端的类:
    java.net.Socket: 此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。套接字: 包含了 IP 地址和端口号的网络单位

构造方法:
    Socket(String host, int port) 创建一个流套接字并将其连接到指定主机上的指定端口号。参数:
        String host: 服务器主机的名称 / 服务器的 IP 地址
        int port: 服务器的端口号

成员方法:
    OutputStream getOutputStream() 返回此套接字的输出流。InputStream getInputStream() 返回此套接字的输入流。void close() 关闭此套接字。实现步骤:
    1. 创建一个客户端对象 Socket, 构造方法绑定服务器的 IP 地址和端口号
    2. 使用 Socket 对象中的方法 getOutputStream()获取网络字节输出流 OutputStream 对象
    3. 使用网络字节输出流 OutputStream 对象中的方法 write, 给服务器发送数据
    4. 使用 Socket 对象中的方法 getInputStream()获取网络字节输入流 InputStream 对象
    5. 使用网络字节输入流 InputStream 对象中的方法 read, 读取服务器回写的数据
    6. 释放资源(Socket)
 注意:
    1. 客户端和服务器端进行交互, 必须使用 Socket 中提供的网络流, 不能使用自己创建的流对象
    2. 当我们创建客户端对象 Socket 的时候, 就会去请求服务器和服务器经过 3 次握手建立连接通路
        这时如果服务器没有启动, 那么就会抛出异常 ConnectException: Connection refused: connect
        如果服务器已经启动, 那么就可以进行交互了

*/
public class TCPClient {

public static void main(String[] args) throws IOException {
    //1. 创建一个客户端对象 Socket, 构造方法绑定服务器的 IP 地址和端口号
    Socket socket = new Socket("127.0.0.1",8888);
    //2. 使用 Socket 对象中的方法 getOutputStream()获取网络字节输出流 OutputStream 对象
    OutputStream os = socket.getOutputStream();
    //3. 使用网络字节输出流 OutputStream 对象中的方法 write, 给服务器发送数据
    os.write("你好服务器".getBytes());

    //4. 使用 Socket 对象中的方法 getInputStream()获取网络字节输入流 InputStream 对象
    InputStream is = socket.getInputStream();

    //5. 使用网络字节输入流 InputStream 对象中的方法 read, 读取服务器回写的数据
    byte[] bytes = new byte[1024];
    int len = is.read(bytes);
    System.out.println(new String(bytes,0,len));

    //6. 释放资源(Socket)
    socket.close();}

}

退出移动版