1. 建立一個(gè)連接(創(chuàng)建ServerSocket實(shí)例,一般還會(huì)給定一個(gè)端口,其實(shí)可以bind(null)以讓操作系統(tǒng)分配一個(gè)可用端口),新啟動(dòng)一個(gè)線程,在新線程中監(jiān)聽(tīng)給定端口(調(diào)用accept方法)。
2. 發(fā)送客戶端請(qǐng)求(創(chuàng)建一個(gè)Socket實(shí)例,并向該Socket寫入請(qǐng)求數(shù)據(jù))。
3. 在接收端讀取數(shù)據(jù),驗(yàn)證寫入的請(qǐng)求和接收到的數(shù)據(jù)相同。
在以上流程實(shí)現(xiàn)中,accept方法返回的接收端Socket需要傳給主線程,同時(shí)要保證使用該Socket是在accept方法返回之后,以我習(xí)慣,我會(huì)使用一個(gè)Lock或CountDownLatch:
private static class SocketHolder {
Socket socket;
}
@Test
public void levinOldWayTest() throws Exception {
final ServerSocket server = new ServerSocket(10240);
final CountDownLatch latch = new CountDownLatch(1);
final SocketHolder socketHolder = new SocketHolder();
new Thread() {
public void run() {
try {
socketHolder.socket = server.accept();
latch.countDown();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}.start();
Socket socket = new Socket(server.getInetAddress(), server.getLocalPort());
socket.getOutputStream().write("My Test String".getBytes());
latch.await(5, TimeUnit.SECONDS);
byte[] receives = new byte[4096];
int length = socketHolder.socket.getInputStream().read(receives);
assertEquals("My Test String", new String(receives, 0, length));
socket.close();
socketHolder.socket.close();
server.close();
}
Socket socket;
}
@Test
public void levinOldWayTest() throws Exception {
final ServerSocket server = new ServerSocket(10240);
final CountDownLatch latch = new CountDownLatch(1);
final SocketHolder socketHolder = new SocketHolder();
new Thread() {
public void run() {
try {
socketHolder.socket = server.accept();
latch.countDown();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}.start();
Socket socket = new Socket(server.getInetAddress(), server.getLocalPort());
socket.getOutputStream().write("My Test String".getBytes());
latch.await(5, TimeUnit.SECONDS);
byte[] receives = new byte[4096];
int length = socketHolder.socket.getInputStream().read(receives);
assertEquals("My Test String", new String(receives, 0, length));
socket.close();
socketHolder.socket.close();
server.close();
}
不知道有多少人也像我一樣把這段代碼寫成這樣?這里有兩個(gè)問(wèn)題:
1. ServerSocket的監(jiān)聽(tīng)的端口不一定是可用的,類似測(cè)試代碼我之前沒(méi)有寫過(guò),我估計(jì)自己正真在寫的時(shí)候應(yīng)該會(huì)想到讓操作系統(tǒng)動(dòng)態(tài)分配。
2. 為了在兩個(gè)線程中傳遞數(shù)據(jù),這里首先創(chuàng)建了一個(gè)SocketHolder類,然后使用CountDownLatch,寫起來(lái)好麻煩。為了簡(jiǎn)化這段代碼,可以使用Exchanger,即當(dāng)一個(gè)生產(chǎn)者線程準(zhǔn)備好數(shù)據(jù)后可以通過(guò)Exchanger將數(shù)據(jù)傳遞給消費(fèi)者,而消費(fèi)者在生產(chǎn)者傳遞過(guò)來(lái)數(shù)據(jù)后就可以消費(fèi)了,這里的數(shù)據(jù)就是Socket。
改進(jìn)后的代碼如下:
@Test
public void levinImprovedWayTest() throws Exception {
final ServerSocket server = new ServerSocket();
server.bind(null);
final Exchanger<Socket> exchanger = new Exchanger<Socket>();
new Thread() {
public void run() {
try {
exchanger.exchange(server.accept());
} catch(Exception ex) {
ex.printStackTrace();
}
}
}.start();
Socket socket = new Socket(server.getInetAddress(), server.getLocalPort());
socket.getOutputStream().write("My Test String".getBytes());
Socket receiverSocket = exchanger.exchange(null, 5, TimeUnit.SECONDS);
byte[] receives = new byte[4096];
int length = receiverSocket.getInputStream().read(receives);
assertEquals("My Test String", new String(receives, 0, length));
socket.close();
receiverSocket.close();
server.close();
}
public void levinImprovedWayTest() throws Exception {
final ServerSocket server = new ServerSocket();
server.bind(null);
final Exchanger<Socket> exchanger = new Exchanger<Socket>();
new Thread() {
public void run() {
try {
exchanger.exchange(server.accept());
} catch(Exception ex) {
ex.printStackTrace();
}
}
}.start();
Socket socket = new Socket(server.getInetAddress(), server.getLocalPort());
socket.getOutputStream().write("My Test String".getBytes());
Socket receiverSocket = exchanger.exchange(null, 5, TimeUnit.SECONDS);
byte[] receives = new byte[4096];
int length = receiverSocket.getInputStream().read(receives);
assertEquals("My Test String", new String(receives, 0, length));
socket.close();
receiverSocket.close();
server.close();
}