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)


相关文章
|
12月前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
268 11
|
4月前
|
JSON 移动开发 网络协议
Java网络编程:Socket通信与HTTP客户端
本文全面讲解Java网络编程,涵盖TCP与UDP协议区别、Socket编程、HTTP客户端开发及实战案例,助你掌握实时通信、文件传输、聊天应用等场景,附性能优化与面试高频问题解析。
|
2月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
177 1
|
2月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
203 1
|
3月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
435 1
|
2月前
|
机器学习/深度学习 分布式计算 Java
Java与图神经网络:构建企业级知识图谱与智能推理系统
图神经网络(GNN)作为处理非欧几里得数据的前沿技术,正成为企业知识管理和智能推理的核心引擎。本文深入探讨如何在Java生态中构建基于GNN的知识图谱系统,涵盖从图数据建模、GNN模型集成、分布式图计算到实时推理的全流程。通过具体的代码实现和架构设计,展示如何将先进的图神经网络技术融入传统Java企业应用,为构建下一代智能决策系统提供完整解决方案。
333 0
|
6月前
|
设计模式 算法 Java
Java SE 与 Java EE 组件封装使用方法及实践指南
本指南详细介绍了Java SE与Java EE的核心技术使用方法及组件封装策略。涵盖集合框架、文件操作、Servlet、JPA、EJB和RESTful API的使用示例,提供通用工具类与基础组件封装建议,如集合工具类、文件工具类、基础Servlet、实体基类和服务基类等。同时,通过分层架构集成示例展示Servlet、EJB和JPA的协同工作,并总结组件封装的最佳实践,包括单一职责原则、接口抽象、依赖注入、事务管理和异常处理等。适合希望提升代码可维护性和扩展性的开发者参考。
203 0
|
9月前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
370 23
|
10月前
|
安全 网络协议 Java
Java网络编程封装
Java网络编程封装原理旨在隐藏底层通信细节,提供简洁、安全的高层接口。通过简化开发、提高安全性和增强可维护性,封装使开发者能更高效地进行网络应用开发。常见的封装层次包括套接字层(如Socket和ServerSocket类),以及更高层次的HTTP请求封装(如RestTemplate)。示例代码展示了如何使用RestTemplate简化HTTP请求的发送与处理,确保代码清晰易维护。
|
10月前
|
缓存 网络协议 Java
JAVA网络IO之NIO/BIO
本文介绍了Java网络编程的基础与历史演进,重点阐述了IO和Socket的概念。Java的IO分为设备和接口两部分,通过流、字节、字符等方式实现与外部的交互。
308 0

热门文章

最新文章