服务端
客户端
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();
}