最近在项目上需要写一个基于TCP的客户端工具,于是便通过Java 编写了一个Cilent界面,并且通过了测试,效果图如下:

java 生成客户端 java写客户端界面_socket

 

首先了解一下客户端主要使用Netty   服务端主要使用ServerSocket

接下来我们看一下项目的整体结构:

 

java 生成客户端 java写客户端界面_netty_02

接下来我们开始看代码吧,界面的话我这里通过eclipse下载了windowbuilder插件,

下载地址如下: 离线安装使用自己百度一下吧(挺简单的)。

链接:https://pan.baidu.com/s/17baY6SsiLv5_6UZWsnu6CA  提取码:rdsn

安装完成以后,可以拖拽设计页面,效果如下:

 

java 生成客户端 java写客户端界面_socket_03

界面MyCilent.java 代码如下:

package com.lhy;

import java.awt.EventQueue;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JToggleButton;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lhy.cilent.NettyClient;

import io.netty.channel.Channel;

import javax.swing.JTextField;
import java.awt.TextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MyCilent {
	private final static Logger logger = LoggerFactory.getLogger(MyCilent.class);
	private JFrame frame;
	private JTextField textField;
	private JTextField textField_1;
	/**
	 * @Author: lhy
	 * @Title:
	 * @Date: 2020/2020年5月11日/下午1:49:13
	 * @Version: 1.0
	 */

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					MyCilent window = new MyCilent();
					window.frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public MyCilent() {
		initialize();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		frame = new JFrame();
		frame.setBounds(100, 100, 546, 451);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().setLayout(null);
		
		JToggleButton tglbtnNewToggleButton = new JToggleButton("发送");
		tglbtnNewToggleButton.setBounds(299, 20, 113, 23);
		
		frame.getContentPane().add(tglbtnNewToggleButton);
		
		JLabel lblNewLabel = new JLabel("IP:");
		lblNewLabel.setBounds(10, 24, 81, 15);
		frame.getContentPane().add(lblNewLabel);
		
		JLabel lblNewLabel_1 = new JLabel("PORT:");
		lblNewLabel_1.setBounds(140, 24, 54, 15);
		frame.getContentPane().add(lblNewLabel_1);
		
		textField = new JTextField();
		textField.setBounds(35, 21, 81, 21);
		frame.getContentPane().add(textField);
		textField.setColumns(10);
		
		textField_1 = new JTextField();
		textField_1.setColumns(10);
		textField_1.setBounds(184, 21, 81, 21);
		frame.getContentPane().add(textField_1);
		
		TextArea textArea = new TextArea();
		textArea.setBounds(10, 241, 491, 150);
		frame.getContentPane().add(textArea);
		
		JLabel lblRequest = new JLabel("request:");
		lblRequest.setBounds(10, 43, 81, 15);
		frame.getContentPane().add(lblRequest);
		
		JLabel lblResponse = new JLabel("response:");
		lblResponse.setBounds(10, 220, 81, 15);
		frame.getContentPane().add(lblResponse);
		
		TextArea textArea_1 = new TextArea();
		textArea_1.setBounds(10, 64, 491, 150);
		frame.getContentPane().add(textArea_1);
		
		/**发送  按钮监听**/
		tglbtnNewToggleButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				try {
					String ip = textField.getText();
					int port = Integer.valueOf(textField_1.getText());
					NettyClient nc = new NettyClient(ip,port,textArea_1);
					nc.start();
					Channel channel = nc.getChannel();
					channel.writeAndFlush(textArea.getText());
				} catch (Exception e) {
					logger.error("系统异常:{}",e);
				}
				
			}
		});
	}
}

 

说明: 类主要是绘制界面的代码,标签文本,大文本,按钮等,其中tglbtnNewToggleButton.addActionListener为监听按钮动作,执行发送操作;

本地启动的时候:IP:127.0.0.1   Port:1234    (温馨提示)

选中MyCilent.java 右键运行 run as/java aplication 即可运行客户端

 

接下来是客户端通信代码如下:

NettyClient.java 通信主要类

package com.lhy.cilent;

import java.awt.TextArea;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;

/**
 * @Author: lhy
 * @Title:
 * @Date: 2020/2020年5月11日/下午2:15:34
 * @Version: 1.0
 */
public class NettyClient {
	private final String host;
    private final int port;
    private Channel channel;
    private TextArea ta;
 
    //连接服务端的端口号地址和端口号
    public NettyClient(String host, int port,TextArea ta) {
        this.host = host;
        this.port = port;
        this.ta = ta;
    }
 
    public void start() throws Exception {
        final EventLoopGroup group = new NioEventLoopGroup();
 
        Bootstrap b = new Bootstrap();
        b.group(group).channel(NioSocketChannel.class)  // 使用NioSocketChannel来作为连接用的channel类
            .handler(new ChannelInitializer<SocketChannel>() { // 绑定连接初始化器
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    System.out.println("正在连接中...");
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(new RpcEncoder(String.class)); //编码request
                    pipeline.addLast(new StringDecoder());
                    pipeline.addLast(new ClientHandler(ta)); //客户端处理类
 
                }
            });
        //发起异步连接请求,绑定连接端口和host信息
        final ChannelFuture future = b.connect(host, port).sync();
 
        future.addListener(new ChannelFutureListener() {
 
            @Override
            public void operationComplete(ChannelFuture arg0) throws Exception {
                if (future.isSuccess()) {
                    System.out.println("连接服务器成功");
 
                } else {
                    System.out.println("连接服务器失败");
                    future.cause().printStackTrace();
                    group.shutdownGracefully(); //关闭线程组
                }
            }
        });
 
        this.channel = future.channel();
    }
 
    public Channel getChannel() {
        return channel;
    }
}

说明:主要使用Netty客户端连接,构造器需要传入NettyClient(String host, int port,TextArea ta) ,ta主要用于应答回显

ClientHandler.java-通信应答处理

package com.lhy.cilent;

import java.awt.TextArea;

/*
 * @author lhy
 * @date 2018/10/12 20:56
 * client消息处理类
 */
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
 
public class ClientHandler extends SimpleChannelInboundHandler<String>{
	
	private TextArea ta;
	
    public ClientHandler(TextArea ta) {
		super();
		this.ta = ta;
	}

	//处理服务端返回的数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String response) throws Exception {
        System.out.println("接受到server响应数据: " + response.toString());
        ta.setText(response.toString());
        ctx.close();
    }
 
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
    }
    
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
 
 
}

说明:通信处理客户端在接收到报文以后,channelRead0()回显报文,并且关闭通信

RpcEncoder-对请求报文进行一定处理

package com.lhy.cilent;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/*
 * @author uv
 * @date 2018/10/13 18:09
 * 编码器(将实体类转换成可传输的数据)
 */
 
import com.alibaba.fastjson.JSON;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
 
public class RpcEncoder extends MessageToByteEncoder {
	private final static Logger logger = LoggerFactory.getLogger(RpcEncoder.class);
    //目标对象类型进行编码
    private Class<?> target;
 
    public RpcEncoder(Class target) {
        this.target = target;
    }
 
    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
    	logger.info("Object:{}",msg);
    	if (target.isInstance(msg)) {
            byte[] data = JSON.toJSONBytes(msg); //使用fastJson将对象转换为byte
            byte[] data1 = JSON.toJSONBytes(data.length);
            out.writeBytes(data1); //先将消息长度写入,也就是消息头
            out.writeBytes(data); //消息体中包含我们要发送的数据
        }
    }
 
}

说明:就是继承了MessageToByteEncoder类,对发送报文进行了重写

接下来是服务端代码:

Server.java 启动类

说明:

package com.lhy.server;

import java.net.ServerSocket;
import java.net.Socket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Author: lhy
 * @Title:
 * @Date: 2020/2020年5月11日/下午4:15:05
 * @Version: 1.0
 */
public class Server {
	private static final Logger log = LoggerFactory.getLogger(SocketWorker.class);
	public static int PORT = 1234;
	public static void main(String[] args) {
		new Server().start();
	}
	public static void start(){
		  try{
		    ServerSocket serverSocket = new ServerSocket(PORT);
		    log.info("server listen on port:{},HOST:{}",PORT,serverSocket.getInetAddress().getHostAddress());
		    while (true){
		      try {
		        Socket client = serverSocket.accept();
		        log.info("receive client connect, localPort=:{}",client.getPort());
		        new Thread(new SocketWorker(client,"UTF-8")).start();
		      }catch (Exception e){
		        System.out.println("client exception,e=" + e.getMessage());
		      }
		    }
		  }catch(Exception e){
		    System.out.println("server exception,e=" + e.getMessage());
		  }
		}
}

SocketWorker.java 线程任务类

说明:接受客户端请求,再回复应答

package com.lhy.server;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SocketWorker implements Runnable {
	private static final Logger log = LoggerFactory.getLogger(SocketWorker.class);

	protected Socket socket; // Socket请求对象
	protected String charset; // 报文字符集

	public SocketWorker(Socket socket, String charset) {
		super();
		this.socket = socket;
		this.charset = charset;
	}

	@Override
	public final void run() {
		try {
			log.info("---------------开始解析报文---------------");
			String read = read();
			log.info("读取:{}", read);
			String wri = "LHY I LOVE YOU";
			this.write(wri);
			log.info("返回:{}", wri);
		} catch (Throwable e) {
			log.error("系统异常:{}", e);
		} finally {
			close();
		}
	}

	/** 读取请求报文并处理。length代表着该报文的报文头长度 */
	private String read() throws Exception {
		InputStream is = socket.getInputStream();
		try {
			return IOUtils.readAll(is, charset);
		} catch (Throwable e) {
			log.error("读取Socket报文异常", e);
			return "";
		}
	}

	private static class IOUtils {
		private static String readAll(InputStream is, String charset) {
			try {
				// 读取客户端数据
				InputStream input = is;
				StringBuffer acceptMsg = new StringBuffer();
				int MsgLong = 0;// 接收数据总长度
				int len = 0; // 每次容器读时的长度
				byte[] b = new byte[1024]; // 容器,存放数据

				while ((len = input.read(b)) != -1) {// 一直读,读到没数据为止
					acceptMsg.append(new String(b, 0, len, charset));
					MsgLong += len;
					if (len < 1024) {// 如果读的长度小于1024,说明是最后一次读,后面已经没有数据,跳出循环
						break;
					}
				}
				// 处理客户端数据
				System.out.println("客户端发过来的内容长度:" + MsgLong);
				System.out.println("客户端发过来的内容:" + acceptMsg.toString());
				//input.close();
				return acceptMsg.toString();
			} catch (Exception e) {
				System.out.println("服务器 run 异常: " + e.getMessage());
			} finally {
			}
			return null;
		}
	}

	/** 输出响应报文,默认报文头长度8位 */
	protected void write(String response) {
		try {
			byte[] bytes = response.getBytes(charset);
			try (OutputStream out = socket.getOutputStream()) {
				out.write(String.format("%08d", bytes.length).getBytes(charset));
				out.write(bytes);
			}
		} catch (Exception e) {
			log.error("发送Socket响应信息异常", e);
		}
	}

	/** 关闭Socket资源 */
	public void close() {
		if (socket != null) {
			try {
				socket.close();
				socket = null;
			} catch (Exception e) {
			}
		}
	}
}

接下来是日志配置如下log4j.properties:

log4j.logger.com.union = DEBUG,file,console

log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File = log/lhySocket.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%-5p]  %C{1} - %m%n

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%-5p]  %C{1} - %m%n

Demo 下载地址: