使用Socket进行网络编程时,本质上就是两个进程之间的网络通信。
其中一个进程必须充当服务器端,它会主动监听某个指定的端口;另一个进程必须充当客户端,它必须主动连接服务器的IP地址和指定端口,如果连接成功,服务器端和客户端就成功地建立了一个TCP连接,双方后续就可以随时发送和接收数据。
因此,当Socket连接成功地在服务器端和客户端之间建立后:
- 对服务器端来说,它的Socket是指定的IP地址和指定的端口号;
- 对客户端来说,它的Socket是它所在计算机的IP地址和一个由操作系统分配的随机端口号。
accept()
accept() 表示每当有新的客户端连接进来后,就返回一个Socket实例,这个Socket实例就是用来和刚连接的客户端进行通信的。
如果没有客户端连接进来,accept()方法会阻塞并一直等待。如果有多个客户端同时连接进来,ServerSocket 会把连接扔到队列里,然后一个一个处理。对于Java程序而言,只需要通过循环不断调用 accept() 就可以获取新的连接。
这里线程池中固定线程3条,能处理3个客户端的连接。未连接上的会被 ServerSocket 扔到队列中,等待一个一个处理。
flush()
当Socket连接创建成功后,无论是服务器端,还是客户端,我们都使用Socket实例进行网络通信。因为TCP是一种基于流的协议,因此,Java标准库使用InputStream和OutputStream来封装Socket的数据流,这样我们使用Socket的流,和普通IO流类似。
如果不调用flush(),我们很可能会发现,客户端和服务器都收不到数据,这并不是Java标准库的设计问题,而是我们以流的形式写入数据的时候,并不是一写入就立刻发送到网络,而是先写入内存缓冲区,直到缓冲区满了以后,才会一次性真正发送到网络,这样设计的目的是为了提高传输效率。如果缓冲区的数据很少,而我们又想强制把这些数据发送到网络,就必须调用flush()强制把缓冲区数据发送出去。
public class ServerByThreadPool {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(10003);
System.out.println("ServerSocket 正在运行...");
ExecutorService executorService = Executors.newFixedThreadPool(3);
while(true) {
final Socket socket;
final SocketAddress socketAddress;
try {
// 阻塞并一直等待客户端连接
socket = serverSocket.accept();
socketAddress = socket.getRemoteSocketAddress();
} catch (IOException e) {
e.printStackTrace();
break;
}
executorService.submit(new Runnable() {
@Override
public void run() {
try (
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
){
// 换行符 \n 标识告诉流已经输出完毕
writer.write("已成功连接!\n");
// 发送给客户端
writer.flush();
String line = "";
// readLine() IO阻塞
while ((line = reader.readLine()) != null) {
System.out.println(">>>>>>>>>>");
System.out.println("客户端 " + socketAddress +" 说:" + line);
System.out.println("为客户端 " + socketAddress +" 服务的线程:" + Thread.currentThread().getName());
System.out.println("<<<<<<<<<<<");
// 客户端发送bye,则退出并关闭socket
if (line.equals("bye")) {
writer.write("bye\n");
writer.flush();
break;
} else {
writer.write("已接收!\n");
writer.flush();
}
}
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
});
}
executorService.shutdown();
serverSocket.close();
}
}
客户端
这里等待用户输入信息,然后发送给服务端。
public class Client {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("localhost", 10003);
SocketAddress socketAddress = socket.getRemoteSocketAddress();
try (
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
){
System.out.println("服务端 " + socketAddress + " 说 : " + reader.readLine());
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.println(">>>>>>>>>>");
// 等待用户输入
String line = scanner.nextLine();
writer.write(line);
// 写一个行分隔符,标识告诉流已经输出完毕
writer.newLine();
writer.flush();
String response = reader.readLine();
System.out.println("服务端 " + socketAddress + " 说 : " + response);
System.out.println("<<<<<<<<<<<");
if (response.equals("bye")) {
break;
}
}
}finally {
socket.close();
}
}
}
测试
启动三个客户端
再启动一个客户端,线程池满了,在 ServerSocket 中等待被处理
关闭客户端1,客户端4就连上了
参考:
网络编程 教程