给服务器使用的的类,需手动绑定端口号
既要给服务器使用,也给客户端使用
!!这两个类都是表示socket文件,抽象了网卡之类的硬件设备.
和UDP实现回显服务器一样,我们写一个简单的TCP协议的客户端/服务器,客户端给服务器发送请求,请求就是控制台输入的字符串,服务器收到请求字符串后直接返回请求字符串,这种功能的服务器称为回显服务器.
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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86180
* Date: 2024-01-22
* Time: 8:47
*/
public class TcpEchoServer {
//ServerSocket给服务器使用的类,用来绑定端口号
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!!!");
//ExecutorService
while (true){
//通过accept方法,把内核中已经建立好的连接拿到应用程序中
//把建立好的连接,拿到服务器里
Socket clientSocket = serverSocket.accept();
processConnection(clientSocket);
}
}
//处理连接
public void processConnection(Socket clientSocket){
//进入方法,先打印一个日志
System.out.printf("[%s,%d] 客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
//进入交互
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
//使用try()方式,避免使用完流对象,忘记关闭
//由于客户端发来的数据可能是多条数据,针对多条数据,就循环的处理
while (true){
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()){
System.out.printf("[%s:%d] 客户端下线!!\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
//1.读取请求并解析,以next来作为读取请求的方式
String request = scanner.next();
//2.根据请求,计算响应
String response = process(request);
//3.把响应写回客户端
// 可以把String转成字节数组,写入到OutputStream字符串
// 也可以使用PrintWriter把OutputStream包裹一下,来写入字符串
PrintWriter printWriter = new PrintWriter(outputStream);
//此处的println不是打印到控制台,而是写入outputStream对应的流对象,也就是写入clientSocket里面
//这个数据通过网络发送出去
//此处使用println,带有\n也是为了后续
printWriter.println(response);
//此处还要刷新缓冲区,刷新内存
printWriter.flush();
//4.打印一次这次请求交互过程的内容
System.out.printf("[%s:%d] req = %s,resp = %s\n",clientSocket.getInetAddress(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
//关闭clientSocket
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86180
* Date: 2024-01-22
* Time: 8:47
*/
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String serverIp,int serverPort) throws IOException {
socket = new Socket(serverIp,serverPort);
}
public void start(){
System.out.print("客户端启动");
Scanner scanner = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
//
PrintWriter writer = new PrintWriter(outputStream);
Scanner scannerNetwork = new Scanner(inputStream);
//
while (true){
//1.从控制台读取用户输入的内容
System.out.print("->");
String request = scanner.next();
//2.把字符串作为请求,发送给服务器
writer.println(request);
writer.flush();
//3,读取服务器返回的响应
String response = scannerNetwork.next();
//4.在界面上显示内容
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("192.168.78.1",9090);
client.start();
}
}
虽然我们对字节流文件都进行了关闭处理,但这只是对clientSocket上的自带的流对象关闭,
但是还有一个文件没有close,就是服务器中的clientSocket对象没有进行close
如果不进行关闭,那么大量的客户端访问就会造成文件资源泄露,我们的操作是 在循环执行一次处理客户端请求的方法后及时关闭,确保每一次方法执行完都进行文件的关闭,这样问题得到解决!!
虽然当前客户端执行情况一切顺利,但是在我们再开一个客户端时,程序出现BUG!!新开的客户端并未得到回显响应
这个情况是因为,处理连接方法中又有一次循环,第一个客户建立连接后,服务器开始循环等待客户端的请求,阻塞在scanner.hasNext这里,如果客户端不退出,永远不出循环,这样就无法再建立一个新的socket和新的客户连接,我们这里采用的办法是多线程,创建新的线程来处理新来的客户端请求,也就是新来一个客户端,就新建立一个线程来进行分配!!!
这回可以看到,客户端可以正常访问了
经过上诉分析,我们对客户端代码进行修改,并且采用线程池来改进代码:
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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86180
* Date: 2024-01-22
* Time: 8:47
*/
public class TcpEchoServer {
//ServerSocket给服务器使用的类,用来绑定端口号
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
ExecutorService service = Executors.newCachedThreadPool();
System.out.println("服务器启动!!!");
//ExecutorService
while (true){
//通过accept方法,把内核中已经建立好的连接拿到应用程序中
//把建立好的连接,拿到服务器里
Socket clientSocket = serverSocket.accept();
//创建新的进程调用prpcessConnection方法
//线程开始单独存活循环处理一个客户端请求
// Thread t = new Thread(()->{
// processConnection(clientSocket);
// }) ;
// t.start();
//更好一点的办法, 是使用线程池.
service.submit(new Runnable() {
@Override
public void run() {
processConnection(clientSocket);
}
});
}
}
//处理连接
public void processConnection(Socket clientSocket){
//进入方法,先打印一个日志
System.out.printf("[%s,%d] 客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
//进入交互
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
//使用try()方式,避免使用完流对象,忘记关闭
//由于客户端发来的数据可能是多条数据,针对多条数据,就循环的处理
while (true){
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()){
System.out.printf("[%s:%d] 客户端下线!!\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
//1.读取请求并解析,以next来作为读取请求的方式
String request = scanner.next();
//2.根据请求,计算响应
String response = process(request);
//3.把响应写回客户端
// 可以把String转成字节数组,写入到OutputStream字符串
// 也可以使用PrintWriter把OutputStream包裹一下,来写入字符串
PrintWriter printWriter = new PrintWriter(outputStream);
//此处的println不是打印到控制台,而是写入outputStream对应的流对象,也就是写入clientSocket里面
//这个数据通过网络发送出去
//此处使用println,带有\n也是为了后续
printWriter.println(response);
//此处还要刷新缓冲区,刷新内存
printWriter.flush();
//4.打印一次这次请求交互过程的内容
System.out.printf("[%s:%d] req = %s,resp = %s\n",clientSocket.getInetAddress(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
//关闭clientSocket
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}