黑马程序员--10.网络编程--04.【UDP_键盘录入】【UDP_聊天】

网络编程--4

UDP键盘输入

UDP聊天

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

1.    UDP键盘方式录入数据

需求:

键盘录入数据的方式将数据从通信一端发送到另一端。采用UDP方式进行数据传输。当在键盘上输入“886”的时候,发送端数据发送完成。

1). 做法I

(1). 发送端代码示例

import java.io.*;
import java.net.*;
class UdpSend2{
    public static void main(String[] args) throws Exception{
       DatagramSocketds =new DatagramSocket();
       BufferedReaderbufr =
           new BufferedReader(new InputStreamReader(System.in));
       Stringline =null;
       while((line =bufr.readLine()) !=null){
           if("886".equals(line)){
              break;
           }
           byte[] buf =line.getBytes();
           DatagramPacketdp =new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 10001);
           System.out.println(InetAddress.getLocalHost().toString());
           ds.send(dp);
       }
       ds.close();
    }
}
 

(2). 接收端代码示例

class UdpRece2{
    public static void main(String[] args) throws Exception{
       DatagramSocketds =null;
       while(true){
           ds=new DatagramSocket(10001);
           byte[] buf =new byte[1024];
           DatagramPacketdp =new DatagramPacket(buf, buf.length);
           ds.receive(dp);
           Stringip =dp.getAddress().getHostAddress();
           Stringdata =new String(dp.getData(), 0,dp.getLength());
           System.out.println(ip+"::"+ data);
       }
    }
}

(3). 运行抛出异常及分析

[1]. 异常结果如下:

接收端抛出了java.net.BindException绑定异常。

[2]. 分析抛出异常的原因

接收端的出问题的程序段:

while(true){
    ds=new DatagramSocket(10001);
byte[] buf =new byte[1024];
    //...
}

{1}. 第一次监听了 10001这个端口。当 发送端发送了 一次数据同时 接收端也接收到了这个数据之后,再次执行了这个循环体。

{2}. 首先ds =newDatagramSocket(10001);再次被执行了一遍。出现了不同的Socket服务端点占用了同一个端口

注意不同的Socket端点体现在出现了两次new,每一次new都会新建一个Socket接收端端点不同的Socket端点对应不同的网络应用程序。此时也就相当于同时存在两个网络应用程序使用了同一个端口,这是不允许的,因此就抛出端口绑定异常。

[3]. 结论

{1}. 服务不能循环建立。也就是同一个端口不能被不同的Socket通信端点占用

注意服务指的是new DatagramSocket对象的建立。new一次xxxSocket对象就称为建立一次服务

{2}. 如果在Java程序中使用了某个进程的端口号,此时就会抛出B端口indException异常。如果这个时候关掉占用的要使用占用这个端口号的进程,此时这个Java程序中的Socket能立刻绑定上这个端口么?

【答】不会!很有可能相应的进程被关掉之后,相应的端口在内存中没有被释放,这样如果在Java中仍然马上使用这个端口,还是会报出端口BindException

2). 修正接收端程序

(1). 接收端代码示例

import java.io.*;
import java.net.*; 
class UdpRece2{
    public static void main(String[] args) throws Exception{
       DatagramSocketds =new DatagramSocket(10001);
       while(true){
           byte[] buf =new byte[1024];
           DatagramPacketdp =new DatagramPacket(buf, buf.length);
           ds.receive(dp);
           Stringip =dp.getAddress().getHostAddress();
           Stringdata =new String(dp.getData(), 0,dp.getLength());
           System.out.println(ip+"::"+ data);
       }
    }
}

(2). 发送端和接收端的双阻塞现象

[1]. 运行接收端的程序,出现下面光标闪烁的场景

[2]. 运行发送端的程序,出现下面光标闪烁的场景


(3). 循环打印结果

2.    UDP简单聊天程序

需求:编写一个聊天程序,有收数据和发数据的部分。这两部分需要同时运行。运行效果刚才的两个CMD窗口的效果,但是希望在同一个CMD窗口运行。

1). 单线程执行情况

经验】注意多线程的题目分析如果不是多线程的情况下,程序该怎么写。此时就是单线程顺序执行

(1). 将发送的数据代码块进行封装

class SendII{
    private DatagramSocket ds;
    public SendII(DatagramSocket ds){
       this.ds =ds;
    }
 
    public void send(){
       try{
           //键盘录入
           BufferedReaderbufr =
              new BufferedReader(new InputStreamReader(System.in));
           Stringline =null;
          
           while((line =bufr.readLine())!=null){
              if("886".equals(line))
                  break;    
              byte[] buf =line.getBytes();
              DatagramPacketdp =new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 10002);
              ds.send(dp);
           }
           ds.close();
       }catch(Exception e){
           throw new RuntimeException("Send Failure!");
       }
    }
}

(2). 将接收的数据代码块进行封装

class ReceII{
    private DatagramSocket ds;
    private int receTime;
    public ReceII(DatagramSocket ds){
       this.ds =ds;
    }
 
    public void receive(){
           try{
              while(true){
              byte[] buf =new byte[1024];
              DatagramPacketdp =new DatagramPacket(buf, buf.length);
             
              ds.receive(dp);
              Stringip = dp.getAddress().getHostAddress();
              Stringdata =new String(dp.getData(), 0,dp.getLength());
              System.out.println(ip+"::"+ data);
              }
           }catch(Exception e){
              throw new RuntimeException("Receive Failure!");
           }
    }
}

(3). 单线程:先接收,再发送

单线程的顺序执行,先接收数据再发送数据。

[1]. 测试代码

class ChatDemoII{
    public static void main(String[] main) throws Exception{
       DatagramSocketsendSocket =new DatagramSocket();
       DatagramSocketreceSocket =new DatagramSocket(10002);
      
       new ReceII(receSocket).receive();
       new SendII(sendSocket).send();
    }
}

[2]. 执行结果:程序 卡在那里不动


分析】单线程运行,先执行发送端。但是由于发送端调用了阻塞式的receive方法。但是接收端的代码在接收端之后才运行。因此,接收端一直没有办法接收到数据,因此一直处于阻塞状态。结果程序卡在那里不动。

(4). 单线程:先发送,再接收

[1]. 测试代码

class ChatDemoII{
    public static void main(String[] main) throws Exception{
       DatagramSocketsendSocket =new DatagramSocket();
       DatagramSocketreceSocket =new DatagramSocket(10002);
      
new SendII(sendSocket).send();
       new ReceII(receSocket).receive();
    }
}

[2]. 执行结果

注意】发送端先执行,这样一直从键盘上录入数据并进行发送。但是由于发送端不输入结束符“886”,发送端就不会结束。因此,发送端要一口气发送到完(也就是输入了结束符“886”)才能结束发送端程序的调用再开始接收端的调用。因此就出现了上面先发送再接收的场面。

(5). 单线程分析

[1].【分析】单线程效果不好原因

因为是顺序执行,要么先接收,要么先发送。必须两者有一个执行完成,相应的发送或者接收才能再开始。无法在书写上是顺序的两段代码但是可以达到同时执行的效果。这就需要使用代码书写上虽然是上下顺序书写形式但是可以达到上下两行代码同时运行的效果。这就需要把原来的单线程程序使用多线程技术进行改造。

[2].【经验单线程的程序要保留

尽管单线程的程序在运行效果上有问题存在,但是单线程的程序多线程的代码雏形。下面通过画图的方法对单线程的代码进行改造。

2). 利用多线程技术改造单线程代码

(1). 标记出单线程代码中需要同时运行的部分


(2). 将从主线程中独立出来的程序代码封装到独立的线程中

具体做法就是将这些代码封装到Runnable实现子类的run方法即可实现多线程程序。

(3). 管理发送端Socket的线程类

class Send implements Runnable{
    private DatagramSocket ds;
    public Send(DatagramSocket ds){
       this.ds =ds;
    }
 
    public void run(){
       try{
           //键盘录入
           BufferedReaderbufr =
              new BufferedReader(new InputStreamReader(System.in));
           Stringline =null;
 
           while((line =bufr.readLine())!=null){
              if("886".equals(line))
                  break;    
              byte[] buf =line.getBytes();
              DatagramPacketdp =new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 10002);
              ds.send(dp);
           }
           ds.close();
       }catch(Exception e){
           throw new RuntimeException("Send Failure!");
       }
    }
}

(4). 管理接收端Socket的线程类

class Rece implements Runnable{
    private DatagramSocket ds;
    public Rece(DatagramSocket ds){
       this.ds =ds;
    }
 
    public void run(){
           try{
              while(true){
              byte[] buf =new byte[1024];
              DatagramPacketdp =new DatagramPacket(buf, buf.length);
             
              ds.receive(dp);
              Stringip = dp.getAddress().getHostAddress();
              Stringdata =new String(dp.getData(), 0,dp.getLength());
 
              System.out.println(ip+"::"+ data);
              }
           }catch(Exception e){
              throw new RuntimeException("Receive Failure!");
           }
    }
}

(5). 测试代码

class ChatDemo
{
    public static void main(String[] main) throws Exception{
       DatagramSocketsendSocket =new DatagramSocket();
       DatagramSocketreceSocket =new DatagramSocket(10002);
 
       new Thread(new Send(sendSocket)).start();
       new Thread(new Rece(receSocket)).start();
    }
}

(6). 运行结果

[1]. 启动ChatDemo,运行结果如下:

[

2]. 程序运行分析

很有可能接收端先获取了CPU的执行权,这个时候,由于没有数据发送来,因此运行到receive方法之后,就被阻塞。这个时候,光标在闪烁。此时CPU就切换到发送端客户端,发送端就可以输入数据,因此程序正常进行通信。

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值