一:TCP协议基础知识
a:TCP协议提供API的两个核心类
ServerSocket | 产生服务器的端口号 |
Socket | 既能产生服务器的端口号,也能产生客户端的端口号 |
b:对于TCP协议的连接解析
在前面学习的过程中,我们学习到传输层的TCP协议是有连接的,即服务器和客户端进行连接.接下来,我将进行进一步的解释说明:
1):服务器有一个,而客户端有多个.且服务器和客户端之间连接代表服务器保存客户端的信息,客户端保存服务器的信息
2):客户端有多个,服务器有一个,在内核中,客户端和服务器的已经建立好的连接很多,应用程序在应用这些连接的时候,得一个一个进行处理.此时,内核就相当于队列,所建立的连接就相当于一个一个的待办事项.
3):TCP的连接相当于多线程学过的生产者-消费者模型,一边生产一边进行消费.
示意图如下:
①:当客户端和服务器尝试建立连接的时候,此时,服务器和客户端便会产生一系列的数据交互,这个称为"握手".
②:等"握手"这个过程完之后,此时,socket对象便会产生"管理连接"的队列,将客户端和服务器建立好的连接加入到队列中.
③:由应用程序从队列取一个一个的连接,即处理一个一个的待办事项.由socket对象调用accept这个静态方法,进行消费队列中的连接
二:TCP协议提供的API的静态方法和回显服务器
a:accept静态方法
b:回显服务器
服务器代码:
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; private ExecutorService service= Executors.newCachedThreadPool(); //在创建socket的同时,创建端口号 public TcpEchoServer(int port) throws IOException { serverSocket=new ServerSocket(port); } //启动服务器 public void start() throws IOException { System.out.println("启动服务器!"); while(true){ //1:处理客户端的连接,由于TCP是有连接的,服务器保存客户端的信息,客户端保存服务器的连接 Socket clientSocket = serverSocket.accept(); //通过上面产生的Socket对象和对方进行网络通信 // Thread t=new Thread(()->{ // try { // processConnection(clientSocket); // } catch (IOException e) { // throw new RuntimeException(e); // } // }); // t.start(); service.submit(new Runnable() { @Override public void run() { try { processConnection(clientSocket); } catch (IOException e) { throw new RuntimeException(e); } } }); } } //通过这个方法来处理一个连接的逻辑 public void processConnection(Socket clientSocket) throws IOException { //通过这两个方法获取客户端的IP地址和端口号 System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort()); //接下来就可以读取请求,根据请求计算响应,返回响应三步走 //Socket对象内部包含了两个字节流对象,就可以把这俩字节流对象获取到,完成后续的读写工作 try(InputStream inputStream=clientSocket.getInputStream(); OutputStream outputStream=clientSocket.getOutputStream();){ //一次连接中,可能会涉及到多次请求.响应 while(true){ //1:读取请求并解析,为了读取方便,使用Scanner Scanner scanner=new Scanner(inputStream); if(!scanner.hasNext()){ //读取完毕,客户端下线 System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort()); break; } //客户端发过来的请求,是文本数据,同时,还得带有空白符 next:代表一直读,一直读到空白符(换行/回车/空格/制表符/翻页符)结束 String request=scanner.next(); //2.根据请求计算响应 String response = process(request); //3.把响应返回给客户端, PrintWriter writer=new PrintWriter(outputStream); //此处使用PrintWriter的println方法,把响应返回给客户端 writer.println(response); writer.flush();//刷新缓冲区,提高程序的效率 //日志:打印请求详情 System.out.printf("[%s:%d] req:%s,resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response); } }catch (IOException e){ e.printStackTrace(); }finally { clientSocket.close(); } } public String process(String request){ return request; } public static void main(String[] args) throws IOException { TcpEchoServer tcpEchoServer=new TcpEchoServer(9090); tcpEchoServer.start(); } }
TCP协议的回显服务器的服务器代码与UDP协议有部分区别,在TCP回显服务器的服务器代码中,主要分为连接+服务器代码必须步骤,下面,我将对TCP服务器端代码进行进一步详解,有问题的可以在底下留言.
a:创建服务器对象
b:手动设定服务器端口号
c:启动服务器,即创建start方法,让服务器启动
启动服务器,对当前日志进行打印
d:客户端和服务器进行连接,调用ServerSocket对象的accept方法
在前面的学习中,我们对于accept方法做出了详细的介绍,通过服务器的对象调用反悔了一个新的对象,小编认为返回的这个对象储存的是客户端的信息,包括客户端的IP地址和端口号.
e:利用process方法对于客户端信息进行处理
processConnection方法内部代码解析:
1:读取从客户端发来的请求,服务端进行进一步解析
2:根据第一步得到的字符解析,计算响应.
3:把响应返回给客户端
*****注意******
服务端是关闭客户端socket连接:防止资源的浪费
客户端代码
public class TcpEchoClient { private Socket socket=null; public TcpEchoClient(String serverIp,int serverport) throws IOException { //与完成了TCP客户端的建立,此时客户端通过new获得服务器的IP地址和端口号 socket=new Socket(serverIp,serverport); } public void start(){ System.out.println("客户端启动"); Scanner scannerConsole=new Scanner(System.in); try(InputStream inputStream=socket.getInputStream(); OutputStream outputStream=socket.getOutputStream()) { while(true){ //1:从控制台输入一个字符串 System.out.println("->"); String request=scannerConsole.next(); //2:构造请求,发送给服务器-->利用printWriter进行包装,将输出的进行打包,同时将从控制台输入的传递给服务器 PrintWriter printWriter=new PrintWriter(outputStream); //使用println带上换行,后续服务器读取请求,就可以使用scanner.next来获取了 printWriter.println(request); printWriter.flush(); //3:从服务器读取响应,并进行处理 Scanner scannerNetwork=new Scanner(inputStream); String respnse= scannerNetwork.next(); //4:打印响应 System.out.println(respnse); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpEchoClient tcpEchoClient=new TcpEchoClient("127.0.0.1",9090); tcpEchoClient.start(); } }
1):客户端核心代码前准备
2):从控制台输入一个字符串
3):将从控制台输入的信息发送给服务器
4):从服务器读取响应,并进行打印
******注意*******
为了实现多客户端在同一服务器一起运行,并且互相不影响.此时我们需要多线程在服务器上来进行处理.
同时为了避免服务器的多次创建/销毁,所以我们可以利用线程池来解决.进一步节省了空间和时间.