菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信、聊天室案例)
菜鸟学习笔记:Java提升篇10(网络2——UDP编程、TCPSocket通信)
- UDP编程
- TCP编程(Socket通信)
- 单个客户端的连接
- 多个客户端的连接(聊天室案例)
UDP编程
在上一篇中讲解了UDP协议是非面向连接的不安全但效率较高的通信协议。在了解完概念之后我们用Java来实现UDP编程。
Java中通过DatagramSocket和DatagramPacket来实现UDP通信,通信过程一般分为以下几步:
发送端(客户端):
- 创建客户端DatagramSocket类+端口
- 准备数据
- 打包DatagramPacket+地址及端口
- 发送
- 释放资源
接收端(服务端): - 创建服务端DatagramSocket类+端口
- 准备接收容器
- 接收数据包
- 分析
- 释放资源
依据这个过程我们直接通过代码来理解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通信、聊天室案例)相关推荐
- Java基础知识强化之网络编程笔记05:UDP之多线程实现聊天室案例
1. 通过多线程改进刚才的聊天程序,这样我就可以实现在一个窗口发送和接收数据了 2. 代码示例: (1)SendThread.java,如下: 1 package com.himi.udpDemo2 ...
- 决策树算法学习笔记(提升篇)
声明:本文虽有部分自己理解成分,但是大部分摘自以下链接. 决策树(decision tree)(三)--连续值处理 决策树参数讲解+实例 数据挖掘十大算法 C4.5算法的改进: 用信息增益率来选择属性 ...
- 菜鸟学习笔记:Java提升篇9(网络1——网络基础、Java网络编程)
菜鸟学习笔记:Java提升篇9(网络1--网络基础.Java网络编程) 网络基础 什么是计算机网络 OS七层模型 Java网络编程 InetAddress InetSocketAddress URL类 ...
- 菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)
Java提升篇11(Java其它高级特性--注解与反射) 注解(Annotation) JDK内置注解 自定义注解 元注解(meta-annotation) 反射(reflection) 动态语言 反 ...
- 菜鸟学习笔记:Java提升篇8(线程2——线程的基本信息、线程安全、死锁、生产者消费者模式、任务调度)
菜鸟学习笔记:Java提升篇8(线程2--线程的基本信息.线程安全.死锁.生产者消费者模式.任务调度) 线程的基本信息 线程同步 线程安全 死锁 生产者消费者模式 任务调度(了解) 线程的基本信息 J ...
- 菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)
菜鸟学习笔记:Java提升篇12(Java动态性2--动态编译.javassist字节码操作) Java的动态编译 通过脚本引擎执行代码 Java字节码操作 JAVAssist的简单使用 常用API ...
- 菜鸟学习笔记:Java提升篇4(容器4——Collections工具类、其他容器)
菜鸟学习笔记:Java容器4--Collections工具类.其他容器 Collections工具类 容器其他知识点 队列Queue Enumeration接口 Hashtable Propertie ...
- 菜鸟学习笔记:Java提升篇3(容器3——泛型、排序)
菜鸟学习笔记:Java容器3--泛型.排序 泛型 泛型类 泛型接口 泛型方法 泛型继承 通配符"?" 泛型知识点补充 容器排序 Comparable接口与compareTo方法 C ...
- 菜鸟学习笔记:Java提升篇2(容器2——Map、Set、迭代器)
菜鸟学习笔记:Java容器2--Map.Set.迭代器 Map容器 HashMap的使用 Hash表讲解 Map实现 Set容器 HashSet的使用 实现 Iterator迭代器 Map容器 Has ...
最新文章
- 八年磨一剑,重新定义HBase——HBase 2.0阿里云HBase解读
- 使用Emit的TypeBUilder动态创建接口程序集的性能报告。
- Tensorflow2.6更新cuda11.2
- 用户注册 - 判断用户名存在
- UNION JOIN 连接表
- 最小生成树【数据结构】
- BugkuCTF-MISC题赛博朋克
- 重读《JAVA与模式》之二
- SQL Server 复制:事务发布
- Yii2如何用migrate快速建表
- 我的JDBC通用DAO(续)
- linux下查看计划任务,linux查看计划任务.docx
- Android 微信聊天记录、联系人备份并导出为表格
- iftop输出详解和命令详解
- QQ音乐歌曲播放源的获取
- 测试代码怎么做抽象才是有意义的?
- 用HTML加css制作立体方块,CSS3 三维变形实现立体方块特效源码
- 哪些期货公司开户手续费更低?
- 生活中不可缺少的日常小知识(转载)
- vcCode前端好用的插件。