1、核心类
ServerSocket :服务器使用的socket
Socket : 服务器和客户端都会使用的socket
accept进行的工作是拉客
对应操作系统来说,建立TCP连接是内核的工作
accept要干的就是等连接建立好了,把这个连接给拿到应用程序中。
如果当前连接还没建立,accept就会阻塞,accept相当于,有人给你打电话了,你按下接听键!如果没人打电话,就阻塞等待。
一、回显服务器
1、服务器
注:
1、代码中,使用了 clientSocket 用完之后,需要关闭。
和代码中 ServerSocket 生命周期不一样,ServerSocket的生命周期跟随整个程序, clientSocket生命周期,只是当前连接。就应该在连接之后,把这里的 socket 关闭,不关就会造成资源泄露。ServerSocket只有一个,clientSocket 会有无数个,每个客户端的连接,都是一个。
2、利用多线程,来实现多个客户端同时运行
一个线程调用一个processConnect。
3、通过线程池,来解决频繁创建销毁线程的问题
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TcpEchoServer {
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
//创建线程池
ExecutorService service = Executors.newCachedThreadPool();
while (true){
Socket clientSocket = serverSocket.accept();
//[版本1] 单线程版本,存在bug,无法处理多个客户端
//不是直接调用,而是创建一个新的线程,让新的线程来调用。
//processConnect(clientSocket);
//[版本2] 多线程版本,主线程负责拉客,新线程负责通信
//虽然比版本1有提升,但是涉及到频繁创建销毁线程,在高并发的情况下,负担是比较重的
// Thread T = new Thread(() -> {
// try {
// processConnect(clientSocket);
// } catch (IOException e) {
// e.printStackTrace();
// }
// });
// T.start();
//[版本3] 使用线程池,来解决频繁创建销毁线程的问题
service.submit(new Runnable() {
@Override
public void run() {
try {
processConnect(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
//通过这个方法,给当前连上的这个客户端,提供服务
//一个连接过来了,服务方式可能有两种:
//1、一个连接只进行一次数据交互(一个请求+一个响应) 短连接
//2、一个连接进行多次数据交互(N个请求 + N个响应) 长连接
//此处写长连接的版本,短连接就是长连接去掉while循环
public void processConnect(Socket clientSocket) throws IOException {
System.out.printf("[%s:%d] 建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
//这里是长连接的写法,需要while来获取多次交互情况
while (true){
if(!scanner.hasNext()){
//读完了,就断开连接。即当客户端断开连接的时候,此时 hasnext 就会返回 false
//读到EOF的时候(end of file)返回false,当客户端进程退出的时候,也就会触发socket文件关闭,此时服务器这边尝试读取请求,就会读到EOF
System.out.printf("[%s:%d] 断开连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
//1、读取请求并解析
String request = scanner.next();
//2、根据请求计算响应
String response = process(request);
//3、把响应写回给客户端
printWriter.println(response);
//4、刷新一下缓冲区,避免数据没有真的发出去
printWriter.flush();
System.out.printf("[%s:%d] req:%s;resp:%s\n",
clientSocket.getInetAddress().toString(),clientSocket.getPort(),
request,response);
}
}finally {
clientSocket.close();
}
}
public String process(String req){
return req;
}
public static void main(String[] args) throws IOException {
TcpEchoServer echoServer = new TcpEchoServer(8000);
echoServer.start();
}
}
2、客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient() throws IOException {
//new这个对象的时候,需要和服务器建立连接 建立连接,就得知道,服务器在哪里
//new Socket的过程就是建立连接的过程
socket = new Socket("127.0.0.1",8000);
}
public void start() throws IOException {
//由于实现的是长连接,一个连接会处理 N 个请求和响应,需要加上while循环
//从键盘输入
Scanner scanner = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
//从网络上输入
Scanner scannerNet = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while(true){
//1、从控制台读取用户输入
System.out.print("> ");
String request = scanner.next();
//2、把请求发送给服务器
printWriter.println(request);
printWriter.flush();
//3、从服务器读取响应
String response = scannerNet.next();
//4、把结果显示到界面上
System.out.printf("req:%s resp:%s\n",request,response);
}
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient echoClient = new TcpEchoClient();
echoClient.start();
}
}
二、翻译服务器
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class TcpDictServer extends TcpEchoServer{
private Map<String,String> dict = new HashMap<>();
public TcpDictServer(int port) throws IOException {
super(port);
dict.put("cat","小猫");
dict.put("dog","小狗");
dict.put("宝贝","瑾瑾");
}
@Override
public String process(String req){
return dict.getOrDefault(req,"俺也不知道");
}
public static void main(String[] args) throws IOException {
TcpDictServer server = new TcpDictServer(8000);
server.start();
}
}