无论是服务端还是客户端,一旦有一方调用socket.close(),都表明此次通信终止,调用close会同时关闭输入输出..
在回显例子,客户端知道接收完了数据,可以先调用close(),然后服务端再调用read将返回-1,表明服务端接收来自客户端的数据完成,然后服务端也可以调用close()
对于Http协议,客户端不知道服务端发送消息的大小,必须先由服务端关闭socket,然后客户端再关闭socket.
还有一种情况,有一方知道输出完毕,但接收还得继续,所以不能直接调用close.此时可以先调用shutdownOutput,然后接收方read返回-1,表明接收完毕,然后就可以flush所有数据出去,可以自己先关闭socket,再然后就是之前的发送方,接收到之前的接收方关闭了socket,就可以处理完数据再关闭socket。
下面是一个例子,客户端发一个文件给服务端,服务端进行压缩数据,返回给客户端,客户端再接收数据保存成另一个文件.
先看客户端
import java.io.*;
import java.net.Socket;
/* WARNING: this code can deadlock if a large file (more than a few
* 10's of thousands of bytes) is sent.
*/
public class CompressClient {
public static final int BUFSIZE = 256; //读缓冲大小
public static void main(String[] args) throws IOException {
String filename = "1.txt";//文件名
FileInputStream fileIn = new FileInputStream(filename);
FileOutputStream fileOut = new FileOutputStream(filename + ".gz");
Socket sock = new Socket("127.0.0.1", 9000);
sendBytes(sock, fileIn);//发送未压缩消息到服务端
//接收从服务端压缩的数据
InputStream sockIn = sock.getInputStream();
int bytesRead;//已读字节数
byte[] buffer = new byte[BUFSIZE]; // Byte buffer
while ((bytesRead = sockIn.read(buffer)) != -1) {
fileOut.write(buffer, 0, bytesRead);
System.out.print("R");//从流入流读数据的标志
}
System.out.println();//已接收完从服务端发送的压缩数据
sock.close();//关闭socket
fileIn.close();//关闭输入文件流
fileOut.close();//关闭输出文件流
}
private static void sendBytes(Socket sock, InputStream fileIn) throws IOException {
OutputStream sockOut = sock.getOutputStream();
int bytesRead;//已读字节数
byte[] buffer = new byte[BUFSIZE];//缓冲
while ((bytesRead = fileIn.read(buffer)) != -1) {
sockOut.write(buffer, 0, bytesRead);
System.out.print("w"); //写到输出流的写数据标志
}
sock.shutdownOutput();//关闭输出
}
}
再看服务端:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
public class TCPEchoServerExecutor {
public static void main(String[] args) throws IOException {
// Create a server socket to accept client connection requests
ServerSocket servSock = new ServerSocket(9000);
Logger logger = Logger.getLogger("practical");
Executor service = Executors.newCachedThreadPool(); // Dispatch svc
// Run forever, accepting and spawning threads to service each connection
while (true) {
Socket clntSock = servSock.accept(); // Block waiting for connection
service.execute(new CompressProtocol(clntSock, logger));
}
/* NOT REACHED */
}
}
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;
public class CompressProtocol implements Runnable {
public static final int BUFSIZE = 1024; //接收缓冲的大小
private Socket clntSock;
private Logger logger;
public CompressProtocol(Socket clntSock, Logger logger) {
this.clntSock = clntSock;
this.logger = logger;
}
public static void handleCompressClient(Socket clntSock, Logger logger) {
try {
InputStream in = clntSock.getInputStream();
GZIPOutputStream out = new GZIPOutputStream(clntSock.getOutputStream());
byte[] buffer = new byte[BUFSIZE];//分配读/写缓冲
int bytesRead;//已读字节数
while ((bytesRead = in.read(buffer)) != -1) {//接收消息直到客户端关闭连接(通过返回-1表明客户端关闭连接,即在客户端调用sock.shutdownOutput())
out.write(buffer, 0, bytesRead);
}
out.finish();//在关闭GZIPOutputStream之前,需刷新提交可能被压缩算法缓存的字节
logger.info("Client " + clntSock.getRemoteSocketAddress() + " finished");
} catch (IOException ex) {
logger.log(Level.WARNING, "Exception in echo protocol", ex);
}
try {//关闭Socket
clntSock.close();
} catch (IOException e) {
logger.info("Exception = " + e.getMessage());
}
}
public void run() {
handleCompressClient(this.clntSock, this.logger);
}
}