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還有資料傳輸搞懂了,基本就可以了