阿华代码,不是逆风,就是我疯
你们的点赞收藏是我前进最大的动力!!
希望本文内容能够帮助到你!!
目录

本文代码建议敲打至少3遍
一:TCP的API
1:SevereScoket类
(1)构造方法
(2)方法
注:accept可以接收多个客户端的请求连接,有阻塞功能
2:Socket类
Socket是客户端Socket,或者服务端收到客户端accept的请求后返回的服务端Socket,不管是客户端还是服务端,都是建立连接以后,保存对端的信息,以及用来与对方接收和发送数据的
(1)构造方法
(2)方法
二:基本代码实现
☆注:此处代码非完整版本,是一个最基本的框架,代码本身还有三个很重要的问题,需要解决,完整代码文章最后有上传
1:服务端
(1)有注释版
package InternetTcp;
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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Hua YY
* Date: 2024-10-11
* Time: 17:01
*/
public class TcpEchoServer {
//不同于Udp的DatagramSocket文件,ServerSocket文件负责揽活
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
//1:建立socket文件,并构造
serverSocket = new ServerSocket(port);//导包抛异常
}
public void start() throws IOException {
System.out.println("服务器启动");
while(true){
//2:再建立一个Socket文件来进行连接通信(负责接待客户端),可阻塞
Socket clientSocket = serverSocket.accept();
//3:写一个方法来处理这次连接,包括了收到请求和发出响应
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket) throws IOException {
//4:可以通过Socket文件获得客户端的ip和端口号
System.out.printf("[%s , %d]客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
//5:循环读取请求和返回响应
//Tcp是面向字节流,单位为字节,Socket本质就是文件,所以可以进行流操作
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
//6:while循环不断读取此次连接的请求并返回响应
while(true){
//7:读取操作
/*
byte[] buffer = new byte[1024];
inputStream.read(buffer);//把读取到的请求放到数组里
因为一会还要把这个请求转化为字符串,我们还有一个更简单拿到方法
*/
//7:不要去放System.in输入,用(Socket对象)inputStream来帮助Scanner进行构造
// Scanner不仅可以从操作系统中读,也可以从文件,网络中读,Scanner放在while循环外也可以
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()){
System.out.printf("[%s , %d],客户端断开连接\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
//8:根据请求计算响应
String request = scanner.next();
String response = process(request);
//9:把响应返回给客户端(封装一下),进行写入
/*
直接用write方法写回响应,不方便添加换行符\n,因为客户端在读取响应的时候使用next,结束标志为\n、空格、tab
outputStream.write(response.getBytes(),0,response.getBytes().length);
*/
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
System.out.printf("[%s,%d] , request: %s , response : %s\n " ,clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String process(String response){
return response;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
(2)无注释版
package repeat2;
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;
/**
* Created with IntelliJ IDEA.
* Description:
* User: Hua YY
* Date: 2024-10-12
* Time: 9:57
*/
public class TcpEchoServer {
//socket文件来读写
private ServerSocket serverSocket = null;//拉客
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动\n");
while(true){
//一次连接
Socket socketClient = serverSocket.accept();//拉完客了给接待
System.out.printf("[%s %d]客户端已上线\n",socketClient.getInetAddress(),socketClient.getPort());
processConnection(socketClient);
}
}
public void processConnection(Socket socketClient){
//面向字节流
try(InputStream inputStream = socketClient.getInputStream();
OutputStream outputStream = socketClient.getOutputStream()){
while(true){
/*
byte[] buffer = new byte[1024];
inputStream.read(buffer);//数组再转字符串
*/
//接收请求
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()){
System.out.printf("[%s %d]客户端断开连接\n", socketClient.getInetAddress(), socketClient.getPort());
break;
}
String request = scanner.next();
String response = process(request);
//怎么发送呢?write不建议,封装outStream
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
System.out.printf("[%s %d] , req: %s resp: %s\n",socketClient.getInetAddress(),socketClient.getPort(),request,response);
}
} 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(9091);
server.start();
}
}
2:客户端
(1)有注释版
package InternetTcp;
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: Hua YY
* Date: 2024-10-11
* Time: 17:01
*/
public class TcpEchoClient {
//1:创建一个Scoket对象
private Socket socket = null;
//2:构造方法因为 TCP是有连接的 所以Scoket对象中包含服务器的IP和端口,实例化对象就会与服务器创立连接,服务器中的accept就会进行呼应
public TcpEchoClient(String serverIp , int serverPort) throws IOException {
socket = new Socket(serverIp , serverPort);
}
public void start(){
System.out.println("客户端启动");
//一:获取控制台输入
//3:基于socket文件创建输入输出流,并实例化两个扫描器
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner scannerConsole = new Scanner(System.in);//控制台扫描器
Scanner scannerNetWork = new Scanner(inputStream);
//5:调用PrintWriter中的write方法发送请求,用outputStream帮助构造
PrintWriter writer = new PrintWriter(outputStream);
while(true){
System.out.print("->");
if (!scannerConsole.hasNext()){
break;
}
//二:构造请求,并发送
String request = scannerConsole.next();
writer.println(request);//呼应server中的获取响应scanner.next()//问题
//三:接收响应
String response = scannerNetWork.next();
//四:在显示器上进行打印
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
client.start();
}
}
(2)无注释版
package repeat2;
import jdk.internal.util.xml.impl.Input;
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: Hua YY
* Date: 2024-10-12
* Time: 9:57
*/
public class TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String serverIp , int serverport){
socket = new Socket();
}
public void start(){
System.out.println("客户端启动");
//从读取控制台的请求
//构造请求并发送
//接收响应
//把相应打印显示出来
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner scannerConsole = new Scanner(System.in);
Scanner scannerNetWork = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);
while(true){
System.out.print("->");
String request = scannerConsole.next();
//怎么发送呢?outputStream(带封装)
writer.println(request);
//怎么接收响应?
String response = scannerNetWork.next();
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
InternetTcp.TcpEchoClient client = new InternetTcp.TcpEchoClient("127.0.0.1",9091);
client.start();
}
}
三:细节问题
优化点①
(读/接受request)用Scanner替代数组转化为字符串,因为有文件流操作,用inputStream来帮助Scanner进行构造
优化点②
直接用outputStream的write方法不方便写换行符\n,所以给outputStream“封装一下”,用outputStream帮助PrintWriter进行构造,再使用println方法实现自动换行
构造方法
四: 过程梳理
五:PrintWriter缓冲区问题
1:问题引入
客户端发出请求没有收到响应
2:解决方式
引入PrintWriter中的flush()方法
之所以会出现上述问题是因为,PrintWriter的内置缓存区在发力,因为文件IO是比较低效的操作,所以操作系统会进行优化,尽可能的让这种操作少一点,就引入了缓存区(内存),把要写入网卡的数据放到内存缓冲区中,等攒一波大的,在统一发送;
换个说法就是如果发送的数据太少,数据就会先滞留在内存缓冲区中,没有被真正的发送出去。
六:Socket文件释放问题
1:问题引入
因为服务器每连接一个客户端都要建立一个Socket文件(名字叫clientSocket),这个文件是会占用文件描述符表的,连接的客户端数量多的话,只建立文件不释放文件的话就会造成——文件描述符表被占满
2:解决方式
每次服务器执行完客户端的请求后(即processConnection方法执行完毕)就释放掉Socket文件
七:多个客户端连接问题
1:问题引入
(1)如何运行多个同一程序
创建两个客户端,让服务器同时对两个服务端进行服务,最后再点运行就会出现两个Client了
(2)实际效果
本质原因:
accept使用了一次while循环,processClient方法中又嵌套了一层while循环
导致在服务器在处理客户端A的请求时,一直在processClient方法中出不来,就执行不了第二次客户端B的accept
(内核中:客户端B和服务器已经建立了TCP连接,但是用户态应用程序这里无法处理到,也就是拿不到)
(理解成:别人给你打电话,电话响了,但是你不接)
重点:虽然处理不了客户端B的数据请求,但是B的数据请求会暂时放在Socket缓冲区中(缓存区不能无限放数据,有大小限制的,所以可能会存在数据丢失的情况),等A端下线后,B端的请求会被瞬间处理掉
2:解决方式
引入多线程——把嵌套的两个while循环给拆分开来
八:线程池优化多线程问题
1:问题引入
上述八中引入多线程解决了多个客户端的上线问题,但是频繁的创建和销毁进程,会带来很大的资源消耗问题,我们给出了方案——引入线程池
2:解决方式
3:其他方案(了解即可)
(1)协程
轻量级线程,本质上还是一个线程,用户态可以通过手动调度的方式让这个线程“并发”的做多个任务
(2)IO多路复用
系统内核级别的机制,本质上是让一个线程同时去负责处理多个Socket(这虽然有多个socket数据,但是同一时刻活跃的socket只是少数,大部分socket都是在等)在Java中也有对应的封装了的API
九:写代码易错的地方
1:服务器
2:客户端
十:完整代码
服务器
package InternetTcp;
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: Hua YY
* Date: 2024-10-11
* Time: 17:01
*/
public class TcpEchoServer {
//不同于Udp的DatagramSocket文件,ServerSocket文件负责揽活
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
//1:建立socket文件,并构造
serverSocket = new ServerSocket(port);//导包抛异常
}
public void start() throws IOException {
System.out.println("服务器启动");
while(true){
//2:再建立一个Socket文件来进行连接通信(负责接待客户端),可阻塞
Socket clientSocket = serverSocket.accept();
//3:写一个方法来处理这次连接,包括了收到请求和发出响应
ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(new Runnable(){
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
/*
Thread thread = new Thread(()->{
try {
processConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
thread.start();
*/
}
}
private void processConnection(Socket clientSocket) throws IOException {
//4:可以通过Socket文件获得客户端的ip和端口号
System.out.printf("[%s , %d]客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
//5:循环读取请求和返回响应
//Tcp是面向字节流,单位为字节,Socket本质就是文件,所以可以进行流操作
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
//6:while循环不断读取此次连接的请求并返回响应
while(true){
//7:读取操作
/*
byte[] buffer = new byte[1024];
inputStream.read(buffer);//把读取到的请求放到数组里
因为一会还要把这个请求转化为字符串,我们还有一个更简单拿到方法
*/
//7:不要去放System.in输入,用(Socket对象)inputStream来帮助Scanner进行构造
// Scanner不仅可以从操作系统中读,也可以从文件,网络中读,Scanner放在while循环外也可以
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()){
System.out.printf("[%s , %d],客户端断开连接\n",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
//8:根据请求计算响应
String request = scanner.next();
String response = process(request);
//9:把响应返回给客户端(封装一下),进行写入
/*
直接用write方法写回响应,不方便添加换行符\n,因为客户端在读取响应的时候使用next,结束标志为\n、空格、tab
outputStream.write(response.getBytes(),0,response.getBytes().length);
*/
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s,%d] , request: %s , response : %s\n " ,clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
clientSocket.close();
}
}
public String process(String response){
return response;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
客户端
package InternetTcp;
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: Hua YY
* Date: 2024-10-11
* Time: 17:01
*/
public class TcpEchoClient {
//1:创建一个Scoket对象
private Socket socket = null;
//2:构造方法因为 TCP是有连接的 所以Scoket对象中包含服务器的IP和端口,实例化对象就会与服务器创立连接,服务器中的accept就会进行呼应
public TcpEchoClient(String serverIp , int serverPort) throws IOException {
socket = new Socket(serverIp , serverPort);
}
public void start(){
System.out.println("客户端启动");
//一:获取控制台输入
//3:基于socket文件创建输入输出流,并实例化两个扫描器
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner scannerConsole = new Scanner(System.in);//控制台扫描器
Scanner scannerNetWork = new Scanner(inputStream);
//5:调用PrintWriter中的write方法发送请求,用outputStream帮助构造
PrintWriter writer = new PrintWriter(outputStream);
while(true){
System.out.print("->");
if (!scannerConsole.hasNext()){
break;
}
//二:构造请求,并发送
String request = scannerConsole.next();
writer.println(request);//呼应server中的获取响应scanner.next()//问题
writer.flush();
//三:接收响应
String response = scannerNetWork.next();
//四:在显示器上进行打印
System.out.println(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
client.start();
}
}