Replaced SocketChannel with Socket.
This commit is contained in:
parent
670b7e6982
commit
4708f17e41
@ -63,10 +63,6 @@ PacketFloodLogged = True
|
||||
# Default: True (disabled)
|
||||
TcpNoDelay = True
|
||||
|
||||
# Connection timeout in milliseconds.
|
||||
# Default 800
|
||||
ConnectionTimeout = 800
|
||||
|
||||
# Packet encryption.
|
||||
# By default packets sent or received are encrypted using the Blowfish algorithm.
|
||||
# Disabling this reduces the resources needed to process any packets transfered,
|
||||
|
@ -774,7 +774,6 @@ public class Config
|
||||
public static boolean PACKET_FLOOD_DROP;
|
||||
public static boolean PACKET_FLOOD_LOGGED;
|
||||
public static boolean TCP_NO_DELAY;
|
||||
public static int CONNECTION_TIMEOUT;
|
||||
public static boolean PACKET_ENCRYPTION;
|
||||
public static boolean FAILED_DECRYPTION_LOGGED;
|
||||
public static String DATABASE_DRIVER;
|
||||
@ -1369,7 +1368,6 @@ public class Config
|
||||
PACKET_FLOOD_DROP = serverConfig.getBoolean("PacketFloodDrop", false);
|
||||
PACKET_FLOOD_LOGGED = serverConfig.getBoolean("PacketFloodLogged", true);
|
||||
TCP_NO_DELAY = serverConfig.getBoolean("TcpNoDelay", true);
|
||||
CONNECTION_TIMEOUT = serverConfig.getInt("ConnectionTimeout", 800);
|
||||
PACKET_ENCRYPTION = serverConfig.getBoolean("PacketEncryption", false);
|
||||
FAILED_DECRYPTION_LOGGED = serverConfig.getBoolean("FailedDecryptionLogged", true);
|
||||
REQUEST_ID = serverConfig.getInt("RequestServerID", 0);
|
||||
|
@ -26,19 +26,15 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
// Iterate client pool.
|
||||
ITERATE: for (E client : _pool)
|
||||
{
|
||||
if (client.getChannel() == null)
|
||||
if (client.getSocket() == null)
|
||||
{
|
||||
_pool.remove(client);
|
||||
continue ITERATE;
|
||||
@ -72,16 +68,12 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Logger;
|
||||
@ -15,27 +16,30 @@ public class NetClient
|
||||
protected static final Logger LOGGER = Logger.getLogger(NetClient.class.getName());
|
||||
|
||||
private String _ip;
|
||||
private SocketChannel _channel;
|
||||
private Socket _socket;
|
||||
private InputStream _inputStream;
|
||||
private OutputStream _outputStream;
|
||||
private NetConfig _netConfig;
|
||||
private Queue<byte[]> _pendingPacketData;
|
||||
private ByteBuffer _pendingByteBuffer;
|
||||
private Queue<byte[]> _packetData;
|
||||
private byte[] _pendingData;
|
||||
private int _pendingPacketSize;
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
* @param channel
|
||||
* @param socket
|
||||
* @param netConfig
|
||||
*/
|
||||
public void init(SocketChannel channel, NetConfig netConfig)
|
||||
public void init(Socket socket, NetConfig netConfig)
|
||||
{
|
||||
_channel = channel;
|
||||
_socket = socket;
|
||||
_netConfig = netConfig;
|
||||
_pendingPacketData = new ConcurrentLinkedQueue<>();
|
||||
_packetData = new ConcurrentLinkedQueue<>();
|
||||
_ip = socket.getInetAddress().toString().substring(1); // Trim out /127.0.0.1
|
||||
|
||||
try
|
||||
{
|
||||
_ip = _channel.getRemoteAddress().toString();
|
||||
_ip = _ip.substring(1, _ip.lastIndexOf(':')); // Trim out /127.0.0.1:12345
|
||||
_inputStream = _socket.getInputStream();
|
||||
_outputStream = _socket.getOutputStream();
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
@ -64,26 +68,28 @@ public class NetClient
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if (_channel != null)
|
||||
if (_socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel.close();
|
||||
_channel = null;
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingPacketData != null)
|
||||
if (_packetData != null)
|
||||
{
|
||||
_pendingPacketData.clear();
|
||||
_packetData.clear();
|
||||
}
|
||||
|
||||
if (_pendingByteBuffer != null)
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingByteBuffer = null;
|
||||
_pendingData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +100,7 @@ public class NetClient
|
||||
public void addPacketData(byte[] data)
|
||||
{
|
||||
// Check packet flooding.
|
||||
final int size = _pendingPacketData.size();
|
||||
final int size = _packetData.size();
|
||||
if (size >= _netConfig.getPacketQueueLimit())
|
||||
{
|
||||
if (_netConfig.isPacketFloodDisconnect())
|
||||
@ -121,7 +127,7 @@ public class NetClient
|
||||
}
|
||||
|
||||
// Add to queue.
|
||||
_pendingPacketData.add(data);
|
||||
_packetData.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,24 +135,24 @@ public class NetClient
|
||||
*/
|
||||
public Queue<byte[]> getPacketData()
|
||||
{
|
||||
return _pendingPacketData;
|
||||
return _packetData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pending read ByteBuffer.
|
||||
* @return the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public ByteBuffer getPendingByteBuffer()
|
||||
public byte[] getPendingData()
|
||||
{
|
||||
return _pendingByteBuffer;
|
||||
return _pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pending read ByteBuffer.
|
||||
* @param pendingByteBuffer the pending read ByteBuffer.
|
||||
* Set the pending read <b>byte[]</b>.
|
||||
* @param pendingData the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public void setPendingByteBuffer(ByteBuffer pendingByteBuffer)
|
||||
public void setPendingData(byte[] pendingData)
|
||||
{
|
||||
_pendingByteBuffer = pendingByteBuffer;
|
||||
_pendingData = pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +172,36 @@ public class NetClient
|
||||
_pendingPacketSize = pendingPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet over the network using the default encryption.
|
||||
* @param packet The packet to send.
|
||||
*/
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((_socket == null) || !_socket.isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] sendableBytes = packet.getSendableBytes(getEncryption());
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
_outputStream.write(sendableBytes);
|
||||
_outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Encryption of this client.
|
||||
*/
|
||||
@ -175,11 +211,27 @@ public class NetClient
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SocketChannel of this client.
|
||||
* @return the Socket of this client.
|
||||
*/
|
||||
public SocketChannel getChannel()
|
||||
public Socket getSocket()
|
||||
{
|
||||
return _channel;
|
||||
return _socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the InputStream of this client.
|
||||
*/
|
||||
public InputStream getInputStream()
|
||||
{
|
||||
return _inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OutputStream of this client.
|
||||
*/
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,6 @@ public class NetConfig
|
||||
{
|
||||
private int _readPoolSize = 100;
|
||||
private int _executePoolSize = 50;
|
||||
private int _connectionTimeout = 800;
|
||||
private int _packetQueueLimit = 80;
|
||||
private boolean _packetFloodDisconnect = false;
|
||||
private boolean _packetFloodDrop = false;
|
||||
@ -50,23 +49,6 @@ public class NetConfig
|
||||
_executePoolSize = executePoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout until a connection is established.
|
||||
*/
|
||||
public int getConnectionTimeout()
|
||||
{
|
||||
return _connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout until a connection is established.
|
||||
* @param connectionTimeout
|
||||
*/
|
||||
public void setConnectionTimeout(int connectionTimeout)
|
||||
{
|
||||
_connectionTimeout = connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the packet queue limit of receivable packets.
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -105,30 +105,26 @@ public class NetServer<E extends NetClient>
|
||||
public void run()
|
||||
{
|
||||
// Create server and bind port.
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open())
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.setReuseAddress(true);
|
||||
server.bind(new InetSocketAddress(_hostname, _port));
|
||||
server.configureBlocking(false); // Non-blocking I/O.
|
||||
server.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Listen for new connections.
|
||||
LOGGER.info(_name + ": Listening on port " + _port + " for incoming connections.");
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
final SocketChannel channel = server.accept();
|
||||
if (channel != null)
|
||||
final Socket socket = server.accept();
|
||||
if (socket != null)
|
||||
{
|
||||
// Configure channel.
|
||||
channel.socket().setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
channel.socket().setSoTimeout(_netConfig.getConnectionTimeout());
|
||||
channel.configureBlocking(false); // Non-blocking I/O.
|
||||
socket.setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
socket.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Create client.
|
||||
final E client = _clientSupplier.get();
|
||||
client.init(channel, _netConfig);
|
||||
client.init(socket, _netConfig);
|
||||
|
||||
// Add to read pool.
|
||||
|
||||
@ -190,11 +186,7 @@ public class NetServer<E extends NetClient>
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly polling the channel.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -12,9 +11,9 @@ import java.util.Set;
|
||||
*/
|
||||
public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
private final ByteBuffer _sizeBuffer = ByteBuffer.allocate(2); // Reusable size buffer.
|
||||
private final ByteBuffer _pendingSizeBuffer = ByteBuffer.allocate(1); // Reusable pending size buffer.
|
||||
private final Set<E> _pool;
|
||||
private final byte[] _sizeBuffer = new byte[2]; // Reusable size buffer.
|
||||
private final byte[] _pendingSizeBuffer = new byte[1]; // Reusable pending size buffer.
|
||||
|
||||
public ReadThread(Set<E> pool)
|
||||
{
|
||||
@ -24,12 +23,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
@ -38,22 +33,29 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
try
|
||||
{
|
||||
final SocketChannel channel = client.getChannel();
|
||||
if (channel == null) // Unexpected disconnection?
|
||||
final InputStream inputStream = client.getInputStream();
|
||||
if (inputStream == null) // Unexpected disconnection?
|
||||
{
|
||||
// Null SocketChannel: client
|
||||
// Null InputStream: client
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending ByteBuffer.
|
||||
final ByteBuffer pendingByteBuffer = client.getPendingByteBuffer();
|
||||
if (pendingByteBuffer != null)
|
||||
// Continue when there are no bytes that can be read.
|
||||
if (inputStream.available() < 1)
|
||||
{
|
||||
// Allocate an additional ByteBuffer based on pending packet size.
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending byte array.
|
||||
final byte[] pendingData = client.getPendingData();
|
||||
if (pendingData != null)
|
||||
{
|
||||
// Allocate an additional byte array based on pending packet size.
|
||||
final int pendingPacketSize = client.getPendingPacketSize();
|
||||
final ByteBuffer additionalData = ByteBuffer.allocate(pendingPacketSize - pendingByteBuffer.position());
|
||||
switch (channel.read(additionalData))
|
||||
final byte[] additionalData = new byte[pendingPacketSize - pendingData.length];
|
||||
final int bytesRead = inputStream.read(additionalData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -70,15 +72,21 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Merge additional read data.
|
||||
pendingByteBuffer.put(pendingByteBuffer.position(), additionalData, 0, additionalData.position());
|
||||
pendingByteBuffer.position(pendingByteBuffer.position() + additionalData.position());
|
||||
final int currentSize = pendingData.length + bytesRead;
|
||||
final byte[] mergedData = new byte[currentSize];
|
||||
System.arraycopy(pendingData, 0, mergedData, 0, pendingData.length);
|
||||
System.arraycopy(additionalData, 0, mergedData, pendingData.length, bytesRead);
|
||||
|
||||
// Read was complete.
|
||||
if (pendingByteBuffer.position() >= pendingPacketSize)
|
||||
if (currentSize >= pendingPacketSize)
|
||||
{
|
||||
// Add packet data to client.
|
||||
client.addPacketData(pendingByteBuffer.array());
|
||||
client.setPendingByteBuffer(null);
|
||||
client.addPacketData(mergedData);
|
||||
client.setPendingData(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setPendingData(mergedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,8 +94,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Read incoming packet size (short).
|
||||
_sizeBuffer.clear();
|
||||
switch (channel.read(_sizeBuffer))
|
||||
boolean sizeRead = false;
|
||||
switch (inputStream.read(_sizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -103,8 +111,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Need to read two bytes to calculate size.
|
||||
case 1:
|
||||
{
|
||||
int attempt = 0; // Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: while ((attempt++ < 10) && (_sizeBuffer.position() < 2))
|
||||
// Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
// Wait for pending data.
|
||||
try
|
||||
@ -116,8 +124,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Try to read the missing extra byte.
|
||||
_pendingSizeBuffer.clear();
|
||||
switch (channel.read(_pendingSizeBuffer))
|
||||
_pendingSizeBuffer[0] = 0;
|
||||
switch (inputStream.read(_pendingSizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -133,14 +141,15 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Merge additional read byte.
|
||||
default:
|
||||
{
|
||||
_sizeBuffer.put(1, _pendingSizeBuffer, 0, 1);
|
||||
_sizeBuffer.position(2);
|
||||
_sizeBuffer[1] = _pendingSizeBuffer[0];
|
||||
sizeRead = true;
|
||||
break COMPLETE_SIZE_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read failed.
|
||||
if (_sizeBuffer.position() < 2)
|
||||
if (!sizeRead)
|
||||
{
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
@ -151,10 +160,11 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Read actual packet bytes.
|
||||
default:
|
||||
{
|
||||
// Allocate a new ByteBuffer based on packet size read.
|
||||
// Allocate a new byte array based on packet size read.
|
||||
final int packetSize = calculatePacketSize();
|
||||
final ByteBuffer packetByteBuffer = ByteBuffer.allocate(packetSize);
|
||||
switch (channel.read(packetByteBuffer))
|
||||
final byte[] packetData = new byte[packetSize];
|
||||
final int bytesRead = inputStream.read(packetData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -171,14 +181,16 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Read was not complete.
|
||||
if (packetByteBuffer.position() < packetSize)
|
||||
if (bytesRead < packetSize)
|
||||
{
|
||||
client.setPendingByteBuffer(packetByteBuffer);
|
||||
final byte[] pendindBytes = new byte[bytesRead];
|
||||
System.arraycopy(packetData, 0, pendindBytes, 0, bytesRead);
|
||||
client.setPendingData(pendindBytes);
|
||||
client.setPendingPacketSize(packetSize);
|
||||
}
|
||||
else // Add packet data to client.
|
||||
{
|
||||
client.addPacketData(packetByteBuffer.array());
|
||||
client.addPacketData(packetData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,24 +209,19 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePacketSize()
|
||||
{
|
||||
_sizeBuffer.rewind();
|
||||
return ((_sizeBuffer.get() & 0xff) | ((_sizeBuffer.get() << 8) & 0xffff)) - 2;
|
||||
return ((_sizeBuffer[0] & 0xff) | ((_sizeBuffer[1] << 8) & 0xffff)) - 2;
|
||||
}
|
||||
|
||||
private void onDisconnection(E client)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -13,7 +12,6 @@ public abstract class WritablePacket
|
||||
{
|
||||
private byte[] _data;
|
||||
private byte[] _sendableBytes;
|
||||
private ByteBuffer _byteBuffer;
|
||||
private int _position = 2; // Allocate space for size (max length 65535 - size header).
|
||||
|
||||
/**
|
||||
@ -270,38 +268,6 @@ public abstract class WritablePacket
|
||||
return _sendableBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public ByteBuffer getSendableByteBuffer()
|
||||
{
|
||||
return getSendableByteBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encryption if EncryptionInterface is used.
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public synchronized ByteBuffer getSendableByteBuffer(EncryptionInterface encryption)
|
||||
{
|
||||
// Generate sendable ByteBuffer.
|
||||
if ((_byteBuffer == null /* Not processed */) || (encryption != null /* Encryption can change */))
|
||||
{
|
||||
final byte[] bytes = getSendableBytes(encryption);
|
||||
if (bytes != null) // Data was actually written.
|
||||
{
|
||||
_byteBuffer = ByteBuffer.wrap(bytes);
|
||||
}
|
||||
}
|
||||
else // Rewind the buffer.
|
||||
{
|
||||
_byteBuffer.rewind();
|
||||
}
|
||||
|
||||
// Return the buffer.
|
||||
return _byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take in consideration that data must be written first.
|
||||
* @return The length of the data (includes size header).
|
||||
|
@ -462,7 +462,6 @@ public class GameServer
|
||||
server.getNetConfig().setPacketFloodDrop(Config.PACKET_FLOOD_DROP);
|
||||
server.getNetConfig().setPacketFloodLogged(Config.PACKET_FLOOD_LOGGED);
|
||||
server.getNetConfig().setTcpNoDelay(Config.TCP_NO_DELAY);
|
||||
server.getNetConfig().setConnectionTimeout(Config.CONNECTION_TIMEOUT);
|
||||
server.getNetConfig().setFailedDecryptionLogged(Config.FAILED_DECRYPTION_LOGGED);
|
||||
server.start();
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.l2jmobius.gameserver.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
@ -224,30 +222,11 @@ public class GameClient extends NetClient
|
||||
_pendingPackets.add(packet);
|
||||
synchronized (_pendingPackets)
|
||||
{
|
||||
final SocketChannel channel = getChannel();
|
||||
if ((channel != null) && channel.isConnected())
|
||||
{
|
||||
final ServerPacket sendPacket = _pendingPackets.poll();
|
||||
final ByteBuffer byteBuffer = sendPacket.getSendableByteBuffer(_encryption);
|
||||
if (byteBuffer != null)
|
||||
{
|
||||
// Send the packet data.
|
||||
try
|
||||
{
|
||||
// Loop while there are remaining bytes in the buffer.
|
||||
while (byteBuffer.hasRemaining())
|
||||
{
|
||||
channel.write(byteBuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
// Run packet implementation.
|
||||
sendPacket.run(_player);
|
||||
}
|
||||
}
|
||||
// Send the packet data.
|
||||
super.sendPacket(packet);
|
||||
|
||||
// Run packet implementation.
|
||||
packet.run(_player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.l2jmobius.loginserver.network;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -145,20 +147,26 @@ public class LoginClient extends NetClient
|
||||
return _connectionStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((packet == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Write into the channel.
|
||||
if ((getChannel() != null) && getChannel().isConnected())
|
||||
final Socket socket = getSocket();
|
||||
if ((socket != null) && socket.isConnected())
|
||||
{
|
||||
final byte[] sendableBytes = packet.getSendableBytes();
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the packet data.
|
||||
getChannel().write(packet.getSendableByteBuffer());
|
||||
final OutputStream outputStream = getOutputStream();
|
||||
synchronized (this)
|
||||
{
|
||||
outputStream.write(sendableBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
|
@ -63,10 +63,6 @@ PacketFloodLogged = True
|
||||
# Default: True (disabled)
|
||||
TcpNoDelay = True
|
||||
|
||||
# Connection timeout in milliseconds.
|
||||
# Default 800
|
||||
ConnectionTimeout = 800
|
||||
|
||||
# Packet encryption.
|
||||
# By default packets sent or received are encrypted using the Blowfish algorithm.
|
||||
# Disabling this reduces the resources needed to process any packets transfered,
|
||||
|
@ -785,7 +785,6 @@ public class Config
|
||||
public static boolean PACKET_FLOOD_DROP;
|
||||
public static boolean PACKET_FLOOD_LOGGED;
|
||||
public static boolean TCP_NO_DELAY;
|
||||
public static int CONNECTION_TIMEOUT;
|
||||
public static boolean PACKET_ENCRYPTION;
|
||||
public static boolean FAILED_DECRYPTION_LOGGED;
|
||||
public static String DATABASE_DRIVER;
|
||||
@ -1381,7 +1380,6 @@ public class Config
|
||||
PACKET_FLOOD_DROP = serverConfig.getBoolean("PacketFloodDrop", false);
|
||||
PACKET_FLOOD_LOGGED = serverConfig.getBoolean("PacketFloodLogged", true);
|
||||
TCP_NO_DELAY = serverConfig.getBoolean("TcpNoDelay", true);
|
||||
CONNECTION_TIMEOUT = serverConfig.getInt("ConnectionTimeout", 800);
|
||||
PACKET_ENCRYPTION = serverConfig.getBoolean("PacketEncryption", false);
|
||||
FAILED_DECRYPTION_LOGGED = serverConfig.getBoolean("FailedDecryptionLogged", true);
|
||||
REQUEST_ID = serverConfig.getInt("RequestServerID", 0);
|
||||
|
@ -26,19 +26,15 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
// Iterate client pool.
|
||||
ITERATE: for (E client : _pool)
|
||||
{
|
||||
if (client.getChannel() == null)
|
||||
if (client.getSocket() == null)
|
||||
{
|
||||
_pool.remove(client);
|
||||
continue ITERATE;
|
||||
@ -72,16 +68,12 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Logger;
|
||||
@ -15,27 +16,30 @@ public class NetClient
|
||||
protected static final Logger LOGGER = Logger.getLogger(NetClient.class.getName());
|
||||
|
||||
private String _ip;
|
||||
private SocketChannel _channel;
|
||||
private Socket _socket;
|
||||
private InputStream _inputStream;
|
||||
private OutputStream _outputStream;
|
||||
private NetConfig _netConfig;
|
||||
private Queue<byte[]> _pendingPacketData;
|
||||
private ByteBuffer _pendingByteBuffer;
|
||||
private Queue<byte[]> _packetData;
|
||||
private byte[] _pendingData;
|
||||
private int _pendingPacketSize;
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
* @param channel
|
||||
* @param socket
|
||||
* @param netConfig
|
||||
*/
|
||||
public void init(SocketChannel channel, NetConfig netConfig)
|
||||
public void init(Socket socket, NetConfig netConfig)
|
||||
{
|
||||
_channel = channel;
|
||||
_socket = socket;
|
||||
_netConfig = netConfig;
|
||||
_pendingPacketData = new ConcurrentLinkedQueue<>();
|
||||
_packetData = new ConcurrentLinkedQueue<>();
|
||||
_ip = socket.getInetAddress().toString().substring(1); // Trim out /127.0.0.1
|
||||
|
||||
try
|
||||
{
|
||||
_ip = _channel.getRemoteAddress().toString();
|
||||
_ip = _ip.substring(1, _ip.lastIndexOf(':')); // Trim out /127.0.0.1:12345
|
||||
_inputStream = _socket.getInputStream();
|
||||
_outputStream = _socket.getOutputStream();
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
@ -64,26 +68,28 @@ public class NetClient
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if (_channel != null)
|
||||
if (_socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel.close();
|
||||
_channel = null;
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingPacketData != null)
|
||||
if (_packetData != null)
|
||||
{
|
||||
_pendingPacketData.clear();
|
||||
_packetData.clear();
|
||||
}
|
||||
|
||||
if (_pendingByteBuffer != null)
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingByteBuffer = null;
|
||||
_pendingData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +100,7 @@ public class NetClient
|
||||
public void addPacketData(byte[] data)
|
||||
{
|
||||
// Check packet flooding.
|
||||
final int size = _pendingPacketData.size();
|
||||
final int size = _packetData.size();
|
||||
if (size >= _netConfig.getPacketQueueLimit())
|
||||
{
|
||||
if (_netConfig.isPacketFloodDisconnect())
|
||||
@ -121,7 +127,7 @@ public class NetClient
|
||||
}
|
||||
|
||||
// Add to queue.
|
||||
_pendingPacketData.add(data);
|
||||
_packetData.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,24 +135,24 @@ public class NetClient
|
||||
*/
|
||||
public Queue<byte[]> getPacketData()
|
||||
{
|
||||
return _pendingPacketData;
|
||||
return _packetData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pending read ByteBuffer.
|
||||
* @return the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public ByteBuffer getPendingByteBuffer()
|
||||
public byte[] getPendingData()
|
||||
{
|
||||
return _pendingByteBuffer;
|
||||
return _pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pending read ByteBuffer.
|
||||
* @param pendingByteBuffer the pending read ByteBuffer.
|
||||
* Set the pending read <b>byte[]</b>.
|
||||
* @param pendingData the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public void setPendingByteBuffer(ByteBuffer pendingByteBuffer)
|
||||
public void setPendingData(byte[] pendingData)
|
||||
{
|
||||
_pendingByteBuffer = pendingByteBuffer;
|
||||
_pendingData = pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +172,36 @@ public class NetClient
|
||||
_pendingPacketSize = pendingPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet over the network using the default encryption.
|
||||
* @param packet The packet to send.
|
||||
*/
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((_socket == null) || !_socket.isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] sendableBytes = packet.getSendableBytes(getEncryption());
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
_outputStream.write(sendableBytes);
|
||||
_outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Encryption of this client.
|
||||
*/
|
||||
@ -175,11 +211,27 @@ public class NetClient
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SocketChannel of this client.
|
||||
* @return the Socket of this client.
|
||||
*/
|
||||
public SocketChannel getChannel()
|
||||
public Socket getSocket()
|
||||
{
|
||||
return _channel;
|
||||
return _socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the InputStream of this client.
|
||||
*/
|
||||
public InputStream getInputStream()
|
||||
{
|
||||
return _inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OutputStream of this client.
|
||||
*/
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,6 @@ public class NetConfig
|
||||
{
|
||||
private int _readPoolSize = 100;
|
||||
private int _executePoolSize = 50;
|
||||
private int _connectionTimeout = 800;
|
||||
private int _packetQueueLimit = 80;
|
||||
private boolean _packetFloodDisconnect = false;
|
||||
private boolean _packetFloodDrop = false;
|
||||
@ -50,23 +49,6 @@ public class NetConfig
|
||||
_executePoolSize = executePoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout until a connection is established.
|
||||
*/
|
||||
public int getConnectionTimeout()
|
||||
{
|
||||
return _connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout until a connection is established.
|
||||
* @param connectionTimeout
|
||||
*/
|
||||
public void setConnectionTimeout(int connectionTimeout)
|
||||
{
|
||||
_connectionTimeout = connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the packet queue limit of receivable packets.
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -105,30 +105,26 @@ public class NetServer<E extends NetClient>
|
||||
public void run()
|
||||
{
|
||||
// Create server and bind port.
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open())
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.setReuseAddress(true);
|
||||
server.bind(new InetSocketAddress(_hostname, _port));
|
||||
server.configureBlocking(false); // Non-blocking I/O.
|
||||
server.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Listen for new connections.
|
||||
LOGGER.info(_name + ": Listening on port " + _port + " for incoming connections.");
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
final SocketChannel channel = server.accept();
|
||||
if (channel != null)
|
||||
final Socket socket = server.accept();
|
||||
if (socket != null)
|
||||
{
|
||||
// Configure channel.
|
||||
channel.socket().setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
channel.socket().setSoTimeout(_netConfig.getConnectionTimeout());
|
||||
channel.configureBlocking(false); // Non-blocking I/O.
|
||||
socket.setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
socket.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Create client.
|
||||
final E client = _clientSupplier.get();
|
||||
client.init(channel, _netConfig);
|
||||
client.init(socket, _netConfig);
|
||||
|
||||
// Add to read pool.
|
||||
|
||||
@ -190,11 +186,7 @@ public class NetServer<E extends NetClient>
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly polling the channel.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -12,9 +11,9 @@ import java.util.Set;
|
||||
*/
|
||||
public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
private final ByteBuffer _sizeBuffer = ByteBuffer.allocate(2); // Reusable size buffer.
|
||||
private final ByteBuffer _pendingSizeBuffer = ByteBuffer.allocate(1); // Reusable pending size buffer.
|
||||
private final Set<E> _pool;
|
||||
private final byte[] _sizeBuffer = new byte[2]; // Reusable size buffer.
|
||||
private final byte[] _pendingSizeBuffer = new byte[1]; // Reusable pending size buffer.
|
||||
|
||||
public ReadThread(Set<E> pool)
|
||||
{
|
||||
@ -24,12 +23,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
@ -38,22 +33,29 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
try
|
||||
{
|
||||
final SocketChannel channel = client.getChannel();
|
||||
if (channel == null) // Unexpected disconnection?
|
||||
final InputStream inputStream = client.getInputStream();
|
||||
if (inputStream == null) // Unexpected disconnection?
|
||||
{
|
||||
// Null SocketChannel: client
|
||||
// Null InputStream: client
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending ByteBuffer.
|
||||
final ByteBuffer pendingByteBuffer = client.getPendingByteBuffer();
|
||||
if (pendingByteBuffer != null)
|
||||
// Continue when there are no bytes that can be read.
|
||||
if (inputStream.available() < 1)
|
||||
{
|
||||
// Allocate an additional ByteBuffer based on pending packet size.
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending byte array.
|
||||
final byte[] pendingData = client.getPendingData();
|
||||
if (pendingData != null)
|
||||
{
|
||||
// Allocate an additional byte array based on pending packet size.
|
||||
final int pendingPacketSize = client.getPendingPacketSize();
|
||||
final ByteBuffer additionalData = ByteBuffer.allocate(pendingPacketSize - pendingByteBuffer.position());
|
||||
switch (channel.read(additionalData))
|
||||
final byte[] additionalData = new byte[pendingPacketSize - pendingData.length];
|
||||
final int bytesRead = inputStream.read(additionalData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -70,15 +72,21 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Merge additional read data.
|
||||
pendingByteBuffer.put(pendingByteBuffer.position(), additionalData, 0, additionalData.position());
|
||||
pendingByteBuffer.position(pendingByteBuffer.position() + additionalData.position());
|
||||
final int currentSize = pendingData.length + bytesRead;
|
||||
final byte[] mergedData = new byte[currentSize];
|
||||
System.arraycopy(pendingData, 0, mergedData, 0, pendingData.length);
|
||||
System.arraycopy(additionalData, 0, mergedData, pendingData.length, bytesRead);
|
||||
|
||||
// Read was complete.
|
||||
if (pendingByteBuffer.position() >= pendingPacketSize)
|
||||
if (currentSize >= pendingPacketSize)
|
||||
{
|
||||
// Add packet data to client.
|
||||
client.addPacketData(pendingByteBuffer.array());
|
||||
client.setPendingByteBuffer(null);
|
||||
client.addPacketData(mergedData);
|
||||
client.setPendingData(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setPendingData(mergedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,8 +94,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Read incoming packet size (short).
|
||||
_sizeBuffer.clear();
|
||||
switch (channel.read(_sizeBuffer))
|
||||
boolean sizeRead = false;
|
||||
switch (inputStream.read(_sizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -103,8 +111,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Need to read two bytes to calculate size.
|
||||
case 1:
|
||||
{
|
||||
int attempt = 0; // Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: while ((attempt++ < 10) && (_sizeBuffer.position() < 2))
|
||||
// Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
// Wait for pending data.
|
||||
try
|
||||
@ -116,8 +124,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Try to read the missing extra byte.
|
||||
_pendingSizeBuffer.clear();
|
||||
switch (channel.read(_pendingSizeBuffer))
|
||||
_pendingSizeBuffer[0] = 0;
|
||||
switch (inputStream.read(_pendingSizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -133,14 +141,15 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Merge additional read byte.
|
||||
default:
|
||||
{
|
||||
_sizeBuffer.put(1, _pendingSizeBuffer, 0, 1);
|
||||
_sizeBuffer.position(2);
|
||||
_sizeBuffer[1] = _pendingSizeBuffer[0];
|
||||
sizeRead = true;
|
||||
break COMPLETE_SIZE_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read failed.
|
||||
if (_sizeBuffer.position() < 2)
|
||||
if (!sizeRead)
|
||||
{
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
@ -151,10 +160,11 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Read actual packet bytes.
|
||||
default:
|
||||
{
|
||||
// Allocate a new ByteBuffer based on packet size read.
|
||||
// Allocate a new byte array based on packet size read.
|
||||
final int packetSize = calculatePacketSize();
|
||||
final ByteBuffer packetByteBuffer = ByteBuffer.allocate(packetSize);
|
||||
switch (channel.read(packetByteBuffer))
|
||||
final byte[] packetData = new byte[packetSize];
|
||||
final int bytesRead = inputStream.read(packetData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -171,14 +181,16 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Read was not complete.
|
||||
if (packetByteBuffer.position() < packetSize)
|
||||
if (bytesRead < packetSize)
|
||||
{
|
||||
client.setPendingByteBuffer(packetByteBuffer);
|
||||
final byte[] pendindBytes = new byte[bytesRead];
|
||||
System.arraycopy(packetData, 0, pendindBytes, 0, bytesRead);
|
||||
client.setPendingData(pendindBytes);
|
||||
client.setPendingPacketSize(packetSize);
|
||||
}
|
||||
else // Add packet data to client.
|
||||
{
|
||||
client.addPacketData(packetByteBuffer.array());
|
||||
client.addPacketData(packetData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,24 +209,19 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePacketSize()
|
||||
{
|
||||
_sizeBuffer.rewind();
|
||||
return ((_sizeBuffer.get() & 0xff) | ((_sizeBuffer.get() << 8) & 0xffff)) - 2;
|
||||
return ((_sizeBuffer[0] & 0xff) | ((_sizeBuffer[1] << 8) & 0xffff)) - 2;
|
||||
}
|
||||
|
||||
private void onDisconnection(E client)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -13,7 +12,6 @@ public abstract class WritablePacket
|
||||
{
|
||||
private byte[] _data;
|
||||
private byte[] _sendableBytes;
|
||||
private ByteBuffer _byteBuffer;
|
||||
private int _position = 2; // Allocate space for size (max length 65535 - size header).
|
||||
|
||||
/**
|
||||
@ -270,38 +268,6 @@ public abstract class WritablePacket
|
||||
return _sendableBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public ByteBuffer getSendableByteBuffer()
|
||||
{
|
||||
return getSendableByteBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encryption if EncryptionInterface is used.
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public synchronized ByteBuffer getSendableByteBuffer(EncryptionInterface encryption)
|
||||
{
|
||||
// Generate sendable ByteBuffer.
|
||||
if ((_byteBuffer == null /* Not processed */) || (encryption != null /* Encryption can change */))
|
||||
{
|
||||
final byte[] bytes = getSendableBytes(encryption);
|
||||
if (bytes != null) // Data was actually written.
|
||||
{
|
||||
_byteBuffer = ByteBuffer.wrap(bytes);
|
||||
}
|
||||
}
|
||||
else // Rewind the buffer.
|
||||
{
|
||||
_byteBuffer.rewind();
|
||||
}
|
||||
|
||||
// Return the buffer.
|
||||
return _byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take in consideration that data must be written first.
|
||||
* @return The length of the data (includes size header).
|
||||
|
@ -470,7 +470,6 @@ public class GameServer
|
||||
server.getNetConfig().setPacketFloodDrop(Config.PACKET_FLOOD_DROP);
|
||||
server.getNetConfig().setPacketFloodLogged(Config.PACKET_FLOOD_LOGGED);
|
||||
server.getNetConfig().setTcpNoDelay(Config.TCP_NO_DELAY);
|
||||
server.getNetConfig().setConnectionTimeout(Config.CONNECTION_TIMEOUT);
|
||||
server.getNetConfig().setFailedDecryptionLogged(Config.FAILED_DECRYPTION_LOGGED);
|
||||
server.start();
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.l2jmobius.gameserver.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
@ -224,30 +222,11 @@ public class GameClient extends NetClient
|
||||
_pendingPackets.add(packet);
|
||||
synchronized (_pendingPackets)
|
||||
{
|
||||
final SocketChannel channel = getChannel();
|
||||
if ((channel != null) && channel.isConnected())
|
||||
{
|
||||
final ServerPacket sendPacket = _pendingPackets.poll();
|
||||
final ByteBuffer byteBuffer = sendPacket.getSendableByteBuffer(_encryption);
|
||||
if (byteBuffer != null)
|
||||
{
|
||||
// Send the packet data.
|
||||
try
|
||||
{
|
||||
// Loop while there are remaining bytes in the buffer.
|
||||
while (byteBuffer.hasRemaining())
|
||||
{
|
||||
channel.write(byteBuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
// Run packet implementation.
|
||||
sendPacket.run(_player);
|
||||
}
|
||||
}
|
||||
// Send the packet data.
|
||||
super.sendPacket(packet);
|
||||
|
||||
// Run packet implementation.
|
||||
packet.run(_player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.l2jmobius.loginserver.network;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -145,20 +147,26 @@ public class LoginClient extends NetClient
|
||||
return _connectionStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((packet == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Write into the channel.
|
||||
if ((getChannel() != null) && getChannel().isConnected())
|
||||
final Socket socket = getSocket();
|
||||
if ((socket != null) && socket.isConnected())
|
||||
{
|
||||
final byte[] sendableBytes = packet.getSendableBytes();
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the packet data.
|
||||
getChannel().write(packet.getSendableByteBuffer());
|
||||
final OutputStream outputStream = getOutputStream();
|
||||
synchronized (this)
|
||||
{
|
||||
outputStream.write(sendableBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
|
@ -63,10 +63,6 @@ PacketFloodLogged = True
|
||||
# Default: True (disabled)
|
||||
TcpNoDelay = True
|
||||
|
||||
# Connection timeout in milliseconds.
|
||||
# Default 800
|
||||
ConnectionTimeout = 800
|
||||
|
||||
# Packet encryption.
|
||||
# By default packets sent or received are encrypted using the Blowfish algorithm.
|
||||
# Disabling this reduces the resources needed to process any packets transfered,
|
||||
|
@ -786,7 +786,6 @@ public class Config
|
||||
public static boolean PACKET_FLOOD_DROP;
|
||||
public static boolean PACKET_FLOOD_LOGGED;
|
||||
public static boolean TCP_NO_DELAY;
|
||||
public static int CONNECTION_TIMEOUT;
|
||||
public static boolean PACKET_ENCRYPTION;
|
||||
public static boolean FAILED_DECRYPTION_LOGGED;
|
||||
public static String DATABASE_DRIVER;
|
||||
@ -1394,7 +1393,6 @@ public class Config
|
||||
PACKET_FLOOD_DROP = serverConfig.getBoolean("PacketFloodDrop", false);
|
||||
PACKET_FLOOD_LOGGED = serverConfig.getBoolean("PacketFloodLogged", true);
|
||||
TCP_NO_DELAY = serverConfig.getBoolean("TcpNoDelay", true);
|
||||
CONNECTION_TIMEOUT = serverConfig.getInt("ConnectionTimeout", 800);
|
||||
PACKET_ENCRYPTION = serverConfig.getBoolean("PacketEncryption", false);
|
||||
FAILED_DECRYPTION_LOGGED = serverConfig.getBoolean("FailedDecryptionLogged", true);
|
||||
REQUEST_ID = serverConfig.getInt("RequestServerID", 0);
|
||||
|
@ -26,19 +26,15 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
// Iterate client pool.
|
||||
ITERATE: for (E client : _pool)
|
||||
{
|
||||
if (client.getChannel() == null)
|
||||
if (client.getSocket() == null)
|
||||
{
|
||||
_pool.remove(client);
|
||||
continue ITERATE;
|
||||
@ -72,16 +68,12 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Logger;
|
||||
@ -15,27 +16,30 @@ public class NetClient
|
||||
protected static final Logger LOGGER = Logger.getLogger(NetClient.class.getName());
|
||||
|
||||
private String _ip;
|
||||
private SocketChannel _channel;
|
||||
private Socket _socket;
|
||||
private InputStream _inputStream;
|
||||
private OutputStream _outputStream;
|
||||
private NetConfig _netConfig;
|
||||
private Queue<byte[]> _pendingPacketData;
|
||||
private ByteBuffer _pendingByteBuffer;
|
||||
private Queue<byte[]> _packetData;
|
||||
private byte[] _pendingData;
|
||||
private int _pendingPacketSize;
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
* @param channel
|
||||
* @param socket
|
||||
* @param netConfig
|
||||
*/
|
||||
public void init(SocketChannel channel, NetConfig netConfig)
|
||||
public void init(Socket socket, NetConfig netConfig)
|
||||
{
|
||||
_channel = channel;
|
||||
_socket = socket;
|
||||
_netConfig = netConfig;
|
||||
_pendingPacketData = new ConcurrentLinkedQueue<>();
|
||||
_packetData = new ConcurrentLinkedQueue<>();
|
||||
_ip = socket.getInetAddress().toString().substring(1); // Trim out /127.0.0.1
|
||||
|
||||
try
|
||||
{
|
||||
_ip = _channel.getRemoteAddress().toString();
|
||||
_ip = _ip.substring(1, _ip.lastIndexOf(':')); // Trim out /127.0.0.1:12345
|
||||
_inputStream = _socket.getInputStream();
|
||||
_outputStream = _socket.getOutputStream();
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
@ -64,26 +68,28 @@ public class NetClient
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if (_channel != null)
|
||||
if (_socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel.close();
|
||||
_channel = null;
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingPacketData != null)
|
||||
if (_packetData != null)
|
||||
{
|
||||
_pendingPacketData.clear();
|
||||
_packetData.clear();
|
||||
}
|
||||
|
||||
if (_pendingByteBuffer != null)
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingByteBuffer = null;
|
||||
_pendingData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +100,7 @@ public class NetClient
|
||||
public void addPacketData(byte[] data)
|
||||
{
|
||||
// Check packet flooding.
|
||||
final int size = _pendingPacketData.size();
|
||||
final int size = _packetData.size();
|
||||
if (size >= _netConfig.getPacketQueueLimit())
|
||||
{
|
||||
if (_netConfig.isPacketFloodDisconnect())
|
||||
@ -121,7 +127,7 @@ public class NetClient
|
||||
}
|
||||
|
||||
// Add to queue.
|
||||
_pendingPacketData.add(data);
|
||||
_packetData.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,24 +135,24 @@ public class NetClient
|
||||
*/
|
||||
public Queue<byte[]> getPacketData()
|
||||
{
|
||||
return _pendingPacketData;
|
||||
return _packetData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pending read ByteBuffer.
|
||||
* @return the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public ByteBuffer getPendingByteBuffer()
|
||||
public byte[] getPendingData()
|
||||
{
|
||||
return _pendingByteBuffer;
|
||||
return _pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pending read ByteBuffer.
|
||||
* @param pendingByteBuffer the pending read ByteBuffer.
|
||||
* Set the pending read <b>byte[]</b>.
|
||||
* @param pendingData the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public void setPendingByteBuffer(ByteBuffer pendingByteBuffer)
|
||||
public void setPendingData(byte[] pendingData)
|
||||
{
|
||||
_pendingByteBuffer = pendingByteBuffer;
|
||||
_pendingData = pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +172,36 @@ public class NetClient
|
||||
_pendingPacketSize = pendingPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet over the network using the default encryption.
|
||||
* @param packet The packet to send.
|
||||
*/
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((_socket == null) || !_socket.isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] sendableBytes = packet.getSendableBytes(getEncryption());
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
_outputStream.write(sendableBytes);
|
||||
_outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Encryption of this client.
|
||||
*/
|
||||
@ -175,11 +211,27 @@ public class NetClient
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SocketChannel of this client.
|
||||
* @return the Socket of this client.
|
||||
*/
|
||||
public SocketChannel getChannel()
|
||||
public Socket getSocket()
|
||||
{
|
||||
return _channel;
|
||||
return _socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the InputStream of this client.
|
||||
*/
|
||||
public InputStream getInputStream()
|
||||
{
|
||||
return _inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OutputStream of this client.
|
||||
*/
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,6 @@ public class NetConfig
|
||||
{
|
||||
private int _readPoolSize = 100;
|
||||
private int _executePoolSize = 50;
|
||||
private int _connectionTimeout = 800;
|
||||
private int _packetQueueLimit = 80;
|
||||
private boolean _packetFloodDisconnect = false;
|
||||
private boolean _packetFloodDrop = false;
|
||||
@ -50,23 +49,6 @@ public class NetConfig
|
||||
_executePoolSize = executePoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout until a connection is established.
|
||||
*/
|
||||
public int getConnectionTimeout()
|
||||
{
|
||||
return _connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout until a connection is established.
|
||||
* @param connectionTimeout
|
||||
*/
|
||||
public void setConnectionTimeout(int connectionTimeout)
|
||||
{
|
||||
_connectionTimeout = connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the packet queue limit of receivable packets.
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -105,30 +105,26 @@ public class NetServer<E extends NetClient>
|
||||
public void run()
|
||||
{
|
||||
// Create server and bind port.
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open())
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.setReuseAddress(true);
|
||||
server.bind(new InetSocketAddress(_hostname, _port));
|
||||
server.configureBlocking(false); // Non-blocking I/O.
|
||||
server.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Listen for new connections.
|
||||
LOGGER.info(_name + ": Listening on port " + _port + " for incoming connections.");
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
final SocketChannel channel = server.accept();
|
||||
if (channel != null)
|
||||
final Socket socket = server.accept();
|
||||
if (socket != null)
|
||||
{
|
||||
// Configure channel.
|
||||
channel.socket().setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
channel.socket().setSoTimeout(_netConfig.getConnectionTimeout());
|
||||
channel.configureBlocking(false); // Non-blocking I/O.
|
||||
socket.setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
socket.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Create client.
|
||||
final E client = _clientSupplier.get();
|
||||
client.init(channel, _netConfig);
|
||||
client.init(socket, _netConfig);
|
||||
|
||||
// Add to read pool.
|
||||
|
||||
@ -190,11 +186,7 @@ public class NetServer<E extends NetClient>
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly polling the channel.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -12,9 +11,9 @@ import java.util.Set;
|
||||
*/
|
||||
public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
private final ByteBuffer _sizeBuffer = ByteBuffer.allocate(2); // Reusable size buffer.
|
||||
private final ByteBuffer _pendingSizeBuffer = ByteBuffer.allocate(1); // Reusable pending size buffer.
|
||||
private final Set<E> _pool;
|
||||
private final byte[] _sizeBuffer = new byte[2]; // Reusable size buffer.
|
||||
private final byte[] _pendingSizeBuffer = new byte[1]; // Reusable pending size buffer.
|
||||
|
||||
public ReadThread(Set<E> pool)
|
||||
{
|
||||
@ -24,12 +23,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
@ -38,22 +33,29 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
try
|
||||
{
|
||||
final SocketChannel channel = client.getChannel();
|
||||
if (channel == null) // Unexpected disconnection?
|
||||
final InputStream inputStream = client.getInputStream();
|
||||
if (inputStream == null) // Unexpected disconnection?
|
||||
{
|
||||
// Null SocketChannel: client
|
||||
// Null InputStream: client
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending ByteBuffer.
|
||||
final ByteBuffer pendingByteBuffer = client.getPendingByteBuffer();
|
||||
if (pendingByteBuffer != null)
|
||||
// Continue when there are no bytes that can be read.
|
||||
if (inputStream.available() < 1)
|
||||
{
|
||||
// Allocate an additional ByteBuffer based on pending packet size.
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending byte array.
|
||||
final byte[] pendingData = client.getPendingData();
|
||||
if (pendingData != null)
|
||||
{
|
||||
// Allocate an additional byte array based on pending packet size.
|
||||
final int pendingPacketSize = client.getPendingPacketSize();
|
||||
final ByteBuffer additionalData = ByteBuffer.allocate(pendingPacketSize - pendingByteBuffer.position());
|
||||
switch (channel.read(additionalData))
|
||||
final byte[] additionalData = new byte[pendingPacketSize - pendingData.length];
|
||||
final int bytesRead = inputStream.read(additionalData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -70,15 +72,21 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Merge additional read data.
|
||||
pendingByteBuffer.put(pendingByteBuffer.position(), additionalData, 0, additionalData.position());
|
||||
pendingByteBuffer.position(pendingByteBuffer.position() + additionalData.position());
|
||||
final int currentSize = pendingData.length + bytesRead;
|
||||
final byte[] mergedData = new byte[currentSize];
|
||||
System.arraycopy(pendingData, 0, mergedData, 0, pendingData.length);
|
||||
System.arraycopy(additionalData, 0, mergedData, pendingData.length, bytesRead);
|
||||
|
||||
// Read was complete.
|
||||
if (pendingByteBuffer.position() >= pendingPacketSize)
|
||||
if (currentSize >= pendingPacketSize)
|
||||
{
|
||||
// Add packet data to client.
|
||||
client.addPacketData(pendingByteBuffer.array());
|
||||
client.setPendingByteBuffer(null);
|
||||
client.addPacketData(mergedData);
|
||||
client.setPendingData(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setPendingData(mergedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,8 +94,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Read incoming packet size (short).
|
||||
_sizeBuffer.clear();
|
||||
switch (channel.read(_sizeBuffer))
|
||||
boolean sizeRead = false;
|
||||
switch (inputStream.read(_sizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -103,8 +111,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Need to read two bytes to calculate size.
|
||||
case 1:
|
||||
{
|
||||
int attempt = 0; // Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: while ((attempt++ < 10) && (_sizeBuffer.position() < 2))
|
||||
// Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
// Wait for pending data.
|
||||
try
|
||||
@ -116,8 +124,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Try to read the missing extra byte.
|
||||
_pendingSizeBuffer.clear();
|
||||
switch (channel.read(_pendingSizeBuffer))
|
||||
_pendingSizeBuffer[0] = 0;
|
||||
switch (inputStream.read(_pendingSizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -133,14 +141,15 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Merge additional read byte.
|
||||
default:
|
||||
{
|
||||
_sizeBuffer.put(1, _pendingSizeBuffer, 0, 1);
|
||||
_sizeBuffer.position(2);
|
||||
_sizeBuffer[1] = _pendingSizeBuffer[0];
|
||||
sizeRead = true;
|
||||
break COMPLETE_SIZE_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read failed.
|
||||
if (_sizeBuffer.position() < 2)
|
||||
if (!sizeRead)
|
||||
{
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
@ -151,10 +160,11 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Read actual packet bytes.
|
||||
default:
|
||||
{
|
||||
// Allocate a new ByteBuffer based on packet size read.
|
||||
// Allocate a new byte array based on packet size read.
|
||||
final int packetSize = calculatePacketSize();
|
||||
final ByteBuffer packetByteBuffer = ByteBuffer.allocate(packetSize);
|
||||
switch (channel.read(packetByteBuffer))
|
||||
final byte[] packetData = new byte[packetSize];
|
||||
final int bytesRead = inputStream.read(packetData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -171,14 +181,16 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Read was not complete.
|
||||
if (packetByteBuffer.position() < packetSize)
|
||||
if (bytesRead < packetSize)
|
||||
{
|
||||
client.setPendingByteBuffer(packetByteBuffer);
|
||||
final byte[] pendindBytes = new byte[bytesRead];
|
||||
System.arraycopy(packetData, 0, pendindBytes, 0, bytesRead);
|
||||
client.setPendingData(pendindBytes);
|
||||
client.setPendingPacketSize(packetSize);
|
||||
}
|
||||
else // Add packet data to client.
|
||||
{
|
||||
client.addPacketData(packetByteBuffer.array());
|
||||
client.addPacketData(packetData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,24 +209,19 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePacketSize()
|
||||
{
|
||||
_sizeBuffer.rewind();
|
||||
return ((_sizeBuffer.get() & 0xff) | ((_sizeBuffer.get() << 8) & 0xffff)) - 2;
|
||||
return ((_sizeBuffer[0] & 0xff) | ((_sizeBuffer[1] << 8) & 0xffff)) - 2;
|
||||
}
|
||||
|
||||
private void onDisconnection(E client)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -13,7 +12,6 @@ public abstract class WritablePacket
|
||||
{
|
||||
private byte[] _data;
|
||||
private byte[] _sendableBytes;
|
||||
private ByteBuffer _byteBuffer;
|
||||
private int _position = 2; // Allocate space for size (max length 65535 - size header).
|
||||
|
||||
/**
|
||||
@ -270,38 +268,6 @@ public abstract class WritablePacket
|
||||
return _sendableBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public ByteBuffer getSendableByteBuffer()
|
||||
{
|
||||
return getSendableByteBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encryption if EncryptionInterface is used.
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public synchronized ByteBuffer getSendableByteBuffer(EncryptionInterface encryption)
|
||||
{
|
||||
// Generate sendable ByteBuffer.
|
||||
if ((_byteBuffer == null /* Not processed */) || (encryption != null /* Encryption can change */))
|
||||
{
|
||||
final byte[] bytes = getSendableBytes(encryption);
|
||||
if (bytes != null) // Data was actually written.
|
||||
{
|
||||
_byteBuffer = ByteBuffer.wrap(bytes);
|
||||
}
|
||||
}
|
||||
else // Rewind the buffer.
|
||||
{
|
||||
_byteBuffer.rewind();
|
||||
}
|
||||
|
||||
// Return the buffer.
|
||||
return _byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take in consideration that data must be written first.
|
||||
* @return The length of the data (includes size header).
|
||||
|
@ -470,7 +470,6 @@ public class GameServer
|
||||
server.getNetConfig().setPacketFloodDrop(Config.PACKET_FLOOD_DROP);
|
||||
server.getNetConfig().setPacketFloodLogged(Config.PACKET_FLOOD_LOGGED);
|
||||
server.getNetConfig().setTcpNoDelay(Config.TCP_NO_DELAY);
|
||||
server.getNetConfig().setConnectionTimeout(Config.CONNECTION_TIMEOUT);
|
||||
server.getNetConfig().setFailedDecryptionLogged(Config.FAILED_DECRYPTION_LOGGED);
|
||||
server.start();
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.l2jmobius.gameserver.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
@ -224,30 +222,11 @@ public class GameClient extends NetClient
|
||||
_pendingPackets.add(packet);
|
||||
synchronized (_pendingPackets)
|
||||
{
|
||||
final SocketChannel channel = getChannel();
|
||||
if ((channel != null) && channel.isConnected())
|
||||
{
|
||||
final ServerPacket sendPacket = _pendingPackets.poll();
|
||||
final ByteBuffer byteBuffer = sendPacket.getSendableByteBuffer(_encryption);
|
||||
if (byteBuffer != null)
|
||||
{
|
||||
// Send the packet data.
|
||||
try
|
||||
{
|
||||
// Loop while there are remaining bytes in the buffer.
|
||||
while (byteBuffer.hasRemaining())
|
||||
{
|
||||
channel.write(byteBuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
// Run packet implementation.
|
||||
sendPacket.run(_player);
|
||||
}
|
||||
}
|
||||
// Send the packet data.
|
||||
super.sendPacket(packet);
|
||||
|
||||
// Run packet implementation.
|
||||
packet.run(_player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.l2jmobius.loginserver.network;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -145,20 +147,26 @@ public class LoginClient extends NetClient
|
||||
return _connectionStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((packet == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Write into the channel.
|
||||
if ((getChannel() != null) && getChannel().isConnected())
|
||||
final Socket socket = getSocket();
|
||||
if ((socket != null) && socket.isConnected())
|
||||
{
|
||||
final byte[] sendableBytes = packet.getSendableBytes();
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the packet data.
|
||||
getChannel().write(packet.getSendableByteBuffer());
|
||||
final OutputStream outputStream = getOutputStream();
|
||||
synchronized (this)
|
||||
{
|
||||
outputStream.write(sendableBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
|
@ -63,10 +63,6 @@ PacketFloodLogged = True
|
||||
# Default: True (disabled)
|
||||
TcpNoDelay = True
|
||||
|
||||
# Connection timeout in milliseconds.
|
||||
# Default 800
|
||||
ConnectionTimeout = 800
|
||||
|
||||
# Packet encryption.
|
||||
# By default packets sent or received are encrypted using the Blowfish algorithm.
|
||||
# Disabling this reduces the resources needed to process any packets transfered,
|
||||
|
@ -773,7 +773,6 @@ public class Config
|
||||
public static boolean PACKET_FLOOD_DROP;
|
||||
public static boolean PACKET_FLOOD_LOGGED;
|
||||
public static boolean TCP_NO_DELAY;
|
||||
public static int CONNECTION_TIMEOUT;
|
||||
public static boolean PACKET_ENCRYPTION;
|
||||
public static boolean FAILED_DECRYPTION_LOGGED;
|
||||
public static String DATABASE_DRIVER;
|
||||
@ -1381,7 +1380,6 @@ public class Config
|
||||
PACKET_FLOOD_DROP = serverConfig.getBoolean("PacketFloodDrop", false);
|
||||
PACKET_FLOOD_LOGGED = serverConfig.getBoolean("PacketFloodLogged", true);
|
||||
TCP_NO_DELAY = serverConfig.getBoolean("TcpNoDelay", true);
|
||||
CONNECTION_TIMEOUT = serverConfig.getInt("ConnectionTimeout", 800);
|
||||
PACKET_ENCRYPTION = serverConfig.getBoolean("PacketEncryption", false);
|
||||
FAILED_DECRYPTION_LOGGED = serverConfig.getBoolean("FailedDecryptionLogged", true);
|
||||
REQUEST_ID = serverConfig.getInt("RequestServerID", 0);
|
||||
|
@ -26,19 +26,15 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
// Iterate client pool.
|
||||
ITERATE: for (E client : _pool)
|
||||
{
|
||||
if (client.getChannel() == null)
|
||||
if (client.getSocket() == null)
|
||||
{
|
||||
_pool.remove(client);
|
||||
continue ITERATE;
|
||||
@ -72,16 +68,12 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Logger;
|
||||
@ -15,27 +16,30 @@ public class NetClient
|
||||
protected static final Logger LOGGER = Logger.getLogger(NetClient.class.getName());
|
||||
|
||||
private String _ip;
|
||||
private SocketChannel _channel;
|
||||
private Socket _socket;
|
||||
private InputStream _inputStream;
|
||||
private OutputStream _outputStream;
|
||||
private NetConfig _netConfig;
|
||||
private Queue<byte[]> _pendingPacketData;
|
||||
private ByteBuffer _pendingByteBuffer;
|
||||
private Queue<byte[]> _packetData;
|
||||
private byte[] _pendingData;
|
||||
private int _pendingPacketSize;
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
* @param channel
|
||||
* @param socket
|
||||
* @param netConfig
|
||||
*/
|
||||
public void init(SocketChannel channel, NetConfig netConfig)
|
||||
public void init(Socket socket, NetConfig netConfig)
|
||||
{
|
||||
_channel = channel;
|
||||
_socket = socket;
|
||||
_netConfig = netConfig;
|
||||
_pendingPacketData = new ConcurrentLinkedQueue<>();
|
||||
_packetData = new ConcurrentLinkedQueue<>();
|
||||
_ip = socket.getInetAddress().toString().substring(1); // Trim out /127.0.0.1
|
||||
|
||||
try
|
||||
{
|
||||
_ip = _channel.getRemoteAddress().toString();
|
||||
_ip = _ip.substring(1, _ip.lastIndexOf(':')); // Trim out /127.0.0.1:12345
|
||||
_inputStream = _socket.getInputStream();
|
||||
_outputStream = _socket.getOutputStream();
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
@ -64,26 +68,28 @@ public class NetClient
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if (_channel != null)
|
||||
if (_socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel.close();
|
||||
_channel = null;
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingPacketData != null)
|
||||
if (_packetData != null)
|
||||
{
|
||||
_pendingPacketData.clear();
|
||||
_packetData.clear();
|
||||
}
|
||||
|
||||
if (_pendingByteBuffer != null)
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingByteBuffer = null;
|
||||
_pendingData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +100,7 @@ public class NetClient
|
||||
public void addPacketData(byte[] data)
|
||||
{
|
||||
// Check packet flooding.
|
||||
final int size = _pendingPacketData.size();
|
||||
final int size = _packetData.size();
|
||||
if (size >= _netConfig.getPacketQueueLimit())
|
||||
{
|
||||
if (_netConfig.isPacketFloodDisconnect())
|
||||
@ -121,7 +127,7 @@ public class NetClient
|
||||
}
|
||||
|
||||
// Add to queue.
|
||||
_pendingPacketData.add(data);
|
||||
_packetData.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,24 +135,24 @@ public class NetClient
|
||||
*/
|
||||
public Queue<byte[]> getPacketData()
|
||||
{
|
||||
return _pendingPacketData;
|
||||
return _packetData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pending read ByteBuffer.
|
||||
* @return the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public ByteBuffer getPendingByteBuffer()
|
||||
public byte[] getPendingData()
|
||||
{
|
||||
return _pendingByteBuffer;
|
||||
return _pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pending read ByteBuffer.
|
||||
* @param pendingByteBuffer the pending read ByteBuffer.
|
||||
* Set the pending read <b>byte[]</b>.
|
||||
* @param pendingData the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public void setPendingByteBuffer(ByteBuffer pendingByteBuffer)
|
||||
public void setPendingData(byte[] pendingData)
|
||||
{
|
||||
_pendingByteBuffer = pendingByteBuffer;
|
||||
_pendingData = pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +172,36 @@ public class NetClient
|
||||
_pendingPacketSize = pendingPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet over the network using the default encryption.
|
||||
* @param packet The packet to send.
|
||||
*/
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((_socket == null) || !_socket.isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] sendableBytes = packet.getSendableBytes(getEncryption());
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
_outputStream.write(sendableBytes);
|
||||
_outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Encryption of this client.
|
||||
*/
|
||||
@ -175,11 +211,27 @@ public class NetClient
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SocketChannel of this client.
|
||||
* @return the Socket of this client.
|
||||
*/
|
||||
public SocketChannel getChannel()
|
||||
public Socket getSocket()
|
||||
{
|
||||
return _channel;
|
||||
return _socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the InputStream of this client.
|
||||
*/
|
||||
public InputStream getInputStream()
|
||||
{
|
||||
return _inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OutputStream of this client.
|
||||
*/
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,6 @@ public class NetConfig
|
||||
{
|
||||
private int _readPoolSize = 100;
|
||||
private int _executePoolSize = 50;
|
||||
private int _connectionTimeout = 800;
|
||||
private int _packetQueueLimit = 80;
|
||||
private boolean _packetFloodDisconnect = false;
|
||||
private boolean _packetFloodDrop = false;
|
||||
@ -50,23 +49,6 @@ public class NetConfig
|
||||
_executePoolSize = executePoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout until a connection is established.
|
||||
*/
|
||||
public int getConnectionTimeout()
|
||||
{
|
||||
return _connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout until a connection is established.
|
||||
* @param connectionTimeout
|
||||
*/
|
||||
public void setConnectionTimeout(int connectionTimeout)
|
||||
{
|
||||
_connectionTimeout = connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the packet queue limit of receivable packets.
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -105,30 +105,26 @@ public class NetServer<E extends NetClient>
|
||||
public void run()
|
||||
{
|
||||
// Create server and bind port.
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open())
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.setReuseAddress(true);
|
||||
server.bind(new InetSocketAddress(_hostname, _port));
|
||||
server.configureBlocking(false); // Non-blocking I/O.
|
||||
server.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Listen for new connections.
|
||||
LOGGER.info(_name + ": Listening on port " + _port + " for incoming connections.");
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
final SocketChannel channel = server.accept();
|
||||
if (channel != null)
|
||||
final Socket socket = server.accept();
|
||||
if (socket != null)
|
||||
{
|
||||
// Configure channel.
|
||||
channel.socket().setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
channel.socket().setSoTimeout(_netConfig.getConnectionTimeout());
|
||||
channel.configureBlocking(false); // Non-blocking I/O.
|
||||
socket.setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
socket.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Create client.
|
||||
final E client = _clientSupplier.get();
|
||||
client.init(channel, _netConfig);
|
||||
client.init(socket, _netConfig);
|
||||
|
||||
// Add to read pool.
|
||||
|
||||
@ -190,11 +186,7 @@ public class NetServer<E extends NetClient>
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly polling the channel.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -12,9 +11,9 @@ import java.util.Set;
|
||||
*/
|
||||
public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
private final ByteBuffer _sizeBuffer = ByteBuffer.allocate(2); // Reusable size buffer.
|
||||
private final ByteBuffer _pendingSizeBuffer = ByteBuffer.allocate(1); // Reusable pending size buffer.
|
||||
private final Set<E> _pool;
|
||||
private final byte[] _sizeBuffer = new byte[2]; // Reusable size buffer.
|
||||
private final byte[] _pendingSizeBuffer = new byte[1]; // Reusable pending size buffer.
|
||||
|
||||
public ReadThread(Set<E> pool)
|
||||
{
|
||||
@ -24,12 +23,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
@ -38,22 +33,29 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
try
|
||||
{
|
||||
final SocketChannel channel = client.getChannel();
|
||||
if (channel == null) // Unexpected disconnection?
|
||||
final InputStream inputStream = client.getInputStream();
|
||||
if (inputStream == null) // Unexpected disconnection?
|
||||
{
|
||||
// Null SocketChannel: client
|
||||
// Null InputStream: client
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending ByteBuffer.
|
||||
final ByteBuffer pendingByteBuffer = client.getPendingByteBuffer();
|
||||
if (pendingByteBuffer != null)
|
||||
// Continue when there are no bytes that can be read.
|
||||
if (inputStream.available() < 1)
|
||||
{
|
||||
// Allocate an additional ByteBuffer based on pending packet size.
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending byte array.
|
||||
final byte[] pendingData = client.getPendingData();
|
||||
if (pendingData != null)
|
||||
{
|
||||
// Allocate an additional byte array based on pending packet size.
|
||||
final int pendingPacketSize = client.getPendingPacketSize();
|
||||
final ByteBuffer additionalData = ByteBuffer.allocate(pendingPacketSize - pendingByteBuffer.position());
|
||||
switch (channel.read(additionalData))
|
||||
final byte[] additionalData = new byte[pendingPacketSize - pendingData.length];
|
||||
final int bytesRead = inputStream.read(additionalData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -70,15 +72,21 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Merge additional read data.
|
||||
pendingByteBuffer.put(pendingByteBuffer.position(), additionalData, 0, additionalData.position());
|
||||
pendingByteBuffer.position(pendingByteBuffer.position() + additionalData.position());
|
||||
final int currentSize = pendingData.length + bytesRead;
|
||||
final byte[] mergedData = new byte[currentSize];
|
||||
System.arraycopy(pendingData, 0, mergedData, 0, pendingData.length);
|
||||
System.arraycopy(additionalData, 0, mergedData, pendingData.length, bytesRead);
|
||||
|
||||
// Read was complete.
|
||||
if (pendingByteBuffer.position() >= pendingPacketSize)
|
||||
if (currentSize >= pendingPacketSize)
|
||||
{
|
||||
// Add packet data to client.
|
||||
client.addPacketData(pendingByteBuffer.array());
|
||||
client.setPendingByteBuffer(null);
|
||||
client.addPacketData(mergedData);
|
||||
client.setPendingData(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setPendingData(mergedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,8 +94,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Read incoming packet size (short).
|
||||
_sizeBuffer.clear();
|
||||
switch (channel.read(_sizeBuffer))
|
||||
boolean sizeRead = false;
|
||||
switch (inputStream.read(_sizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -103,8 +111,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Need to read two bytes to calculate size.
|
||||
case 1:
|
||||
{
|
||||
int attempt = 0; // Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: while ((attempt++ < 10) && (_sizeBuffer.position() < 2))
|
||||
// Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
// Wait for pending data.
|
||||
try
|
||||
@ -116,8 +124,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Try to read the missing extra byte.
|
||||
_pendingSizeBuffer.clear();
|
||||
switch (channel.read(_pendingSizeBuffer))
|
||||
_pendingSizeBuffer[0] = 0;
|
||||
switch (inputStream.read(_pendingSizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -133,14 +141,15 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Merge additional read byte.
|
||||
default:
|
||||
{
|
||||
_sizeBuffer.put(1, _pendingSizeBuffer, 0, 1);
|
||||
_sizeBuffer.position(2);
|
||||
_sizeBuffer[1] = _pendingSizeBuffer[0];
|
||||
sizeRead = true;
|
||||
break COMPLETE_SIZE_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read failed.
|
||||
if (_sizeBuffer.position() < 2)
|
||||
if (!sizeRead)
|
||||
{
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
@ -151,10 +160,11 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Read actual packet bytes.
|
||||
default:
|
||||
{
|
||||
// Allocate a new ByteBuffer based on packet size read.
|
||||
// Allocate a new byte array based on packet size read.
|
||||
final int packetSize = calculatePacketSize();
|
||||
final ByteBuffer packetByteBuffer = ByteBuffer.allocate(packetSize);
|
||||
switch (channel.read(packetByteBuffer))
|
||||
final byte[] packetData = new byte[packetSize];
|
||||
final int bytesRead = inputStream.read(packetData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -171,14 +181,16 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Read was not complete.
|
||||
if (packetByteBuffer.position() < packetSize)
|
||||
if (bytesRead < packetSize)
|
||||
{
|
||||
client.setPendingByteBuffer(packetByteBuffer);
|
||||
final byte[] pendindBytes = new byte[bytesRead];
|
||||
System.arraycopy(packetData, 0, pendindBytes, 0, bytesRead);
|
||||
client.setPendingData(pendindBytes);
|
||||
client.setPendingPacketSize(packetSize);
|
||||
}
|
||||
else // Add packet data to client.
|
||||
{
|
||||
client.addPacketData(packetByteBuffer.array());
|
||||
client.addPacketData(packetData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,24 +209,19 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePacketSize()
|
||||
{
|
||||
_sizeBuffer.rewind();
|
||||
return ((_sizeBuffer.get() & 0xff) | ((_sizeBuffer.get() << 8) & 0xffff)) - 2;
|
||||
return ((_sizeBuffer[0] & 0xff) | ((_sizeBuffer[1] << 8) & 0xffff)) - 2;
|
||||
}
|
||||
|
||||
private void onDisconnection(E client)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -13,7 +12,6 @@ public abstract class WritablePacket
|
||||
{
|
||||
private byte[] _data;
|
||||
private byte[] _sendableBytes;
|
||||
private ByteBuffer _byteBuffer;
|
||||
private int _position = 2; // Allocate space for size (max length 65535 - size header).
|
||||
|
||||
/**
|
||||
@ -270,38 +268,6 @@ public abstract class WritablePacket
|
||||
return _sendableBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public ByteBuffer getSendableByteBuffer()
|
||||
{
|
||||
return getSendableByteBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encryption if EncryptionInterface is used.
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public synchronized ByteBuffer getSendableByteBuffer(EncryptionInterface encryption)
|
||||
{
|
||||
// Generate sendable ByteBuffer.
|
||||
if ((_byteBuffer == null /* Not processed */) || (encryption != null /* Encryption can change */))
|
||||
{
|
||||
final byte[] bytes = getSendableBytes(encryption);
|
||||
if (bytes != null) // Data was actually written.
|
||||
{
|
||||
_byteBuffer = ByteBuffer.wrap(bytes);
|
||||
}
|
||||
}
|
||||
else // Rewind the buffer.
|
||||
{
|
||||
_byteBuffer.rewind();
|
||||
}
|
||||
|
||||
// Return the buffer.
|
||||
return _byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take in consideration that data must be written first.
|
||||
* @return The length of the data (includes size header).
|
||||
|
@ -470,7 +470,6 @@ public class GameServer
|
||||
server.getNetConfig().setPacketFloodDrop(Config.PACKET_FLOOD_DROP);
|
||||
server.getNetConfig().setPacketFloodLogged(Config.PACKET_FLOOD_LOGGED);
|
||||
server.getNetConfig().setTcpNoDelay(Config.TCP_NO_DELAY);
|
||||
server.getNetConfig().setConnectionTimeout(Config.CONNECTION_TIMEOUT);
|
||||
server.getNetConfig().setFailedDecryptionLogged(Config.FAILED_DECRYPTION_LOGGED);
|
||||
server.start();
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.l2jmobius.gameserver.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
@ -224,30 +222,11 @@ public class GameClient extends NetClient
|
||||
_pendingPackets.add(packet);
|
||||
synchronized (_pendingPackets)
|
||||
{
|
||||
final SocketChannel channel = getChannel();
|
||||
if ((channel != null) && channel.isConnected())
|
||||
{
|
||||
final ServerPacket sendPacket = _pendingPackets.poll();
|
||||
final ByteBuffer byteBuffer = sendPacket.getSendableByteBuffer(_encryption);
|
||||
if (byteBuffer != null)
|
||||
{
|
||||
// Send the packet data.
|
||||
try
|
||||
{
|
||||
// Loop while there are remaining bytes in the buffer.
|
||||
while (byteBuffer.hasRemaining())
|
||||
{
|
||||
channel.write(byteBuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
// Run packet implementation.
|
||||
sendPacket.run(_player);
|
||||
}
|
||||
}
|
||||
// Send the packet data.
|
||||
super.sendPacket(packet);
|
||||
|
||||
// Run packet implementation.
|
||||
packet.run(_player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.l2jmobius.loginserver.network;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -145,20 +147,26 @@ public class LoginClient extends NetClient
|
||||
return _connectionStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((packet == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Write into the channel.
|
||||
if ((getChannel() != null) && getChannel().isConnected())
|
||||
final Socket socket = getSocket();
|
||||
if ((socket != null) && socket.isConnected())
|
||||
{
|
||||
final byte[] sendableBytes = packet.getSendableBytes();
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the packet data.
|
||||
getChannel().write(packet.getSendableByteBuffer());
|
||||
final OutputStream outputStream = getOutputStream();
|
||||
synchronized (this)
|
||||
{
|
||||
outputStream.write(sendableBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
|
@ -63,10 +63,6 @@ PacketFloodLogged = True
|
||||
# Default: True (disabled)
|
||||
TcpNoDelay = True
|
||||
|
||||
# Connection timeout in milliseconds.
|
||||
# Default 800
|
||||
ConnectionTimeout = 800
|
||||
|
||||
# Packet encryption.
|
||||
# By default packets sent or received are encrypted using the Blowfish algorithm.
|
||||
# Disabling this reduces the resources needed to process any packets transfered,
|
||||
|
@ -772,7 +772,6 @@ public class Config
|
||||
public static boolean PACKET_FLOOD_DROP;
|
||||
public static boolean PACKET_FLOOD_LOGGED;
|
||||
public static boolean TCP_NO_DELAY;
|
||||
public static int CONNECTION_TIMEOUT;
|
||||
public static boolean PACKET_ENCRYPTION;
|
||||
public static boolean FAILED_DECRYPTION_LOGGED;
|
||||
public static String DATABASE_DRIVER;
|
||||
@ -1380,7 +1379,6 @@ public class Config
|
||||
PACKET_FLOOD_DROP = serverConfig.getBoolean("PacketFloodDrop", false);
|
||||
PACKET_FLOOD_LOGGED = serverConfig.getBoolean("PacketFloodLogged", true);
|
||||
TCP_NO_DELAY = serverConfig.getBoolean("TcpNoDelay", true);
|
||||
CONNECTION_TIMEOUT = serverConfig.getInt("ConnectionTimeout", 800);
|
||||
PACKET_ENCRYPTION = serverConfig.getBoolean("PacketEncryption", false);
|
||||
FAILED_DECRYPTION_LOGGED = serverConfig.getBoolean("FailedDecryptionLogged", true);
|
||||
REQUEST_ID = serverConfig.getInt("RequestServerID", 0);
|
||||
|
@ -26,19 +26,15 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
// Iterate client pool.
|
||||
ITERATE: for (E client : _pool)
|
||||
{
|
||||
if (client.getChannel() == null)
|
||||
if (client.getSocket() == null)
|
||||
{
|
||||
_pool.remove(client);
|
||||
continue ITERATE;
|
||||
@ -72,16 +68,12 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Logger;
|
||||
@ -15,27 +16,30 @@ public class NetClient
|
||||
protected static final Logger LOGGER = Logger.getLogger(NetClient.class.getName());
|
||||
|
||||
private String _ip;
|
||||
private SocketChannel _channel;
|
||||
private Socket _socket;
|
||||
private InputStream _inputStream;
|
||||
private OutputStream _outputStream;
|
||||
private NetConfig _netConfig;
|
||||
private Queue<byte[]> _pendingPacketData;
|
||||
private ByteBuffer _pendingByteBuffer;
|
||||
private Queue<byte[]> _packetData;
|
||||
private byte[] _pendingData;
|
||||
private int _pendingPacketSize;
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
* @param channel
|
||||
* @param socket
|
||||
* @param netConfig
|
||||
*/
|
||||
public void init(SocketChannel channel, NetConfig netConfig)
|
||||
public void init(Socket socket, NetConfig netConfig)
|
||||
{
|
||||
_channel = channel;
|
||||
_socket = socket;
|
||||
_netConfig = netConfig;
|
||||
_pendingPacketData = new ConcurrentLinkedQueue<>();
|
||||
_packetData = new ConcurrentLinkedQueue<>();
|
||||
_ip = socket.getInetAddress().toString().substring(1); // Trim out /127.0.0.1
|
||||
|
||||
try
|
||||
{
|
||||
_ip = _channel.getRemoteAddress().toString();
|
||||
_ip = _ip.substring(1, _ip.lastIndexOf(':')); // Trim out /127.0.0.1:12345
|
||||
_inputStream = _socket.getInputStream();
|
||||
_outputStream = _socket.getOutputStream();
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
@ -64,26 +68,28 @@ public class NetClient
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if (_channel != null)
|
||||
if (_socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel.close();
|
||||
_channel = null;
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingPacketData != null)
|
||||
if (_packetData != null)
|
||||
{
|
||||
_pendingPacketData.clear();
|
||||
_packetData.clear();
|
||||
}
|
||||
|
||||
if (_pendingByteBuffer != null)
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingByteBuffer = null;
|
||||
_pendingData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +100,7 @@ public class NetClient
|
||||
public void addPacketData(byte[] data)
|
||||
{
|
||||
// Check packet flooding.
|
||||
final int size = _pendingPacketData.size();
|
||||
final int size = _packetData.size();
|
||||
if (size >= _netConfig.getPacketQueueLimit())
|
||||
{
|
||||
if (_netConfig.isPacketFloodDisconnect())
|
||||
@ -121,7 +127,7 @@ public class NetClient
|
||||
}
|
||||
|
||||
// Add to queue.
|
||||
_pendingPacketData.add(data);
|
||||
_packetData.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,24 +135,24 @@ public class NetClient
|
||||
*/
|
||||
public Queue<byte[]> getPacketData()
|
||||
{
|
||||
return _pendingPacketData;
|
||||
return _packetData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pending read ByteBuffer.
|
||||
* @return the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public ByteBuffer getPendingByteBuffer()
|
||||
public byte[] getPendingData()
|
||||
{
|
||||
return _pendingByteBuffer;
|
||||
return _pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pending read ByteBuffer.
|
||||
* @param pendingByteBuffer the pending read ByteBuffer.
|
||||
* Set the pending read <b>byte[]</b>.
|
||||
* @param pendingData the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public void setPendingByteBuffer(ByteBuffer pendingByteBuffer)
|
||||
public void setPendingData(byte[] pendingData)
|
||||
{
|
||||
_pendingByteBuffer = pendingByteBuffer;
|
||||
_pendingData = pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +172,36 @@ public class NetClient
|
||||
_pendingPacketSize = pendingPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet over the network using the default encryption.
|
||||
* @param packet The packet to send.
|
||||
*/
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((_socket == null) || !_socket.isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] sendableBytes = packet.getSendableBytes(getEncryption());
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
_outputStream.write(sendableBytes);
|
||||
_outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Encryption of this client.
|
||||
*/
|
||||
@ -175,11 +211,27 @@ public class NetClient
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SocketChannel of this client.
|
||||
* @return the Socket of this client.
|
||||
*/
|
||||
public SocketChannel getChannel()
|
||||
public Socket getSocket()
|
||||
{
|
||||
return _channel;
|
||||
return _socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the InputStream of this client.
|
||||
*/
|
||||
public InputStream getInputStream()
|
||||
{
|
||||
return _inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OutputStream of this client.
|
||||
*/
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,6 @@ public class NetConfig
|
||||
{
|
||||
private int _readPoolSize = 100;
|
||||
private int _executePoolSize = 50;
|
||||
private int _connectionTimeout = 800;
|
||||
private int _packetQueueLimit = 80;
|
||||
private boolean _packetFloodDisconnect = false;
|
||||
private boolean _packetFloodDrop = false;
|
||||
@ -50,23 +49,6 @@ public class NetConfig
|
||||
_executePoolSize = executePoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout until a connection is established.
|
||||
*/
|
||||
public int getConnectionTimeout()
|
||||
{
|
||||
return _connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout until a connection is established.
|
||||
* @param connectionTimeout
|
||||
*/
|
||||
public void setConnectionTimeout(int connectionTimeout)
|
||||
{
|
||||
_connectionTimeout = connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the packet queue limit of receivable packets.
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -105,30 +105,26 @@ public class NetServer<E extends NetClient>
|
||||
public void run()
|
||||
{
|
||||
// Create server and bind port.
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open())
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.setReuseAddress(true);
|
||||
server.bind(new InetSocketAddress(_hostname, _port));
|
||||
server.configureBlocking(false); // Non-blocking I/O.
|
||||
server.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Listen for new connections.
|
||||
LOGGER.info(_name + ": Listening on port " + _port + " for incoming connections.");
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
final SocketChannel channel = server.accept();
|
||||
if (channel != null)
|
||||
final Socket socket = server.accept();
|
||||
if (socket != null)
|
||||
{
|
||||
// Configure channel.
|
||||
channel.socket().setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
channel.socket().setSoTimeout(_netConfig.getConnectionTimeout());
|
||||
channel.configureBlocking(false); // Non-blocking I/O.
|
||||
socket.setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
socket.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Create client.
|
||||
final E client = _clientSupplier.get();
|
||||
client.init(channel, _netConfig);
|
||||
client.init(socket, _netConfig);
|
||||
|
||||
// Add to read pool.
|
||||
|
||||
@ -190,11 +186,7 @@ public class NetServer<E extends NetClient>
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly polling the channel.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -12,9 +11,9 @@ import java.util.Set;
|
||||
*/
|
||||
public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
private final ByteBuffer _sizeBuffer = ByteBuffer.allocate(2); // Reusable size buffer.
|
||||
private final ByteBuffer _pendingSizeBuffer = ByteBuffer.allocate(1); // Reusable pending size buffer.
|
||||
private final Set<E> _pool;
|
||||
private final byte[] _sizeBuffer = new byte[2]; // Reusable size buffer.
|
||||
private final byte[] _pendingSizeBuffer = new byte[1]; // Reusable pending size buffer.
|
||||
|
||||
public ReadThread(Set<E> pool)
|
||||
{
|
||||
@ -24,12 +23,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
@ -38,22 +33,29 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
try
|
||||
{
|
||||
final SocketChannel channel = client.getChannel();
|
||||
if (channel == null) // Unexpected disconnection?
|
||||
final InputStream inputStream = client.getInputStream();
|
||||
if (inputStream == null) // Unexpected disconnection?
|
||||
{
|
||||
// Null SocketChannel: client
|
||||
// Null InputStream: client
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending ByteBuffer.
|
||||
final ByteBuffer pendingByteBuffer = client.getPendingByteBuffer();
|
||||
if (pendingByteBuffer != null)
|
||||
// Continue when there are no bytes that can be read.
|
||||
if (inputStream.available() < 1)
|
||||
{
|
||||
// Allocate an additional ByteBuffer based on pending packet size.
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending byte array.
|
||||
final byte[] pendingData = client.getPendingData();
|
||||
if (pendingData != null)
|
||||
{
|
||||
// Allocate an additional byte array based on pending packet size.
|
||||
final int pendingPacketSize = client.getPendingPacketSize();
|
||||
final ByteBuffer additionalData = ByteBuffer.allocate(pendingPacketSize - pendingByteBuffer.position());
|
||||
switch (channel.read(additionalData))
|
||||
final byte[] additionalData = new byte[pendingPacketSize - pendingData.length];
|
||||
final int bytesRead = inputStream.read(additionalData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -70,15 +72,21 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Merge additional read data.
|
||||
pendingByteBuffer.put(pendingByteBuffer.position(), additionalData, 0, additionalData.position());
|
||||
pendingByteBuffer.position(pendingByteBuffer.position() + additionalData.position());
|
||||
final int currentSize = pendingData.length + bytesRead;
|
||||
final byte[] mergedData = new byte[currentSize];
|
||||
System.arraycopy(pendingData, 0, mergedData, 0, pendingData.length);
|
||||
System.arraycopy(additionalData, 0, mergedData, pendingData.length, bytesRead);
|
||||
|
||||
// Read was complete.
|
||||
if (pendingByteBuffer.position() >= pendingPacketSize)
|
||||
if (currentSize >= pendingPacketSize)
|
||||
{
|
||||
// Add packet data to client.
|
||||
client.addPacketData(pendingByteBuffer.array());
|
||||
client.setPendingByteBuffer(null);
|
||||
client.addPacketData(mergedData);
|
||||
client.setPendingData(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setPendingData(mergedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,8 +94,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Read incoming packet size (short).
|
||||
_sizeBuffer.clear();
|
||||
switch (channel.read(_sizeBuffer))
|
||||
boolean sizeRead = false;
|
||||
switch (inputStream.read(_sizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -103,8 +111,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Need to read two bytes to calculate size.
|
||||
case 1:
|
||||
{
|
||||
int attempt = 0; // Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: while ((attempt++ < 10) && (_sizeBuffer.position() < 2))
|
||||
// Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
// Wait for pending data.
|
||||
try
|
||||
@ -116,8 +124,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Try to read the missing extra byte.
|
||||
_pendingSizeBuffer.clear();
|
||||
switch (channel.read(_pendingSizeBuffer))
|
||||
_pendingSizeBuffer[0] = 0;
|
||||
switch (inputStream.read(_pendingSizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -133,14 +141,15 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Merge additional read byte.
|
||||
default:
|
||||
{
|
||||
_sizeBuffer.put(1, _pendingSizeBuffer, 0, 1);
|
||||
_sizeBuffer.position(2);
|
||||
_sizeBuffer[1] = _pendingSizeBuffer[0];
|
||||
sizeRead = true;
|
||||
break COMPLETE_SIZE_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read failed.
|
||||
if (_sizeBuffer.position() < 2)
|
||||
if (!sizeRead)
|
||||
{
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
@ -151,10 +160,11 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Read actual packet bytes.
|
||||
default:
|
||||
{
|
||||
// Allocate a new ByteBuffer based on packet size read.
|
||||
// Allocate a new byte array based on packet size read.
|
||||
final int packetSize = calculatePacketSize();
|
||||
final ByteBuffer packetByteBuffer = ByteBuffer.allocate(packetSize);
|
||||
switch (channel.read(packetByteBuffer))
|
||||
final byte[] packetData = new byte[packetSize];
|
||||
final int bytesRead = inputStream.read(packetData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -171,14 +181,16 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Read was not complete.
|
||||
if (packetByteBuffer.position() < packetSize)
|
||||
if (bytesRead < packetSize)
|
||||
{
|
||||
client.setPendingByteBuffer(packetByteBuffer);
|
||||
final byte[] pendindBytes = new byte[bytesRead];
|
||||
System.arraycopy(packetData, 0, pendindBytes, 0, bytesRead);
|
||||
client.setPendingData(pendindBytes);
|
||||
client.setPendingPacketSize(packetSize);
|
||||
}
|
||||
else // Add packet data to client.
|
||||
{
|
||||
client.addPacketData(packetByteBuffer.array());
|
||||
client.addPacketData(packetData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,24 +209,19 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePacketSize()
|
||||
{
|
||||
_sizeBuffer.rewind();
|
||||
return ((_sizeBuffer.get() & 0xff) | ((_sizeBuffer.get() << 8) & 0xffff)) - 2;
|
||||
return ((_sizeBuffer[0] & 0xff) | ((_sizeBuffer[1] << 8) & 0xffff)) - 2;
|
||||
}
|
||||
|
||||
private void onDisconnection(E client)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -13,7 +12,6 @@ public abstract class WritablePacket
|
||||
{
|
||||
private byte[] _data;
|
||||
private byte[] _sendableBytes;
|
||||
private ByteBuffer _byteBuffer;
|
||||
private int _position = 2; // Allocate space for size (max length 65535 - size header).
|
||||
|
||||
/**
|
||||
@ -270,38 +268,6 @@ public abstract class WritablePacket
|
||||
return _sendableBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public ByteBuffer getSendableByteBuffer()
|
||||
{
|
||||
return getSendableByteBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encryption if EncryptionInterface is used.
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public synchronized ByteBuffer getSendableByteBuffer(EncryptionInterface encryption)
|
||||
{
|
||||
// Generate sendable ByteBuffer.
|
||||
if ((_byteBuffer == null /* Not processed */) || (encryption != null /* Encryption can change */))
|
||||
{
|
||||
final byte[] bytes = getSendableBytes(encryption);
|
||||
if (bytes != null) // Data was actually written.
|
||||
{
|
||||
_byteBuffer = ByteBuffer.wrap(bytes);
|
||||
}
|
||||
}
|
||||
else // Rewind the buffer.
|
||||
{
|
||||
_byteBuffer.rewind();
|
||||
}
|
||||
|
||||
// Return the buffer.
|
||||
return _byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take in consideration that data must be written first.
|
||||
* @return The length of the data (includes size header).
|
||||
|
@ -474,7 +474,6 @@ public class GameServer
|
||||
server.getNetConfig().setPacketFloodDrop(Config.PACKET_FLOOD_DROP);
|
||||
server.getNetConfig().setPacketFloodLogged(Config.PACKET_FLOOD_LOGGED);
|
||||
server.getNetConfig().setTcpNoDelay(Config.TCP_NO_DELAY);
|
||||
server.getNetConfig().setConnectionTimeout(Config.CONNECTION_TIMEOUT);
|
||||
server.getNetConfig().setFailedDecryptionLogged(Config.FAILED_DECRYPTION_LOGGED);
|
||||
server.start();
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.l2jmobius.gameserver.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
@ -224,30 +222,11 @@ public class GameClient extends NetClient
|
||||
_pendingPackets.add(packet);
|
||||
synchronized (_pendingPackets)
|
||||
{
|
||||
final SocketChannel channel = getChannel();
|
||||
if ((channel != null) && channel.isConnected())
|
||||
{
|
||||
final ServerPacket sendPacket = _pendingPackets.poll();
|
||||
final ByteBuffer byteBuffer = sendPacket.getSendableByteBuffer(_encryption);
|
||||
if (byteBuffer != null)
|
||||
{
|
||||
// Send the packet data.
|
||||
try
|
||||
{
|
||||
// Loop while there are remaining bytes in the buffer.
|
||||
while (byteBuffer.hasRemaining())
|
||||
{
|
||||
channel.write(byteBuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
// Run packet implementation.
|
||||
sendPacket.run(_player);
|
||||
}
|
||||
}
|
||||
// Send the packet data.
|
||||
super.sendPacket(packet);
|
||||
|
||||
// Run packet implementation.
|
||||
packet.run(_player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.l2jmobius.loginserver.network;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -145,20 +147,26 @@ public class LoginClient extends NetClient
|
||||
return _connectionStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((packet == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Write into the channel.
|
||||
if ((getChannel() != null) && getChannel().isConnected())
|
||||
final Socket socket = getSocket();
|
||||
if ((socket != null) && socket.isConnected())
|
||||
{
|
||||
final byte[] sendableBytes = packet.getSendableBytes();
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the packet data.
|
||||
getChannel().write(packet.getSendableByteBuffer());
|
||||
final OutputStream outputStream = getOutputStream();
|
||||
synchronized (this)
|
||||
{
|
||||
outputStream.write(sendableBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
|
@ -63,10 +63,6 @@ PacketFloodLogged = True
|
||||
# Default: True (disabled)
|
||||
TcpNoDelay = True
|
||||
|
||||
# Connection timeout in milliseconds.
|
||||
# Default 800
|
||||
ConnectionTimeout = 800
|
||||
|
||||
# Packet encryption.
|
||||
# By default packets sent or received are encrypted using the Blowfish algorithm.
|
||||
# Disabling this reduces the resources needed to process any packets transfered,
|
||||
|
@ -772,7 +772,6 @@ public class Config
|
||||
public static boolean PACKET_FLOOD_DROP;
|
||||
public static boolean PACKET_FLOOD_LOGGED;
|
||||
public static boolean TCP_NO_DELAY;
|
||||
public static int CONNECTION_TIMEOUT;
|
||||
public static boolean PACKET_ENCRYPTION;
|
||||
public static boolean FAILED_DECRYPTION_LOGGED;
|
||||
public static String DATABASE_DRIVER;
|
||||
@ -1387,7 +1386,6 @@ public class Config
|
||||
PACKET_FLOOD_DROP = serverConfig.getBoolean("PacketFloodDrop", false);
|
||||
PACKET_FLOOD_LOGGED = serverConfig.getBoolean("PacketFloodLogged", true);
|
||||
TCP_NO_DELAY = serverConfig.getBoolean("TcpNoDelay", true);
|
||||
CONNECTION_TIMEOUT = serverConfig.getInt("ConnectionTimeout", 800);
|
||||
PACKET_ENCRYPTION = serverConfig.getBoolean("PacketEncryption", false);
|
||||
FAILED_DECRYPTION_LOGGED = serverConfig.getBoolean("FailedDecryptionLogged", true);
|
||||
REQUEST_ID = serverConfig.getInt("RequestServerID", 0);
|
||||
|
@ -26,19 +26,15 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
// Iterate client pool.
|
||||
ITERATE: for (E client : _pool)
|
||||
{
|
||||
if (client.getChannel() == null)
|
||||
if (client.getSocket() == null)
|
||||
{
|
||||
_pool.remove(client);
|
||||
continue ITERATE;
|
||||
@ -72,16 +68,12 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Logger;
|
||||
@ -15,27 +16,30 @@ public class NetClient
|
||||
protected static final Logger LOGGER = Logger.getLogger(NetClient.class.getName());
|
||||
|
||||
private String _ip;
|
||||
private SocketChannel _channel;
|
||||
private Socket _socket;
|
||||
private InputStream _inputStream;
|
||||
private OutputStream _outputStream;
|
||||
private NetConfig _netConfig;
|
||||
private Queue<byte[]> _pendingPacketData;
|
||||
private ByteBuffer _pendingByteBuffer;
|
||||
private Queue<byte[]> _packetData;
|
||||
private byte[] _pendingData;
|
||||
private int _pendingPacketSize;
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
* @param channel
|
||||
* @param socket
|
||||
* @param netConfig
|
||||
*/
|
||||
public void init(SocketChannel channel, NetConfig netConfig)
|
||||
public void init(Socket socket, NetConfig netConfig)
|
||||
{
|
||||
_channel = channel;
|
||||
_socket = socket;
|
||||
_netConfig = netConfig;
|
||||
_pendingPacketData = new ConcurrentLinkedQueue<>();
|
||||
_packetData = new ConcurrentLinkedQueue<>();
|
||||
_ip = socket.getInetAddress().toString().substring(1); // Trim out /127.0.0.1
|
||||
|
||||
try
|
||||
{
|
||||
_ip = _channel.getRemoteAddress().toString();
|
||||
_ip = _ip.substring(1, _ip.lastIndexOf(':')); // Trim out /127.0.0.1:12345
|
||||
_inputStream = _socket.getInputStream();
|
||||
_outputStream = _socket.getOutputStream();
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
@ -64,26 +68,28 @@ public class NetClient
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if (_channel != null)
|
||||
if (_socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel.close();
|
||||
_channel = null;
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingPacketData != null)
|
||||
if (_packetData != null)
|
||||
{
|
||||
_pendingPacketData.clear();
|
||||
_packetData.clear();
|
||||
}
|
||||
|
||||
if (_pendingByteBuffer != null)
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingByteBuffer = null;
|
||||
_pendingData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +100,7 @@ public class NetClient
|
||||
public void addPacketData(byte[] data)
|
||||
{
|
||||
// Check packet flooding.
|
||||
final int size = _pendingPacketData.size();
|
||||
final int size = _packetData.size();
|
||||
if (size >= _netConfig.getPacketQueueLimit())
|
||||
{
|
||||
if (_netConfig.isPacketFloodDisconnect())
|
||||
@ -121,7 +127,7 @@ public class NetClient
|
||||
}
|
||||
|
||||
// Add to queue.
|
||||
_pendingPacketData.add(data);
|
||||
_packetData.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,24 +135,24 @@ public class NetClient
|
||||
*/
|
||||
public Queue<byte[]> getPacketData()
|
||||
{
|
||||
return _pendingPacketData;
|
||||
return _packetData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pending read ByteBuffer.
|
||||
* @return the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public ByteBuffer getPendingByteBuffer()
|
||||
public byte[] getPendingData()
|
||||
{
|
||||
return _pendingByteBuffer;
|
||||
return _pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pending read ByteBuffer.
|
||||
* @param pendingByteBuffer the pending read ByteBuffer.
|
||||
* Set the pending read <b>byte[]</b>.
|
||||
* @param pendingData the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public void setPendingByteBuffer(ByteBuffer pendingByteBuffer)
|
||||
public void setPendingData(byte[] pendingData)
|
||||
{
|
||||
_pendingByteBuffer = pendingByteBuffer;
|
||||
_pendingData = pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +172,36 @@ public class NetClient
|
||||
_pendingPacketSize = pendingPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet over the network using the default encryption.
|
||||
* @param packet The packet to send.
|
||||
*/
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((_socket == null) || !_socket.isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] sendableBytes = packet.getSendableBytes(getEncryption());
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
_outputStream.write(sendableBytes);
|
||||
_outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Encryption of this client.
|
||||
*/
|
||||
@ -175,11 +211,27 @@ public class NetClient
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SocketChannel of this client.
|
||||
* @return the Socket of this client.
|
||||
*/
|
||||
public SocketChannel getChannel()
|
||||
public Socket getSocket()
|
||||
{
|
||||
return _channel;
|
||||
return _socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the InputStream of this client.
|
||||
*/
|
||||
public InputStream getInputStream()
|
||||
{
|
||||
return _inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OutputStream of this client.
|
||||
*/
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,6 @@ public class NetConfig
|
||||
{
|
||||
private int _readPoolSize = 100;
|
||||
private int _executePoolSize = 50;
|
||||
private int _connectionTimeout = 800;
|
||||
private int _packetQueueLimit = 80;
|
||||
private boolean _packetFloodDisconnect = false;
|
||||
private boolean _packetFloodDrop = false;
|
||||
@ -50,23 +49,6 @@ public class NetConfig
|
||||
_executePoolSize = executePoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout until a connection is established.
|
||||
*/
|
||||
public int getConnectionTimeout()
|
||||
{
|
||||
return _connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout until a connection is established.
|
||||
* @param connectionTimeout
|
||||
*/
|
||||
public void setConnectionTimeout(int connectionTimeout)
|
||||
{
|
||||
_connectionTimeout = connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the packet queue limit of receivable packets.
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -105,30 +105,26 @@ public class NetServer<E extends NetClient>
|
||||
public void run()
|
||||
{
|
||||
// Create server and bind port.
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open())
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.setReuseAddress(true);
|
||||
server.bind(new InetSocketAddress(_hostname, _port));
|
||||
server.configureBlocking(false); // Non-blocking I/O.
|
||||
server.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Listen for new connections.
|
||||
LOGGER.info(_name + ": Listening on port " + _port + " for incoming connections.");
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
final SocketChannel channel = server.accept();
|
||||
if (channel != null)
|
||||
final Socket socket = server.accept();
|
||||
if (socket != null)
|
||||
{
|
||||
// Configure channel.
|
||||
channel.socket().setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
channel.socket().setSoTimeout(_netConfig.getConnectionTimeout());
|
||||
channel.configureBlocking(false); // Non-blocking I/O.
|
||||
socket.setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
socket.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Create client.
|
||||
final E client = _clientSupplier.get();
|
||||
client.init(channel, _netConfig);
|
||||
client.init(socket, _netConfig);
|
||||
|
||||
// Add to read pool.
|
||||
|
||||
@ -190,11 +186,7 @@ public class NetServer<E extends NetClient>
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly polling the channel.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -12,9 +11,9 @@ import java.util.Set;
|
||||
*/
|
||||
public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
private final ByteBuffer _sizeBuffer = ByteBuffer.allocate(2); // Reusable size buffer.
|
||||
private final ByteBuffer _pendingSizeBuffer = ByteBuffer.allocate(1); // Reusable pending size buffer.
|
||||
private final Set<E> _pool;
|
||||
private final byte[] _sizeBuffer = new byte[2]; // Reusable size buffer.
|
||||
private final byte[] _pendingSizeBuffer = new byte[1]; // Reusable pending size buffer.
|
||||
|
||||
public ReadThread(Set<E> pool)
|
||||
{
|
||||
@ -24,12 +23,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
@ -38,22 +33,29 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
try
|
||||
{
|
||||
final SocketChannel channel = client.getChannel();
|
||||
if (channel == null) // Unexpected disconnection?
|
||||
final InputStream inputStream = client.getInputStream();
|
||||
if (inputStream == null) // Unexpected disconnection?
|
||||
{
|
||||
// Null SocketChannel: client
|
||||
// Null InputStream: client
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending ByteBuffer.
|
||||
final ByteBuffer pendingByteBuffer = client.getPendingByteBuffer();
|
||||
if (pendingByteBuffer != null)
|
||||
// Continue when there are no bytes that can be read.
|
||||
if (inputStream.available() < 1)
|
||||
{
|
||||
// Allocate an additional ByteBuffer based on pending packet size.
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending byte array.
|
||||
final byte[] pendingData = client.getPendingData();
|
||||
if (pendingData != null)
|
||||
{
|
||||
// Allocate an additional byte array based on pending packet size.
|
||||
final int pendingPacketSize = client.getPendingPacketSize();
|
||||
final ByteBuffer additionalData = ByteBuffer.allocate(pendingPacketSize - pendingByteBuffer.position());
|
||||
switch (channel.read(additionalData))
|
||||
final byte[] additionalData = new byte[pendingPacketSize - pendingData.length];
|
||||
final int bytesRead = inputStream.read(additionalData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -70,15 +72,21 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Merge additional read data.
|
||||
pendingByteBuffer.put(pendingByteBuffer.position(), additionalData, 0, additionalData.position());
|
||||
pendingByteBuffer.position(pendingByteBuffer.position() + additionalData.position());
|
||||
final int currentSize = pendingData.length + bytesRead;
|
||||
final byte[] mergedData = new byte[currentSize];
|
||||
System.arraycopy(pendingData, 0, mergedData, 0, pendingData.length);
|
||||
System.arraycopy(additionalData, 0, mergedData, pendingData.length, bytesRead);
|
||||
|
||||
// Read was complete.
|
||||
if (pendingByteBuffer.position() >= pendingPacketSize)
|
||||
if (currentSize >= pendingPacketSize)
|
||||
{
|
||||
// Add packet data to client.
|
||||
client.addPacketData(pendingByteBuffer.array());
|
||||
client.setPendingByteBuffer(null);
|
||||
client.addPacketData(mergedData);
|
||||
client.setPendingData(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setPendingData(mergedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,8 +94,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Read incoming packet size (short).
|
||||
_sizeBuffer.clear();
|
||||
switch (channel.read(_sizeBuffer))
|
||||
boolean sizeRead = false;
|
||||
switch (inputStream.read(_sizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -103,8 +111,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Need to read two bytes to calculate size.
|
||||
case 1:
|
||||
{
|
||||
int attempt = 0; // Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: while ((attempt++ < 10) && (_sizeBuffer.position() < 2))
|
||||
// Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
// Wait for pending data.
|
||||
try
|
||||
@ -116,8 +124,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Try to read the missing extra byte.
|
||||
_pendingSizeBuffer.clear();
|
||||
switch (channel.read(_pendingSizeBuffer))
|
||||
_pendingSizeBuffer[0] = 0;
|
||||
switch (inputStream.read(_pendingSizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -133,14 +141,15 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Merge additional read byte.
|
||||
default:
|
||||
{
|
||||
_sizeBuffer.put(1, _pendingSizeBuffer, 0, 1);
|
||||
_sizeBuffer.position(2);
|
||||
_sizeBuffer[1] = _pendingSizeBuffer[0];
|
||||
sizeRead = true;
|
||||
break COMPLETE_SIZE_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read failed.
|
||||
if (_sizeBuffer.position() < 2)
|
||||
if (!sizeRead)
|
||||
{
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
@ -151,10 +160,11 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Read actual packet bytes.
|
||||
default:
|
||||
{
|
||||
// Allocate a new ByteBuffer based on packet size read.
|
||||
// Allocate a new byte array based on packet size read.
|
||||
final int packetSize = calculatePacketSize();
|
||||
final ByteBuffer packetByteBuffer = ByteBuffer.allocate(packetSize);
|
||||
switch (channel.read(packetByteBuffer))
|
||||
final byte[] packetData = new byte[packetSize];
|
||||
final int bytesRead = inputStream.read(packetData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -171,14 +181,16 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Read was not complete.
|
||||
if (packetByteBuffer.position() < packetSize)
|
||||
if (bytesRead < packetSize)
|
||||
{
|
||||
client.setPendingByteBuffer(packetByteBuffer);
|
||||
final byte[] pendindBytes = new byte[bytesRead];
|
||||
System.arraycopy(packetData, 0, pendindBytes, 0, bytesRead);
|
||||
client.setPendingData(pendindBytes);
|
||||
client.setPendingPacketSize(packetSize);
|
||||
}
|
||||
else // Add packet data to client.
|
||||
{
|
||||
client.addPacketData(packetByteBuffer.array());
|
||||
client.addPacketData(packetData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,24 +209,19 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePacketSize()
|
||||
{
|
||||
_sizeBuffer.rewind();
|
||||
return ((_sizeBuffer.get() & 0xff) | ((_sizeBuffer.get() << 8) & 0xffff)) - 2;
|
||||
return ((_sizeBuffer[0] & 0xff) | ((_sizeBuffer[1] << 8) & 0xffff)) - 2;
|
||||
}
|
||||
|
||||
private void onDisconnection(E client)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -13,7 +12,6 @@ public abstract class WritablePacket
|
||||
{
|
||||
private byte[] _data;
|
||||
private byte[] _sendableBytes;
|
||||
private ByteBuffer _byteBuffer;
|
||||
private int _position = 2; // Allocate space for size (max length 65535 - size header).
|
||||
|
||||
/**
|
||||
@ -270,38 +268,6 @@ public abstract class WritablePacket
|
||||
return _sendableBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public ByteBuffer getSendableByteBuffer()
|
||||
{
|
||||
return getSendableByteBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encryption if EncryptionInterface is used.
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public synchronized ByteBuffer getSendableByteBuffer(EncryptionInterface encryption)
|
||||
{
|
||||
// Generate sendable ByteBuffer.
|
||||
if ((_byteBuffer == null /* Not processed */) || (encryption != null /* Encryption can change */))
|
||||
{
|
||||
final byte[] bytes = getSendableBytes(encryption);
|
||||
if (bytes != null) // Data was actually written.
|
||||
{
|
||||
_byteBuffer = ByteBuffer.wrap(bytes);
|
||||
}
|
||||
}
|
||||
else // Rewind the buffer.
|
||||
{
|
||||
_byteBuffer.rewind();
|
||||
}
|
||||
|
||||
// Return the buffer.
|
||||
return _byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take in consideration that data must be written first.
|
||||
* @return The length of the data (includes size header).
|
||||
|
@ -474,7 +474,6 @@ public class GameServer
|
||||
server.getNetConfig().setPacketFloodDrop(Config.PACKET_FLOOD_DROP);
|
||||
server.getNetConfig().setPacketFloodLogged(Config.PACKET_FLOOD_LOGGED);
|
||||
server.getNetConfig().setTcpNoDelay(Config.TCP_NO_DELAY);
|
||||
server.getNetConfig().setConnectionTimeout(Config.CONNECTION_TIMEOUT);
|
||||
server.getNetConfig().setFailedDecryptionLogged(Config.FAILED_DECRYPTION_LOGGED);
|
||||
server.start();
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.l2jmobius.gameserver.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
@ -224,30 +222,11 @@ public class GameClient extends NetClient
|
||||
_pendingPackets.add(packet);
|
||||
synchronized (_pendingPackets)
|
||||
{
|
||||
final SocketChannel channel = getChannel();
|
||||
if ((channel != null) && channel.isConnected())
|
||||
{
|
||||
final ServerPacket sendPacket = _pendingPackets.poll();
|
||||
final ByteBuffer byteBuffer = sendPacket.getSendableByteBuffer(_encryption);
|
||||
if (byteBuffer != null)
|
||||
{
|
||||
// Send the packet data.
|
||||
try
|
||||
{
|
||||
// Loop while there are remaining bytes in the buffer.
|
||||
while (byteBuffer.hasRemaining())
|
||||
{
|
||||
channel.write(byteBuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
// Run packet implementation.
|
||||
sendPacket.run(_player);
|
||||
}
|
||||
}
|
||||
// Send the packet data.
|
||||
super.sendPacket(packet);
|
||||
|
||||
// Run packet implementation.
|
||||
packet.run(_player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.l2jmobius.loginserver.network;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -145,20 +147,26 @@ public class LoginClient extends NetClient
|
||||
return _connectionStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((packet == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Write into the channel.
|
||||
if ((getChannel() != null) && getChannel().isConnected())
|
||||
final Socket socket = getSocket();
|
||||
if ((socket != null) && socket.isConnected())
|
||||
{
|
||||
final byte[] sendableBytes = packet.getSendableBytes();
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the packet data.
|
||||
getChannel().write(packet.getSendableByteBuffer());
|
||||
final OutputStream outputStream = getOutputStream();
|
||||
synchronized (this)
|
||||
{
|
||||
outputStream.write(sendableBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
|
@ -63,10 +63,6 @@ PacketFloodLogged = True
|
||||
# Default: True (disabled)
|
||||
TcpNoDelay = True
|
||||
|
||||
# Connection timeout in milliseconds.
|
||||
# Default 800
|
||||
ConnectionTimeout = 800
|
||||
|
||||
# Packet encryption.
|
||||
# By default packets sent or received are encrypted using the Blowfish algorithm.
|
||||
# Disabling this reduces the resources needed to process any packets transfered,
|
||||
|
@ -773,7 +773,6 @@ public class Config
|
||||
public static boolean PACKET_FLOOD_DROP;
|
||||
public static boolean PACKET_FLOOD_LOGGED;
|
||||
public static boolean TCP_NO_DELAY;
|
||||
public static int CONNECTION_TIMEOUT;
|
||||
public static boolean PACKET_ENCRYPTION;
|
||||
public static boolean FAILED_DECRYPTION_LOGGED;
|
||||
public static String DATABASE_DRIVER;
|
||||
@ -1408,7 +1407,6 @@ public class Config
|
||||
PACKET_FLOOD_DROP = serverConfig.getBoolean("PacketFloodDrop", false);
|
||||
PACKET_FLOOD_LOGGED = serverConfig.getBoolean("PacketFloodLogged", true);
|
||||
TCP_NO_DELAY = serverConfig.getBoolean("TcpNoDelay", true);
|
||||
CONNECTION_TIMEOUT = serverConfig.getInt("ConnectionTimeout", 800);
|
||||
PACKET_ENCRYPTION = serverConfig.getBoolean("PacketEncryption", false);
|
||||
FAILED_DECRYPTION_LOGGED = serverConfig.getBoolean("FailedDecryptionLogged", true);
|
||||
REQUEST_ID = serverConfig.getInt("RequestServerID", 0);
|
||||
|
@ -26,19 +26,15 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
// Iterate client pool.
|
||||
ITERATE: for (E client : _pool)
|
||||
{
|
||||
if (client.getChannel() == null)
|
||||
if (client.getSocket() == null)
|
||||
{
|
||||
_pool.remove(client);
|
||||
continue ITERATE;
|
||||
@ -72,16 +68,12 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Logger;
|
||||
@ -15,27 +16,30 @@ public class NetClient
|
||||
protected static final Logger LOGGER = Logger.getLogger(NetClient.class.getName());
|
||||
|
||||
private String _ip;
|
||||
private SocketChannel _channel;
|
||||
private Socket _socket;
|
||||
private InputStream _inputStream;
|
||||
private OutputStream _outputStream;
|
||||
private NetConfig _netConfig;
|
||||
private Queue<byte[]> _pendingPacketData;
|
||||
private ByteBuffer _pendingByteBuffer;
|
||||
private Queue<byte[]> _packetData;
|
||||
private byte[] _pendingData;
|
||||
private int _pendingPacketSize;
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
* @param channel
|
||||
* @param socket
|
||||
* @param netConfig
|
||||
*/
|
||||
public void init(SocketChannel channel, NetConfig netConfig)
|
||||
public void init(Socket socket, NetConfig netConfig)
|
||||
{
|
||||
_channel = channel;
|
||||
_socket = socket;
|
||||
_netConfig = netConfig;
|
||||
_pendingPacketData = new ConcurrentLinkedQueue<>();
|
||||
_packetData = new ConcurrentLinkedQueue<>();
|
||||
_ip = socket.getInetAddress().toString().substring(1); // Trim out /127.0.0.1
|
||||
|
||||
try
|
||||
{
|
||||
_ip = _channel.getRemoteAddress().toString();
|
||||
_ip = _ip.substring(1, _ip.lastIndexOf(':')); // Trim out /127.0.0.1:12345
|
||||
_inputStream = _socket.getInputStream();
|
||||
_outputStream = _socket.getOutputStream();
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
@ -64,26 +68,28 @@ public class NetClient
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if (_channel != null)
|
||||
if (_socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel.close();
|
||||
_channel = null;
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingPacketData != null)
|
||||
if (_packetData != null)
|
||||
{
|
||||
_pendingPacketData.clear();
|
||||
_packetData.clear();
|
||||
}
|
||||
|
||||
if (_pendingByteBuffer != null)
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingByteBuffer = null;
|
||||
_pendingData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +100,7 @@ public class NetClient
|
||||
public void addPacketData(byte[] data)
|
||||
{
|
||||
// Check packet flooding.
|
||||
final int size = _pendingPacketData.size();
|
||||
final int size = _packetData.size();
|
||||
if (size >= _netConfig.getPacketQueueLimit())
|
||||
{
|
||||
if (_netConfig.isPacketFloodDisconnect())
|
||||
@ -121,7 +127,7 @@ public class NetClient
|
||||
}
|
||||
|
||||
// Add to queue.
|
||||
_pendingPacketData.add(data);
|
||||
_packetData.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,24 +135,24 @@ public class NetClient
|
||||
*/
|
||||
public Queue<byte[]> getPacketData()
|
||||
{
|
||||
return _pendingPacketData;
|
||||
return _packetData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pending read ByteBuffer.
|
||||
* @return the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public ByteBuffer getPendingByteBuffer()
|
||||
public byte[] getPendingData()
|
||||
{
|
||||
return _pendingByteBuffer;
|
||||
return _pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pending read ByteBuffer.
|
||||
* @param pendingByteBuffer the pending read ByteBuffer.
|
||||
* Set the pending read <b>byte[]</b>.
|
||||
* @param pendingData the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public void setPendingByteBuffer(ByteBuffer pendingByteBuffer)
|
||||
public void setPendingData(byte[] pendingData)
|
||||
{
|
||||
_pendingByteBuffer = pendingByteBuffer;
|
||||
_pendingData = pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +172,36 @@ public class NetClient
|
||||
_pendingPacketSize = pendingPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet over the network using the default encryption.
|
||||
* @param packet The packet to send.
|
||||
*/
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((_socket == null) || !_socket.isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] sendableBytes = packet.getSendableBytes(getEncryption());
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
_outputStream.write(sendableBytes);
|
||||
_outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Encryption of this client.
|
||||
*/
|
||||
@ -175,11 +211,27 @@ public class NetClient
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SocketChannel of this client.
|
||||
* @return the Socket of this client.
|
||||
*/
|
||||
public SocketChannel getChannel()
|
||||
public Socket getSocket()
|
||||
{
|
||||
return _channel;
|
||||
return _socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the InputStream of this client.
|
||||
*/
|
||||
public InputStream getInputStream()
|
||||
{
|
||||
return _inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OutputStream of this client.
|
||||
*/
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,6 @@ public class NetConfig
|
||||
{
|
||||
private int _readPoolSize = 100;
|
||||
private int _executePoolSize = 50;
|
||||
private int _connectionTimeout = 800;
|
||||
private int _packetQueueLimit = 80;
|
||||
private boolean _packetFloodDisconnect = false;
|
||||
private boolean _packetFloodDrop = false;
|
||||
@ -50,23 +49,6 @@ public class NetConfig
|
||||
_executePoolSize = executePoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout until a connection is established.
|
||||
*/
|
||||
public int getConnectionTimeout()
|
||||
{
|
||||
return _connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout until a connection is established.
|
||||
* @param connectionTimeout
|
||||
*/
|
||||
public void setConnectionTimeout(int connectionTimeout)
|
||||
{
|
||||
_connectionTimeout = connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the packet queue limit of receivable packets.
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -105,30 +105,26 @@ public class NetServer<E extends NetClient>
|
||||
public void run()
|
||||
{
|
||||
// Create server and bind port.
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open())
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.setReuseAddress(true);
|
||||
server.bind(new InetSocketAddress(_hostname, _port));
|
||||
server.configureBlocking(false); // Non-blocking I/O.
|
||||
server.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Listen for new connections.
|
||||
LOGGER.info(_name + ": Listening on port " + _port + " for incoming connections.");
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
final SocketChannel channel = server.accept();
|
||||
if (channel != null)
|
||||
final Socket socket = server.accept();
|
||||
if (socket != null)
|
||||
{
|
||||
// Configure channel.
|
||||
channel.socket().setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
channel.socket().setSoTimeout(_netConfig.getConnectionTimeout());
|
||||
channel.configureBlocking(false); // Non-blocking I/O.
|
||||
socket.setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
socket.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Create client.
|
||||
final E client = _clientSupplier.get();
|
||||
client.init(channel, _netConfig);
|
||||
client.init(socket, _netConfig);
|
||||
|
||||
// Add to read pool.
|
||||
|
||||
@ -190,11 +186,7 @@ public class NetServer<E extends NetClient>
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly polling the channel.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -12,9 +11,9 @@ import java.util.Set;
|
||||
*/
|
||||
public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
private final ByteBuffer _sizeBuffer = ByteBuffer.allocate(2); // Reusable size buffer.
|
||||
private final ByteBuffer _pendingSizeBuffer = ByteBuffer.allocate(1); // Reusable pending size buffer.
|
||||
private final Set<E> _pool;
|
||||
private final byte[] _sizeBuffer = new byte[2]; // Reusable size buffer.
|
||||
private final byte[] _pendingSizeBuffer = new byte[1]; // Reusable pending size buffer.
|
||||
|
||||
public ReadThread(Set<E> pool)
|
||||
{
|
||||
@ -24,12 +23,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
@ -38,22 +33,29 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
try
|
||||
{
|
||||
final SocketChannel channel = client.getChannel();
|
||||
if (channel == null) // Unexpected disconnection?
|
||||
final InputStream inputStream = client.getInputStream();
|
||||
if (inputStream == null) // Unexpected disconnection?
|
||||
{
|
||||
// Null SocketChannel: client
|
||||
// Null InputStream: client
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending ByteBuffer.
|
||||
final ByteBuffer pendingByteBuffer = client.getPendingByteBuffer();
|
||||
if (pendingByteBuffer != null)
|
||||
// Continue when there are no bytes that can be read.
|
||||
if (inputStream.available() < 1)
|
||||
{
|
||||
// Allocate an additional ByteBuffer based on pending packet size.
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending byte array.
|
||||
final byte[] pendingData = client.getPendingData();
|
||||
if (pendingData != null)
|
||||
{
|
||||
// Allocate an additional byte array based on pending packet size.
|
||||
final int pendingPacketSize = client.getPendingPacketSize();
|
||||
final ByteBuffer additionalData = ByteBuffer.allocate(pendingPacketSize - pendingByteBuffer.position());
|
||||
switch (channel.read(additionalData))
|
||||
final byte[] additionalData = new byte[pendingPacketSize - pendingData.length];
|
||||
final int bytesRead = inputStream.read(additionalData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -70,15 +72,21 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Merge additional read data.
|
||||
pendingByteBuffer.put(pendingByteBuffer.position(), additionalData, 0, additionalData.position());
|
||||
pendingByteBuffer.position(pendingByteBuffer.position() + additionalData.position());
|
||||
final int currentSize = pendingData.length + bytesRead;
|
||||
final byte[] mergedData = new byte[currentSize];
|
||||
System.arraycopy(pendingData, 0, mergedData, 0, pendingData.length);
|
||||
System.arraycopy(additionalData, 0, mergedData, pendingData.length, bytesRead);
|
||||
|
||||
// Read was complete.
|
||||
if (pendingByteBuffer.position() >= pendingPacketSize)
|
||||
if (currentSize >= pendingPacketSize)
|
||||
{
|
||||
// Add packet data to client.
|
||||
client.addPacketData(pendingByteBuffer.array());
|
||||
client.setPendingByteBuffer(null);
|
||||
client.addPacketData(mergedData);
|
||||
client.setPendingData(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setPendingData(mergedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,8 +94,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Read incoming packet size (short).
|
||||
_sizeBuffer.clear();
|
||||
switch (channel.read(_sizeBuffer))
|
||||
boolean sizeRead = false;
|
||||
switch (inputStream.read(_sizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -103,8 +111,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Need to read two bytes to calculate size.
|
||||
case 1:
|
||||
{
|
||||
int attempt = 0; // Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: while ((attempt++ < 10) && (_sizeBuffer.position() < 2))
|
||||
// Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
// Wait for pending data.
|
||||
try
|
||||
@ -116,8 +124,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Try to read the missing extra byte.
|
||||
_pendingSizeBuffer.clear();
|
||||
switch (channel.read(_pendingSizeBuffer))
|
||||
_pendingSizeBuffer[0] = 0;
|
||||
switch (inputStream.read(_pendingSizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -133,14 +141,15 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Merge additional read byte.
|
||||
default:
|
||||
{
|
||||
_sizeBuffer.put(1, _pendingSizeBuffer, 0, 1);
|
||||
_sizeBuffer.position(2);
|
||||
_sizeBuffer[1] = _pendingSizeBuffer[0];
|
||||
sizeRead = true;
|
||||
break COMPLETE_SIZE_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read failed.
|
||||
if (_sizeBuffer.position() < 2)
|
||||
if (!sizeRead)
|
||||
{
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
@ -151,10 +160,11 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Read actual packet bytes.
|
||||
default:
|
||||
{
|
||||
// Allocate a new ByteBuffer based on packet size read.
|
||||
// Allocate a new byte array based on packet size read.
|
||||
final int packetSize = calculatePacketSize();
|
||||
final ByteBuffer packetByteBuffer = ByteBuffer.allocate(packetSize);
|
||||
switch (channel.read(packetByteBuffer))
|
||||
final byte[] packetData = new byte[packetSize];
|
||||
final int bytesRead = inputStream.read(packetData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -171,14 +181,16 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Read was not complete.
|
||||
if (packetByteBuffer.position() < packetSize)
|
||||
if (bytesRead < packetSize)
|
||||
{
|
||||
client.setPendingByteBuffer(packetByteBuffer);
|
||||
final byte[] pendindBytes = new byte[bytesRead];
|
||||
System.arraycopy(packetData, 0, pendindBytes, 0, bytesRead);
|
||||
client.setPendingData(pendindBytes);
|
||||
client.setPendingPacketSize(packetSize);
|
||||
}
|
||||
else // Add packet data to client.
|
||||
{
|
||||
client.addPacketData(packetByteBuffer.array());
|
||||
client.addPacketData(packetData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,24 +209,19 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePacketSize()
|
||||
{
|
||||
_sizeBuffer.rewind();
|
||||
return ((_sizeBuffer.get() & 0xff) | ((_sizeBuffer.get() << 8) & 0xffff)) - 2;
|
||||
return ((_sizeBuffer[0] & 0xff) | ((_sizeBuffer[1] << 8) & 0xffff)) - 2;
|
||||
}
|
||||
|
||||
private void onDisconnection(E client)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -13,7 +12,6 @@ public abstract class WritablePacket
|
||||
{
|
||||
private byte[] _data;
|
||||
private byte[] _sendableBytes;
|
||||
private ByteBuffer _byteBuffer;
|
||||
private int _position = 2; // Allocate space for size (max length 65535 - size header).
|
||||
|
||||
/**
|
||||
@ -270,38 +268,6 @@ public abstract class WritablePacket
|
||||
return _sendableBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public ByteBuffer getSendableByteBuffer()
|
||||
{
|
||||
return getSendableByteBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encryption if EncryptionInterface is used.
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public synchronized ByteBuffer getSendableByteBuffer(EncryptionInterface encryption)
|
||||
{
|
||||
// Generate sendable ByteBuffer.
|
||||
if ((_byteBuffer == null /* Not processed */) || (encryption != null /* Encryption can change */))
|
||||
{
|
||||
final byte[] bytes = getSendableBytes(encryption);
|
||||
if (bytes != null) // Data was actually written.
|
||||
{
|
||||
_byteBuffer = ByteBuffer.wrap(bytes);
|
||||
}
|
||||
}
|
||||
else // Rewind the buffer.
|
||||
{
|
||||
_byteBuffer.rewind();
|
||||
}
|
||||
|
||||
// Return the buffer.
|
||||
return _byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take in consideration that data must be written first.
|
||||
* @return The length of the data (includes size header).
|
||||
|
@ -476,7 +476,6 @@ public class GameServer
|
||||
server.getNetConfig().setPacketFloodDrop(Config.PACKET_FLOOD_DROP);
|
||||
server.getNetConfig().setPacketFloodLogged(Config.PACKET_FLOOD_LOGGED);
|
||||
server.getNetConfig().setTcpNoDelay(Config.TCP_NO_DELAY);
|
||||
server.getNetConfig().setConnectionTimeout(Config.CONNECTION_TIMEOUT);
|
||||
server.getNetConfig().setFailedDecryptionLogged(Config.FAILED_DECRYPTION_LOGGED);
|
||||
server.start();
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.l2jmobius.gameserver.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
@ -224,30 +222,11 @@ public class GameClient extends NetClient
|
||||
_pendingPackets.add(packet);
|
||||
synchronized (_pendingPackets)
|
||||
{
|
||||
final SocketChannel channel = getChannel();
|
||||
if ((channel != null) && channel.isConnected())
|
||||
{
|
||||
final ServerPacket sendPacket = _pendingPackets.poll();
|
||||
final ByteBuffer byteBuffer = sendPacket.getSendableByteBuffer(_encryption);
|
||||
if (byteBuffer != null)
|
||||
{
|
||||
// Send the packet data.
|
||||
try
|
||||
{
|
||||
// Loop while there are remaining bytes in the buffer.
|
||||
while (byteBuffer.hasRemaining())
|
||||
{
|
||||
channel.write(byteBuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
// Run packet implementation.
|
||||
sendPacket.run(_player);
|
||||
}
|
||||
}
|
||||
// Send the packet data.
|
||||
super.sendPacket(packet);
|
||||
|
||||
// Run packet implementation.
|
||||
packet.run(_player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.l2jmobius.loginserver.network;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -145,20 +147,26 @@ public class LoginClient extends NetClient
|
||||
return _connectionStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((packet == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Write into the channel.
|
||||
if ((getChannel() != null) && getChannel().isConnected())
|
||||
final Socket socket = getSocket();
|
||||
if ((socket != null) && socket.isConnected())
|
||||
{
|
||||
final byte[] sendableBytes = packet.getSendableBytes();
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the packet data.
|
||||
getChannel().write(packet.getSendableByteBuffer());
|
||||
final OutputStream outputStream = getOutputStream();
|
||||
synchronized (this)
|
||||
{
|
||||
outputStream.write(sendableBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
|
@ -63,10 +63,6 @@ PacketFloodLogged = True
|
||||
# Default: True (disabled)
|
||||
TcpNoDelay = True
|
||||
|
||||
# Connection timeout in milliseconds.
|
||||
# Default 800
|
||||
ConnectionTimeout = 800
|
||||
|
||||
# Packet encryption.
|
||||
# By default packets sent or received are encrypted using the Blowfish algorithm.
|
||||
# Disabling this reduces the resources needed to process any packets transfered,
|
||||
|
@ -779,7 +779,6 @@ public class Config
|
||||
public static boolean PACKET_FLOOD_DROP;
|
||||
public static boolean PACKET_FLOOD_LOGGED;
|
||||
public static boolean TCP_NO_DELAY;
|
||||
public static int CONNECTION_TIMEOUT;
|
||||
public static boolean PACKET_ENCRYPTION;
|
||||
public static boolean FAILED_DECRYPTION_LOGGED;
|
||||
public static String DATABASE_DRIVER;
|
||||
@ -1416,7 +1415,6 @@ public class Config
|
||||
PACKET_FLOOD_DROP = serverConfig.getBoolean("PacketFloodDrop", false);
|
||||
PACKET_FLOOD_LOGGED = serverConfig.getBoolean("PacketFloodLogged", true);
|
||||
TCP_NO_DELAY = serverConfig.getBoolean("TcpNoDelay", true);
|
||||
CONNECTION_TIMEOUT = serverConfig.getInt("ConnectionTimeout", 800);
|
||||
PACKET_ENCRYPTION = serverConfig.getBoolean("PacketEncryption", false);
|
||||
FAILED_DECRYPTION_LOGGED = serverConfig.getBoolean("FailedDecryptionLogged", true);
|
||||
REQUEST_ID = serverConfig.getInt("RequestServerID", 0);
|
||||
|
@ -26,19 +26,15 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
// Iterate client pool.
|
||||
ITERATE: for (E client : _pool)
|
||||
{
|
||||
if (client.getChannel() == null)
|
||||
if (client.getSocket() == null)
|
||||
{
|
||||
_pool.remove(client);
|
||||
continue ITERATE;
|
||||
@ -72,16 +68,12 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Logger;
|
||||
@ -15,27 +16,30 @@ public class NetClient
|
||||
protected static final Logger LOGGER = Logger.getLogger(NetClient.class.getName());
|
||||
|
||||
private String _ip;
|
||||
private SocketChannel _channel;
|
||||
private Socket _socket;
|
||||
private InputStream _inputStream;
|
||||
private OutputStream _outputStream;
|
||||
private NetConfig _netConfig;
|
||||
private Queue<byte[]> _pendingPacketData;
|
||||
private ByteBuffer _pendingByteBuffer;
|
||||
private Queue<byte[]> _packetData;
|
||||
private byte[] _pendingData;
|
||||
private int _pendingPacketSize;
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
* @param channel
|
||||
* @param socket
|
||||
* @param netConfig
|
||||
*/
|
||||
public void init(SocketChannel channel, NetConfig netConfig)
|
||||
public void init(Socket socket, NetConfig netConfig)
|
||||
{
|
||||
_channel = channel;
|
||||
_socket = socket;
|
||||
_netConfig = netConfig;
|
||||
_pendingPacketData = new ConcurrentLinkedQueue<>();
|
||||
_packetData = new ConcurrentLinkedQueue<>();
|
||||
_ip = socket.getInetAddress().toString().substring(1); // Trim out /127.0.0.1
|
||||
|
||||
try
|
||||
{
|
||||
_ip = _channel.getRemoteAddress().toString();
|
||||
_ip = _ip.substring(1, _ip.lastIndexOf(':')); // Trim out /127.0.0.1:12345
|
||||
_inputStream = _socket.getInputStream();
|
||||
_outputStream = _socket.getOutputStream();
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
@ -64,26 +68,28 @@ public class NetClient
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if (_channel != null)
|
||||
if (_socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel.close();
|
||||
_channel = null;
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingPacketData != null)
|
||||
if (_packetData != null)
|
||||
{
|
||||
_pendingPacketData.clear();
|
||||
_packetData.clear();
|
||||
}
|
||||
|
||||
if (_pendingByteBuffer != null)
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingByteBuffer = null;
|
||||
_pendingData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +100,7 @@ public class NetClient
|
||||
public void addPacketData(byte[] data)
|
||||
{
|
||||
// Check packet flooding.
|
||||
final int size = _pendingPacketData.size();
|
||||
final int size = _packetData.size();
|
||||
if (size >= _netConfig.getPacketQueueLimit())
|
||||
{
|
||||
if (_netConfig.isPacketFloodDisconnect())
|
||||
@ -121,7 +127,7 @@ public class NetClient
|
||||
}
|
||||
|
||||
// Add to queue.
|
||||
_pendingPacketData.add(data);
|
||||
_packetData.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,24 +135,24 @@ public class NetClient
|
||||
*/
|
||||
public Queue<byte[]> getPacketData()
|
||||
{
|
||||
return _pendingPacketData;
|
||||
return _packetData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pending read ByteBuffer.
|
||||
* @return the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public ByteBuffer getPendingByteBuffer()
|
||||
public byte[] getPendingData()
|
||||
{
|
||||
return _pendingByteBuffer;
|
||||
return _pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pending read ByteBuffer.
|
||||
* @param pendingByteBuffer the pending read ByteBuffer.
|
||||
* Set the pending read <b>byte[]</b>.
|
||||
* @param pendingData the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public void setPendingByteBuffer(ByteBuffer pendingByteBuffer)
|
||||
public void setPendingData(byte[] pendingData)
|
||||
{
|
||||
_pendingByteBuffer = pendingByteBuffer;
|
||||
_pendingData = pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +172,36 @@ public class NetClient
|
||||
_pendingPacketSize = pendingPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet over the network using the default encryption.
|
||||
* @param packet The packet to send.
|
||||
*/
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((_socket == null) || !_socket.isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] sendableBytes = packet.getSendableBytes(getEncryption());
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
_outputStream.write(sendableBytes);
|
||||
_outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Encryption of this client.
|
||||
*/
|
||||
@ -175,11 +211,27 @@ public class NetClient
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SocketChannel of this client.
|
||||
* @return the Socket of this client.
|
||||
*/
|
||||
public SocketChannel getChannel()
|
||||
public Socket getSocket()
|
||||
{
|
||||
return _channel;
|
||||
return _socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the InputStream of this client.
|
||||
*/
|
||||
public InputStream getInputStream()
|
||||
{
|
||||
return _inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OutputStream of this client.
|
||||
*/
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,6 @@ public class NetConfig
|
||||
{
|
||||
private int _readPoolSize = 100;
|
||||
private int _executePoolSize = 50;
|
||||
private int _connectionTimeout = 800;
|
||||
private int _packetQueueLimit = 80;
|
||||
private boolean _packetFloodDisconnect = false;
|
||||
private boolean _packetFloodDrop = false;
|
||||
@ -50,23 +49,6 @@ public class NetConfig
|
||||
_executePoolSize = executePoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout until a connection is established.
|
||||
*/
|
||||
public int getConnectionTimeout()
|
||||
{
|
||||
return _connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout until a connection is established.
|
||||
* @param connectionTimeout
|
||||
*/
|
||||
public void setConnectionTimeout(int connectionTimeout)
|
||||
{
|
||||
_connectionTimeout = connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the packet queue limit of receivable packets.
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -105,30 +105,26 @@ public class NetServer<E extends NetClient>
|
||||
public void run()
|
||||
{
|
||||
// Create server and bind port.
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open())
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.setReuseAddress(true);
|
||||
server.bind(new InetSocketAddress(_hostname, _port));
|
||||
server.configureBlocking(false); // Non-blocking I/O.
|
||||
server.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Listen for new connections.
|
||||
LOGGER.info(_name + ": Listening on port " + _port + " for incoming connections.");
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
final SocketChannel channel = server.accept();
|
||||
if (channel != null)
|
||||
final Socket socket = server.accept();
|
||||
if (socket != null)
|
||||
{
|
||||
// Configure channel.
|
||||
channel.socket().setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
channel.socket().setSoTimeout(_netConfig.getConnectionTimeout());
|
||||
channel.configureBlocking(false); // Non-blocking I/O.
|
||||
socket.setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
socket.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Create client.
|
||||
final E client = _clientSupplier.get();
|
||||
client.init(channel, _netConfig);
|
||||
client.init(socket, _netConfig);
|
||||
|
||||
// Add to read pool.
|
||||
|
||||
@ -190,11 +186,7 @@ public class NetServer<E extends NetClient>
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly polling the channel.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -12,9 +11,9 @@ import java.util.Set;
|
||||
*/
|
||||
public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
private final ByteBuffer _sizeBuffer = ByteBuffer.allocate(2); // Reusable size buffer.
|
||||
private final ByteBuffer _pendingSizeBuffer = ByteBuffer.allocate(1); // Reusable pending size buffer.
|
||||
private final Set<E> _pool;
|
||||
private final byte[] _sizeBuffer = new byte[2]; // Reusable size buffer.
|
||||
private final byte[] _pendingSizeBuffer = new byte[1]; // Reusable pending size buffer.
|
||||
|
||||
public ReadThread(Set<E> pool)
|
||||
{
|
||||
@ -24,12 +23,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
@ -38,22 +33,29 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
try
|
||||
{
|
||||
final SocketChannel channel = client.getChannel();
|
||||
if (channel == null) // Unexpected disconnection?
|
||||
final InputStream inputStream = client.getInputStream();
|
||||
if (inputStream == null) // Unexpected disconnection?
|
||||
{
|
||||
// Null SocketChannel: client
|
||||
// Null InputStream: client
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending ByteBuffer.
|
||||
final ByteBuffer pendingByteBuffer = client.getPendingByteBuffer();
|
||||
if (pendingByteBuffer != null)
|
||||
// Continue when there are no bytes that can be read.
|
||||
if (inputStream.available() < 1)
|
||||
{
|
||||
// Allocate an additional ByteBuffer based on pending packet size.
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending byte array.
|
||||
final byte[] pendingData = client.getPendingData();
|
||||
if (pendingData != null)
|
||||
{
|
||||
// Allocate an additional byte array based on pending packet size.
|
||||
final int pendingPacketSize = client.getPendingPacketSize();
|
||||
final ByteBuffer additionalData = ByteBuffer.allocate(pendingPacketSize - pendingByteBuffer.position());
|
||||
switch (channel.read(additionalData))
|
||||
final byte[] additionalData = new byte[pendingPacketSize - pendingData.length];
|
||||
final int bytesRead = inputStream.read(additionalData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -70,15 +72,21 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Merge additional read data.
|
||||
pendingByteBuffer.put(pendingByteBuffer.position(), additionalData, 0, additionalData.position());
|
||||
pendingByteBuffer.position(pendingByteBuffer.position() + additionalData.position());
|
||||
final int currentSize = pendingData.length + bytesRead;
|
||||
final byte[] mergedData = new byte[currentSize];
|
||||
System.arraycopy(pendingData, 0, mergedData, 0, pendingData.length);
|
||||
System.arraycopy(additionalData, 0, mergedData, pendingData.length, bytesRead);
|
||||
|
||||
// Read was complete.
|
||||
if (pendingByteBuffer.position() >= pendingPacketSize)
|
||||
if (currentSize >= pendingPacketSize)
|
||||
{
|
||||
// Add packet data to client.
|
||||
client.addPacketData(pendingByteBuffer.array());
|
||||
client.setPendingByteBuffer(null);
|
||||
client.addPacketData(mergedData);
|
||||
client.setPendingData(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setPendingData(mergedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,8 +94,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Read incoming packet size (short).
|
||||
_sizeBuffer.clear();
|
||||
switch (channel.read(_sizeBuffer))
|
||||
boolean sizeRead = false;
|
||||
switch (inputStream.read(_sizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -103,8 +111,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Need to read two bytes to calculate size.
|
||||
case 1:
|
||||
{
|
||||
int attempt = 0; // Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: while ((attempt++ < 10) && (_sizeBuffer.position() < 2))
|
||||
// Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
// Wait for pending data.
|
||||
try
|
||||
@ -116,8 +124,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Try to read the missing extra byte.
|
||||
_pendingSizeBuffer.clear();
|
||||
switch (channel.read(_pendingSizeBuffer))
|
||||
_pendingSizeBuffer[0] = 0;
|
||||
switch (inputStream.read(_pendingSizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -133,14 +141,15 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Merge additional read byte.
|
||||
default:
|
||||
{
|
||||
_sizeBuffer.put(1, _pendingSizeBuffer, 0, 1);
|
||||
_sizeBuffer.position(2);
|
||||
_sizeBuffer[1] = _pendingSizeBuffer[0];
|
||||
sizeRead = true;
|
||||
break COMPLETE_SIZE_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read failed.
|
||||
if (_sizeBuffer.position() < 2)
|
||||
if (!sizeRead)
|
||||
{
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
@ -151,10 +160,11 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Read actual packet bytes.
|
||||
default:
|
||||
{
|
||||
// Allocate a new ByteBuffer based on packet size read.
|
||||
// Allocate a new byte array based on packet size read.
|
||||
final int packetSize = calculatePacketSize();
|
||||
final ByteBuffer packetByteBuffer = ByteBuffer.allocate(packetSize);
|
||||
switch (channel.read(packetByteBuffer))
|
||||
final byte[] packetData = new byte[packetSize];
|
||||
final int bytesRead = inputStream.read(packetData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -171,14 +181,16 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Read was not complete.
|
||||
if (packetByteBuffer.position() < packetSize)
|
||||
if (bytesRead < packetSize)
|
||||
{
|
||||
client.setPendingByteBuffer(packetByteBuffer);
|
||||
final byte[] pendindBytes = new byte[bytesRead];
|
||||
System.arraycopy(packetData, 0, pendindBytes, 0, bytesRead);
|
||||
client.setPendingData(pendindBytes);
|
||||
client.setPendingPacketSize(packetSize);
|
||||
}
|
||||
else // Add packet data to client.
|
||||
{
|
||||
client.addPacketData(packetByteBuffer.array());
|
||||
client.addPacketData(packetData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,24 +209,19 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePacketSize()
|
||||
{
|
||||
_sizeBuffer.rewind();
|
||||
return ((_sizeBuffer.get() & 0xff) | ((_sizeBuffer.get() << 8) & 0xffff)) - 2;
|
||||
return ((_sizeBuffer[0] & 0xff) | ((_sizeBuffer[1] << 8) & 0xffff)) - 2;
|
||||
}
|
||||
|
||||
private void onDisconnection(E client)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -13,7 +12,6 @@ public abstract class WritablePacket
|
||||
{
|
||||
private byte[] _data;
|
||||
private byte[] _sendableBytes;
|
||||
private ByteBuffer _byteBuffer;
|
||||
private int _position = 2; // Allocate space for size (max length 65535 - size header).
|
||||
|
||||
/**
|
||||
@ -270,38 +268,6 @@ public abstract class WritablePacket
|
||||
return _sendableBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public ByteBuffer getSendableByteBuffer()
|
||||
{
|
||||
return getSendableByteBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encryption if EncryptionInterface is used.
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public synchronized ByteBuffer getSendableByteBuffer(EncryptionInterface encryption)
|
||||
{
|
||||
// Generate sendable ByteBuffer.
|
||||
if ((_byteBuffer == null /* Not processed */) || (encryption != null /* Encryption can change */))
|
||||
{
|
||||
final byte[] bytes = getSendableBytes(encryption);
|
||||
if (bytes != null) // Data was actually written.
|
||||
{
|
||||
_byteBuffer = ByteBuffer.wrap(bytes);
|
||||
}
|
||||
}
|
||||
else // Rewind the buffer.
|
||||
{
|
||||
_byteBuffer.rewind();
|
||||
}
|
||||
|
||||
// Return the buffer.
|
||||
return _byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take in consideration that data must be written first.
|
||||
* @return The length of the data (includes size header).
|
||||
|
@ -482,7 +482,6 @@ public class GameServer
|
||||
server.getNetConfig().setPacketFloodDrop(Config.PACKET_FLOOD_DROP);
|
||||
server.getNetConfig().setPacketFloodLogged(Config.PACKET_FLOOD_LOGGED);
|
||||
server.getNetConfig().setTcpNoDelay(Config.TCP_NO_DELAY);
|
||||
server.getNetConfig().setConnectionTimeout(Config.CONNECTION_TIMEOUT);
|
||||
server.getNetConfig().setFailedDecryptionLogged(Config.FAILED_DECRYPTION_LOGGED);
|
||||
server.start();
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.l2jmobius.gameserver.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
@ -224,30 +222,11 @@ public class GameClient extends NetClient
|
||||
_pendingPackets.add(packet);
|
||||
synchronized (_pendingPackets)
|
||||
{
|
||||
final SocketChannel channel = getChannel();
|
||||
if ((channel != null) && channel.isConnected())
|
||||
{
|
||||
final ServerPacket sendPacket = _pendingPackets.poll();
|
||||
final ByteBuffer byteBuffer = sendPacket.getSendableByteBuffer(_encryption);
|
||||
if (byteBuffer != null)
|
||||
{
|
||||
// Send the packet data.
|
||||
try
|
||||
{
|
||||
// Loop while there are remaining bytes in the buffer.
|
||||
while (byteBuffer.hasRemaining())
|
||||
{
|
||||
channel.write(byteBuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
// Run packet implementation.
|
||||
sendPacket.run(_player);
|
||||
}
|
||||
}
|
||||
// Send the packet data.
|
||||
super.sendPacket(packet);
|
||||
|
||||
// Run packet implementation.
|
||||
packet.run(_player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.l2jmobius.loginserver.network;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -145,20 +147,26 @@ public class LoginClient extends NetClient
|
||||
return _connectionStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((packet == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Write into the channel.
|
||||
if ((getChannel() != null) && getChannel().isConnected())
|
||||
final Socket socket = getSocket();
|
||||
if ((socket != null) && socket.isConnected())
|
||||
{
|
||||
final byte[] sendableBytes = packet.getSendableBytes();
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the packet data.
|
||||
getChannel().write(packet.getSendableByteBuffer());
|
||||
final OutputStream outputStream = getOutputStream();
|
||||
synchronized (this)
|
||||
{
|
||||
outputStream.write(sendableBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
|
@ -63,10 +63,6 @@ PacketFloodLogged = True
|
||||
# Default: True (disabled)
|
||||
TcpNoDelay = True
|
||||
|
||||
# Connection timeout in milliseconds.
|
||||
# Default 800
|
||||
ConnectionTimeout = 800
|
||||
|
||||
# Packet encryption.
|
||||
# By default packets sent or received are encrypted using the Blowfish algorithm.
|
||||
# Disabling this reduces the resources needed to process any packets transfered,
|
||||
|
@ -770,7 +770,6 @@ public class Config
|
||||
public static boolean PACKET_FLOOD_DROP;
|
||||
public static boolean PACKET_FLOOD_LOGGED;
|
||||
public static boolean TCP_NO_DELAY;
|
||||
public static int CONNECTION_TIMEOUT;
|
||||
public static boolean PACKET_ENCRYPTION;
|
||||
public static boolean FAILED_DECRYPTION_LOGGED;
|
||||
public static String DATABASE_DRIVER;
|
||||
@ -1407,7 +1406,6 @@ public class Config
|
||||
PACKET_FLOOD_DROP = serverConfig.getBoolean("PacketFloodDrop", false);
|
||||
PACKET_FLOOD_LOGGED = serverConfig.getBoolean("PacketFloodLogged", true);
|
||||
TCP_NO_DELAY = serverConfig.getBoolean("TcpNoDelay", true);
|
||||
CONNECTION_TIMEOUT = serverConfig.getInt("ConnectionTimeout", 800);
|
||||
PACKET_ENCRYPTION = serverConfig.getBoolean("PacketEncryption", false);
|
||||
FAILED_DECRYPTION_LOGGED = serverConfig.getBoolean("FailedDecryptionLogged", true);
|
||||
REQUEST_ID = serverConfig.getInt("RequestServerID", 0);
|
||||
|
@ -26,19 +26,15 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
// Iterate client pool.
|
||||
ITERATE: for (E client : _pool)
|
||||
{
|
||||
if (client.getChannel() == null)
|
||||
if (client.getSocket() == null)
|
||||
{
|
||||
_pool.remove(client);
|
||||
continue ITERATE;
|
||||
@ -72,16 +68,12 @@ public class ExecuteThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Logger;
|
||||
@ -15,27 +16,30 @@ public class NetClient
|
||||
protected static final Logger LOGGER = Logger.getLogger(NetClient.class.getName());
|
||||
|
||||
private String _ip;
|
||||
private SocketChannel _channel;
|
||||
private Socket _socket;
|
||||
private InputStream _inputStream;
|
||||
private OutputStream _outputStream;
|
||||
private NetConfig _netConfig;
|
||||
private Queue<byte[]> _pendingPacketData;
|
||||
private ByteBuffer _pendingByteBuffer;
|
||||
private Queue<byte[]> _packetData;
|
||||
private byte[] _pendingData;
|
||||
private int _pendingPacketSize;
|
||||
|
||||
/**
|
||||
* Initialize the client.
|
||||
* @param channel
|
||||
* @param socket
|
||||
* @param netConfig
|
||||
*/
|
||||
public void init(SocketChannel channel, NetConfig netConfig)
|
||||
public void init(Socket socket, NetConfig netConfig)
|
||||
{
|
||||
_channel = channel;
|
||||
_socket = socket;
|
||||
_netConfig = netConfig;
|
||||
_pendingPacketData = new ConcurrentLinkedQueue<>();
|
||||
_packetData = new ConcurrentLinkedQueue<>();
|
||||
_ip = socket.getInetAddress().toString().substring(1); // Trim out /127.0.0.1
|
||||
|
||||
try
|
||||
{
|
||||
_ip = _channel.getRemoteAddress().toString();
|
||||
_ip = _ip.substring(1, _ip.lastIndexOf(':')); // Trim out /127.0.0.1:12345
|
||||
_inputStream = _socket.getInputStream();
|
||||
_outputStream = _socket.getOutputStream();
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
@ -64,26 +68,28 @@ public class NetClient
|
||||
*/
|
||||
public void disconnect()
|
||||
{
|
||||
if (_channel != null)
|
||||
if (_socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_channel.close();
|
||||
_channel = null;
|
||||
_socket.close();
|
||||
_socket = null;
|
||||
_inputStream = null;
|
||||
_outputStream = null;
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingPacketData != null)
|
||||
if (_packetData != null)
|
||||
{
|
||||
_pendingPacketData.clear();
|
||||
_packetData.clear();
|
||||
}
|
||||
|
||||
if (_pendingByteBuffer != null)
|
||||
if (_pendingData != null)
|
||||
{
|
||||
_pendingByteBuffer = null;
|
||||
_pendingData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +100,7 @@ public class NetClient
|
||||
public void addPacketData(byte[] data)
|
||||
{
|
||||
// Check packet flooding.
|
||||
final int size = _pendingPacketData.size();
|
||||
final int size = _packetData.size();
|
||||
if (size >= _netConfig.getPacketQueueLimit())
|
||||
{
|
||||
if (_netConfig.isPacketFloodDisconnect())
|
||||
@ -121,7 +127,7 @@ public class NetClient
|
||||
}
|
||||
|
||||
// Add to queue.
|
||||
_pendingPacketData.add(data);
|
||||
_packetData.add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,24 +135,24 @@ public class NetClient
|
||||
*/
|
||||
public Queue<byte[]> getPacketData()
|
||||
{
|
||||
return _pendingPacketData;
|
||||
return _packetData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the pending read ByteBuffer.
|
||||
* @return the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public ByteBuffer getPendingByteBuffer()
|
||||
public byte[] getPendingData()
|
||||
{
|
||||
return _pendingByteBuffer;
|
||||
return _pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pending read ByteBuffer.
|
||||
* @param pendingByteBuffer the pending read ByteBuffer.
|
||||
* Set the pending read <b>byte[]</b>.
|
||||
* @param pendingData the pending read <b>byte[]</b>.
|
||||
*/
|
||||
public void setPendingByteBuffer(ByteBuffer pendingByteBuffer)
|
||||
public void setPendingData(byte[] pendingData)
|
||||
{
|
||||
_pendingByteBuffer = pendingByteBuffer;
|
||||
_pendingData = pendingData;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,6 +172,36 @@ public class NetClient
|
||||
_pendingPacketSize = pendingPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet over the network using the default encryption.
|
||||
* @param packet The packet to send.
|
||||
*/
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((_socket == null) || !_socket.isConnected())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] sendableBytes = packet.getSendableBytes(getEncryption());
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
_outputStream.write(sendableBytes);
|
||||
_outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Encryption of this client.
|
||||
*/
|
||||
@ -175,11 +211,27 @@ public class NetClient
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the SocketChannel of this client.
|
||||
* @return the Socket of this client.
|
||||
*/
|
||||
public SocketChannel getChannel()
|
||||
public Socket getSocket()
|
||||
{
|
||||
return _channel;
|
||||
return _socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the InputStream of this client.
|
||||
*/
|
||||
public InputStream getInputStream()
|
||||
{
|
||||
return _inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OutputStream of this client.
|
||||
*/
|
||||
public OutputStream getOutputStream()
|
||||
{
|
||||
return _outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,6 @@ public class NetConfig
|
||||
{
|
||||
private int _readPoolSize = 100;
|
||||
private int _executePoolSize = 50;
|
||||
private int _connectionTimeout = 800;
|
||||
private int _packetQueueLimit = 80;
|
||||
private boolean _packetFloodDisconnect = false;
|
||||
private boolean _packetFloodDrop = false;
|
||||
@ -50,23 +49,6 @@ public class NetConfig
|
||||
_executePoolSize = executePoolSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout until a connection is established.
|
||||
*/
|
||||
public int getConnectionTimeout()
|
||||
{
|
||||
return _connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout until a connection is established.
|
||||
* @param connectionTimeout
|
||||
*/
|
||||
public void setConnectionTimeout(int connectionTimeout)
|
||||
{
|
||||
_connectionTimeout = connectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the packet queue limit of receivable packets.
|
||||
*/
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -105,30 +105,26 @@ public class NetServer<E extends NetClient>
|
||||
public void run()
|
||||
{
|
||||
// Create server and bind port.
|
||||
try (ServerSocketChannel server = ServerSocketChannel.open())
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.setReuseAddress(true);
|
||||
server.bind(new InetSocketAddress(_hostname, _port));
|
||||
server.configureBlocking(false); // Non-blocking I/O.
|
||||
server.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Listen for new connections.
|
||||
LOGGER.info(_name + ": Listening on port " + _port + " for incoming connections.");
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
final SocketChannel channel = server.accept();
|
||||
if (channel != null)
|
||||
final Socket socket = server.accept();
|
||||
if (socket != null)
|
||||
{
|
||||
// Configure channel.
|
||||
channel.socket().setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
channel.socket().setSoTimeout(_netConfig.getConnectionTimeout());
|
||||
channel.configureBlocking(false); // Non-blocking I/O.
|
||||
socket.setTcpNoDelay(_netConfig.isTcpNoDelay());
|
||||
socket.setSoTimeout(0); // Non-blocking I/O.
|
||||
|
||||
// Create client.
|
||||
final E client = _clientSupplier.get();
|
||||
client.init(channel, _netConfig);
|
||||
client.init(socket, _netConfig);
|
||||
|
||||
// Add to read pool.
|
||||
|
||||
@ -190,11 +186,7 @@ public class NetServer<E extends NetClient>
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly polling the channel.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
Thread.sleep(1);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -12,9 +11,9 @@ import java.util.Set;
|
||||
*/
|
||||
public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
private final ByteBuffer _sizeBuffer = ByteBuffer.allocate(2); // Reusable size buffer.
|
||||
private final ByteBuffer _pendingSizeBuffer = ByteBuffer.allocate(1); // Reusable pending size buffer.
|
||||
private final Set<E> _pool;
|
||||
private final byte[] _sizeBuffer = new byte[2]; // Reusable size buffer.
|
||||
private final byte[] _pendingSizeBuffer = new byte[1]; // Reusable pending size buffer.
|
||||
|
||||
public ReadThread(Set<E> pool)
|
||||
{
|
||||
@ -24,12 +23,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
long executionStart;
|
||||
long currentTime;
|
||||
while (true)
|
||||
{
|
||||
executionStart = System.currentTimeMillis();
|
||||
|
||||
// No need to iterate when pool is empty.
|
||||
if (!_pool.isEmpty())
|
||||
{
|
||||
@ -38,22 +33,29 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
{
|
||||
try
|
||||
{
|
||||
final SocketChannel channel = client.getChannel();
|
||||
if (channel == null) // Unexpected disconnection?
|
||||
final InputStream inputStream = client.getInputStream();
|
||||
if (inputStream == null) // Unexpected disconnection?
|
||||
{
|
||||
// Null SocketChannel: client
|
||||
// Null InputStream: client
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending ByteBuffer.
|
||||
final ByteBuffer pendingByteBuffer = client.getPendingByteBuffer();
|
||||
if (pendingByteBuffer != null)
|
||||
// Continue when there are no bytes that can be read.
|
||||
if (inputStream.available() < 1)
|
||||
{
|
||||
// Allocate an additional ByteBuffer based on pending packet size.
|
||||
continue ITERATE;
|
||||
}
|
||||
|
||||
// Continue read if there is a pending byte array.
|
||||
final byte[] pendingData = client.getPendingData();
|
||||
if (pendingData != null)
|
||||
{
|
||||
// Allocate an additional byte array based on pending packet size.
|
||||
final int pendingPacketSize = client.getPendingPacketSize();
|
||||
final ByteBuffer additionalData = ByteBuffer.allocate(pendingPacketSize - pendingByteBuffer.position());
|
||||
switch (channel.read(additionalData))
|
||||
final byte[] additionalData = new byte[pendingPacketSize - pendingData.length];
|
||||
final int bytesRead = inputStream.read(additionalData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -70,15 +72,21 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Merge additional read data.
|
||||
pendingByteBuffer.put(pendingByteBuffer.position(), additionalData, 0, additionalData.position());
|
||||
pendingByteBuffer.position(pendingByteBuffer.position() + additionalData.position());
|
||||
final int currentSize = pendingData.length + bytesRead;
|
||||
final byte[] mergedData = new byte[currentSize];
|
||||
System.arraycopy(pendingData, 0, mergedData, 0, pendingData.length);
|
||||
System.arraycopy(additionalData, 0, mergedData, pendingData.length, bytesRead);
|
||||
|
||||
// Read was complete.
|
||||
if (pendingByteBuffer.position() >= pendingPacketSize)
|
||||
if (currentSize >= pendingPacketSize)
|
||||
{
|
||||
// Add packet data to client.
|
||||
client.addPacketData(pendingByteBuffer.array());
|
||||
client.setPendingByteBuffer(null);
|
||||
client.addPacketData(mergedData);
|
||||
client.setPendingData(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setPendingData(mergedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,8 +94,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Read incoming packet size (short).
|
||||
_sizeBuffer.clear();
|
||||
switch (channel.read(_sizeBuffer))
|
||||
boolean sizeRead = false;
|
||||
switch (inputStream.read(_sizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -103,8 +111,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Need to read two bytes to calculate size.
|
||||
case 1:
|
||||
{
|
||||
int attempt = 0; // Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: while ((attempt++ < 10) && (_sizeBuffer.position() < 2))
|
||||
// Keep it under 10 attempts (100ms).
|
||||
COMPLETE_SIZE_READ: for (int attempt = 0; attempt < 10; attempt++)
|
||||
{
|
||||
// Wait for pending data.
|
||||
try
|
||||
@ -116,8 +124,8 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Try to read the missing extra byte.
|
||||
_pendingSizeBuffer.clear();
|
||||
switch (channel.read(_pendingSizeBuffer))
|
||||
_pendingSizeBuffer[0] = 0;
|
||||
switch (inputStream.read(_pendingSizeBuffer))
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -133,14 +141,15 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Merge additional read byte.
|
||||
default:
|
||||
{
|
||||
_sizeBuffer.put(1, _pendingSizeBuffer, 0, 1);
|
||||
_sizeBuffer.position(2);
|
||||
_sizeBuffer[1] = _pendingSizeBuffer[0];
|
||||
sizeRead = true;
|
||||
break COMPLETE_SIZE_READ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read failed.
|
||||
if (_sizeBuffer.position() < 2)
|
||||
if (!sizeRead)
|
||||
{
|
||||
onDisconnection(client);
|
||||
continue ITERATE;
|
||||
@ -151,10 +160,11 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
// Read actual packet bytes.
|
||||
default:
|
||||
{
|
||||
// Allocate a new ByteBuffer based on packet size read.
|
||||
// Allocate a new byte array based on packet size read.
|
||||
final int packetSize = calculatePacketSize();
|
||||
final ByteBuffer packetByteBuffer = ByteBuffer.allocate(packetSize);
|
||||
switch (channel.read(packetByteBuffer))
|
||||
final byte[] packetData = new byte[packetSize];
|
||||
final int bytesRead = inputStream.read(packetData);
|
||||
switch (bytesRead)
|
||||
{
|
||||
// Disconnected.
|
||||
case -1:
|
||||
@ -171,14 +181,16 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
default:
|
||||
{
|
||||
// Read was not complete.
|
||||
if (packetByteBuffer.position() < packetSize)
|
||||
if (bytesRead < packetSize)
|
||||
{
|
||||
client.setPendingByteBuffer(packetByteBuffer);
|
||||
final byte[] pendindBytes = new byte[bytesRead];
|
||||
System.arraycopy(packetData, 0, pendindBytes, 0, bytesRead);
|
||||
client.setPendingData(pendindBytes);
|
||||
client.setPendingPacketSize(packetSize);
|
||||
}
|
||||
else // Add packet data to client.
|
||||
{
|
||||
client.addPacketData(packetByteBuffer.array());
|
||||
client.addPacketData(packetData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,24 +209,19 @@ public class ReadThread<E extends NetClient> implements Runnable
|
||||
}
|
||||
|
||||
// Prevent high CPU caused by repeatedly looping.
|
||||
currentTime = System.currentTimeMillis();
|
||||
if ((currentTime - executionStart) < 1)
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculatePacketSize()
|
||||
{
|
||||
_sizeBuffer.rewind();
|
||||
return ((_sizeBuffer.get() & 0xff) | ((_sizeBuffer.get() << 8) & 0xffff)) - 2;
|
||||
return ((_sizeBuffer[0] & 0xff) | ((_sizeBuffer[1] << 8) & 0xffff)) - 2;
|
||||
}
|
||||
|
||||
private void onDisconnection(E client)
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.l2jmobius.commons.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
@ -13,7 +12,6 @@ public abstract class WritablePacket
|
||||
{
|
||||
private byte[] _data;
|
||||
private byte[] _sendableBytes;
|
||||
private ByteBuffer _byteBuffer;
|
||||
private int _position = 2; // Allocate space for size (max length 65535 - size header).
|
||||
|
||||
/**
|
||||
@ -270,38 +268,6 @@ public abstract class WritablePacket
|
||||
return _sendableBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public ByteBuffer getSendableByteBuffer()
|
||||
{
|
||||
return getSendableByteBuffer(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param encryption if EncryptionInterface is used.
|
||||
* @return ByteBuffer of the sendable packet data, including a size header.
|
||||
*/
|
||||
public synchronized ByteBuffer getSendableByteBuffer(EncryptionInterface encryption)
|
||||
{
|
||||
// Generate sendable ByteBuffer.
|
||||
if ((_byteBuffer == null /* Not processed */) || (encryption != null /* Encryption can change */))
|
||||
{
|
||||
final byte[] bytes = getSendableBytes(encryption);
|
||||
if (bytes != null) // Data was actually written.
|
||||
{
|
||||
_byteBuffer = ByteBuffer.wrap(bytes);
|
||||
}
|
||||
}
|
||||
else // Rewind the buffer.
|
||||
{
|
||||
_byteBuffer.rewind();
|
||||
}
|
||||
|
||||
// Return the buffer.
|
||||
return _byteBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take in consideration that data must be written first.
|
||||
* @return The length of the data (includes size header).
|
||||
|
@ -486,7 +486,6 @@ public class GameServer
|
||||
server.getNetConfig().setPacketFloodDrop(Config.PACKET_FLOOD_DROP);
|
||||
server.getNetConfig().setPacketFloodLogged(Config.PACKET_FLOOD_LOGGED);
|
||||
server.getNetConfig().setTcpNoDelay(Config.TCP_NO_DELAY);
|
||||
server.getNetConfig().setConnectionTimeout(Config.CONNECTION_TIMEOUT);
|
||||
server.getNetConfig().setFailedDecryptionLogged(Config.FAILED_DECRYPTION_LOGGED);
|
||||
server.start();
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.l2jmobius.gameserver.network;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
@ -224,30 +222,11 @@ public class GameClient extends NetClient
|
||||
_pendingPackets.add(packet);
|
||||
synchronized (_pendingPackets)
|
||||
{
|
||||
final SocketChannel channel = getChannel();
|
||||
if ((channel != null) && channel.isConnected())
|
||||
{
|
||||
final ServerPacket sendPacket = _pendingPackets.poll();
|
||||
final ByteBuffer byteBuffer = sendPacket.getSendableByteBuffer(_encryption);
|
||||
if (byteBuffer != null)
|
||||
{
|
||||
// Send the packet data.
|
||||
try
|
||||
{
|
||||
// Loop while there are remaining bytes in the buffer.
|
||||
while (byteBuffer.hasRemaining())
|
||||
{
|
||||
channel.write(byteBuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
// Run packet implementation.
|
||||
sendPacket.run(_player);
|
||||
}
|
||||
}
|
||||
// Send the packet data.
|
||||
super.sendPacket(packet);
|
||||
|
||||
// Run packet implementation.
|
||||
packet.run(_player);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.l2jmobius.loginserver.network;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -145,20 +147,26 @@ public class LoginClient extends NetClient
|
||||
return _connectionStartTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(WritablePacket packet)
|
||||
{
|
||||
if ((packet == null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Write into the channel.
|
||||
if ((getChannel() != null) && getChannel().isConnected())
|
||||
final Socket socket = getSocket();
|
||||
if ((socket != null) && socket.isConnected())
|
||||
{
|
||||
final byte[] sendableBytes = packet.getSendableBytes();
|
||||
if (sendableBytes == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Send the packet data.
|
||||
getChannel().write(packet.getSendableByteBuffer());
|
||||
final OutputStream outputStream = getOutputStream();
|
||||
synchronized (this)
|
||||
{
|
||||
outputStream.write(sendableBytes);
|
||||
outputStream.flush();
|
||||
}
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
|
@ -63,10 +63,6 @@ PacketFloodLogged = True
|
||||
# Default: True (disabled)
|
||||
TcpNoDelay = True
|
||||
|
||||
# Connection timeout in milliseconds.
|
||||
# Default 800
|
||||
ConnectionTimeout = 800
|
||||
|
||||
# Packet encryption.
|
||||
# By default packets sent or received are encrypted using the Blowfish algorithm.
|
||||
# Disabling this reduces the resources needed to process any packets transfered,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user