CH9-网络编程

2023-11-17

【案例9-2】 模拟微信聊天

【案例介绍】

**1.**案例描述

​ 在如今,微信聊天已经人们生活中必不可少的重要组成部分,人们的交流很多都是通过微信来进行的。本案例要求:将多线程与UDP通信相关知识结合,模拟实现微信聊天小程序。通过监听指定的端口号、目标IP地址和目标端口号,实现消息的发送和接收功能,并显示聊天的内容。

**2.**运行结果

img

img

【案例目标】

  • 学会分析“模拟微信聊天”任务的实现思路。

  • 根据思路独立完成“模拟微信聊天”任务的源代码编写、编译及运行。

  • 掌握网络通信中UDP协议的编程原理。

  • 掌握UDP网络通信DatagramPacket和DatagramSocket的使用。

【案例分析】

​ (1)第一要知道用什么技术实现,通过上述任务描述可知此任务是使用多线程与UDP通信相关知识实现的。要实现图中的聊天窗口界面。首先需要定义一个实现微信聊天功能的类,类中需要定义访问微信聊天的输出语句,从而获取输入的发送端端口号、接收端端口号以及实现发送和接收功能的方法。

​ (2)实现发送数据的功能。该功能通过一个实现了Runnable接口的类实现,类中需要定义获取发送数据的端口号,并在实现run()的方法中,编写发送数据的方法。

​ (3)实现接收数据的功能。该功能通过一个实现了Runnable接口的类实现,类中需要定义获取接收数据的端口号,并在实现run()的方法中,编写显示接收到的数据的方法。

​ (4)创建完所有的类与方法后,运行两次程序,同时开启两个窗口来实现聊天功能。

【案例实现】

(1)创建微信聊天程序,其代码具体如下所示。

Room.java

import java.util.Scanner;
public class Room {
	public static void main(String[] args) {
		System.out.println("微信聊天欢迎您!");
		Scanner sc = new Scanner(System.in);
		System.out.print("请输入您的微信号登录:");
		int sendPort = sc.nextInt();
		System.out.print("请输入您要发送消息的微信号:");
		int receivePort = sc.nextInt();
		System.out.println("微信聊天系统启动!!");		
         //发送操作
		new Thread(new SendTask(sendPort), "发送端任务").start();
         //接收操作
		new Thread(new ReceiveTask(receivePort), "接收端任务").start();
	}
}

​ 上述代码中,第12行代码用多线程实现发送端口号以及实现发送端功能的方法。第14行代码用多线程实现接收端口号以及实现接收端功能的方法。

(2)创建发送数据的任务类,其代码如下所示。

SendTask.java

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class SendTask implements Runnable {
	private int sendPort; // 发数据的端口号
	// 构造方法
	public SendTask(int sendPort) {
		this.sendPort = sendPort;
	}
	@Override
	public void run() {
		try {
			// 1. 创建DatagramSocket对象
			DatagramSocket ds = new DatagramSocket();
			// 2.输入要发送的数据
			Scanner sc = new Scanner(System.in);
			while (true) {
				String data = sc.nextLine();// 获取键盘输入的数据
				// 3.封装数据到 DatagramPacket对象中
			byte[] buf = data.getBytes();
			DatagramPacket dp = new DatagramPacket(buf, buf.length,
			InetAddress.getByName("127.0.0.255"),sendPort);
				// 4.发送数据
				ds.send(dp);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

​ 上述代码中,第6~10行代码声明了一个名称为sendPort的变量表示发送数据的端口号,并通过该类的构造方法获取所输入的端口号。第12~29行代码,实现run()方法,在run()方法中,首先创建了DatagramSocket对象,然后通过Scanner对象和循环方法获取键盘输入的数据,并将获取的数据封装到了DatagramPacket对象中,最后通过DatagramSocket对象的send()方法发送数据。在循环方法中,由于发送数据时可能多次连续发送,所以将循环条件设置为true,表示不断循环获取和发送数据。在创建DatagramPacket对象时,为了让当前局域网的所有人都接收到消息,所以将IP地址设置为127.0.0.255。

(3)创建接收数据的任务类,其代码如下所示。

import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiveTask implements Runnable{
	private int receivePort;// 接收数据的端口号
	public ReceiveTask(int receivePort) {
		this.receivePort = receivePort;
	}
	@Override
	public void run() {
		try {
			// 1.DatagramSocket对象
			DatagramSocket ds = new DatagramSocket(receivePort);
			// 2.创建DatagramPacket对象
			byte[] buf = new byte[1024];
			DatagramPacket dp = new DatagramPacket(buf, buf.length);
			// 3.接收数据
			while (true) {
				ds.receive(dp);
				// 4.显示接收到的数据
				String str = new String(dp.getData(), 0, 
                                               dp.getLength());
				System.out.println("收到" + 
                   dp.getAddress().getHostAddress()
			     + "--发送的数据--" + str);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

​ 上述代码中,第4行代码声明了一个名称为receivePort的变量来表示接收数据的端口号,第5~7行代码定义一个该类的构造方法,并通过该类的构造方法获取所输入的端口号,第9~28行代码在run()方法中,首先创建了DatagramSocket对象,创建该对象时需要传入接收数据的端口号,然后创建用于接收数据的DatagramPacket对象,接下来通过循环的方法来接收数据,最后通过输出语句来显示接收到的数据。

【案例9-3】 字符串反转

【案例介绍】

1.案例描述

​ 在使用软件或浏览网页时,总会查询一些数据,查询数据的过程其实就是客户端与服务器交互的过程。用户(客户端)将查询信息发送给服务器,服务器接收到查询消息后进行处理,将查询结果返回给用户(客户端)。本案例要求编写一个模拟客户端与服务端交互的程序,客户端向服务器传递一个字符串(键盘录入),服务器将字符串反转后写回,客户端再次接收到的是反转后的字符串。本案例要求使用多线程与TCP通信相关知识完成数据交互。

2.运行结果

img

img

【案例目标】

  • 学会分析“字符串反转”思路。

  • 根据思路独立完成“字符串反转”任务的源代码编写、编译及运行。

  • 掌握网络通信中TCP协议的编程原理。

  • 掌握TCP网络通信ServerSocket和Socket的使用。

【案例分析】

​ (1)根据任务描述可以知道该程序用TCP通信技术实现,所以第一条就是定义客户端,键盘录入数据定义Scanner来实现,然后创建客户端指定IP地址和端口号,之后获取输出流,与输入流,最后将字符串写到服务器并将反转后的结果读出来打印在控制台。

​ (2)实现服务端的代码编写,首先创建服务端绑定客户端的端口号,并用Server的accept()方法接受客户端的请求。

​ (3)服务端定义run()方法实现之后获取输入输出流,将客户端发送过来的数据读取出来并采用链式编程的思想将字符串反转后返回到客户端。

【案例实现】

(1)创建客户端,用于录入输入的数据。其代码具体如下所示。

Client.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class client {
      public static void main(String[] args) throws UnknownHostException, 
                                                        IOException {
         //创建键盘录如对象
		Scanner sc = new Scanner(System.in);			
         //创建客户端,指定ip地址和端口号	
		Socket socket = new Socket("127.0.0.1", 54321);		
		BufferedReader br = new BufferedReader(new 
         InputStreamReader(socket.getInputStream()));	//获取输入流
         //获取输出流
		PrintStream ps = new PrintStream(socket.getOutputStream());
         //将字符串写到服务器去
		ps.println(sc.nextLine());							
		System.out.println(br.readLine()); //将反转后的结果读出来 
		socket.close();
	}
}

​ 上述代码中,第14行代码创建客户端指定IP地址与端口号,第15~18行代码获取输入与输出流,第20行代码用于将字符串写到服务器中去。第21行代码用于将反转后的结果读取出来。

(2)创建服务端实现将客户端数据反转并返回到客户端,其代码如下所示。

Server.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class server
{
    public static void main(String[] args) throws IOException
    {
        ServerSocket server = new ServerSocket(54321);
        System.out.println("服务器启动,绑定54321端口");
        while(true)
        {
            final Socket socket = server.accept();  //接受客户端的请求
            new Thread()        //开启一条线程
            {
                public void run()
                {
                    try
                    {
                        BufferedReader br = new BufferedReader(new InputStreamReader socket.getInputStream());  //获取输入流
                        PrintStream ps = new  PrintStream(socket.getOutputStream());//获取输出流
                        //将客户端写过来的数据读取出来
                        String line = br.readLine()
                        line = new StringBuilder(line).reverse().toString();   //链式编程
                        ps.println(line);   //反转后写回去
                        socket.close();
                    }
                    catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            } .start();
        }
    }
}

​ 上述代码中,第9行代码用于创建Server服务器绑定端口号。第12行代码用Server的accept()方法来接收客户端的请求。第22行代码用readLine()方法将客户端写过来的数据读取出来,第23~24行代码用链式编程的方式将字符串反转,第25行代码将反转后的字符串返回给客户端打印。

【案例9-4】 客户端向服务端上传文件

【案例介绍】

1.案例描述

​ 编写一个客户端向服务端上传文件的程序,要求使用TCP通信的的知识,完成将本地机器输入的路径下的文件上传到D盘中名称为upload的文件夹中。并把客户端的IP地址加上count标识作为上传后文件的文件名,即IP(count)的形式。其中,count随着文件的增多而增大,例如127.0.0.(1).jpg、127.0.0.(2).jpg。

2.效果显示

上传文件之前

img

上传文件之后

img

【案例目标】

  • 学会分析“文件上传”思路。

  • 根据思路独立完成“文件上传”任务的源代码编写、编译及运行。

  • 掌握网络通信中TCP协议的编程原理。

  • 掌握TCP网络通信ServerSocket和Socket的使用。

  • 掌握多线程的使用。

【案例分析】

​ (1)根据任务描述中使用TCP通信的知识实现文件上传功能可知,要实现此功能,需要定义一个服务器接收文件的程序和 一个客户端上传文件的程序。

​ (2)首先要编写服务器端程序来接收文件。服务器端需要使用ServerSocket对象的accept()方法接收客户端的请求,由于一个服务器可能对于多个客户端,所以当客户端与服务器端简历连接后,服务器需要单独开启一个新的线程来处理与客户端的交互,这时需要在服务器端编写开启新线程的方法。在新线程的方法中,需要获取客户端的端口号,并且使用输入输出流来传输文件到指定的目录中。

​ (3)编写客户端的功能代码,客户端功能的实现,因为是用户自己输入上传文件。所以要定义键盘录入。录入后需要使用Socket类来创建客户对象,并通过输入输出流来定义指定的文件。

​ (4)最后我们启动程序,先启动服务端程序,再运行客户端程序来测试上传的结果。

【案例实现】

(1)首先编写服务器端的程序,用来接收文件,其代码具体如下所示。

FileServer.java

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class FileServer
{
    public static void main(String[] args) throws Exception
    {
        //创建ServerSocket对象
        ServerSocket serverSocket = new ServerSocket(10001);
        while (true)
        {
            // 调用accept()方法接收客户端请求,得到Socket对象
            Socket s = serverSocket.accept();
            // 每当和客户端建立Socket连接后,单独开启一个线程处理和客户端的交互
            new Thread(new ServerThread(s)).start();
        }
    }
}
class ServerThread implements Runnable
{
    // 持有一个Socket类型的属性
    private Socket socket;
    // 构造方法中把Socket对象作为实参传入
    public ServerThread(Socket socket)
    {
        this.socket = socket;
    }
    public void run()
    {
        // 获取客户端的IP地址
        String ip = socket.getInetAddress().getHostAddress();
        // 上传图片个数
        int count = 1;
        try
        {
            InputStream in = socket.getInputStream();
            // 创建上传图片目录的File对象
            File parentFile = new File("D:\\upload\\");
            // 如果不存在,就创建这个目录
            if (!parentFile.exists())
            {
                parentFile.mkdir();
            }
            // 把客户端的IP地址作为上传文件的文件名
            File file = new File(parentFile, ip + "(" + count +").jpg");
            while (file.exists())
            {
                // 如果文件名存在,则把count++
                file = new File(parentFile, ip + "(" + (count++) +").jpg");
            }
            // 创建FileOutputStream对象
            FileOutputStream fos = new FileOutputStream(file);
            // 定义一个字节数组
            byte[] buf = new byte[1024];
            // 定义一个int类型的变量len,初始值为0
            int len = 0;
            // 循环读取数据
            while ((len = in.read(buf)) != -1)
            {
                fos.write(buf, 0, len);
            }
            // 获取服务端的输出流
            OutputStream out = socket.getOutputStream();
            // 上传成功后向客户端写出“上传成功”
            out.write("上传成功".getBytes());
            // 关闭输出流对象
            fos.close();
            // 关闭Socket对象
            socket.close();
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }
}

服务器端运行结果,运行结果如图所示。

img

​ 上述代码中,第10行代码用于创建一个ServerSocket对象,第11~16行代码用于在while(true)无限循环中调用ServerSocket的accept()方法来接收客户端的请求,没当和一个客户端建立Socket连接后,就开启一个新的线程和这个客户端进行交互,开启的新线程是通过实现Runnable接口创建的,重写的run()方法中实现了服务端接收并保存客户端上传文件的功能在第34行代码上对文件的保存目录用一个File对象进行封装,如果这个目录不存在就调用File的mkdir()方法创建这个目录,为了避免存放的图片名重复而导致的新上传的文件将已经存在的文件覆盖,在第30行代码定义了一个整型变量count,用于统计文件的数目,使用“IP地址(count).jpg”作为上传文件的名称。

​ 在第42~46行代码用于对表示文件名的File对象进行循环判断,如果文件名存在则一直执行count++。最后将从客户端接收的文件信息写入到指定的目录中,在第58~60行代码用于获取服务端的输出流,向客户端输出“上传成功”信息。通过图中运行结果可以看出,服务器端进入阻塞状态,等待客户端连接。

(2)创建客户端用于上传文件,其代码如下所示。

FileClient.java

import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class FileClient {
    public static void main(String[] args) throws Exception {
        // 创建客户端Socket
        Socket socket = new Socket("127.0.0.1", 10001);
        // 获取Socket的输出流对象
        OutputStream out = socket.getOutputStream(); 
        // 创建FileInputStream对象
        System.out.println("请输入你要上传文件的路径:");
        Scanner sc =new Scanner(System.in);
        String upload = sc.nextLine();
        if(!upload.isEmpty()){
            FileInputStream fis = new FileInputStream(upload);
            // 定义一个字节数组
            byte[] buf = new byte[1024];
            // 定义一个int类型的变量len
            int len; 
            // 循环读取数据
            while ((len = fis.read(buf)) != -1) { 
                out.write(buf, 0, len);
            }
            // 关闭客户端输出流
            socket.shutdownOutput();
            // 获取Socket的输入流对象
            InputStream in = socket.getInputStream(); 
            // 定义一个字节数组
            byte[] bufMsg = new byte[1024];
            // 接收服务端的信息
            int num = in.read(bufMsg); 
            String Msg = new String(bufMsg, 0, num);
            System.out.println(Msg);
            // 关键输入流对象
            fis.close(); 
            // 关闭Socket对象
            socket.close(); 
        }else {
            System.out.println("对不起请您输入文件路径后再上传!!!");
        }
    }
}

客户端运行结果如图所示。

img

​ 上述代码中,第9行代码用于创建一个Socket对象,指定连接服务器的IP地址和端口号,然后获取Socket的输出流对象。第17~25行代码用于创建FileInputStream对象读取我们键盘录入的文件名称,并通过Socket的输出流对象向服务端发送文件。

​ 发送完毕后调用Socket的shutDownOutput()方法关闭客户端的输出流。需要注意的是shutDownOutput()方法非常重要,因为服务器端程序在while循环中读取客户端发送的数据时,如果读取不到数据,fis.read(buf)方法会返回-1。也就是说,只要返回的值不是-1。就说明还有数据,需要一直读取,只有fis.read(buf)方法返回的值是-1时循环才会结束。

​ 如果客户端不调用shutDownOutput()方法关闭输出流,服务端的fis.read(buf)方法就不会返回-1,而会一直执行while循环,同时客户端读取服务端数据的read(byte [])方法也是一个阻塞方法,这样服务端和客户端程序就进入了一个“死锁”状态。两个程序都不能结束。客户端上传图片成功后,会读取服务端发送的“上传成功”信息,至此,客户端的程序编写完成。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CH9-网络编程 的相关文章

随机推荐

  • 区域气象-大气化学在线耦合模式(WRF/Chem)在大气环境领域实践技术应用

    大气污染是工农业生产 生活 交通 城市化等方面人为活动的综合结果 同时气象因素是控制大气污染的关键自然因素 大气污染问题既是局部 当地的 也是区域的 甚至是全球的 本地的污染物排放除了对当地造成严重影响外 同时还会在动力输送作用下 极大地影
  • 【小程序】解析小程序原理

    本文首发自 前端修罗场 一个专注 Web 技术 答疑解惑 面试辅导 职业发展的社区 实际学习过程中 有些同学常常会对小程序和 Web 应用之间的差别产生疑惑 它们之间到底有什么不同 Web 应用不能作为小程序吗 本期文章将会带你比较小程序和
  • JS如何判断是否为null、undefined、NaN

    判断null var exp null if exp typeof exp undefined exp 0 alert is null typeof exp undefined 排除了 undefined exp 0 排除了数字零和 fal
  • 【观影笔记】地平线:大数据时代(BBC)

    地平线 大数据时代 BBC 影片中的实例 大数据分析所需要素 感悟 影片中的实例 洛杉矶 利用预测地震余震发生的模型来预测犯罪 数据挖掘起源 约翰 格兰特Graunt 伦敦黑死病死亡记录 Phil Beales 基因生物学寻找疾病治疗方法
  • PostgreSQL 基本安装总结

    一 Mac 环境下的安装 brew install postgresql 1 1 查看当前环境版本 pg ctl V 1 2 初始化数据库 在开始使用数据库前 需要在磁盘上初始化一个数据库存储区域 通常称之为一个数据库集簇 SQL标准使用的
  • fastjson 问题

    问题 1 fastjson value 为null key 会丢失问题 2 SerializerFeature 配置参数 背景 和第三方系统进行对接 两边商量好了接口定义 有些是非必填项 从数据库查询出来的数据赋值给相应的key 有些Str
  • 会议是浪费工作时间的最佳去处

    本文为翻译初稿 更多精彩内容 敬请关注 高效能程序员的修炼 人民邮电出版社 今天你开了多少个会 这个星期呢 这个月呢 现在你再自问一下 那些会议中有多少是值得参加的 如果把相同的时间用在工作上 你又能完成多少事情 这不禁让人想知道 我们究竟
  • 【设计模式

    every blog every motto You can do more than you think https blog csdn net weixin 39190382 type blog 0 前言 设计模式 上 创建型 设计模式
  • 基于51单片机无线NRF24L01的温湿度光照采集

    接收端 原理图 发送端 原理图 实物焊接图 主端源程序 发送端程序 从机NRF24L01程序 ifndef API DEF define API DEF Define interface to nRF24L01 Define SPI pin
  • cJSON介绍与应用—基于VS以及STM32单片机

    一 cJSON介绍 cJSON是一个使用C语言编写的JSON数据解析器 具有超轻便 可移植 单文件的特点 使用MIT开源协议 cJSON的源码文件只有两个 1 cJSON h 2 cJSON c 使用的时候 只需要将这两个文件复制到工程目录
  • 数据仓库是什么?和数据库有何区别?

    在具体学习数据仓库之前先看一下数据中心的整体构架以及数据流向 DB 是现有的数据来源 可以为mysql SQLserver 文件日志等 为数据仓库提供数据来源的一般存在于现有的业务系统之中 ETL 是 Extract Transform L
  • Doxygen 详细使用

    doxygen的安装和基本使用可参考 Doxygen的安装和基本使用 常用选项 doxygen的所有选项的参考文档 doxygen官网文档 2 样式说明 doxygen可以自己自定义样式 手写 css文件 可以查看doxygen的源码 进行
  • 激光雷达LMS111在ROS上的使用

    LMS111 10100 在ROS上的测试与使用 准备工作 设备 硬件 LMS111 101000激光雷达 软件 ubuntu16 04 ROS 开始 设备连接 将激光雷达与处理器 电脑 工控机等 通过以太网连接好 激光雷达默认的IP地址为
  • Pytorch学习笔记(I)——预训练模型(三):VGG11网络结构

    VGG VGG11 VGG13 VGG16 VGG19 ResNet ResNet18 ResNet34 ResNet50 ResNet101 ResNet152 VGG features Sequential 0 Conv2d 3 64
  • Matlab神经网络训练函数train

    0 前言 本文基于MatlabR2009a分享神经网络的训练过程 1 启动训练 在Matlab中使用train函数对神经网络进行训练的时候 会弹出以下窗体 图1 1 由上图中的Netrual Network项可见 这是一个两层的网络 2 算
  • 适合Python入门的5本基础书籍

    Python 3标准库 对程序员而言 标准库与语言本身同样重要 它好比一个百宝箱 能为各种常见的任务提供完美的解决方案 所以本书是所有Python程序员都必备的工具书 全书以案例驱动的方式讲解了标准库中数百个模块的使用方法 如何工作 和工作
  • Java Web 远程调试

    Java Web 远程 调试 Tomcat 下载压缩版服务器 环境 Tomcat Eclipse 做远程调试我们并不需要其他特殊插件 1 配置Tomcat bin startup bat 在前面增加代码 SET CATALINA OPTS
  • linux三剑客awk命令详解之函数

    awk函数 在awk命令中 可以自定义函数 awk也有内置的函数 本篇文章主要介绍awk中的内置函数 awk内置函数分类 在awk中 内置函数主要分为算数函数 字符串函数 时间函数 其他函数等 以下列出一些常用的内置函数 算数函数 常用的主
  • html无法导入,如何修复“ImportError:无法导入名称'HTMLParseError'”?

    我正在尝试导入BeautifulGroup 但运行脚本时遇到错误 Traceback most recent call last File LinkCrawler py line 5 in from bs4 import Beautiful
  • CH9-网络编程

    案例9 2 模拟微信聊天 案例介绍 1 案例描述 在如今 微信聊天已经人们生活中必不可少的重要组成部分 人们的交流很多都是通过微信来进行的 本案例要求 将多线程与UDP通信相关知识结合 模拟实现微信聊天小程序 通过监听指定的端口号 目标IP