Java实现TCP Socke通信

需求:公司的物联网设备要实现远程交互,只支持TCPSocket协议,估写此文章以做记录

不了解网络协议的可以先看一下这篇文章:

网络编程tcp协议简述

1.准备工作

1.安装配置好Java环境,这边是jdk1.8

2.下载一个网络调试助手做测试

2.服务端代码编写

package com.lianxian.web.core.config;

import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author: QiuHong
 * @Date: 2023/11/15 13:29
 * @className: lianxian
 * @Description:
 * @Version: 1.0
 */
@Component
public class TcpSocketServer {

    // 这个集合保存目前在线的socket
    public static List<Socket> onLineSocketList = new ArrayList<>();
    private int serverPort = 18081;

    public void server() {
        try {
            // 创建TCP服务器并绑定到指定端口
            ServerSocket serverSocket = new ServerSocket(serverPort);
            System.out.println("---服务器已启动,等待客户端连接---");

            // 创建线程池,处理消息管道中的任务
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                    32,    // 核心线程数 io密集型 一般是物理设备的线程数*2
                    32,    // 最大线程数
                    0,  // 临时线程存活时间
                    TimeUnit.SECONDS, // 单位s
                    new ArrayBlockingQueue<>(8), // 缓存客户端请求数
                    Executors.defaultThreadFactory(), // 创建工厂(这里是默认工厂)
                    new ThreadPoolExecutor.AbortPolicy() // 拒绝策略(这里是直接拒绝)
            );

            while (true) {
                // 等待客户端连接
                Socket socket = serverSocket.accept();
                System.out.println("clientSocket" + socket);
                System.out.println("客户端连接成功");

                // 同步目前在线的socket
                onLineSocketList.add(socket);

                // 通过线程池将任务分发给其他线程
                threadPoolExecutor.execute(new TcpSocketServerRunnable(socket));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.服务器逻辑处理

package com.lianxian.web.core.config;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * @Author: QiuHong
 * @Date: 2024/1/17 13:40
 * @className: lianxian
 * @Description: tcpSocket处理消息的线程
 * @Version: 1.0
 */
public class TcpSocketServerRunnable implements Runnable {

    private Socket socket;

    public TcpSocketServerRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 创建输入流和输出流
            //要和客户端的输入输出流保持一致
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();

            while (true) {
                try {
                    // 读取客户端发送的数据
                    byte[] bytes = new byte[1024];
                    int read = inputStream.read(bytes);
                    String message = new String(bytes, 0, read, StandardCharsets.UTF_8);
                    System.out.println(socket + "---serverMessage" + message);

                    // 将消息分发到所有在线socket通道
                    sendMessageAll(message);
                } catch (IOException e) {
                    // 客户端下线抛出异常
                    // 同步在线列表,关闭输入流,关闭输出流,关闭socket,停止等待消息
                    TcpSocketServer.onLineSocketList.remove(socket);
                    inputStream.close();
                    outputStream.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 将消息分发到所有在线socket通道
     *
     * @param message
     * @throws IOException
     */
    private void sendMessageAll(String message) throws IOException {
        for (Socket onLineSocket : TcpSocketServer.onLineSocketList) {
            OutputStream outputStream = onLineSocket.getOutputStream();
            outputStream.write(message.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();
        }
    }
}

4.客户端代码编写

package com.lianxian.web.core.config;

import com.sun.org.apache.bcel.internal.generic.NEW;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * @Author: QiuHong
 * @Date: 2024/1/17 14:07
 * @className: lianxian
 * @Description:
 * @Version: 1.0
 */
public class TcpSocketClient {

    public static void creatTcpSocketClient(String message) {
        try {
            // 创建socket对象,与服务器建立连接
            Socket socket = new Socket("192.168.1.110", 18081);

            // 创建独立线程,接收消息
            new Thread(new TcpSocketClientRunnable(socket)).start();

            // 获取字节输出流
            OutputStream outputStream = socket.getOutputStream();

            // 这里由于业务需求写完数据直接断开连接了,
            // 如果是需要不断地写入可以写死循环,让线程等待消息
            // 写数据
            while (true){
                outputStream.write(message.getBytes(StandardCharsets.UTF_8));
                outputStream.flush();
            }

//            outputStream.close();
//            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5.客户端逻辑处理

package com.lianxian.web.core.config;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * @Author: QiuHong
 * @Date: 2024/1/17 13:40
 * @className: lianxian
 * @Description: tcpSocket处理消息的线程
 * @Version: 1.0
 */
public class TcpSocketClientRunnable implements Runnable {

    private Socket socket;

    public TcpSocketClientRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 获取输入流
            InputStream inputStream = socket.getInputStream();

            while (true) {
                try {
                    // 读取客户端发送的数据
                    byte[] bytes = new byte[1024];
                    int read = inputStream.read(bytes);
                    String message = new String(bytes, 0, read, StandardCharsets.UTF_8);
                    System.out.println(socket + "---serverMessage" + message);
                } catch (IOException e) {
                    // 下线抛出异常
                    // 关闭输入流,关闭socket,停止等待消息
                    inputStream.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6.总结

1.serverSocket.accept()方法本身是一个线程阻塞的方法,理论上只能一对一通信。
2.引入多线程,每加入一个客户端连接,就创建一个子线程去处理消息,这样主线程就可以继续等待新的连接,从而实现多客户端连接。
3.引入线程池,每次加入一个客户端就创建一个新线程比较浪费资源,可以使用线程池进行管理,创建几个核心线程和几个临时线程一般就够用了。
4.为了实现客户端和客户端之间的通信,必须记录当前在线的所有通道,当有任一通道有新的消息发送时,服务器接收到之后统一再发送到每个客户端。

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值