服務端
客戶端
Java中Socket通訊是通過java.net.ServerSocket
和java.net.Socket
實現的,ServerSocket用於服務端偵聽,Socket用於真實的連接。
服務端
服務端所有操作是通過ServerSocket完成。
偵聽
服務端需要先綁定要偵聽的端口,然後通過accept等待客戶端連接:
-
new時傳入端口:在所有本機地址上偵聽;
-
通過bind綁定:在指定的本機地址上偵聽;
以偵聽本地迴環地址爲例(只能通過127或localhost進行連接),因accept會阻塞,一般需要以線程方式運行:
class AcceptThread extends Thread {
@Override
public void run() {
try (ServerSocket srvSock = new ServerSocket()) {
InetAddress inAddr = Inet4Address.getLoopbackAddress();
InetSocketAddress sockAddr = new InetSocketAddress(inAddr, 8900);
srvSock.bind(sockAddr);
System.out.println("Listen at: " + srvSock.getLocalSocketAddress());
while (true) {
Socket sockClient = srvSock.accept();
System.out.println("Accept: " + sockClient.getRemoteSocketAddress());
ClientThread client = new ClientThread(sockClient);
client.start();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
socket連接
當客戶端有連接到來時,就會自動產生一個新的Socket,通過此Socket即可與客戶端進行交互。
Java中Socket提供了輸入與輸出流,因此可以使用各種流操作類(如緩衝讀)方便地進行操作。在使用Java流進行寫時,一定要注意及時刷新(創建時設定autoFlush,或寫完成時調用flush接口)。
如下,等待客戶端的輸入(每次一行),並在接收到後以Echo方式返回:
class ClientThread extends Thread {
private Socket _sockClient;
public ClientThread(Socket sock) {
_sockClient = sock;
}
@Override
public void run() {
try (// in stream
InputStream inStream = _sockClient.getInputStream();
InputStreamReader inReader = new InputStreamReader(inStream, StandardCharsets.UTF_8);
BufferedReader buffReader = new BufferedReader(inReader);
// out stream
OutputStream outStream = _sockClient.getOutputStream();
OutputStreamWriter outWriter = new OutputStreamWriter(outStream, StandardCharsets.UTF_8);
PrintWriter printer = new PrintWriter(outWriter, true);) {
String strReceive;
System.out.println("Wait for client msg");
while ((strReceive = buffReader.readLine()) != null) {
System.out.println(strReceive);
if (strReceive.equals("bye")) {
printer.println("bye");
break;
}
printer.println("Echo: " + strReceive);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
if (!_sockClient.isClosed())
_sockClient.close();
} catch (Exception ex) {
}
}
System.out.println("Socket closed");
}
}
客戶端
客戶端只需要使用Socket直接連接即可,在創建時傳遞服務端的地址與端口號,就會自動連接。
如下實現了一個可在命令行中輸入發送消息的示例,爲了能接收命令行消息,並通過socket發送與接收,創建了三個流:讀標準輸入的流,socket寫流與socket讀流。
此處PrintWriter在創建時沒有設定autoFlush,因此在數據寫入完成後一定要調用flush,否則很可能是緩衝在本地沒有發出去,然後後面的read就一直獲取不到服務端的數據(因服務端是在收到客戶端數據後再應答的)。
try (Socket sock = new Socket("127.0.0.1", 8900)) {
System.out.println("Connected: " + sock.getRemoteSocketAddress());
try (// Input
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
// Socket out
OutputStreamWriter outStream = new OutputStreamWriter(sock.getOutputStream(),
StandardCharsets.UTF_8);
PrintWriter sockWriter = new PrintWriter(outStream);
// Socket in
InputStreamReader inStream = new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8);
BufferedReader sockReader = new BufferedReader(inStream);) {
String strLine;
while (!(strLine = input.readLine()).equals("end")) {
sockWriter.println(strLine);
sockWriter.flush();
String strReceive;
if ((strReceive = sockReader.readLine()) == null)
break;
System.out.println(strReceive);
if(strReceive.equals("bye"))break;
}
}
} catch (Exception ex) {
ex.printStackTrace();
}