天天看點

java socket BIO (ServerSocket,Socket,多線程)Java Socket

Java Socket

今天給同僚寫了一個socket bio的demo讓他來測試用,順便分享給大家,希望可以幫助正在學習和了解java bio的新人們 java.net.socket 是java中最基本的socket bio實作方式。 本例實作了多人互動廣播

Server端

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 
 * @author Allen 2017年7月19日
 *
 */
public class SocketServer {
	/**
	 * class constant
	 */
	static int POST = 44554;
	static String NOTE_FORMAT_INFO = "[%s:%s]<ID:%s> %s %s \n";
	static ExecutorService threads;
	static PrintWriter pw = null;
	static Map<Integer, Socket> alls = new HashMap<Integer, Socket>();//儲存ClientSocket的容器,可以進行統計,定向廣播等

	public static void main(String[] args) throws IOException {
                //初始化指定大小的線程池
		threads = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() << 3);
		SocketServer socketServer = new SocketServer();
		try {
			socketServer.openServer();
		} catch (BindException e) {
			System.out.printf("%s [%s] %s", "端口", POST, "被占用");
		}
	}

	public void openServer() throws BindException, IOException {
		ServerSocket server = null;
		int uid = 0;
		try {
			server = new ServerSocket(POST);
			System.out.println("伺服器啟動成功");
			while (true) {//為什麼while(true)因為要不斷的保證進入下面的阻塞來接收新的用戶端
                               //SocketServer通過阻塞來擷取一個new Socket
                               //java如何實作的accept阻塞?通過源碼最終指向下面方法
                               //當看到native我們就不需要在去考慮了
                               //native是什麼?native是調用本地方法
                               //static native int accept0(int fd, InetSocketAddress[] isaa) throws IOException;
				Socket socket = server.accept();
                               //當使用者連接配接成功時我們給他發送一條歡迎消息
				sendHelp("連接配接成功 --  From SocketServer", null, socket); 
                               //通過線程池啟動線程并把我們uid+1,當然這裡的uid沒有原子性
                               //原子性怎麼做?synchronized/CAS/AtomicInteger皆可
				threads.execute(new ClientSocekt(socket, ++uid));
			}
		} finally {
			if (server != null && !server.isClosed())
				server.close();

		}
	}

	private void sendHelp(String msg, Socket nowSocket, Socket... sockets) throws IOException {
		if (sockets == null || sockets.length == 0)
                        //把alls的值通過toArray(T[] t)轉成Socket[]
			sockets = alls.values().toArray(new Socket[0]);
		for (Socket s : sockets) {
                        //把消息廣播到sockets[]
			pw = new PrintWriter(s.getOutputStream());
                        //如果不是println就一定要在後面加上"\n"
			pw.println(nowSocket != null && s.hashCode() == nowSocket.hashCode() ? "send success" : msg);
			pw.flush();
		}

	}

	class ClientSocekt extends Thread {
		Socket socket;
		Integer uid;
		BufferedReader br;

		public ClientSocekt(Socket socket, Integer uid) {
			this.socket = socket;
			alls.put(uid, socket);
			this.uid = uid;
		}

		@Override
		public void run() {
			try {
                                //通過bufferedReader從緩沖區讀取資料
				br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				String line = null;
                                //readline也是阻塞的
                                //本demo沒有把粘包等問題考慮
                                //如果想解決粘包最簡單的就是用read,建立一個Byte
                                //用byte去接,byte設定一個大小
                                //傳輸的文本改用 {Length|Context|Type|End} 這種方式
                                //當得到length > byte.length的時候繼續去緩沖區擷取資料,知道此資料擷取完畢
				while ((line = br.readLine()) != null) {
					System.out.printf(NOTE_FORMAT_INFO, socket.getInetAddress().toString(), socket.getPort(), uid,
							"收到: ", line);
					sendHelp("來自: "+socket.getInetAddress().toString()+":"+socket.getPort()+"的消息: "+line, socket);
				}
			} catch (SocketException e) {
				System.out.printf(NOTE_FORMAT_INFO, socket.getInetAddress().toString(), socket.getPort(), uid, "狀态: ",
						"離開了");
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				alls.remove(uid);
				clear();
			}

		}

		private void clear() {
			if (br != null)
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			if (socket != null)
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
                        //銷毀用戶端線程
			this.interrupt();
		}
	}
}
           

Client端

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketException;
import java.util.Scanner;

public class SocketClient { // 搭建用戶端
	/**
	 * class constant
	 */
	static int POST = 44554;
	static String IP = "127.0.0.1";

	public static void main(String[] args) throws IOException {
		SocketClient socketClient = new SocketClient();
		try {
			socketClient.openClient();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("連接配接失敗");
		}

	}

	@SuppressWarnings("resource")
	public void openClient() throws IOException {
		Socket socket = new Socket(IP, POST);
                //連接配接成功後我們就建一個阻塞的IO來擷取server廣播的消息
		new ReadMsg(socket).start();
		System.out.println("用戶端啟動成功");
		PrintWriter pw = null;
		while (true) {
                        //通過Scanner阻塞的接收鍵盤輸入的資訊發送到server
			pw = new PrintWriter(socket.getOutputStream());
			pw.println(new Scanner(System.in).next());
			pw.flush();
		}

	}

	/**
	 * 監聽收到的消息
	 * 
	 * @author Allen 2017年7月19日
	 *
	 */
	class ReadMsg extends Thread {
		Socket socket;
		BufferedReader br;

		public ReadMsg(Socket socket) {
			this.socket = socket;
		}

		@Override
		public void run() {
			try {
				br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				String line = null;
				while (true) {
					while ((line = br.readLine()) != null)
						System.out.printf("%s\n",line);
				}

			} catch (SocketException e) {
				System.out.printf("%s\n","伺服器斷開了你的連接配接");
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				clear();
			}
		}
		private void clear() {
			if (br != null)
				try {
					br.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			if (socket != null)
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			this.interrupt();
		}
	}
}
           

輸出

伺服器啟動成功
[/172.26.106.38:53305]<ID:1> 收到:  我是Allen 
[/172.26.106.38:53307]<ID:2> 收到:  我是趙錢孫 
[/172.26.106.47:50359]<ID:3> 收到:  RRRR 
           
用戶端啟動成功
連接配接成功 --  From SocketServer
我是Allen
send success
來自: /172.26.106.38:53307的消息: 我是趙錢孫
來自: /172.26.106.47:50359的消息: RRRR
           
用戶端啟動成功
連接配接成功 --  From SocketServer
來自: /172.26.106.38:53305的消息: 我是Allen
我是趙錢孫
send success
來自: /172.26.106.47:50359的消息: RRRR
           

是不是很簡單,沒什麼複雜的,把線程和容器搞明白了,阻塞IO還有資料傳輸搞懂了,基本就可以了