Önceki bir yazımda (Java Socket Programlama – TCP/IP) Java ile socket programlamaya bir giriş yapıp basit bir uygulama ile örneklendirmiştim. Yeni başlayanlar, başlangıç olarak bu örneği inceleyebilir.

Şimdi ise okulda lab dersinde yaptığım Dama oyununun tasarımını ve kodlarını paylaşacağım.

Dama oyun sistemi sunucu ve istemci olmak üzere 2 bileşenden oluşmaktadır.

Sunucunun görevi, kendisine bağlanan istemcilerin bilgisini tutmak ve bu istemcilerden gelen isteklere cevap vermektir. Ayrıca bir istemci bağlandığında veya bağlantıyı kestiğinde güncel kullanıcı listesini bütün istemcilere iletmektir.

İstemci ise sunucuya bağlantı yaptıktan sonra kullanıcı listesini almaktadır. Oyunun başlaması, kullanıcının bu listeden oyun oynamak istediği kişiyi seçerek oyun başlatma isteği göndermesi ve karşı tarafın da bunu onaylamasıyla mümkün olmaktadır. Oyun başladıktan sonra oyun oynanmasıyla ilgili iletişim sunucu üzerinden değil, istemciler arasında olacaktır (p2p yapısı).

Bu yazıda Dama oyununun sunucu programı anlatılacaktır. İstemci programını da
TCP/IP Socket Programlama – Dama Oyunu – Dama Client (2/2) adresinde inceleyebilirsiniz.

Dama server:

İlk önce koda bakalım:

import java.io.*;
import java.net.*;
import java.util.Hashtable;
import java.util.Vector;

public class server {

	private int port;
	private ServerSocket ssocket = null;
	private int numConnections = 0;

	private Hashtable<String, Socket> userList = new Hashtable<String, Socket>(); // userName-Socket pair list
	private Hashtable<String, String> userInfoList = new Hashtable<String, String>();
	private Hashtable<String, ObjectOutputStream> userOutputStreamList = new Hashtable<String, ObjectOutputStream>();

	private File inputFile = null;
	private FileWriter fwriter;
	private PrintWriter writer;

	public server(int port) {

		this.port = port;
		try {
			this.ssocket = new ServerSocket(port);
		} catch (IOException e) {
			System.out.println("Server: " + e.getMessage());
			e.printStackTrace();
			System.exit(0);
		}
	}

	public void startServer() throws UnknownHostException {

		Socket socket = null;
		System.out.println("Server started at "
				+ InetAddress.getLocalHost().toString() + " port "
				+ ssocket.getLocalPort());
		createLogFile();

		while (true) {
			try {
				socket = ssocket.accept();
				if (socket != null) {
					writeToLog(socket, "connected");
					new ServerThread(socket, this); // create new thread
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public synchronized void addUser(String userName, Socket socket,
			ObjectOutputStream oos, String info) {

		synchronized (userList) {
			userList.put(userName, socket);
		}
		synchronized (userOutputStreamList) {
			userOutputStreamList.put(userName, oos);
		}
		synchronized (userInfoList) {
			userInfoList.put(userName, info);
		}
		System.out.println(userName + " connected ");
	}

	public void sendUserList() {
		Message msg = new Message(Message.USERS_LIST);
		msg.setUserList(new Vector(userInfoList.keySet()));
		sendToClients(msg);
	}

	public void sendToClients(Message message) {
		System.out.println(message.getUserList());
		for (ObjectOutputStream oos : userOutputStreamList.values()) {
			try {
				oos.writeObject(message);
				oos.flush();
			} catch (IOException e) {
				System.out.println("sendToClients : Ex:" + e.getMessage());
				e.printStackTrace();
			}
		}
	}

	public synchronized void removeUser(String userName) {
		synchronized (userList) {
			writeToLog(userList.get(userName), "disconnected");
			userList.remove(userName);
			System.out.println(userName + " disconnected");
		}
		synchronized (userOutputStreamList) {
			userOutputStreamList.remove(userName);
		}
		synchronized (userInfoList) {
			userInfoList.remove(userName);
		}
	}

	public void createLogFile() {
		this.inputFile = new File("server.log");
		try {
			this.fwriter = new FileWriter(inputFile, true);
		} catch (IOException e) {
			System.out.println("Error creating log file : "
					+ inputFile.getName());
		}
		this.setWriter(new PrintWriter(fwriter));
		writer.close();
	}

	private void writeToLog(Socket socket, String cond) {
		try {
			writer = new PrintWriter(new FileWriter(this.inputFile, true));
		} catch (IOException e) {
			e.printStackTrace();
		}
		writer.println(socket.getLocalAddress().getHostAddress() + " "
				+ socket.getLocalPort() + " " + cond);
		writer.close();
	}

        /* GET - SET METHODLARI... */

        public static void main(String[] args) { // Main program
		try {
			server damaServer = new server(Integer.parseInt(args[0]));
			damaServer.startServer();
		} catch (UnknownHostException e) {
			System.out.println("Server error :" + e.getMessage());
		} catch (ArrayIndexOutOfBoundsException e) {
			System.out.println("missing parameter!");
			System.exit(0);
		}
	}

main fonksiyonuna bakarsak, sunucunun başlatılacağı port numarasını parametre olarak alıyorum, damaServer adında bir server nesnesi yaratıyorum ve startServer() methodu ile sunucu başlatıyorum.

startServer() methodunu inceleyelim;

while ile sonsuz bir döngü içinde (sunucu mantığı, sunucu hep açık kalacak) accept() ile bir istemcinin istek yapmasını bekliyoruz (yeni bir istek gelene kadar program bu satırda bekleyecek). Yeni bir istek geldiğinde accept() methodu bu istemcinin socketini döndürüyor. Ardından bu oluşturulan socket nesnesi ve server nesnesi ile bir Thread oluşturuyorum:
new ServerThread(socket, this);
Thread oluşturmamdaki sebep sunucunun birden fazla istemciye aynı anda hizmet verebilmesini sağlamaktır. (yine sunucu mantığı). Yani her istemci için bir thread yaratılıyor.

ServerThread:

Bir istemci için sunucu tarafında bir thread yaratıldığında artık o istemcinin istekleri bu thread tarafından yorumlanacak ve isteklerine cevap verilecektir.

ServerThread in kurucusunda start() methoduyla Thread in çalışmaya başlaması sağlanıyor. Yani run() methodu server programı ile paralel olarak işletiliyor.

ServerThread ne yapar ?
run() methodunu incelersek; thread in ait olduğu istemcinin input ve output akışlarını yaratarak bir protokol dahilinde istemci ile “haberleşir”. Bu protokol belli bir yapısı, düzeni olan bir string de olabilir bir nesne de olabilir. Nesne kullanılacaksa dikkat edilmesi gereken nokta sınıfın serileştirilebilir olmasıdır(Serializable). Ben dama oyunu için Message adında bir sınıf kullandım. Yani istemci ve sunucu birbirine Message nesnesi yollayarak haberleşiyorlar. Mesajın ne olduğunu type adlı nitelik ile belirleyebiliyorum. Sonuç olarak dama oyununda kendi protokolümü oluşturmuş oluyorum.

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.*;

public class ServerThread extends Thread {
	
	private server server;
	private Socket clientSocket;
	
	ObjectInputStream ois;
	ObjectOutputStream oos;
	
	public ServerThread(Socket clientSocket, server server) {
		super("ServerThread");
		this.clientSocket = clientSocket;
		this.server = server;
		
		start();
	}

	@Override
	public void run() {

		try {
			oos = new ObjectOutputStream(getClientSocket().getOutputStream());
			oos.flush();
			ois = new ObjectInputStream(getClientSocket().getInputStream());
		} catch (IOException e) {
			System.out.println("ServerThread streams: " + e);
		}

		sendMessage(new Message(Message.WELCOME));

		try {
			while (true) {
				Message message = (Message) ois.readObject(); // get a message
																// from client			
				processClientMessage(message); // handle this message			
				if(message.getType() == Message.CLIENT_DISCONNECT)
					break;
			}

		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				oos.close();
				ois.close();
				clientSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void processClientMessage(Message message) { // handle message and answer message (you must answer!)
		
		switch (message.getType()) {
		
		case Message.CLIENT_CONNECT:
			server.addUser(message.getUserName(), this.clientSocket, this.oos, message.getMessage());
			server.sendUserList();
			break;
			
		case Message.CLIENT_DISCONNECT:
			server.removeUser(message.getUserName());
			sendMessage(new Message(Message.BYE));
			server.sendUserList();
			break;
			
		case Message.USERS_LIST:
			server.sendUserList();
			break;
			
		case Message.REQUEST_MATCH:			
			message.setMessage(server.getUserInfoList().get(message.getRivalName())); //set message to users adress and port
			
			sendMessageTo(message, server.getUserOutputStreamList().get(message.getRivalName())); // send request to rival
			message.setType(Message.USER_INFO);
			sendMessageTo(message, server.getUserOutputStreamList().get(message.getUserName())); // to special user(request)
			
			break;
			
		case Message.START_MATCH:
			System.out.println("starting match: "+message.getUserName() +"and" + message.getRivalName());			
			sendMessageTo(message, server.getUserOutputStreamList().get(message.getUserName())); // to special user(request)
			break;

		default:
			break;
		}	
	}

	public void sendMessage(Message message) {	
		try{
			oos.writeObject(message);
			oos.flush();
		}
		catch(IOException ioException){
			ioException.printStackTrace();
		}
	}
	
	public void sendMessageTo(Message message, ObjectOutputStream oos) {	
		try{
			oos.writeObject(message);
			oos.flush();
		}
		catch(IOException ioException){
			ioException.printStackTrace();
		}
	}

	public server getServer() {
		return server;
	}

	public void setServer(server server) {
		this.server = server;
	}

	public Socket getClientSocket() {
		return clientSocket;
	}

	public void setClientSocket(Socket clientSocket) {
		this.clientSocket = clientSocket;
	}
	
}

Message sınıfı:

import java.util.Vector;

public class Message implements java.io.Serializable {
	
	//Message constants
	public static final int WELCOME				= 0;
	public static final int CLIENT_CONNECT  	= 1;
	public static final int CLIENT_DISCONNECT	= 2;
	public static final int USERS_LIST			= 3;
	public static final int BYE					= 4;
	public static final int REQUEST_MATCH		= 5;
	public static final int START_MATCH 		= 6;
	public static final int USER_INFO 			= 7;
	public static final int MOVE	 			= 8;
	public static final int TAKE 				= 9;
	public static final int KING 				= 10;
	
	private static final long serialVersionUID = 1L;
		
	private int type;
	private String userName;
	private String rivalName;
	private String message;
	private int from;
	private int to;
	private int taken;
	
	private Vector<String> userList;
	
	public Message() {
	}
	
	public Message(int type) {
		this.type = type;
	}
	
	public Message(int type, String userName) {
		this.type = type;
		this.userName = userName;
	}
	
	public Message(int type, String userName, String message) {
		this.type = type;
		this.userName = userName;
		this.message = message;
	}

	public Message(int type, String userName, int from, int to) {
		this.type = type;
		this.userName = userName;
		this.from = from;
		this.to = to;
	}

	public Message(int type, String userName, int from, int to, int take) {
		this.type = type;
		this.userName = userName;
		this.from = from;
		this.to = to;
		this.setTaken(take);
	}

	public int getType() {
		return type;
	}

	public void setType(int type) {
		this.type = type;
	}

	/* GET - SET Methodları */
	
}

Dama server çalıştırılması:

server, parametre olarak sadece sunucunun başlatılacağı port numarasını almaktadır.
java server server-port-no

Örnek server çalışması

2 Comments

  • this.setWriter(new PrintWriter(fwriter));
    writer.close();

    Cok yararli bir paylasim lakin

    this.setWriter(new PrintWriter(fwriter));

    satirinda hata veriyor. Bu satirin neden hata verdigini verdigini ve islevini aciklayabilir misiniz?

    Ayrica writer.close(); bunu da aciklarsaniz sevinirim.

    Tesekkür ederim.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.