网络编程TCP
TCP的核心特点:面向字节流(UDP是数据报),所有的读写的基本单位都是byte
ServerSocket:专门给服务器使用的,负责连接,不对数据进行操作
Socket:服务器和客户端都可以使用
当服务器启动的时候绑定端口号
因为TCP是有连接的,所以要使用accept去确保连接的开始
Socket要传入服务器的ip和端口号
因为TCP面向的是字节流,所以他没有向UDP数据报的那种sent和review方法,有的是字节流的读取(和之前文件IO很像)
TCPEchoServer
思路
实现TCPEchoServer,首先我们需要知道,所有的代码都是无法直接操作网卡的,一开始,我创建了一个serversocket类型的空变量,然后在构造方法中传入端口号,这个就相当于,我使用serversocket类这个代理人去帮我向操作系统申请一个小窗口,和网卡进行沟通,连接之后,因为serversocket只是连接,无法操作数据,所以就要使用socket来操作,然后在start方法中,首先我使用printf格式打印出来日志,然后因为TCP面向的是字节流,所以我不能向UDP那样使用数据报datagrampacket去进行操作,而是要使用inputstream和outputstream进行操作,这两个本来是需要使用read和write进行操作的,但是为了方便的情况下,我就使用scanner和printwrite进行套壳,然后因为我们这个是服务器,所以要在processconnecition方法中写内层一个死循环反复进行读取,然后当有输入的时候,使用一个string类型的去接收,接收到之后,就要进行处理,因为现在是回显式所以先不管,然后处理完响应只需要发回去就行,这个时候也有一个注意的地方,就是缓冲区,在java中,因为每次直接对网络或者文件进行修改太慢了,所有就会存在一个缓冲区,把你写的全部先存在一个地方,所以他并没有马上就传输出去,必须要等你使用flush把他冲走,他才会真的发送
具体实现
第一步
这一步首先就是创建一个空的serversocket类型的变量,然后在构造方法中初始化的时候传入使用的端口号,说人话就是你没办法直接操作网卡,所以要找serversocket这个代理人帮你和操作系统申请一个窗口,让你能够和网卡进行沟通
第二步
这一步首先就是打印日志,服务器启动,然后因为TCP是有连接的,所以我们需要先处理外层的连接,然后再去处理连接之后的事情,使用socket这个来处理文件的读取注意这个时候因为可能不只一个连接所以我们需要加入一个死循环确保可以支持多个客户端使用
第三步(核心)
这一步就是整个服务器最核心的地方了,首先当确立了连接之后,我们会记录日志,把对方的IP和端口号全部获取到,因为TCP协议是字节流的,所以这个时候就需要使用inputstream和outputstream这两个操作字节流,这里可以使用另外一种写法,每次读取一个数组的长度,读到-1就停止那种,但是就很麻烦,所以我们直接使用scanner和printwrite这两个更方便,只需要对inputstream和outputstream进行套个壳就行,套完壳之后就可以直接使用了。之后就很简单了,只要连接没断开就会一直等,后续的逻辑就是先读取请求,然后计算并返回响应,注意要刷新缓冲区,不然就会出现发不出去的情况,最后打印一下日志就结束。
源码
package NetWork; 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: zhany * Date: 2025-12-15 * Time: 15:07 */ /*tcp协议的特点:有连接,面向字节流,全双工,可靠传输*/ public class TcpServerDemo { //首先,创建一个serversocket的空变量,Serversocket就是专门用来连接的 ServerSocket serverSocket = null; //初始化的同时指定端口号 public TcpServerDemo(int serverPort) throws IOException { serverSocket = new ServerSocket(serverPort); } //start方法 public void start() throws IOException { //首先,这个是服务器,所以需要24小时工作 System.out.println("服务器启动"); while(true){ //前面的serversocket任务已经结束,只负责连接不负责数据的修改,下面登场的是socket Socket clientSocket = serverSocket.accept(); //连接完就要负责处理数据了 Thread thread = new Thread(()->{ processconection(clientSocket); }); thread.start(); } } private void processconection(Socket clientSocket) { //先打印日志 System.out.printf("[%s,%d],客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort()); try(InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) { while (true){ //字节流需要使用input和output //1.获取并解析连接 Scanner scanner = new Scanner(inputStream); PrintWriter writer = new PrintWriter(outputStream); if (!scanner.hasNext()){ System.out.printf("[%s,%d],客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort()); break; } String request = scanner.next(); //计算并返回 String response = process(request); writer.println(response); //刷新缓冲区 writer.flush(); } } 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 { TcpServerDemo tcpServerDemo = new TcpServerDemo(9090); tcpServerDemo.start(); } }TcpEchoClient
思路
客户端的思路就比较简单,首先在构造方法中,需要初始化一下要去访问的服务器和端口号,然后在start方法中也是比较简单的,因为tcp是字节流的,所以需要直接使用inputstream和outputstream,直接使用套壳的scanner和printwrite会简单一些,不使用就要使用文件io的办法,然后在控制台读取用户的输入,然后发送给服务器,然后接受服务器的请求就可以了,注意这个也是要释放缓冲区的。
具体实现
第一步
Tcp中是提供构造方法让你可以直接输入网址的类似于127.0.0.1,在udp中要调用方法getname才行,这里初始化了要访问的ip和端口号
第二步
核心的start方法,这里就很简单了,因为TCP是字节流,所以他进行的操作也是需要使用inputstream和outputstream来进行操作的,使用scanner和printwrite进行套壳就不需要使用基础的文件io那种复杂的写法,然后只需要在控制台读取用户的输入,然后发送,接收返回值就行了
源码
package NetWork; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; public class TcpClientDemo { private Socket socket; private static final String EXIT_CMD = "exit"; public TcpClientDemo(String serverIP, int serverPort) throws IOException { socket = new Socket(serverIP, serverPort); } public void start() { System.out.println("客户端启动"); System.out.println("输入内容发送到服务器(输入'exit'退出)"); try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); Scanner scanner = new Scanner(System.in); Scanner scannerNet = new Scanner(inputStream); PrintWriter writer = new PrintWriter(outputStream)) { while (true) { String request = scanner.nextLine(); if (EXIT_CMD.equalsIgnoreCase(request)) { break; } writer.println(request); writer.flush(); if (scannerNet.hasNextLine()) { String response = scannerNet.nextLine(); System.out.println("服务器响应: " + response); } } } catch (IOException e) { System.err.println("通信异常: " + e.getMessage()); } finally { try { if (socket != null && !socket.isClosed()) { socket.close(); } } catch (IOException e) { System.err.println("关闭socket异常: " + e.getMessage()); } } } public static void main(String[] args) { try { TcpClientDemo tcpClientDemo = new TcpClientDemo("127.0.0.1", 9090); tcpClientDemo.start(); } catch (IOException e) { System.err.println("连接服务器失败: " + e.getMessage()); } } }上述的代码中,存在一个问题,就是多个客户端去访问的时候,会被卡住,阻塞在
因为当代码执行到确立连接之后,他就会往下走,跳到processconnection里面去,这个时候,后面的线程想要连接就必须等待前面的结束完才可以用
如何解决这个问题?
多线程这个时候就诞生了
只需要在在服务器里面加一个线程
这样就能实现让多线程去实现直接开启分身模式(小心超人上线)
但是每次都创建一个线程和销毁一个线程,开销的很大的,有没有更好的方法,有
线程池方法
在一开始就创建好n个线程,每当一个客户端来了就使用一下线程,这样就能最大限度使用服务器