wait()
). This is done by calling sync (), which provides a lock, but does not cause the application to hang.java.lang.String
and sends it to the server. The server converts this string with toUpperCase()
and sends the resulting string back to the client. The client displays a string in the user interface.EchoServer
and EchoClient
contain main()
functions, which are entry points for server and client processes. EchoServer
contains Netty code for bootstrapping, linking and creating a pipeline with a special EchoServerHandler
handler. EchoClient
creates an EchoClient
user interface EchoClientController
that contains Netty code for creating a connection, breaking a connection, sending and receiving. The EchoClientController
also creates a client pipeline using the EchoClientHandler
.EchoClientController
controller EchoClientController
and connects to the EchoServer
.writeAndFlush()
operation is called on the channel. The channelRead()
and channelReadComplete()
methods of the channelRead()
handler are EchoServerHandler
.channelRead()
method of the channelRead()
handler performs its own write()
method, and the channelReadComplete()
method performs flush()
.EchoClientHandler
receives dataEchoClientHandler
sets the EchoClientHandler
property associated with the UI. Automatically updated TextField
field in the UI.EchoClientController
class.connect()
method takes a host and a port from the UI and creates a Netty channel, which is then saved as an EchoClientController
field. @FXML HBox hboxStatus; @FXML ProgressIndicator piStatus; @FXML Label lblStatus; private BooleanProperty connected = new SimpleBooleanProperty(false); private StringProperty receivingMessageModel = new SimpleStringProperty(""); private Channel channel; @FXML public void connect() { if( connected.get() ) { return; // ; } String host = tfHost.getText(); int port = Integer.parseInt(tfPort.getText()); group = new NioEventLoopGroup(); Task<Channel> task = new Task<Channel>() { @Override protected Channel call() throws Exception { updateMessage("Bootstrapping"); updateProgress(0.1d, 1.0d); Bootstrap b = new Bootstrap(); b .group(group) .channel(NioSocketChannel.class) .remoteAddress( new InetSocketAddress(host, port) ) .handler( new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new EchoClientHandler(receivingMessageModel)); } }); ChannelFuture f = b.connect(); Channel chn = f.channel(); updateMessage("Connecting"); updateProgress(0.2d, 1.0d); f.sync(); return chn; } @Override protected void succeeded() { channel = getValue(); connected.set(true); } @Override protected void failed() { Throwable exc = getException(); logger.error( "client connect error", exc ); Alert alert = new Alert(AlertType.ERROR); alert.setTitle("Client"); alert.setHeaderText( exc.getClass().getName() ); alert.setContentText( exc.getMessage() ); alert.showAndWait(); connected.set(false); } }; hboxStatus.visibleProperty().bind( task.runningProperty() ); lblStatus.textProperty().bind( task.messageProperty() ); piStatus.progressProperty().bind(task.progressProperty()); new Thread(task).start(); }
runningProperty
, messageProperty
, progressProperty
. I associate them with UI elements: a HBox container, a Label label, a ProgressIndicator indicator. Thanks to JavaFX binding, there is no need to register listeners and call setter () methods on user interface controls.call()
method returns the channel. In this implementation, I do not care about the asynchronous behavior of Netty - because I already work in the new Thread()
- so I can wait until the sync()
call returns. The return value of the channel is set in the succeeded()
method field If Netty throws an exception, the failed()
method is called, the message is logged and displayed to the user in a dialog box.succeeded()
, failed()
, updateMessage()
and updateProgress()
are executed in the FX stream, but call()
is not. The call()
method should not update the UI in any way. The call () method should deal only with the long-term operation of Netty.send()
method uses the saved Channel
object to call the writeAndFlush()
method. This writeAndFlush()
will be launched using the EchoClientHandler
delegate via the Netty framework. @FXML public void send() { if( !connected.get() ) { return; } final String toSend = tfSend.getText(); Task task = new Task() { @Override protected Void call() throws Exception { ChannelFuture f = channel.writeAndFlush( Unpooled.copiedBuffer(toSend, CharsetUtil.UTF_8) ); f.sync(); return null; } @Override protected void failed() { Throwable exc = getException(); logger.error( "client send error", exc ); Alert alert = new Alert(AlertType.ERROR); alert.setTitle("Client"); alert.setHeaderText( exc.getClass().getName() ); alert.setContentText( exc.getMessage() ); alert.showAndWait(); connected.set(false); } }; hboxStatus.visibleProperty().bind( task.runningProperty() ); lblStatus.textProperty().bind( task.messageProperty() ); piStatus.progressProperty().bind(task.progressProperty()); new Thread(task).start(); }
connect()
. The newly created task is connected with all the same three progress objects. There is no succeeded()
method, and the failed()
method contains the same logic as the error handler in the connect()
implementation.call()
method is already in my new thread, I can afford to wait in the sync()
method.disconnect()
method works with the Task task on the same principle as the two previous methods. The other two methods use one updateMessage / Progress pair. In this method, wrapping a connection with Netty takes place in two separate steps. Not much time is needed to execute sync()
in close (). The shutdownGracefully()
method takes significantly longer. However, in my experiments, the UI never hung. @FXML public void disconnect() { if( !connected.get() ) { return; } Task<Voidgt; task = new Task<Void>() { @Override protected Void call() throws Exception { updateMessage("Disconnecting"); updateProgress(0.1d, 1.0d); channel.close().sync(); updateMessage("Closing group"); updateProgress(0.5d, 1.0d); group.shutdownGracefully().sync(); return null; } @Override protected void succeeded() { connected.set(false); } @Override protected void failed() { connected.set(false); Throwable t = getException(); logger.error( "client disconnect error", t ); Alert alert = new Alert(AlertType.ERROR); alert.setTitle("Client"); alert.setHeaderText( t.getClass().getName() ); alert.setContentText( t.getMessage() ); alert.showAndWait(); } }; hboxStatus.visibleProperty().bind( task.runningProperty() ); lblStatus.textProperty().bind( task.messageProperty() ); piStatus.progressProperty().bind(task.progressProperty()); new Thread(task).start(); }
EchoClientHandler
object. When creating this object, a reference is made to the StringProperty
property, which is a model element with which the user interface is also associated. I could pass UI elements directly to the handler, but at the same time the principle of sharing responsibility is violated and it becomes more difficult to apply this notification to several views at once. Thus, the StringProperty
property can communicate with any number of UI elements, and when an update comes from the handler, all these UI elements are updated.channelRead0()
method. @Sharable public class EchoClientHandler extends SimpleChannelInboundHandler { private Logger logger = LoggerFactory.getLogger( EchoClientHandler.class ); private final StringProperty receivingMessageModel; public EchoClientHandler(StringProperty receivingMessageModel) { this.receivingMessageModel = receivingMessageModel; } @Override protected void channelRead0(ChannelHandlerContext arg0, ByteBuf in) throws Exception { final String cm = in.toString(CharsetUtil.UTF_8); Platform.runLater( () -> receivingMessageModel.set(cm) ); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.error( "error in echo client", cause ); ctx.close(); } }
channelRead0()
(in this case, we rely on asynchronous Netty), but when such a call occurs, we will set the model object. I finish updating the model object, providing some protection for FX Thread. FX - since it is a binding framework - will update all UI elements, for example, TextField
. public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public static void main(String[] args) throws Exception { if( args.length != 1 ) { System.err.println("usage: java EchoServer port"); System.exit(1); } int port = Integer.parseInt(args[0]); new EchoServer(port).start(); } public void start() throws Exception { final EchoServerHandler echoServerHandler = new EchoServerHandler(); EventLoopGroup group = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b .group(group) .channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast( echoServerHandler ); } }); ChannelFuture f = b.bind().sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully().sync(); } } } EchoServerHandler.java @Sharable public class EchoServerHandler extends ChannelInboundHandlerAdapter { private Logger logger = LoggerFactory.getLogger( EchoServerHandler.class ); @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf in = (ByteBuf)msg; String in_s = in.toString(CharsetUtil.UTF_8); String uc = in_s.toUpperCase(); if( logger.isInfoEnabled() ) { logger.info("[READ] read " + in_s + ", writing " + uc); } in.setBytes(0, uc.getBytes(CharsetUtil.UTF_8)); ctx.write(in); // ( ) } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { if( logger.isDebugEnabled() ) { logger.debug("[READ COMPLETE]"); } ctx.flush(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); if(logger.isDebugEnabled() ) { logger.debug("[CHANNEL ACTIVE]"); } ctx.channel().closeFuture().addListener(f -> logger.debug("[CLOSE]")); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.error( "error in echo server", cause); ctx.close(); } }
Source: https://habr.com/ru/post/301298/
All Articles