I'm completely new to serial port communication and need some help grasping it.
I need to communicate with a control board. This board can sometimes send events that I need to react to, and I need to send events to the board and await a response.
We have established a protocol where each event is always 12 bytes and the first 2 bytes determine the event type.
I know that when I send a specific message, I need to await a message with specific signifying bytes. At the same time I want it to be possible to react to events that are sent from the board. For instance the board might say that it is overheating, and at the same time I'm asking it to perform some command and reply.
My question is, if I write to the port and block for a second while awaiting the expected response, how I do ensure I don't "steal" the data my listener expects? E.g. do a serial ports work like a stream, where once I've read I've advanced past the point where it can be re-read.
I've done some implementation of this using jSerialComm, hopefully this can shed some light on my question.
First a listener that is registered using the addDataListener method. I want this to trigger when an event is present on the port that starts with "T".
private static LockerSerialPort getLockerSerialPort(final DeviceClient client) {
return MySerialPort.create(COM_PORT)
.addListener(EventListener.newBuilder()
.addEventHandler(createLocalEventHandler())
.build());
}
private static EventHandler createLocalEventHandler() {
return new EventHandler() {
#Override
public void execute(final byte[] event) {
System.out.println(new String(event));
}
#Override
public byte[] getEventIdentifier() {
// I want this listener to be executed when events that start with T are sent to the port
return "T".getBytes();
}
#Override
public String getName() {
return "T handler";
}
};
}
Next, I want to be able to write to the port and immediately get the response because it is needed to know if the command was successful or not.
private byte[] waitForResponse(final byte[] bytes) throws LockerException {
write(bytes);
return blockingRead();
}
private void write(final byte[] bytes) throws LockerException {
try (var out = serialPort.getOutputStream()) {
out.write(bytes);
} catch (final IOException e) {
throw Exception.from(e, "Failed to write to serial port %s", getComPort());
}
}
public byte[] blockingRead() {
return blockingRead(DEFAULT_READ_TIMEOUT);
}
private byte[] blockingRead(final int readTimeout) {
serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, readTimeout, 0);
try {
byte[] readBuffer = new byte[PACKET_SIZE];
final int bytesRead = serialPort.readBytes(readBuffer, readBuffer.length);
if (bytesRead != PACKET_SIZE) {
throw RuntimeException.from(null, "Expected %d bytes in packet, got %d", PACKET_SIZE, bytesRead);
}
return readBuffer;
} catch (final Exception e) {
throw RuntimeException.from(e, "Failed to read packet within specified time (%d ms)", readTimeout);
}
}
When I call waitForResponse("command"), how do I know my blocking read doesn't steal data from my listener?
Are these two patterns incompatible? How would one usually handle a scenario like this?
Related
I am trying to accomplish an Unity game demo with network function, using C# for programming of client, and Java for server.
To be specific, server communication is implemented by Netty.
I also brought in Protobuf, which helps me define protocols of messages.
As I am new to server programming, dealing with packet merging and loss in TCP has not been considered in my code yet.
When I created sockets from client, and sent message to server, everything went well.
Problem happened when server replied:
In the client, an async method is ready to receive message. When I simply sent a string-format message from server, the method were able to get it.
But when I replaced the message with a 4-length byte[], which encoded from a Protobuf Message object, client just showed that it received NOTHING.
when I print what I've sent in the server console, it is like this:
00001000
00000001
00010000
00000001
My server code overrides channelRead and channelReadComplete functions of Netty.
In channelRead, ChannelHandlerContext.write was invoked to write the message to the transmission cache.
And in channelReadComplete, ChannelHandlerContext.flush was invoked, so that the message could be sent finally.
channelRead()
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
Request.MsgPack msgPack = (Request.MsgPack) msg;
Request.MsgPack.MsgType type = msgPack.getType();
switch (type)
{
case GetServerState:
final Request.GetServerState gssbody = msgPack.getGetServerState();
System.out.println("收到类型为" + type + "的消息,内容为:" +
"\nrequestId = " + gssbody.getRequestId()
);
byte[] bytes = ServerStateManager.getState(gssbody.getRequestId());
ctx.write(bytes);
break;
getState(): including Protobuf-encoding procedure
public static byte[] getState(int requestId)
{
ReturnServerState.Message.Builder replyBuilder = ReturnServerState.Message.newBuilder();
replyBuilder.setRequestId(requestId);
replyBuilder.setIsIdle(new ServerStateManager().isIdle());
return replyBuilder.build().toByteArray();
}
channelReadComplete()
#Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
try
{
ctx.flush();
}
finally
{
ctx.close();
}
}
Client code:
public class ShortLink
{
Socket clientSocket = null;
static byte[] result = new byte[1024];
Task ReceiveAsync<T>(string ip, int port)
{
return Task.Run(() =>
{
T component = default(T);
while (clientSocket.Receive(result) == 0)
{
break;
ReceiveAsync is invoked in the way of:
await ReceiveAsync<ReturnServerState>(ip, port);
when I found clientSocket.Receive(result) always output 0, I tried to log result[0], result[1], result[2], result[3] like this:
Debug.Log(Convert.ToString(result[0]) + ", " +
Convert.ToString(result[1]) + ", " +
Convert.ToString(result[2]) + ", " +
Convert.ToString(result[3]));
And the log turned to be 0,0,0,0.
I will be grateful for any idea of "why the client socket received nothing", and the solution.
Since I come from Asia, there may be a time lag between your reply and mine, and also English is not my mother tongue. However, I will try my best to reply in time.
Thanks a lot!
Okay..I have finally solve it myself
1.The usage "return replyBuilder.build().toByteArray()" is wrong because ProtoEncoder has already do toByteArray() for me:
public class ProtobufEncoder extends MessageToMessageEncoder<MessageLiteOrBuilder> {
public ProtobufEncoder() {
}
protected void encode(ChannelHandlerContext ctx, MessageLiteOrBuilder msg, List<Object> out) throws Exception {
if (msg instanceof MessageLite) {
out.add(Unpooled.wrappedBuffer(((MessageLite)msg).toByteArray()));
} else {
if (msg instanceof Builder) {
out.add(Unpooled.wrappedBuffer(((Builder)msg).build().toByteArray()));
}
}
}
}
So once I registered "new ProtobufEncoder()" in the Netty Channel Pipeline, I can just use "return replyBuilder.build()" - that is correct.
2.In "static byte[] result = new byte[1024];", The length of received message is defined casually, and it doesn't matter - until it really receives a message.
When receiving message, I shall always copy the message bytes to a new byte[] with a correct length firstly - or there will be just a 1024-length bytes[], with the data I need at the beginning, and several zeroes following, which will certainly fail to be decoded.
I have a Service class that communicates with my another process, let's say process_A, by local socket.
My Service class is as follows:
public class MyService extends Service {
private LocalSocket localSock;
private LocalSocketAddress localSockAddr;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getAction().equals(START_SERVICE_ACTION)) {
localSock = new LocalSocket();
localSockAddr = new LocalSocketAddress(LOCAL_SOCK_ADDR, LocalSocketAddress.Namespace.ABSTRACT);
try {
localSock.connect(localSockAddr);
} catch (IOException e) {
// Ignore
}
if (localSock.isConnected()) {
new LocalSockInitTask().execute(localSock);
}
} else if (intent.getAction().equals(STOP_SERVICE_ACTION)) {
new LocalSockTermTask().execute(localSock);
}
}
}
The behaviour should be as follows:
When my service is being started by user, the service uses LocalSocket.connect() to connect with process_A. Once connected successfully, the service executes an AsyncTask to send an INIT message to process_A and wait for an INIT message from process_A.
When my service is being stopped by user, the service executes another AsyncTask to send a TERM message to process_A and wait for a TERM message from process_A.
LocalSockInitTask.java:
public class LocalSockInitTask extends AsyncTask<LocalSocket, Void, Boolean> {
#Override
protected Boolean doInBackground(LocalSocket... params) {
LocalSocket localSock = params[0];
FileChannel inChannel;
FileChannel outChannel;
ByteBuffer sendBuf, recvBuf;
byte[] bytes;
String result, recvMsg;
int attempt;
try {
inChannel = new FileInputStream(localSock.getFileDescriptor()).getChannel();
outChannel = new FileOutputStream(localSock.getFileDescriptor()).getChannel();
// Send INIT Message
sendBuf = ByteBuffer.wrap(MSG_INIT.getBytes());
outChannel.write(sendBuf);
// Wait for INIT Message
recvBuf = ByteBuffer.allocate(BUFFER_SIZE);
attempt = 0;
while (inChannel.read(recvBuf) < 0) {
attempt++;
if(attempt == 5)
return false;
Thread.sleep(1000);
}
recvBuf.flip();
bytes = new byte[recvBuf.remaining()];
recvBuf.get(bytes);
result = new String(bytes);
if(!result.equals(MSG_INIT))
return false;
inChannel.close();
outChannel.close();
return true;
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return false;
}
}
LocalSockTermTask.java is nearly doing the same as LocalSockInitTask.java, the major difference is just the message being send and receive is "MSG_TERM".
The Init task is doing perfectly, both write and read are successful. However, when executing the second AsyncTask (which is LocalSockTermTask), seems both write and read are unsuccessful. I've done some testing on this line:
inChannel.read(recvBuf);
In the first AsyncTask execution (LocalSockInitTask), if nothing can be read, this method will immediately return -1 and that's why I set a while loop and count the attempt.
In the second AsyncTask execution (LocalSockTermTask), if nothing can be read, this method will be blocked, and this makes my while loop and attempt count become useless. This cause the AsyncTask never complete. Also, My process_A is waiting for "MSG_TERM" to terminate, and it remains running, that's why I think outChannel.write(sendBuf) also failed in Term task.
Currently I am passing the LocalSocket object to both AsyncTask and create a pair of in/out FileChannel in the AsyncTask. I've also tried to create a pair of in/out FileChannel in the service and pass the two FileChannel to AsyncTask, but still facing the same problem.
Any help is greatly appreciated!
OK, I just found out that this is my careless mistake. The problem is solved.
My another process handles the TERM message incorrectly, so it just simply ignore the TERM message sent by my AsyncTask, and therefore it continues to run and wait for messages.
Since it ignores the TERM message, it won't send back a TERM message to my AsyncTask, and this cause the inChannel.read(recvBuf) has nothing to read.
The blocking behavior of inChannel.read(recvBuf) is absolutely normal, returning -1 should be the case that I use BufferedReader before I changed to use FileChannel.
I am trying to send a UDP packet to a computer on the same network as my tablet. In order to do this, I began by setting up a class to hold all the appropriate objects that I need in order to create a UDP socket, create a packet, and then send that packet. This class is shown below:
public static class Session
{
public InetAddress address;
public DatagramSocket socket;
public DatagramPacket packet;
public String client;
public int port;
public byte[] receive_buffer;
public byte[] send_buffer;
public String message;
public Session (InetAddress address, DatagramSocket socket, DatagramPacket packet,
String client, int port, byte[] receive_buffer, byte[] send_buffer,
String message)
{
this.address = address;
this.socket = socket;
this.packet = packet;
this.client = client;
this.receive_buffer = receive_buffer;
this.send_buffer = send_buffer;
this.message = message;
this.port = port;
}
}
Whenever I try to send a UDP packet, I begin by creating a Session object that contains a socket, packet, message, and a few other things. I also have a Send class that I use to actually send the UDP packet. This is shown below:
public static Session Send (Session session)
{
// Execute within "try" function to catch exceptions
try
{
/* Create address*/
session.address = InetAddress.getByName(session.client);
/* Create socket */
session.socket = new DatagramSocket(session.port);
/* Create packet */
session.packet = new DatagramPacket(session.message.getBytes(),
session.message.length(), session.address, session.port);
/* Send packet */
session.socket.send(session.packet);
/* Return session */
return session;
}
catch (Exception e)
{
Log.e("MYAPP", "exception: " + e.getMessage());
Log.e("MYAPP", "exception: " + e.toString());
}
return null;
}
However, Send() cannot be called directly, since one cannot perform network operations on the UI thread. In order to remedy this, I created an AsyncTask that calls Send(). Now all I have to do is pass the Session object to the AsyncTask and it will send the packet (in theory). The AsyncTask that I created is shown below:
class sendData extends AsyncTask<UDP.Session, Void, UDP.Session>
{
/* Pre-Execute Function */
#Override
protected void onPreExecute ()
{
super.onPreExecute();
}
/* Main AsyncTask Function */
#Override
protected UDP.Session doInBackground(UDP.Session... arguments)
{
/* Send UDP packet */
UDP.Session session = UDP.Send(arguments[0]);
return session;
}
/* Post-Execute Function */
#Override
protected void onPostExecute (UDP.Session session)
{
super.onPostExecute(session);
}
}
The issue that I am experiencing is that I cannot figure out how to return the session object from my AsyncTask. It is critical that I return the session that I passed to the AsyncTask, because otherwise the socket/port becomes corrupted and I get binding exceptions and a whole bunch of other issues when I try to send another packet. I tried using:
UDP.Session nsession = new sendData().execute(ssession).get();
but I get an error that states "Unhandled exceptions: java.lang.InterruptedException, java.util.concurrent.ExecutionException". So my question is what is the best way for me to return the session object that I pass to the AsyncTask so that I can use that same session object next time I want to send a packet.
I was being silly and not paying attention to the compiler warnings about the unhandled exceptions. The AsyncTask call made above (UDP.Session nsession = new sendData().execute(ssession).get();) is indeed correct, but it just needs to be placed inside a try/catch statement catch any possible exceptions. When I changed my code to the following...
try{
session = new sendData().execute(session).get();}
catch (Exception exception){}
...everything worked. I am able to properly return an object from the AsyncTask, and my socket no longer gets corrupted when the AsyncTask finished, allowing me to send as many packets as I want
I wrote a REST server based on netty 4. The client handler looks something like the following.
The bytebuffer capacity in the msg provided by netty varies. When the client message is larger than the buffer the message gets split. What I find is that both channelRead and ChannelReadComplete get called for each fragment. What I usually see is that the ByteBuf is around 512, and the message around 600. I get a channelRead for the first 512 bytes, followed by a ChannelReadComplete for them, and then another channelRead for the remaining 100 bytes and a channelReadComplete for them - 2 messages instead of 1.
I found a few related questions here, but I am wondering what is the point of channelReadComplete? Is it really called after every channelRead? As long as there are bytes available, shouldn't they be read in before channelReadComplete is called?
public class ClientHandler extends ChannelInboundHandlerAdapter {
....
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Report.debug("Read from client");
ByteBuf buf = (ByteBuf) msg;
String contents = buf.toString(io.netty.util.CharsetUtil.US_ASCII);
ReferenceCountUtil.release(msg);
ClientConnection client = ClientConnection.get(ctx);
if (client != null) {
client.messageText(contents); // adds text to buffer
return;
}
((parse serial number from contents, process registration))
ClientConnection.online(serialNumber, ctx); // register success, create the client object
}
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ClientConnection client = ClientConnection.get(ctx);
if (client == null)
Report.debug("completed read of message from unregistered client");
else {
Report.debug("completed read of message from client " + client.serialNumber());
String contents = client.messageText();
... ((process message))
}
}
}
channelReadComplete is NOT called after each channelRead. The netty event loop will read from NIO socket and fire multiple channelRead until no more data to read or it should give up, then channelReadComplete is fired.
Yes, channelReadComplete() is called after each channelRead() in the pipeline has finished. If an exception occurs in channelRead() then it will jump to the method ecxeptionCaught().
So you should put code into channelReadComplete() that you only want to have executed on a successful channelRead().
For example this is what our project does:
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// compute msg
ctx.fireChannelRead(msg); //tells the next handler
//in pipeline (if existing) to read the channel
}
#Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("OK");
ctx.fireChannelReadComplete();
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.error(Message.RCV_ERROR, cause.getMessage());
ctx.writeAndFlush(cause.getMessage());
ctx.close();
}
If the Client receives something different than "OK" then he doesn't have to send the rest.
If you're looking for a method that gets called after all packages have arrived then:
#Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//close the writer that wrote the message to file (for example)
}
EDIT: You could also try sending bigger packages. The message size is controlled by the client, I think.
I'm writing a server to exchange messages among clients. One issue left to be solved is how to release a channel when a client happens to be closed. What I do is to start a monitor thread in which the all-Clients map is monitored, and I attempt to remove() a channel if an exception been detected when trying write() to it. However, after closing a client, the write() method in monitor thread don't throw an exception so the useless channel will never be released. Anybody know why?
public class ServerMonitor extends Thread{
private Map<String, SocketChannel> allClients;
private Set set;
private Iterator it;
private Entry entry;
private SocketChannel channel;
private ByteBuffer buf;
public ServerMonitor(Map<String, SocketChannel> allClients) {
this.allClients = allClients;
buf = ByteBuffer.allocateDirect(10);
byte b = 0;
buf.put(b);
buf.flip();
}
public void run(){
while(true) {
if(!allClients.isEmpty()) {
set = allClients.entrySet();
it = set.iterator();
while(it.hasNext()) {
entry = (Entry) it.next();
channel = (SocketChannel) entry.getValue();
try{
channel.write(buf);
} catch(Exception e) {
allClients.remove(entry.getKey());
//set.remove(entry);
}
}
}
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Writing to a TCP socket is buffered locally and put on the wire asynchronously. So you can't rely on the first write after the peer closes to fail. You can rely on a subsequent write failing, but it could take a number of writes to get there.
I've run into this issue when writing applications that send data over TCP. You've discovered that the only real way to know if a client has closed the connection is by the IOException on a call to write(...). This is pretty much the way it works.
There is a cleaner solution. First of all, you must always handle the case that a client disconnects without you knowing, and properly remove them when you get the IOException on write(...). However, if the client sends a message telling the server it is disconnecting, you can use that to close the connection when you see it.