Java SE:浅析网络编程(中)

简介: Java SE:浅析网络编程(中)

五. TCP网络编程

5.1 网络通信

Java语言的基于套接字TCP编程分为服务端编程和客户端编程,其通信模型如图所示:

基于TCP的Socket通图:

5.2 网络编程案例

5.2.1 基于TCP协议的网络编程案例

案例一:服务器端发送消息给客户端,客户端接收消息

🔔服务器端需求:

  1. 开启一个TCP协议的服务,在8888端口号监听客户端的连接。
  2. 接收一个客户端的连接
  3. 给这个客户端发一句话: 欢迎登录
  4. 关闭服务器

🔔客户端需求:

  1. 与服务器建立连接,或者说和服务器发起连接请求
  2. 直接接收服务器发过来的“欢迎登录”
  3. 关闭客户端连接

代码演示如下:

public class Server1 {
    public static void main(String[] args) throws IOException {
        //开启一个TCP协议的服务,在8888端口号监听客户端的连接
        ServerSocket server=new ServerSocket(8888);
        //正式接收客户端的连接
        Socket socket=server.accept();
        /*
        这是一个阻塞式方法,
    如果此时没有客户端来请求,则这句代码一直等待。这句代码执行一次,就表示有一个客户端请求连接了,并且    连接成功;
    连接成功后,会给它分配一个Socket对象,这个socket对象用来和这个客户端进行数据的传输通信。
        */
        //如果连接成功,则会打印如下这句话
        System.out.println(socket.getInetAddress()+"连接成功");
        //给该客户端发送一句话,欢迎登录
        OutputStream out=socket.getOutputStream();//套接字的输出流,发送消息
        String info="欢迎登录";
        out.write(info.getBytes());//将字符串转换为字节数据
        //如果后面不和这个客户端通信,服务器就要关闭
        socket.close();
        //如果服务器后面不接受其他客户端连接,那么服务关闭,一般不关闭。
        server.close();
    }
}
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket=new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
        InputStream input = socket.getInputStream(); //流套接字的输入流,用来接收并读取消息
        byte[] data=new byte[1024];//用来装接收来自的服务器的字节数据
        int len;
        while ((len=input.read(data))!=-1){//从输入流中读取一定数量的字节,并将其存储在缓冲区数组 data 中,返回的是实际读取的字节数
            System.out.println(new String(data,0,len));// 通过使用平台的默认字符集解码指定的 byte 数组data,构造一个新的 String。
                                                             //即将每次读取的实际字节数解码为string输出到控制台
        }
        //后续不不需要通信,就关闭连接
        socket.close();
    }
}

案例二:客户端与服务器端多次通信

🔔服务器端需求:

(1)开启一个TCP协议的服务,在8888端口号监听客户端的连接(2)接收一个客户端的连接

(3)接收客户端发过来的单词或词语,然后我把它“反转”例如: 客户端发过来“heLLo",反转"oLLeh"

(4)把反转后的单词或词语,返回给客户端

(5)直到客户端发过来"bye"为止

🔔客户端需求:

(1) 与服务器建立连接,或者说和服务器发起连接请求(2)从键盘输入单词,并且按行给服务器发送每一个单词

(3) 直到输入bye结束

(4)结束后要断开连接

代码演示如下:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket server=new ServerSocket(8888);
        Socket socket=server.accept();
        System.out.println(socket.getInetAddress()+"连接成功");
        InputStream input = socket.getInputStream();//服务器端接收客户端发来的信息【字节输入流】
        InputStreamReader isr=new InputStreamReader(input); //将字节输入流通过转换流转换为字符输入流
        BufferedReader br=new BufferedReader(isr);//缓冲流包裹字符输入流,我时希望可以使用缓冲流独有的方法readLine()
        OutputStream output = socket.getOutputStream();//服务器发送给客户端的消息
/*        OutputStreamWriter osw=new OutputStreamWriter(output);//将字节输出流转换为字符输出流
        BufferedWriter bw=new BufferedWriter(osw);//缓冲流包裹字符输出流,可以使用它独有的方法;*/
        PrintStream pr=new PrintStream(output);
        String s ;
        while (!(("bye".equals(s=br.readLine())))){
            System.out.println("客户端发来的单词或词语:"+s);
            if (s.equals(null)){
                throw new NullPointerException("客户端发来的字符串为空");
            }
            StringBuilder sb=new StringBuilder(s);
            sb.reverse();
            System.out.println("服务器已反转单词:"+sb.toString());
            pr.println(sb.toString());//将反转后的字符串换发给客户端
        }
        socket.close();
        server.close();
    }
}
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket=new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
        OutputStream output = socket.getOutputStream();//字节输出流;output输出,从内存写出到文件,可以发送消息
/*        OutputStreamWriter osw=new OutputStreamWriter(output);//字节流转换为字符输出流
        BufferedWriter bw=new BufferedWriter(osw);//缓冲流包裹字符流*/
        PrintStream pr=new PrintStream(output);
        Scanner input=new Scanner(System.in);//System.in:默认键盘输入
        InputStream in = socket.getInputStream();//字节输入流;接收消息
        InputStreamReader isr=new InputStreamReader(in);//字节输入流转换为字符输入流
        BufferedReader br=new BufferedReader(isr);//缓冲流包裹字符输入流
        while (true){
            System.out.print("请输入单词或词语:");
            String word=input.next();
            pr.println(word);
            if (word.equals("bye")){
                break;
            }
            System.out.println("服务器返回的结果:"+br.readLine());
        }
        socket.close();
    }
}

案例三:上述需求不变,服务器端要与多个客户端多次通信

🔔服务器端需求:

(1)开启一个TCP协议的服务,在8888端口号监听客户端的连接(2)接收一个客户端的连接

(3)接收客户端发过来的单词或词语,然后我把它“反转”例如: 客户端发过来“heLLo",反转"oLLeh"

(4)把反转后的单词或词语,返回给客户端

(5)直到客户端发过来"bye"为止

🔔客户端需求:

(1) 与服务器建立连接,或者说和服务器发起连接请求(2)从键盘输入单词,并且按行给服务器发送每一个单词

(3) 直到输入bye结束

(4)结束后要断开连接

🤮服务器端要同时与多个客户端通信

核心实现思路:运用多线程实现同时与多个客户端通信

代码演示如下:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(8888);
        //接收多个客户端连接
        while (true) {
            Socket socket = server.accept();
            System.out.println(socket.getInetAddress()+"连接成功");
            new ClientHandle(socket).start();
            //服务器端一直运行
//            server.close();
        }
    }
}
 class ClientHandle extends Thread{
    private Socket socket;
    public ClientHandle(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run() {
            //这里编译器报红了,此异常为受检异常,要么抛出去;要么try...catch。但是这里不能跑出去,run()方法是重写方法,对于
            //受检异常来说,如果被重写方法没有抛出此受检异常,那么重写的方法不能跑出去,只能自己try..catch
         try (
                 InputStream input = socket.getInputStream();//服务器端接收客户端发来的信息【字节输入流】
                 InputStreamReader isr = new InputStreamReader(input); //将字节输入流通过转换流转换为字符输入流
                 BufferedReader br = new BufferedReader(isr);//缓冲流包裹字符输入流,我时希望可以使用缓冲流独有的方法readLine()
                 OutputStream output = socket.getOutputStream();//服务器发送给客户端的消息
                 PrintStream pr = new PrintStream(output);
                 ) {
             String s;
             //这里编译器报红了,此异常为受检异常,要么抛出去;要么try...catch
             while (!(("bye".equals(s = br.readLine())))) {
                 System.out.println("客户端发来的单词或词语:" + s);
                 if (s.equals(null)) {
                     throw new NullPointerException("客户端发来的字符串为空");
                 }
                 StringBuilder sb = new StringBuilder(s);
                 sb.reverse();
                 System.out.println("服务器已反转单词:" + sb.toString());
                 pr.println(sb.toString());//将反转后的字符串换发给客户端
             }
         }catch (Exception e){
             e.printStackTrace();
         }finally {
             //这里编译器报红了,此异常为受检异常,要么抛出去;要么try...catch
             try {
                 socket.close();
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
    }
}
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket=new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
        OutputStream output = socket.getOutputStream();//字节输出流;output输出,从内存写出到文件,可以发送消息
        PrintStream pr=new PrintStream(output);
        Scanner input=new Scanner(System.in);//System.in:默认键盘输入
        InputStream in = socket.getInputStream();//字节输入流;接收消息
        InputStreamReader isr=new InputStreamReader(in);//字节输入流转换为字符输入流
        BufferedReader br=new BufferedReader(isr);//缓冲流包裹字符输入流
        while (true){
            System.out.print("请输入单词或词语:");
            String word=input.next();
            pr.println(word);
            if (word.equals("bye")){
                break;
            }
            System.out.println("服务器返回的结果:"+br.readLine());
        }
        socket.close();
    }
}

案例四:多个客户端上传文件

🔔需求:

每一个客户端启动后都可以给服务器上传一个文件,服务器接收到文件后保存到一个upLoad目录中,可以同时接收多个客户端的文件

代码演示如下:

第一版代码:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
//需求:
//每一个客户端启动后都可以给服务器上传一个文件,服务器接收到文件后保存到一个upLoad目录中,可以同时接收多个客户端的文件
public class Server2 {
    public static void main(String[] args) throws Exception{
        ServerSocket server=new ServerSocket(8888);
        while (true){
            Socket socket = server.accept();//建立与客户端的连接
            System.out.println(socket.getLocalAddress()+"连接成功");
            new ClientsHandle(socket).start();//实现服务器端同时与多个客户端通信
        }
    }
}
class ClientsHandle extends Thread {
    private Socket socket;
    public ClientsHandle(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try (
                InputStream in = socket.getInputStream();//字节输入流,接收文件
                BufferedInputStream bis = new BufferedInputStream(in);//缓冲流
                ObjectInputStream ois = new ObjectInputStream(bis);//接收的数据中包含文件名和文件
                OutputStream out = socket.getOutputStream();//字节输出流,发送消息
                PrintStream pr=new PrintStream(out);
        ) {
            String fileName = ois.readUTF();
//            String ext= fiLeName.substring(fiLeName.lastIndexOf("."));//拿到文件后缀名
            String newFileName=System.currentTimeMillis()+"&"+socket.getInetAddress().getHostAddress()+"&"+fileName;
            FileOutputStream fos=new FileOutputStream(new File("E:\\JavaDemo\\upLoad",newFileName));
            BufferedOutputStream bos=new BufferedOutputStream(fos);
            byte[] data=new byte[1024];//1kb
            int len;
            while ((len=ois.read(data))!=-1){
                bos.write(data,0,len);
            }
            bos.flush();
            pr.println(newFileName+"已上传完毕");//告诉客户端文件已上传完毕
            bos.close();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class Client2 {
    public static void main(String[] args) throws Exception {
        Socket socket=new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
        try(
                OutputStream out = socket.getOutputStream();//向服务器发送文件
                ObjectOutputStream oos=new ObjectOutputStream(out);
                InputStream in = socket.getInputStream();//字节输入流,接收服务器返回的消息
                InputStreamReader isr=new InputStreamReader(in);//将字节输入流转换为字符输入流
                BufferedReader br=new BufferedReader(isr);
            ){
//            File file=new File("D:\\BaiduNetdiskDownload\\尚硅谷2022Java\\01-Java基础【完结】\\尚硅谷_JavaEE_01_JavaSE课程资料\\09_api","JDK_API_1.6_zh_中文.CHM");
            Scanner input=new Scanner(System.in);
            System.out.print("请输入要上传的文件路径:");
            String filePath = input.nextLine();
            File file=new File(filePath);
            FileInputStream fis=new FileInputStream(file);//文件输入流,从本地文件读取文件内容
            BufferedInputStream bis=new BufferedInputStream(fis);//给文件输入流套上字节缓冲流,加快读取数据速度
            oos.writeUTF(file.getName());//发送文件名
            byte[] data=new byte[1024];//1kb
            int len;
            while ((len=bis.read(data))!=-1){
                oos.write(data,0,len);
            }
            System.out.println("客户端已经发送完毕");
            oos.flush();
            //D:\Test\Image\dog.png
            //不加下面的代码,出现了这样的问题:客户端输入本地文件路径后,一直卡住不动,服务器端也是卡住不动,
            //目的文件夹中的图片虽然有边框,但是无法显示,数据没有完全写入进去
 /*
            视频中给出的原因:客户端发完文件以后,没有关闭输出通道。服务器端在读取客户端送过来的文件数据中一直没有读到“-1”这个标记
            然后就没有返回“newFileName+"已上传完毕" ” 这句话给客户端
            我个人的理解:文件的字节数据就像水一样,IO流就像水管,服务器端和客户端就像两个抽水机,客户端把字节数据抓取,放在流中,服务器端一直
            在往流中拿数据,若客户端把字节数据都放完了,它那一端的流入口没有关闭,服务器端就一直在流里抓数据,若一直到达流的末尾,它会一直阻塞
            若客户端关闭了它那边的流通道,服务器端便读到流的末尾,因为此时的流就像开盖的啤酒瓶,服务器端在瓶口,客户端在瓶底,见底了
            api文档这样解释:public abstract int read()
                  throws IOException从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。
                  如果因为已经到达流末尾而没有可用的字节,则返回值 -1。
                  在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。
*/
            socket.shutdownOutput();//关闭输出通道,对方才能读取到-1标识
            System.out.println(br.readLine());
            bis.close();
            fis.close();
/*
*           我在客户端与服务器端中对流的构建均采用了try()...catch,此try()...catch会自动关闭消耗资源的流对象
*           之前测试了几次代码,出现了socket closed的异常:Java.net.SocketEception
*           原因:tcp协议在进行三次握手,四次挥手的过程中,程序的底层中socket与所有的IO流都关闭了,因此出现了异常,而tcp挥手的时间点与
*           程序底层try()...catch自动关闭各种流的系统资源的时间点不好把控。
*           解决方案:使用普通的try...catch
*
* */
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            socket.close();
        }
    }
}![1678981728071](C:\Users\king\AppData\Roaming\Typora\typora-user-images\1678981728071.png)


相关文章
|
13天前
|
Java
【思维导图】JAVA网络编程思维升级:URL与URLConnection的逻辑梳理,助你一臂之力!
【思维导图】JAVA网络编程思维升级:URL与URLConnection的逻辑梳理,助你一臂之力!
30 1
|
13天前
|
XML JSON 搜索推荐
【高手过招】JAVA网络编程对决:URL与URLConnection的高级玩法,你敢挑战吗?
【高手过招】JAVA网络编程对决:URL与URLConnection的高级玩法,你敢挑战吗?
36 0
|
13天前
|
Java
【实战演练】JAVA网络编程高手养成记:URL与URLConnection的实战技巧,一学就会!
【实战演练】JAVA网络编程高手养成记:URL与URLConnection的实战技巧,一学就会!
28 3
|
13天前
|
安全 Java 网络安全
【认知革命】JAVA网络编程新视角:重新定义URL与URLConnection,让网络资源触手可及!
【认知革命】JAVA网络编程新视角:重新定义URL与URLConnection,让网络资源触手可及!
28 2
|
13天前
|
存储 算法 Java
Java中的集合框架深度解析云上守护:云计算与网络安全的协同进化
【8月更文挑战第29天】在Java的世界中,集合框架是数据结构的代言人。它不仅让数据存储变得优雅而高效,还为程序员提供了一套丰富的工具箱。本文将带你深入理解集合框架的设计哲学,探索其背后的原理,并分享一些实用的使用技巧。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇通往高效编程的大门。
|
14天前
|
编解码 网络协议 Oracle
java网络编程入门以及项目实战
这篇文章是Java网络编程的入门教程,涵盖了网络编程的基础知识、IP地址、端口、通讯协议(TCP和UDP)的概念与区别,并提供了基于TCP和UDP的网络编程实例,包括远程聊天和文件传输程序的代码实现。
java网络编程入门以及项目实战
|
11天前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
31 0
|
13天前
|
缓存 Java API
【技术前沿】JAVA网络编程黑科技:URL与URLConnection的创新应用,带你飞越极限!
【技术前沿】JAVA网络编程黑科技:URL与URLConnection的创新应用,带你飞越极限!
24 0
|
网络协议 Java API
|
缓存 网络协议 Java