【Java】TCP网络编程:从可靠传输到Socket实战
活动发起人@小虚竹 想对你说:
这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!我们一起发掘写作的魅力,书写出属于我们的故事。我们诚挚邀请你参加为期14天的创作挑战赛!
提醒:在发布作品前,请将不需要的内容删除。
各位看官,大家早安午安晚安呀~~~
如果您觉得这篇文章对您有帮助的话
欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦
今天我们来学习【Java】TCP网络编程:从可靠传输到Socket实战
目录
1.首先我们再说一下TCP和UDP的区别和相同点
2.连接:通信双方都会记录对方的信息
3.主要是两个api ServerSocket和Socket
4.TCP服务端实战代码演示
5.TCP客户端实战代码演示
TCP的socket的api的差异很大,但是和前面的IO有很大的关联
1.首先我们再说一下TCP和UDP的区别和相同点
1.TCP是有连接的,UDP无连接(这一点可以在代码中体现)
2.TCP是面向字节流流的,UDP是面向数据报的
3.TCP是可靠传输的,UDP是不可靠传输的(这一点在代码中体现不出来)
4.TCP和UDP都是全双工的
2.连接:通信双方都会记录对方的信息
UDP:每次发送数据报都要指定对方的地址(UDP没有存储这个信息)
一张图
TCP:不需要(不过需要内核自动和客户端建连接(TCP的三次握手,后面我会进行讲解)这个过程是系统内核自动完成的)
对于应用程序来说,客户端是发起“建立连接”
服务器这边:把内核中建立好的连接拿到应用程序里面()
这个ServerSocket只负责绑定端口号,然后通过accept方法 把建立好的连接拿过来(但是一瞬间有很多连接的话,就像生产者消费者模型里面只能进行阻塞等待)
3.主要是两个api ServerSocket和Socket
ServerSocket是给服务器用的类,使用这个类用来绑定端口号(这个类负责把系统内核里面已经建立好的连接从队列里面拿过来,)
Socket:既会给服务器使用的类,也会给客户端使用,通过socket这个对象和客户端进行交互
(这两个类都是用来表示socket文件的,抽象了网卡这样的硬件设备)
4.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.Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TcpEchoServer { // 读取用Scanner ,发送用PrintWriter // 1.首先serverSocket调用系统API把连接拿过来,然后交给socket // 我们就有了socket这个对象,这个时候我们就可以宣布这个客户端成功和我们服务器建立了联系并且我们拿到了 // 2.然后我们通过字节流把数据从socket抽象的文件里面读取到,用try包起来,用scanner从流里面读取 // 3. 我们拿到字符串进行响应 // 4. 把返回的字符串通过字节流(我们的字符串通过字符流转换成字节流)写回去 // 5. 把信息打印出来 private ServerSocket serverSocket = null; public TcpEchoServer(int serverPort) throws IOException { // 利用这个系统API从内核中取到已经建立好的连接 // 这个和客户端构造的socket完全不一样,客户端的那个是我们已经拿到的连接 serverSocket = new ServerSocket(serverPort); // 服务器自己分配端口号, } public void start() throws IOException { System.out.println(\"服务器启动\"); // 把队列里面建立好的连接拿过来 while(true){ // 要一直不断地从那个内核里面不断地拿我们已经建立好的连接!!! Socket Clientsocket = serverSocket.accept(); /* //创建一个新的线程把这个请求进行响应 Thread t = new Thread(() ->{ possessCollection(Clientsocket); }); t.start();*/ // 但是线程池是更好一点点的选择 ExecutorService service = Executors.newCachedThreadPool(); service.submit(() ->{ possessCollection(Clientsocket); }); } } public void possessCollection(Socket Clientsocket){ // System.out.printf(\"[%s,%d] 客户端上线\\n\" , Clientsocket.getInetAddress(),Clientsocket.getPort()); // 把端口号和IP拿到 try( InputStream inputStream = Clientsocket.getInputStream(); // 这里要用 \" ; \" OutputStream outputStream = Clientsocket.getOutputStream()){ // 客户可能等会还会继续发送请求(我们循环处理) Scanner scanner = new Scanner(inputStream);//每一次读一次缓冲区,缓冲区里面的东西就少一次,都被我读出来了嘛 while(true){ if(!scanner.hasNext()){// 用户不再输入的时候,就直接跳出循环!!!,一直等待用户输入 System.out.printf(\"[%s,%d] 客户端下线\\n\" , Clientsocket.getInetAddress(),Clientsocket.getPort()); break; } String request = scanner.next(); // 拿到字符串进行响应 String response = process(request);// 拿到响应 //4. 拿到字符串的响应,然后我们通过字符流转字节流传递出去 PrintWriter printWriter = new PrintWriter(outputStream); printWriter.println(response); // 此处的println不是写到控制台了,而是写到outputStream的流对象了,也就是写入到ClientSocket里面了 // 自然这个数据也是通过网卡发出去了 printWriter.flush();// 再刷新一下缓存,防止没有发出去, // 总结: 发送和接收数据都是通过socket文件的字节流输入输出来实现,用scanner读字节流,用printWriter写字符串 // 5.打印一下交互过程 System.out.printf(\"[%s,%d] req=%s resp=%s\\n\",Clientsocket.getInetAddress(),Clientsocket.getPort(), request,response); } }catch(IOException e){ e.printStackTrace(); }finally{ try { Clientsocket.close(); } catch (IOException e) { throw new RuntimeException(e); } } } private String process(String request) { // 回显服务器 return request; } public static void main(String[] args) throws IOException { TcpEchoServer tcpEchoServer = new TcpEchoServer(9090); tcpEchoServer.start(); }}
但是这里面会出现两个问题
问题一:为什么我们的ServerSocket对象没有进行close操作,但是Socket对象却需要close操作呢?这样不会出现文件资源泄露吗?
首先我们需要知道什么时候回造成文件资源泄露?
一直频繁申请但是一直不释放就会(什么文件的表项啥的)
但是ServerSocket对象从头到尾只创建过一次对象,而且一直在把内核中建立的连接拿到。所以说ServerSocket这个对象的生命周期是是伴随着进程消失的(因此不需要特地的进行回收,等到进程解释JVM会把这个进程里面的东西一起回收了)
但是?
问题二:等待我们写完客户端的代码之后进行讲述
如果启动多个客户端和服务器进行连接(就不行了,这个服务器一直在等待客户端进行输入,我们就需要多个线程并发拿到这个连接)
但是频繁地创建和销毁线程也会有很大开销,线程池是更好一点点选择
5.TCP客户端实战代码演示
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;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 TcpEchoClient { /** * 1.随机分配一个端口的发出信息(我们要把服务器的IP地址和端口号给搞进去) * 2.我们循环输入一个字符串,把这个字符串用字符流转换成字节流写到socket抽象的文件里面 * 3.然后我们接收响应(不像服务器,我们又不需要进行处理) * 4.我们直接通过Scanner把字节流里面的内容读出来就好了 */ 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(OutputStream outputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream()){ while(true){ /* if(!scanner.hasNext()){ // 用户不想输入了,就直接退出了 break; 不需要这个 }*/ // * 2.我们循环输入一个字符串,把这个字符串用字符流转换成字节流写到socket抽象的文件里面 String request = scanner.next(); PrintWriter printWriter = new PrintWriter(outputStream); // 这些流尽量都放到try()里面 printWriter.println(request); printWriter.flush();// 一定不要忘记刷新缓存区\' // * 4.我们直接通过Scanner把字节流里面的内容读出来就好了 Scanner scannerNetwork = new Scanner(inputStream); // 这个Scanner尽量也是放到try()里面 String response = scannerNetwork.next(); System.out.println(response); } }catch(IOException e){ e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpEchoClient tcpEchoClient = new TcpEchoClient(\"127.0.0.1\",9090); tcpEchoClient.start(); }}
上述就是【Java】TCp网络编程:TCP网络编程:从可靠传输到Socket实战的全部内容啦
能看到这里相信您一定对小编的文章有了一定的认可。
有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正~~