最近在项目上需要写一个基于TCP的客户端工具,于是便通过Java 编写了一个Cilent界面,并且通过了测试,效果图如下:
首先了解一下客户端主要使用Netty 服务端主要使用ServerSocket
接下来我们看一下项目的整体结构:
接下来我们开始看代码吧,界面的话我这里通过eclipse下载了windowbuilder插件,
下载地址如下: 离线安装使用自己百度一下吧(挺简单的)。
链接:https://pan.baidu.com/s/17baY6SsiLv5_6UZWsnu6CA 提取码:rdsn
安装完成以后,可以拖拽设计页面,效果如下:
界面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 下载地址: