Tcp套接字

一、Tcp套接字

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在TCP/IP协议族中,TCP协议是互联网中最核心、最广泛使用的网络协议之一。TCP套接字(Socket)是实现TCP协议进行网络通信的关键机制。

1、TCP套接字的基本概念

TCP套接字提供了一种端到端的通信方式,通过它,两台计算机上的应用程序可以相互交换数据。套接字可以被看作是在网络上不同计算机之间建立的一个通信通道。每个套接字都由一个IP地址和一个端口号唯一标识。

  • IP地址:用于在网络中唯一标识一个设备。
  • 端口号:在同一台设备上,用于区分不同的服务或应用程序。端口号的范围是0到65535。

2、TCP套接字的通信过程

  1. 服务器监听

    • 服务器首先在其指定的端口上创建一个TCP套接字,并设置为监听状态。
    • 服务器等待客户端的连接请求。
  2. 客户端请求连接

    • 客户端创建一个TCP套接字,并指定要连接的服务器IP地址和端口号。
    • 客户端向服务器发送连接请求(SYN包)。
  3. 服务器接受连接

    • 服务器收到客户端的连接请求后,确认连接请求(SYN-ACK包)。
    • 客户端收到服务器的确认后,再次发送一个确认包(ACK包),此时TCP连接建立成功。
  4. 数据交换

    • 连接建立后,客户端和服务器可以开始交换数据。
    • TCP协议确保数据的可靠传输,包括数据的完整性、顺序性和无丢失。
  5. 关闭连接

    • 当数据交换完成后,客户端或服务器可以发起关闭连接的请求。
    • 双方通过发送FIN包和确认ACK包来关闭TCP连接。

3、TCP套接字的应用

TCP套接字广泛应用于各种需要可靠传输数据的网络应用程序中,如Web服务器(HTTP)、文件传输(FTP)、远程登录(SSH)等。这些应用程序通过TCP套接字在客户端和服务器之间建立连接,实现数据的可靠传输。

4、编程实现

在编程中,TCP套接字的实现依赖于操作系统提供的套接字API。

ServerSocket

  • 定义ServerSocket是java.net包中的一个类,它实现了服务器套接字。这个类的对象可以绑定到一个特定的端口上,并侦听该端口上的客户端连接请求。
  • 作用ServerSocket接受到一个客户端的连接请求时,会调用accept()方法创建一个新的Socket对象,通过这个Socket对象,服务器可以与客户端进行通信。
  • 构造方法ServerSocket(int port),创建一个绑定到特定端口的服务器套接字。如果端口为0,则使用任何空闲端口(一般不将端口置为0,否则端口会随机分配给服务器,这会使客户端无法准确与服务器连接)。

Socket

  • 服务器端:通过ServerSocket类来创建一个套接字,并绑定到一个特定的端口上,然后监听该端口上的连接请求。当有客户端发起连接请求时,服务器端的ServerSocket会接受该请求,并创建一个新的Socket对象与客户端进行通信。
  • 客户端:通过Socket类来创建一个套接字,并指定要连接的服务器IP地址和端口号。然后,客户端向服务器发送连接请求,一旦连接建立成功,就可以通过该Socket对象与服务器进行数据的交换。

Socket构造方法:Socket(String host, int port),创建⼀个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接。

Socket常用方法:

方法签名说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输⼊流
OutputStreamgetOutputStream()返回此套接字的输出流

二、用TCP套接字构建客户端-服务器

服务器

  1. 建立连接
    创建ServerSocket对象,使用accept方法生成与客户端连接的Socket,每个Socket只能服务一个客户端。为了处理不同客户端的请求,使用多线程的形式,给每个客户端分配一个Socket对象
  2. 读取请求并解析
    通过字节流的形式读取请求并解析数据,首先建立一个流对象,通过Scanner类的方法代替read方法读取数据并解析(直接使用read方法读取的数据是byte类型,还需要转成String类才能完成解析)。
  3. 根据请求计算响应
    将解析完的数据进行计算,生成响应。
  4. 将响应返回客户端
    将响应通过write方法写回到客户端中

注意:当客户端断开连接后,与客户端相连接的Socket对象也应当被关闭,否则其将会占用文件描述符表,导致资源浪费。

public class TcpEchoServer {
    ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
    	// 创建ServerSocket对象
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("tcp服务器启动!");

        ExecutorService pool = Executors.newCachedThreadPool();
        while (true) {
            // 1. 建立连接
                // 不能使用Try()的形式关闭socket对象,如果调用start方法后关闭会影响run方法的使用
            Socket clientSocket = serverSocket.accept();
            /* // 每个客户端进来就创建一个线程
            Thread t = new Thread(() -> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();*/

                // 使用线程池创建线程,更加快速高效
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            while (true) {
                if (!scanner.hasNext()) {
                    System.out.printf("[%s:%d]客户端下线!", clientSocket.getInetAddress(), clientSocket.getPort());
                    break;
                }
                // 2. 读取请求并解析,使用scanner类方法代替read方法,一步完成读取和解析
                String request = scanner.next();
                // 3. 将解析完的数据生成响应
                String response = process(request);
                // 4. 将响应返回到客户端
                outputStream.write(response.getBytes());
                System.out.printf("[%s:%d] req:%s resp:%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
        	// 使用完关闭Socket对象
            clientSocket.close();
        }
    }

    private String process(String request) {
    	// 客户端读取数据也是使用scanner类的next方法,该方法遇到空白符才会停止读取,否则会一直阻塞
    	// 这里加上\n表示为结束符
        return request + "\n";
    }
}

客户端

  1. 建立连接
    创建Socket对象,与服务器建立连接。
  2. 生成并发送请求
    输入请求并通过write方法将请求发送给服务器。
  3. 接收响应并解析
    使用Scanner类的next方法接收响应。
  4. 根据响应作出进行下一步操作
    对响应作出操作,如打印。
public class TcpEchoClient {
    Socket socket = null;

    public TcpEchoClient(String serverIP, int sercerPort) throws IOException {
        // 1. 创建Socket对象
        socket = new Socket(serverIP, sercerPort);
    }

    public void start() {
        System.out.println("客户端启动!");
        Scanner scannerConsole = new Scanner(System.in);

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            Scanner serverNetwork = new Scanner(inputStream);
            while (true) {
                // 2. 生成并发送请求
                String request = scannerConsole.next();
                request += "\n";
                outputStream.write(request.getBytes());
                // 3. 接收响应并解析
                String response = serverNetwork.next();
                // 4. 打印响应
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值