import
java.io.BufferedReader;
import
java.io.IOException;
import
java.io.InputStreamReader;
import
java.io.PrintWriter;
import
java.net.ServerSocket;
import
java.net.Socket;
public
class
EchoServer {
private
static
final
int
ECHO_SERVER_PORT =
6789
;
public
static
void
main(String[] args) {
try
(ServerSocket server =
new
ServerSocket(ECHO_SERVER_PORT)) {
System.out.println(
"服务器已经启动..."
);
while
(
true
) {
Socket client = server.accept();
new
Thread(
new
ClientHandler(client)).start();
}
}
catch
(IOException e) {
e.printStackTrace();
}
}
private
static
class
ClientHandler
implements
Runnable {
private
Socket client;
public
ClientHandler(Socket client) {
this
.client = client;
}
@Override
public
void
run() {
try
(BufferedReader br =
new
BufferedReader(
new
InputStreamReader(client.getInputStream()));
PrintWriter pw =
new
PrintWriter(client.getOutputStream())) {
String msg = br.readLine();
System.out.println(
"收到"
+ client.getInetAddress() +
"发送的: "
+ msg);
pw.println(msg);
pw.flush();
}
catch
(Exception ex) {
ex.printStackTrace();
}
finally
{
try
{
client.close();
}
catch
(IOException e) {
e.printStackTrace();
}
}
}
}
}
注意:上面的代码使用了Java 7的TWR语法,由于很多外部资源类都间接的实现了AutoCloseable接口(单方法回调接口),因此可以利用TWR语法在try结束的时候通过回调的方式自动调用外部资源类的close()方法,避免书写冗长的finally代码块。此外,上面的代码用一个静态内部类实现线程的功能,使用多线程可以避免一个用户I/O操作所产生的中断影响其他用户对服务器的访问,简单的说就是一个用户的输入操作不会造成其他用户的阻塞。当然,上面的代码使用线程池可以获得更好的性能,因为频繁的创建和销毁线程所造成的开销也是不可忽视的。
下面是一段回显客户端测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; public class EchoClient { public static void main(String[] args) throws Exception { Socket client = new Socket( "localhost" , 6789 ); Scanner sc = new Scanner(System.in); System.out.print( "请输入内容: " ); String msg = sc.nextLine(); sc.close(); PrintWriter pw = new PrintWriter(client.getOutputStream()); pw.println(msg); pw.flush(); BufferedReader br = new BufferedReader( new InputStreamReader(client.getInputStream())); System.out.println(br.readLine()); client.close(); } } |
如果希望用NIO的多路复用套接字实现服务器,代码如下所示。NIO的操作虽然带来了更好的性能,但是有些操作是比较底层的,对于初学者来说还是有些难于理解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class EchoServerNIO { private static final int ECHO_SERVER_PORT = 6789 ; private static final int ECHO_SERVER_TIMEOUT = 5000 ; private static final int BUFFER_SIZE = 1024 ; private static ServerSocketChannel serverChannel = null ; private static Selector selector = null ; // 多路复用选择器 private static ByteBuffer buffer = null ; // 缓冲区 public static void main(String[] args) { init(); listen(); } private static void init() { try { serverChannel = ServerSocketChannel.open(); buffer = ByteBuffer.allocate(BUFFER_SIZE); serverChannel.socket().bind( new InetSocketAddress(ECHO_SERVER_PORT)); serverChannel.configureBlocking( false ); selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (Exception e) { throw new RuntimeException(e); } } private static void listen() { while ( true ) { try { if (selector.select(ECHO_SERVER_TIMEOUT) != 0 ) { Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = it.next(); it.remove(); handleKey(key); } } } catch (Exception e) { e.printStackTrace(); } } } private static void handleKey(SelectionKey key) throws IOException { SocketChannel channel = null ; try { if (key.isAcceptable()) { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); channel = serverChannel.accept(); channel.configureBlocking( false ); channel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { channel = (SocketChannel) key.channel(); buffer.clear(); if (channel.read(buffer) > 0 ) { buffer.flip(); CharBuffer charBuffer = CharsetHelper.decode(buffer); String msg = charBuffer.toString(); System.out.println( "收到" + channel.getRemoteAddress() + "的消息:" + msg); channel.write(CharsetHelper.encode(CharBuffer.wrap(msg))); } else { channel.close(); } } } catch (Exception e) { e.printStackTrace(); if (channel != null ) { channel.close(); } } } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; public final class CharsetHelper { private static final String UTF_8 = "UTF-8" ; private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder(); private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder(); private CharsetHelper() { } public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{ return encoder.encode(in); } public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{ return decoder.decode(in); } } |