Replaced SocketChannel with Socket.

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