菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信)

  • UDP编程
  • TCP编程(Socket通信)
    • 单个客户端的连接
    • 多个客户端的连接(聊天室案例)

UDP编程

在上一篇中讲解了UDP协议是非面向连接的不安全但效率较高的通信协议。在了解完概念之后我们用Java来实现UDP编程。
Java中通过DatagramSocket和DatagramPacket来实现UDP通信,通信过程一般分为以下几步:
发送端(客户端):

  1. 创建客户端DatagramSocket类+端口
  2. 准备数据
  3. 打包DatagramPacket+地址及端口
  4. 发送
  5. 释放资源
    接收端(服务端):
  6. 创建服务端DatagramSocket类+端口
  7. 准备接收容器
  8. 接收数据包
  9. 分析
  10. 释放资源
    依据这个过程我们直接通过代码来理解UDP发送过程:

首先构建服务端:

public class MyServer {public static void main(String[] args) throws IOException {//1、创建服务端 +端口DatagramSocket server = new DatagramSocket(8888);//2、准备接受容器byte[] container = new byte[1024];//3、封装成 包 DatagramPacket(byte[] buf, int length)      DatagramPacket packet =new DatagramPacket(container, container.length) ;//4、接受数据server.receive(packet);//5、分析数据byte[] data =packet.getData();int len =packet.getLength();System.out.println(new String(data,0,len));//6、释放server.close();}}

这时服务端运行就会进入阻塞状态等待数据:

然后构建客户端

public class MyClient {public static void main(String[] args) throws IOException {//1、创建客户端 +端口DatagramSocket client = new DatagramSocket(6666);//2、准备数据String msg ="udp编程";byte[] data =msg.getBytes();//3、打包(发送的地点 及端口) DatagramPacket(byte[] buf, int length, InetAddress address, int port)DatagramPacket packet = new DatagramPacket(data,data.length,new InetSocketAddress("localhost",8888));//4、发送client.send(packet);//5、释放client.close();}
}

运行客户端发送数据后服务端就会得到响应:

UDP的特点就是不管有没有服务器等待数据,客户端都可以发送数据,上例中不启动服务客户端运行依然不会报错,但数据会丢失。

TCP编程(Socket通信)

单个客户端的连接

TCP相对UDP而言,需要建立连接、安全可靠但效率较低。它在通信时两端必须连接才能进行通信。
何为Socket,比如用浏览器访问一个网站,点击访问后,浏览器会申请与服务器进行连接,连接后才能进行数据交互操作,在关闭网页后连接会自动断开。我们把客户端与服务端之间建立的连接就称之为Socket。
Java中Socket通信主要使用ServerSocket类来建立连接,通过Socket类来进行数据交互,我们直接通过代码来讲解,注意,交互过程采用的时数据处理流,如果大家忘了可以回去看看,链接:
服务端:

public class Server {public static void main(String[] args) throws IOException {//1、创建服务器  指定端口   ServerSocket(int port) ServerSocket server = new ServerSocket(8888);//2、接收客户端连接   阻塞式Socket socket =server.accept();System.out.println("一个客户端建立连接");//3、发送数据String msg ="欢迎使用";//输出流/*BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));bw.write(msg);bw.newLine();bw.flush();*///socket.getOutputStream()方法可以和之前的FileOutputStream()//对应起来它表示socket流,下面的write操作会在流中写入数据DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeUTF(msg);dos.flush();}
}

与UDP协议不同,在连接之前必须先启动服务器,在建立连接后服务端也可以向客户端发送数据。
客户端:

public class Client {public static void main(String[] args) throws UnknownHostException, IOException {//1、创建客户端   必须指定服务器+端口    此时就在连接Socket client = new Socket("localhost",8888);//2、接收数据/*BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));String echo =br.readLine(); //阻塞式方法System.out.println(echo);*///这里采用之前讲的数据处理流进行交互DataInputStream dis = new DataInputStream(client.getInputStream());String echo = dis.readUTF();System.out.println(echo);}
}

现在如果直接运行客户端,那么会报如下错误:

需要启动服务器才可以进行数据交互:

多个客户端的连接(聊天室案例)

与多用户交互最简单的方式就是将交互代码写道死循环中,所以服务器端可以改成如下代码:

 public static void main(String[] args) throws IOException {//1、创建服务器  指定端口   ServerSocket(int port) ServerSocket server = new ServerSocket(8888);//2、接收客户端连接   阻塞式while(true){ //死循环  一个accept()一个客户端Socket socket =server.accept();System.out.println("一个客户端建立连接");//3、发送数据String msg ="欢迎使用";//输出流DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeUTF(msg);dos.flush();}

但是这又带来了一个问题,客户端只能一个一个的连接,如果循环体中出现阻塞(比如死循环、等待用户输入等操作),那么后面的客户端就无法建立连接,这里就需要我们之前所学的多线程的知识来解决这个问题。为全面说明这个问题我们采用一个聊天室的案例来讲解。

聊天室案例
首先说明我们要实现的聊天室的功能,多个用户可以通过控制台进行交互,自己输入的内容在其他用户的控制台上会显示。
为实现这个功能,我们需要一个Server端,让其他Client端都与其建立连接,当用户发送消息时,服务器端会把消息转发给其他Client端。依照这个思路我们来完成聊天室。
为便于我们关闭线程,首先创建一个用于关闭线程的工具:

public class CloseUtil {//Closeable... 表示可以接受不定数量的Closeable类型的参数,它必须数函数接受参数的最后一个public static void closeAll(Closeable... io){for(Closeable temp:io){try {if (null != temp) {temp.close();}} catch (Exception e) {// TODO: handle exception}}}
}

针对客户端,我们思考聊天室需要实时的接受和发送数据,这就需要多线程来实现,为此我们创建Send和Receive两个线程类:
Send类

public class Send implements Runnable{//控制台输入流private BufferedReader console;//管道输出流private DataOutputStream dos;//控制线程private boolean isRunning =true;//无参构造,初始化打印流public Send() {console =new BufferedReader(new InputStreamReader(System.in));}//有参构造传入与服务器建立的Socket连接public Send(Socket client){this();try {//初始化输出流dos =new DataOutputStream(client.getOutputStream());} catch (IOException e) {//e.printStackTrace();isRunning =false;CloseUtil.closeAll(dos,console);}}//1、从控制台接收数据private String getMsgFromConsole(){try {//接受控制台输入return console.readLine();} catch (IOException e) {//e.printStackTrace();}return "";}/*** 1、从控制台接收数据* 2、发送数据*/public void send(){//接收控制台输入String msg = getMsgFromConsole();try {if(null!=msg&& !msg.equals("")){//发送输入给服务器dos.writeUTF(msg);dos.flush(); //强制刷新}} catch (IOException e) {//e.printStackTrace();isRunning =false;CloseUtil.closeAll(dos,console);}}@Overridepublic void run() {//线程体while(isRunning){send();}}}

Receive类:

public class Receive implements Runnable {//输入流private  DataInputStream dis ;//线程标识private boolean isRunning = true;public Receive() {}public Receive(Socket client){try {//初始化输入流dis = new DataInputStream(client.getInputStream());} catch (IOException e) {e.printStackTrace();isRunning =false;CloseUtil.closeAll(dis);}}//接收数据public String  receive(){String msg ="";try {msg=dis.readUTF();} catch (IOException e) {e.printStackTrace();isRunning =false;CloseUtil.closeAll(dis);}return msg;}@Overridepublic void run() {//线程体while(isRunning){System.out.println(receive());}}
}

在Client中我们启动这两个线程:

public class Client {public static void main(String[] args) throws UnknownHostException, IOException {Socket client = new Socket("localhost",9999);new Thread(new Send(client)).start(); //一条路径new Thread(new Receive(client)).start(); //一条路径}}

由于存在多个用户,所以建立连接过程(server.accept())和用户发送消息过程必须并行处理,所以还需要一个Channel线程来为各个Client端发送数据,为了方便访问Server类中的private属性,我们采用内部类的方式对Channel进行管理,并且还需要建立一个容器来存放已有用户。综上Server端的代码如下:

public class Server {private List<MyChannel> all = new ArrayList<MyChannel>();/*** @param args* @throws IOException */public static void main(String[] args) throws IOException {new Server().start();    }public void start() throws IOException{ServerSocket server =new ServerSocket(9999);while(true){Socket client =server.accept();       MyChannel channel = new MyChannel(client);all.add(channel);//统一管理new Thread(channel).start(); //一条道路}}/*** 一个客户端 一条道路* 1、输入流* 2、输出流* 3、接收数据* 4、发送数据* @author Administrator**/private class MyChannel implements Runnable{private DataInputStream dis ;private DataOutputStream dos ;private boolean isRunning =true;public MyChannel(Socket client ) {try {dis = new DataInputStream(client.getInputStream());dos = new DataOutputStream(client.getOutputStream());} catch (IOException e) {//e.printStackTrace();CloseUtil.closeAll(dis,dos);isRunning =false;}}/*** 读取数据* @return*/private String receive(){String msg ="";try {msg=dis.readUTF();} catch (IOException e) {//e.printStackTrace();CloseUtil.closeAll(dis);isRunning =false;all.remove(this); //移除自身}return msg;}/*** 发送数据*/private void send(String msg){if(null==msg ||msg.equals("")){return ;}try {dos.writeUTF(msg);dos.flush();} catch (IOException e) {//e.printStackTrace();CloseUtil.closeAll(dos);isRunning =false;all.remove(this); //移除自身}}/*** 发送给其他客户端*/private void sendOthers(){String msg = this.receive();//遍历容器for(MyChannel other:all){if(other ==this){continue;}//发送其他客户端other.send(msg);}}@Overridepublic void run() {while(isRunning){sendOthers();}}}}

这样就完成了聊天室的功能,我们创建两个客户Tom和Bob(可以有多个),代码测试结果如下:


以上就是Java网络的所有内容,这也是第一次展示综合案例,可能代码一多大家对逻辑理解起来会困难一些,建议大家多看几遍,结合聊天室也对之前的IO流、线程、容器好好的做做复习。相信大家要是能搞明白这段代码Java基础的内容差不多就没什么问题了。
上一篇:菜鸟学习笔记:Java提升篇9(网络1——网络基础、Java网络编程)
下一篇:菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)

菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信、聊天室案例)相关推荐

  1. Java基础知识强化之网络编程笔记05:UDP之多线程实现聊天室案例

    1. 通过多线程改进刚才的聊天程序,这样我就可以实现在一个窗口发送和接收数据了 2.  代码示例: (1)SendThread.java,如下: 1 package com.himi.udpDemo2 ...

  2. 决策树算法学习笔记(提升篇)

    声明:本文虽有部分自己理解成分,但是大部分摘自以下链接. 决策树(decision tree)(三)--连续值处理 决策树参数讲解+实例 数据挖掘十大算法 C4.5算法的改进: 用信息增益率来选择属性 ...

  3. 菜鸟学习笔记:Java提升篇9(网络1——网络基础、Java网络编程)

    菜鸟学习笔记:Java提升篇9(网络1--网络基础.Java网络编程) 网络基础 什么是计算机网络 OS七层模型 Java网络编程 InetAddress InetSocketAddress URL类 ...

  4. 菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)

    Java提升篇11(Java其它高级特性--注解与反射) 注解(Annotation) JDK内置注解 自定义注解 元注解(meta-annotation) 反射(reflection) 动态语言 反 ...

  5. 菜鸟学习笔记:Java提升篇8(线程2——线程的基本信息、线程安全、死锁、生产者消费者模式、任务调度)

    菜鸟学习笔记:Java提升篇8(线程2--线程的基本信息.线程安全.死锁.生产者消费者模式.任务调度) 线程的基本信息 线程同步 线程安全 死锁 生产者消费者模式 任务调度(了解) 线程的基本信息 J ...

  6. 菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

    菜鸟学习笔记:Java提升篇12(Java动态性2--动态编译.javassist字节码操作) Java的动态编译 通过脚本引擎执行代码 Java字节码操作 JAVAssist的简单使用 常用API ...

  7. 菜鸟学习笔记:Java提升篇4(容器4——Collections工具类、其他容器)

    菜鸟学习笔记:Java容器4--Collections工具类.其他容器 Collections工具类 容器其他知识点 队列Queue Enumeration接口 Hashtable Propertie ...

  8. 菜鸟学习笔记:Java提升篇3(容器3——泛型、排序)

    菜鸟学习笔记:Java容器3--泛型.排序 泛型 泛型类 泛型接口 泛型方法 泛型继承 通配符"?" 泛型知识点补充 容器排序 Comparable接口与compareTo方法 C ...

  9. 菜鸟学习笔记:Java提升篇2(容器2——Map、Set、迭代器)

    菜鸟学习笔记:Java容器2--Map.Set.迭代器 Map容器 HashMap的使用 Hash表讲解 Map实现 Set容器 HashSet的使用 实现 Iterator迭代器 Map容器 Has ...

最新文章

  1. 八年磨一剑,重新定义HBase——HBase 2.0阿里云HBase解读
  2. 使用Emit的TypeBUilder动态创建接口程序集的性能报告。
  3. Tensorflow2.6更新cuda11.2
  4. 用户注册 - 判断用户名存在
  5. UNION JOIN 连接表
  6. 最小生成树【数据结构】
  7. BugkuCTF-MISC题赛博朋克
  8. 重读《JAVA与模式》之二
  9. SQL Server 复制:事务发布
  10. Yii2如何用migrate快速建表
  11. 我的JDBC通用DAO(续)
  12. linux下查看计划任务,linux查看计划任务.docx
  13. Android 微信聊天记录、联系人备份并导出为表格
  14. iftop输出详解和命令详解
  15. QQ音乐歌曲播放源的获取
  16. 测试代码怎么做抽象才是有意义的?
  17. 用HTML加css制作立体方块,CSS3 三维变形实现立体方块特效源码
  18. 哪些期货公司开户手续费更低?
  19. 生活中不可缺少的日常小知识(转载)
  20. vcCode前端好用的插件。

热门文章

  1. python判断字符串中[提取、判断只含有、判断不含有]中文
  2. mysql利用binlog删除数据库_MySQL数据库之mysql手动删除BINLOG的方法
  3. 唯一可译码判断c语言_单片机基础实验数码管原理与C语言
  4. sql查询时间过长和什么有关系_2020年成人高考成绩查询时间是什么时候?
  5. Ubuntu iso下载地址(14、16、18)
  6. stm32-再谈GPIO
  7. 第四次scrum冲刺
  8. JavaScript 统计中英混合字符串的长度
  9. Windows7系统下编译安装X264
  10. 各种字符串合并处理示例.sql