Author: Lao Jiu – technology big millet
Developing Games in Java
Socializing: Zhihu
Public account: Lao Jiu School (Surprise for newcomers)
Special statement: the original is not easy, without authorization shall not be reproduced or copied, if you need to reproduce can contact the author authorized
preface
After Apache, the most popular Web server in the world is the Tomcat server. Tomcat is a Web server written in pure Java, and its underlying network services are implemented by NIO and AIO technologies of Java.
Today we discuss the NIO technology that must be used to write game servers in Java.
ServerSocketChannel class
The ServerSocketChannel abstract class in the Java.nio. Channels package is an alternative channel for a flow-oriented listening Socket. The digital signature of this class is:
public abstract class ServerSocketChannel extends AbstractSelectChannel
The server socket channel is an abstraction of incomplete listening network sockets. So binding and manipulating socket selection must be done through the ServerSocket helper object – just call the socket() method.
It is not possible to create a channel for an arbitrary, pre-stored server socket, nor to specify a SocketImpl object with a server socket. A server Socket channel can only be created using the open method of the class, but it cannot be bound to a host and port
If you call the Socket’s Accept () method without binding the channel, a NotYetBoundException is thrown;
The server socket channel calls the bind method of the server socket to bind the host to the port.
The class parent AbstractSelectableChannel is an abstract class, it implements the Channel interface, is a SelectableChannel implementation.
AbstractSelectableChannel class processing channel registration, release and closing machine. It maintains the current locking mode of the channel, that is, the group for which the Key is currently selected. It performs all the synchronization behavior needed to implement the specification requirements of SelectableChannel.
The protected methods defined in this class do not need to be synchronized because other threads may do the same.
The server side demonstrates the code
IServer interface
package com.funfree.game.chat;
import java.nio.*;
import java.nio.channels.*;
/** Function: define an interface to abstract the operation of a web server. Use this interface: IServer Server = netManager.buildServer (" implementation class name "); * /
public interface IServer{
public void accept(a)throws Exception; // Receive the client connection
public void read(a)throws Exception; // Read the message from the client
public void write(SocketChannel channel, ByteBuffer writeBuffer)throws Exception; // Write a message to the client
}
Copy the code
ChatterServer class
package com.funfree.game.chat;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.*;
import java.net.*;
import java.io.*;
import org.apache.log4j.*;
Note: The methods that a socket accepts, reads from, and writes to the socket must be written in the accept, read, and write methods defined by the interface */
public class ChatterServer extends Thread implements IServer{
private static final int BUFFER_SIZE = 255; // The size of buffer capacity
private static final long CHANNEL_WRITE_SLEEP = 10L; // The interval between channel write operations
private static final int PORT = 10997; / / the port number
private Logger log = Logger.getLogger(ChatterServer.class); // Perform log operations on the class
private ServerSocketChannel serverSocketChannel; // There is a server channel
private Selector acceptSelector; // Accept connection selector
private Selector readSelector; // Read the selector
private SelectionKey selectKey; // The selected key
private boolean running; // Indicates whether it is running
private LinkedList clients; // Use a linked list to save
private ByteBuffer readBuffer; // Read using byte buffering
private ByteBuffer writeBuffer; // Write using byte buffering
private CharsetDecoder asciiDecoder; // Character decode
public static void main(String args[]) {
/ / configure log4j
BasicConfigurator.configure();
// Initialize the chat server, and then start
ChatterServer cs = new ChatterServer();
cs.start();
}
public ChatterServer(a) {
clients = new LinkedList(); // Use a linked list to save the client
readBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
writeBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
asciiDecoder = Charset.forName( "gb2312").newDecoder();
}
/** In this method, the server creates the socket, binds the local port, and listens. * /
private void initServerSocket(a) {
try {
// Open an unlocked server socket channel
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
// Bind the listening port
InetAddress addr = InetAddress.getLocalHost();
serverSocketChannel.socket().bind(new InetSocketAddress(addr, PORT));
// Get a selector to process multiple client channels
readSelector = Selector.open();
}
catch (Exception e) {
log.error("Error initializing server", e); }}public void run(a) {
initServerSocket();
log.info("The chat server is running...");
running = true;
int numReady = 0;
// Lock while connecting to the client until there is a client connection
while (running) {
try{
// Check for new client connections
accept();
// Check the received message
read();
}
catch(Exception e){
log.error("Error:",e);
}
// Accept connections and messages at an interval of 100 milliseconds
try {
Thread.sleep(100);
}catch(InterruptedException ie) { ie.printStackTrace(); }}}/** In this method, the server sends a greeting message to the client for the first time after completing three successful handshakes with the client! * /
public void accept(a) throws Exception{
// Define a socket channel
SocketChannel clientChannel;
Because serverSocketChannel is unlocked, it returns immediately regardless of whether the connection is being used
while((clientChannel = serverSocketChannel.accept()) ! =null) {
addNewClient(clientChannel);
log.info("From:" + clientChannel.socket().getInetAddress() + "Get the connection.");
sendBroadcastMessage("From:" + clientChannel.socket().getInetAddress() + "Login", clientChannel);
sendMessage(clientChannel, "\n\n Welcome to the chat room, currently available" +
clients.size() + "User online.");
sendMessage(clientChannel, "Enter 'quit' to exit the system."); }}/** This method is a two-way socket interaction loop after three handshakes and the first reply to the client! * /
public void read(a) throws Exception{
// Use an unlocked selection, so it returns immediately regardless of how many keys are in preparation
readSelector.selectNow();
// Get the key value
Set readyKeys = readSelector.selectedKeys();
// Run by key and process
Iterator i = readyKeys.iterator();
while (i.hasNext()) {
SelectionKey key = (SelectionKey) i.next();
i.remove();
SocketChannel channel = (SocketChannel) key.channel();
readBuffer.clear();
// Read the contents of the channel into the buffer
long nbytes = channel.read(readBuffer);
// Check the end of the stream
if (nbytes == -1) {
log.info("Disconnect:" + channel.socket().getInetAddress() + ", because end of stream was encountered.);
channel.close();
clients.remove(channel);
sendBroadcastMessage("User: + channel.socket().getInetAddress() + "Logout", channel);
}
else {
// Put the append into the character buffer
StringBuffer sb = (StringBuffer)key.attachment();
// Use the character set decoder to parse these bytes into strings, which are then appended to the string buffer
readBuffer.flip( );
String str = asciiDecoder.decode( readBuffer).toString( );
readBuffer.clear( );
sb.append( str);
// Check all the content lines
String line = sb.toString();
if ((line.indexOf("\n") != -1) || (line.indexOf("\r") != -1)) {
line = line.trim();
if (line.startsWith("quit")) {
// If it is a quit message, judge the channel and remove it from the online users
log.info("Got quit message, now close:" + channel.socket().getInetAddress());
channel.close();
clients.remove(channel);
sendBroadcastMessage("User: + channel.socket().getInetAddress() + "Quit", channel);
}
else {
// Otherwise broadcast the message to all clients
log.info("Broadcast:" + line);
sendBroadcastMessage(channel.socket().getInetAddress() + ":" + line, channel);
sb.delete(0,sb.length());
}
}
}
}
}
private void addNewClient(SocketChannel chan) throws Exception{
// Add the socket channel to the collection
clients.add(chan);
// Register the channel with a selector that stores the new string buffer as an attachment to the Key that holds the message that needs to be read
chan.configureBlocking( false);
SelectionKey readKey = chan.register(readSelector,
SelectionKey.OP_READ, new StringBuffer());
}
private void sendMessage(SocketChannel channel, String mesg)throws Exception {
prepWriteBuffer(mesg);
write(channel, writeBuffer);
}
private void sendBroadcastMessage(String mesg, SocketChannel from)throws Exception {
prepWriteBuffer(mesg);
Iterator i = clients.iterator();
while (i.hasNext()) {
SocketChannel channel = (SocketChannel)i.next();
if (channel != from)
write(channel, writeBuffer);
}
}
private void prepWriteBuffer(String mesg) {
// Populates the buffer from the given string so that a channel performs a write operation
writeBuffer.clear();
writeBuffer.put(mesg.getBytes());
writeBuffer.putChar('\n');
writeBuffer.flip();
}
public void write(SocketChannel channel, ByteBuffer writeBuffer)throws Exception {
long nbytes = 0;
long toWrite = writeBuffer.remaining();
// Loop over channel writes, but not necessary, since all bytes can be written at once
while(nbytes ! = toWrite) { nbytes += channel.write(writeBuffer);try {
Thread.sleep(CHANNEL_WRITE_SLEEP);
}catch (InterruptedException e) {
}
}
// Prepare another write operationwriteBuffer.rewind(); }}Copy the code
SocketChannel class
The SocketChannel class is a stream oriented channel of choice for connecting sockets. It is digitally signed as follows:
public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel
This class is an incomplete abstraction of the socket that connects to the network, and therefore requires the assistance of socket objects to bind hosts, close connections, and manipulate socket options.
Socket channel objects can only be created using the open method of the class. A new Socket channel is open, but not connected to the server. A NotYetConnectedException is thrown if an I/O operation on an existing call channel.
When the socketchannel.connect () method is called, the channel is connected to the server, and its life cycle stops when the socketchannel.close () method is called. To determine whether the channel isConnected to the server, call the isConnected() method.
Socket channels support unlocked connections: a Socket channel can be created, initially connected to a remote Socket object using Connect, and finally connected to the finishConnect method.
To determine whether the current connection operation is in progress, call the isConnectionPending method. When reading or writing data to a socket channel, the “shut down” action is performed independently, but the channel itself is not actually closed. Shut down the output channel and call the shutdownInput method, and call the shutdownOutput method. If the write channel operation is performed again, CloseChannelException is thrown.
Socket channels support asynchronous closure, similar to the abnormal closure of the Channel class. If one read socket thread closes the read, the other thread, which locks the read, immediately ends the read and returns -1. The write socket thread closes the operation, and another thread that locks the write operation receives an AsynchrousCloseException. Multi-threaded concurrent operations on Socket channels are safe.
Client demo code
ChatterClient class
package com.funfree.game.chat;
import java.nio.*;
import java.nio.charset.*;
import java.nio.channels.*;
import java.util.*;
import java.net.*;
import java.io.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/** Function: write a chat client, using the use of NIO to achieve a multi-person chat application
public class ChatterClient extends Thread implements IServer{
private static final int BUFFER_SIZE = 255;
private static final long CHANNEL_WRITE_SLEEP = 10L;
private static final int PORT = 10997;
private ByteBuffer writeBuffer;
private ByteBuffer readBuffer;
private boolean running;
private SocketChannel channel;
private String host;
private Selector readSelector;
private CharsetDecoder asciiDecoder;
private InputThread it;
public static void main(String args[]) {
String host = args[0];
ChatterClient cc = new ChatterClient(host);
cc.start();
}
public ChatterClient(String host) {
this.host = host;
writeBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
readBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
asciiDecoder = Charset.forName( "gb2312").newDecoder();;
}
public void run(a) {
try{
accept(); // Connect to the server
it = new InputThread(this);// Start an input thread to read keyboard input
it.start();
running = true;
// Check whether the current client is running properly
while (running) {
read(); // Read the message from the server
// Read messages from the server every 50 milliseconds
try {
Thread.sleep(50);
}
catch (InterruptedException ie) {
}
}
}catch(Exception e){ e.printStackTrace(); }}/** This method creates socket objects, binds local ports, and connects to the server */
public void accept(a) throws Exception{
// Open the read selector
readSelector = Selector.open();
InetAddress addr = InetAddress.getByName(host);
channel = SocketChannel.open(new InetSocketAddress(addr, PORT));
channel.configureBlocking(false);
// Register the channel and put the content into the string buffer
channel.register(readSelector, SelectionKey.OP_READ, new StringBuffer());
}
/** In this method, the client completes sending a message to the server in response to the Accept () action. In the selectedKey set, this is the interaction after a successful handshake with the server! * /
public void read(a) throws Exception{
// Check whether there are messages from the server
// Because the selector is not locked, it returns immediately, with few keys ready to read
readSelector.selectNow();
/ / get the key
Set readyKeys = readSelector.selectedKeys();
// Run by key and process
Iterator i = readyKeys.iterator();
while (i.hasNext()) {
SelectionKey key = (SelectionKey) i.next();
i.remove();
SocketChannel channel = (SocketChannel) key.channel();
readBuffer.clear();
// Read the contents of the channel into the buffer
long nbytes = channel.read(readBuffer);
// Check if the stream ends
if (nbytes == -1) {
System.out.println("Disconnect from the server because the stream has ended");
channel.close();
shutdown();
it.shutdown();
}
else {
// Otherwise get attachment information and put it in the string buffer
StringBuffer sb = (StringBuffer)key.attachment();
// Use CharsetDecoder to convert the retrieved bytes to a string and append them to the string buffer
readBuffer.flip( );
String str = asciiDecoder.decode( readBuffer).toString( );
sb.append( str );
readBuffer.clear( );
// Check the entire line, then print the console
String line = sb.toString();
if ((line.indexOf("\n") != -1) || (line.indexOf("\r") != -1)) {
sb.delete(0,sb.length());
System.out.print("" + line);
System.out.print(">"); }}}}private void sendMessage(String mesg) throws Exception{
prepWriteBuffer(mesg);
write(channel, writeBuffer);
}
private void prepWriteBuffer(String mesg) throws Exception{
// Populate the buffer with the given string so that the channel can write
writeBuffer.clear();
writeBuffer.put(mesg.getBytes());
//writeBuffer.putChar('\n');
writeBuffer.flip();
}
public void write(SocketChannel channel, ByteBuffer writeBuffer) throws Exception{
long nbytes = 0;
long toWrite = writeBuffer.remaining();
// Write operations
while(nbytes ! = toWrite) { nbytes += channel.write(writeBuffer); }// Prepare the next write operation
writeBuffer.rewind();
}
public void shutdown(a) {
running = false;
interrupt();
}
/** * function: This thread completes the keyboard input function */
class InputThread extends Thread {
private ChatterClient cc;
private boolean running;
public InputThread(ChatterClient cc) {
this.cc = cc;
}
public void run(a) {
/*JFrame chatFrame = new JFrame(" chat client "); ChatFrame. SetSize (300250); chatFrame.setLocationRelativeTo(null); chatFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); chatFrame.setVisible(true); Final JTextArea text = new JTextArea(15,15); JButton sendButton = new JButton(" send "); chatFrame.add(text,BorderLayout.CENTER); chatFrame.add(sendButton,BorderLayout.SOUTH); sendButton.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ String content = text.getText().trim(); try{ if (content.length() > 0){ cc.sendMessage(content + "\n"); } if(content.equals("quit")){ cc.shutdown(); } }catch(Exception ee){ ee.printStackTrace(); }}}); * /
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
running = true;
while (running) {
try {
String s;
System.out.print(">");
System.out.flush();
s = br.readLine();
if (s.length() > 0)
cc.sendMessage(s);
if (s.equals("quit"))
running = false;
}catch (Exception ioe) {
running = false;
}
}
cc.shutdown();
}
public void shutdown(a) {
running = false; interrupt(); }}}Copy the code
The Selector class
The Selector class is a multiplexer of a SelectableChannel object. Its digital signature is:
public abstract class Selector extends Object
A selector object can be created by the open method of the class, which uses the default selector provider provided by the operating system to create a new selector. A selector object can also be created by calling the user-defined Selector provider’s openSelector method.
The selector stays open until it is closed when the close method is called. The SelectionKey object is generated when an optional channel is registered with a selector. The selector maintains three sets of selection keys:
- Key sets – These keys represent the channels that are currently registered with the selector. The call Selector. Keys () method returns
- Selected-key – Indicates a channel that has been detected by the system and is ready. The call Selector. SelectedKeys () method returns, which is a subset of keys
- Cancelled -key – represents channel objects that have been cancelled and cannot be accessed directly.
All three collections are empty when a new selector is created.
The key placed in the selector’s key collection is a valid channel object registered with the channel. Cancelled keys are deleted during key operations because the key collection itself cannot be modified directly. Canceling a key causes the channel to which it belongs to to be deregistered. Key objects added to the selected key collection can be removed by calling set.remove (). Keys cannot be added directly to a selected Set. The Selector class implements concurrent operations on multiple threads, including its Set of keys. The selection of the selector itself is also synchronous, whether the key set is selected or unselected. Thread-locked SELECT () and SELECT (long) operations can be terminated in three ways:
- Call Selector. Wakeup method
- Call Selector. Close
- The interrupt method that calls the locked thread
SelectionKey class
SelectionKey is an abstract class that represents a SelectableChannel channel successfully registered with Selector. Its digital signature is as follows
public abstract class SelectionKey extends Object
A selection key is created after a channel is successfully registered with a selector. A key’s life cycle lasts until the selectionkey.cancel () method is called, or until the selectionkey.close () method is called. The selectionkey.cancel () method is to call selectionKey.cancel () instead of immediately removing the channel, it saves it in the Selector’s unkey collection and lets the Selector destroy it. The select concurrent operation of this class is safe and synchronous for read and write interest collections. In real development, using application-level data structures, such as an object representing high-level protocols, enables sequential protocol reading. Therefore, SelectionKey supports throwing arbitrary objects as attachments to this key. As the object of the attachment, call the SelectionKey.attach() method to add to the channel and call the Attachment method when retrieving the attachment.
ByteBuffer class
This class is a byte buffered abstraction with the following digital signature:
public abstract class ByteBuffer extends Buffer implements Comparable
This class defines six operations that operate on bytes:
- Absolute and relative GET and PUT operations, which are read and write operations on a single byte
- The block get method, as opposed to the block get method, passes connected bytes into an array
- The block put method is the opposite of get
Absolute and relative get and PUT methods for reading and writing other primitive data types.
Character set
The java.nio.charsets package defines a new character set and a new mechanism for character transcoding. The new character set classes represent these abstract concepts in a more standard way. Character set – A collection of characters. For example, the letter “A” is A Character and “%” is A Character; Both direct numeric values, ASCII values and Unicode values are characters. They existed before computers were invented. Coded character set – A set of specified values that represent a character set. These specified values can digitally represent a particular character encoding set. Other sets of encoded characters may represent the same character with different numeric values. Such as US-ASCII code, ISO 8859-1 code, Unicode (ISO 10646-1) code and so on. The above three encodings reflect the members of the coded character set into byte (8-bit) sequences. The encoding strategy defines how to encode a sequence of characters, which is a sequence of bytes. It’s kind of like the act of serialization and unsequence.
Character data is usually encoded to be transmitted over the network or stored as a file. Encoding strategy is not a character set, it’s just an innuendo mechanism! (An encoding scheme is not a character set, it’s a mapping) For example, UTF-8 is a special encoding strategy for the Unicode character set, but it can also be used for multiple character sets. The UTF-8 encoding character code value is less than the 0x80 value, which is treated as a byte value (standard ASCII value). Other Unicode characters are encoded using a two-byte (16-bit) sequence. Character (Charset) – This term is a synthesis of coded character set and byte encoding policy. The java.nio.charset package is its abstraction.
The following illustration illustrates how the java.nio.charsets package encapsulates characters:
Most operating systems still use byte-oriented I/O operations and file storage, so no matter what encodings are used, such as Unicode or other encodings, they must be converted between byte sequences and character set encodings! The java.nio.charset package was created to solve this problem. It is not the character set encoding described by the Java platform; it is the most systematic, complex, and flexible solution. The java.nio.charset.spi package provides a service provider interface (SPI) that allows new encoders and decoders to be added.
Charset class
Charset encapsulates fixed information. For character encoding, we use Charset to encode and decode. For non-blocking socket programming, Must use Charset class, CharsetDecoder class, CharsetEncoder class to encode and decode the binary information, in order to send and display correctly.
Charset demo code
package com.funfree.game.chat;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;
import java.net.*;
import java.nio.channels.spi.*;
import java.nio.charset.*;
/** Function: write the simplest version of a class on the NIO server
public class NIOChatServer{
private ServerSocketChannel serverChannel;
private Selector connectSelector;
private PrintWriter out = new PrintWriter(System.out,true);
private static int PORT = 1818;
/** Initializes the member variable in the constructor and completes the listening client access action */
public NIOChatServer(a)throws Exception{
// Create a non-blocking server channel
serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
// Create a selector to register the server channel
connectSelector = SelectorProvider.provider().openSelector();
// Bind the server channel to the specified address and port
/*InetSocketAddress address = new InetSocketAddress( InetAddress.getByName("az-1"),PORT); * /
serverChannel.socket().bind(new InetSocketAddress(PORT));
// Register a server channel waiting for a new connection
serverChannel.register(connectSelector, SelectionKey.OP_ACCEPT);
// Start a child thread to handle all concurrent client requests
new ClientHandler(connectSelector).start();
out.println("Server started...");
}
public static void main(String[] args){
try{
new NIOChatServer();
}catch(Exception e){ e.printStackTrace(); }}}/** Function: use a child thread to handle communication with clients. Note: Concurrency is determined by key. All clients use a SocketChannel. The difference between non-blocking technology and normal I/O streaming technology is that a thread is used to process multiple client socket objects; Instead of assigning a thread to each client socket to handle! * /
class ClientHandler extends Thread{
static LinkedList clients = new LinkedList(); // Used to save the client socket object
private Selector selector;
static int count = 0;
private PrintWriter out = new PrintWriter(System.out, true);
static boolean flag = false; //false indicates that it has not been initialized, true indicates that it has been initialized
public ClientHandler(Selector selector)throws Exception{
this.selector = selector;
}
/** In the run method to determine whether there is a client connection and message read. * /
public void run(a){
try{
while(true) {// View registered channels
selector.select();
Iterator selectedKeys = selector.selectedKeys().iterator();
while(selectedKeys.hasNext()){
SelectionKey key = (SelectionKey)selectedKeys.next();
selectedKeys.remove();
if(! key.isValid()){// If no suitable socket channel is found
continue; / / not as
}
// Otherwise, determine whether the client is connected or the client is read according to isXXXX
if(key.isAcceptable()){// If there is client access
// Handle multiple client access cases
accept(key);
}else if(key.isReadable()){ // If a message is read in
sendAll(key);
}
}
sleep(100); // Receive client access at an interval of 100 ms
if(! flag){// Periodically send messages to each client
//new MyTimer().start();}}}catch(Exception e){ e.printStackTrace(); }}private void accept(SelectionKey key)throws Exception{
ServerSocketChannel serverChannel = (ServerSocketChannel)key.channel();
// Get the client connection
SocketChannel socketChannel = serverChannel.accept();
Socket socket = socketChannel.socket();// Get a socket to say Hello to the client
PrintWriter output = new PrintWriter(socket.getOutputStream(),true);
output.println("Welcome to the chat room!");
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ,new StringBuffer());
count++;
out.println("Currently available" + count + "Client connects to server.");
clients.addLast(socketChannel); // Save to the collection
}
private void sendAll(SelectionKey key)throws Exception{
SocketChannel client = (SocketChannel)key.channel();
client.configureBlocking(false); // If this is set to non-blocking, immediately register the channel as follows
client.register(selector, SelectionKey.OP_READ,new StringBuffer());// The operating system is told to read the current information immediately
ByteBuffer buffer = ByteBuffer.allocate(255);
int flag = client.read(buffer); The key code is the socketChannel.read method
// Because the system is aware of, it needs to judge the communication content of the server by the input of the client.
if(flag == -1){
client.close();
clients.remove(client);
}else{
StringBuffer sb = (StringBuffer)key.attachment();
buffer.flip();
// CharsetDecoder must be used to process the received character in the loop reading process, otherwise it will not appear
// We want the character result!!
CharsetDecoder decoder = Charset.forName("gb2312").newDecoder();
//String str = new String(buffer.array()); // There is a problem with not decoding
String str = decoder.decode(buffer).toString();
buffer.clear();
sb.append(str);
String line = sb.toString();
if((line.indexOf("\n") != -1) || (line.indexOf("\r") != -1)){
line = line.trim();
out.println("The client input is:" + line);
if(line.startsWith("quit")){
client.close();
clients.remove(client);
}else{
for(int i = 0; i < clients.size(); i++){
SocketChannel x = (SocketChannel)clients.get(i);
if(x ! = client){ ByteBuffer tempBuffer = ByteBuffer.wrap((client.socket().getInetAddress() +"Say:" + line + "\n").getBytes());
// Then write to the client
x.write(tempBuffer);
tempBuffer.flip();
}
}
sb.delete(0,sb.length()); }}}}class MyTimer extends Thread{
public MyTimer(a){
ClientHandler.flag = true; // indicates that it has been initialized
}
public void run(a){
try{
while(true){
sleep(500);
for(int i = 0; i < clients.size(); i++){
SocketChannel x = (SocketChannel)clients.get(i);
ByteBuffer tempBuffer = ByteBuffer.wrap("Test my timer.".getBytes());
// Then write to the clientx.write(tempBuffer); tempBuffer.flip(); }}}catch(Exception e){ e.printStackTrace(); }}}}Copy the code
Game data design instructions
On the server side of the game, in addition to the high concurrency performance achieved by the above non-blocking approach, there is another very important aspect. That is the game data storage problem, generally using a relational database. It is used to store player data, high and medium scores, game statistics, companion lists, and other game-specific data. Because we use a standard Java application server: the GameServer class, we will not use a J2EE application server.
So how to choose database? Most programs choose to do this using the JDBC API directly. The benefit is speed, and it’s a great strategy if the data we’re using is small. If speed is not important and the data volume is very large, it is best to use ORM technologies such as JDO, Hibernate and iBats framework technologies. MMORPG servers also provide the ability to find lists of friends and chat with those friends.
Find friends features include friend lists (similar to instant messaging), which show you in which halls of play and who is on which servers when your friends are logged in. Player matching features, based on the same skills, playability, or other key meetings, chats, and game quest challenges of the relevant players in the hall. Chat feature All the necessary game features. Based on the chat feature, we can ask the administrator to kick out the relevant user. To ensure that the players in the game world are normal players.
Server Management
Each application requires at least one error log and debug log. In addition, we may need a chat log, a game history log, a connection/access log, or a game specific log. JDK 1.4 provides a new logging API in the java.util.logging package. But the actual standard is Apache’s Log4J framework. Log4j is a highly configurable, easy-to-use, and efficient framework. Generating logs on the client side of the game is very important, we need to see the data, but it is very important during the testing phase. Even after the game is set up, if the user is having trouble, we can ask the client to send his/her log to help us analyze the problem.
Server administrator console: The administrative console can be used remotely or locally. Local applications can be represented by the RPSConsoleEventReader class, which reads commands from the keyboard and then enters those commands into events. Remote control can be modified with GameClient and RPSClient. Of course you can control or GUI interface. Either way, send the GameEvent to the GameController, like another client.
The difference is that we need to filter the user through the administrator’s list and remote IP address. The administrator client can talk to either the normal GameController or a specific AdminController. An AdminController class provides administrative functions such as server performance monitoring, game monitoring, log queries, and remote shutdown/startup. For security requirements, we can use another port to manage access, or set up the corresponding fireproof rules to restrict port access.
Game monitoring — a form of in-game espionage that is important for problem analysis and player behavior analysis. To implement this functionality, modify the GameClient class so that it does not need to respond to user input. Also the GameController class needs to be modified to allow the admin client to attach to the game and then start receiving all events. For example, the sendEvent and sendBroadcastEvent methods need to check for spy attachments and then send all events to the game monitor client.
Disconnect and reconnect — One of the biggest headaches in game server development is dealing with client disconnections. Players can drop out of the game for a variety of reasons. For example, the network is busy or physically disconnected. Either way, we need to get the player to reconnect and move on. We need a Reaper to handle connections that have already been disconnected and have been closed. The reaper is a thread that uses either java.util.Timer or a user-defined thread to monitor closed socket clients that have sent shutdown events but have not yet been activated. The metrics for game server testing are
- Maximum number of concurrent users supported by the server
- Event pass rate after concurrent connection (Events/SEC)
- The delay time for concurrent connections
- CPU usage after concurrent connections
- Event delay condition (the time from the time each event is received to the time it begins processing)
- EventQueue backlog rate (used to adjust the Wrap pool size)
- Memory usage time, note if there is a continuous increase, which indicates a memory leak (this happens with Java)
- Abnormal exceptions and errors occur
- The thread deadlock
You need to separate the client machine from the server machine when testing, because it is impossible for the client and the server to have concurrent effects on the same machine. The GameServer server has the following key points that affect performance:
- Event Processing Time
- Game Logic Times for each GameEvent Type
- Event Latency (how long events wait in queues for processing)
- Database Query times
- Time to add a new connection
Statistics in RPSController. ProcessEvent method can add code optimization performance need to focus on two points:
- Make Events smaller – If events are small, then less memory allocation time, less serialization time, and eventually less transfer time for them
- Send fewer Events – As few events as possible. If the client does not really need the data, there is no need to send it to them. Such as the GameController. SendBroadcastEvent () method, we don’t need to broadcast the news event sources.
In addition we need to optimize the GC – use – Xincgc command, and JDK1.4 later use – XX: XX + UseParallelGC + UseConcMarkSweepGC and command See also: www.oracle.com/technetwork…
There is also object reuse – having as few objects as possible to do the same thing. The RPSController is set up for this purpose. In general, using object pools is the best solution. GameServer, for example, can create GameEvent instances very quickly. However, these events have a very short life cycle; they are created, sent to the client, and then destroyed. This is a programming strategy using C/C ++. The GameEvent pool has a significant impact on the game service’s expanded load performance. The hardest part is determining how many objects are appropriate in the pool, depending on the automated test results, or whether the object pool can be captured by threads with lower permissions and then resized. Most Web and application servers use such techniques to manage their request-processing thread pools. The final optimization strategy – keep the number of threads to a minimum. Using Wrap ensures that fewer threads are used to check the average queue size. Since each thread requires additional resources, we should keep the thread count to a minimum. Synchronization techniques also need to be carefully considered, keeping synchronization to a minimum is a technical problem that goes without saying and you want to avoid loops like this:
while(true){
checkForSomeCondition();
try{
Thread.sleep(SLEEP_TIME);
}catch(Exception e){}
}
Copy the code
Finally, remove all code in the loop that doesn’t need the log. Too many logs can kill server performance.
conclusion
Note to the game server running efficiency, then must be related to the hardware, therefore, Java must provide high efficiency API to third-party programmers to use. To write efficient server-side code, NIO programming skills are almost a must from a Java perspective.
If you are interested and want the full code, use the following contact information:
QQ consultation: Pangda QQ:3038443845
Wechat plus: Laojiujun
The last
Remember to give dashu ❤️ attention + like + collect + comment + forward ❤️
Author: Lao Jiu School – technology big millet
Copyright belongs to the author. Commercial reprint please contact the author for authorization, non-commercial reprint please indicate the source.