专门给TCP服务器用的
既需要给服务器用,又需要给客户端用
主要通过这样的类来描述一个socket文件即可,而不需要专门的类来表示传输包,面向字节流以字节为单位传输的。
就好像买房子会有西装革履的小哥哥带你进入销售楼盘,然后喊出一个小姐姐销售,由她给你介绍这个楼盘的详细情况。然后他转身溜走了到马路牙子继续拉人。小哥哥好像就是serverSocket,销售小姐姐就是clientSocket。这里之所以分成两步就是要建立连接,一个专门负责建立连接,一个专门负责通信。
Socket clientSocket = serverSocket.accept();
try{
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
上述代码针对clientSocket已关闭了,但是对于serverClient就没有关闭。同时在UDP版本的代码里,也没有针对Socket的关闭,原因?
关闭是为了“释放资源”,释放资源的前提是已经不需要使用这个资源了,但是对于UDP的程序和serverSocket,这些socket都是贯穿程序始终的。这些资源最迟也是跟随进程一起退出释放了。(进程才是系统分配资源的基本单位)。clientSocket每一个连接都有一个,数目很多连接断开也就不再需要了。每次都得保证处理完的连接都进行释放。
对于UDP的socket,和TCP的serverSocket来说,构造方法指定的端口表示自己绑定哪个端口。
对于TCP的socket来说,构造方法的指定端口表示要连接的服务器的端口。
服务器同一时刻需要处理多个连接,如果如下所示则不能实现。
当上面的代码第一次accept结束之后,就会进入processConnection,在processConnection又会有一个循环,如果processConnection里面的循环不结束,processConnection就无法执行完成,外层就无法二次调用accept,就不能接受第二个客户端的链接了。
让processConnection的执行和前面的accept的执行互不干扰,不能让processConnection里面的循环导致accpet无法及时调用。因此使用多线程。
主线程循环调用accpet,当有客户端连接上来的时候,就直接让主线程创建一个新线程,由新线程负责对客户端的若干请求提供服务(在新线程里通过while循环来处理请求)。这个时候多个线程是并发执行的关系,就是各自执行各自的了就不会互相干扰。
因为UDP不需要处理连接,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;
/**
* ClassName TcpEchoServer
* Description
* Create by 93900
* Date 2022/8/2 22:16
*/
public class TcpEchoServer {
//listen => 监听
//但是在java socket 体现不出来监听的含义
//之所以这么叫 是因为操作系统的原生API有一个操作叫做listen
//private ServerSocket listenSocket = null;
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while(true){
//由于TCP是有连接的,不能一上来就读数据,而要先建立连接
//accept就是在接电话,接电话的前提是有人先给你打了,如果当前没有客户端尝试建立连接,此处的accept就会阻塞
//accept返回了一个socket对象,称为clientSocket,后续和客户端之间的沟通,都是通过clientSocket来完成的
//进一步讲,serverSocket就干了一件事,接电话
Socket clientSocket = serverSocket.accept();
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket){
System.out.printf("[%s:%d] 客户端建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
//接下来处理请求和响应
//这里的针对TCP socket的读写就和文件读写十一摸一样的!
try(InputStream inputStream = clientSocket.getInputStream()) {
try(OutputStream outputStream = clientSocket.getOutputStream()){
//循环的处理每个请求,分别返回响应
Scanner scanner = new Scanner(inputStream);
while(true){
//1.读取请求
if(!scanner.hasNext()){
System.out.printf("[%s:%d 客户端断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
break;
}
//此处用Scanner更方便,如果不用scanner就用原生的InputStream的read也是可以的
String request = scanner.next();
//2,根据请求,计算响应
String response = process(request);
//3.把这个响应返回给客户端
//为了方便起见,可以使用PrintWriter把OutputStream包裹一下
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//冲刷缓冲区,如果没有这个功能可能客户端就不能第一时间看到响应结果
printWriter.flush();
System.out.printf("[%s:%d] req: %s, resp: %s\n",clientSocket.getInetAddress().toString(), clientSocket.getPort(), request, response);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//此处记得要来个关闭操作
try{
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9093);
tcpEchoServer.start();
}
}
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* ClassName TcpEchoClient
* Description
* Create by 93900
* Date 2022/8/2 22:49
*/
public class TcpEchoClient {
//此处用普通的socket即可,不用SeverSocket
//此处也不用手动给客户端指定端口号,让系统自由分配即可
private Socket socket = null;
public TcpEchoClient(String serverIP, int serverPort) throws IOException {
socket = new Socket(serverIP, serverPort);
}
public void start(){
System.out.println("和服务器连接成功!");
Scanner scanner = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream()) {
try(OutputStream outputStream = socket.getOutputStream()){
while(true){
//1.从控制台读取字符串
System.out.print("-> ");
String request = scanner.next();
//2.根据读取的字符串,构造请求把请求发送给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();//如果不刷新,可能服务器无法及时看到数据
//3.从服务器读取响应,并解析
Scanner respScanner = new Scanner(inputStream);
String response = respScanner.next();
//4.把结果显示到控制台上
System.out.printf("req: %s, resp: %s\n", request, response);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9094);
tcpEchoClient.start();
}
}