day23(上)_网络编程(UDP传输与TCP传输)


 

1.网络概述:

/*
两台主机之间通过QQ或飞秋进行通信:
1.找到对方IP
2.数据发送到对方指定的应用程序上.为了标识这些
  应用程序,所以给这些网络应用程序用数字进行标识
  为了方便称呼这个数字,叫做端口(逻辑端口)
  例如:
    QQ  ------->QQ 4000(数字标识:0~65535,0~1024被系统程序保留)
    FeiQ------->FeiQ 2900
    类比:先找到具体几号楼(IP),在找楼里对应的教室(端口)
 3.定义通信规则,通讯规则称为协议
  例如:两人面对面交谈,约定使用共同语言(英语,汉语....)
   国际组织定义了通用协议:TCP/IP
 
4.网络模型
(示意图)
*/

OSI参考模型(七层)与TCP/IP参考模型(四层)

参考模型

  2.IP地址:

 

/*
 IP地址:
  网络中设备的标识
 不易记忆,可用主机名
 本地回环地址:127.0.0.1,主机名:localhost 
127.0.0.1的作用:(百度百科)
 一是测试本机的网络配置,
 能PING通127.0.0.1说明本机的网卡和IP协议安装都没有问题;
 另一个作用是某些SERVER/CLIENT的应用程序在运行时需调用服务器上的资源,
 一般要指定SERVER的IP地址,但当该程序要在同一台机器上运行而没有别的SERVER时就可以把SERVER的资源装在本机,
 SERVER的IP地址设为127.0.0.1同样也可以运行。
*/
package ipdemo;
import java.net.InetAddress;//此类表示互联网协议 (IP) 地址。 
                           //IP 地址是IP使用的 32 位或 128 位无符号数字,它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的。
import java.util.Arrays;
class IPDemo{
    public static void main(String[] args)throws Exception{
        InetAddress ia=InetAddress.getLocalHost();
        System.out.println(ia.toString());//UnknownHostException:指示主机 IP 地址无法确定而抛出的异常。    
                                                              //返回InetAddress对象封装本地主机名+本地主机IP
      System.out.println(ia.getHostAddress()+"\n"+ia.getHostName()); 

        
    }
}

IP

3.UDP,TCP,Socket概述:

/*
传输协议:
一.UDP:(User Datagram Protocol,用户数据报协议)
    1.将数据及源和目的封装成数据包中,不需要建立连接
    2.每个数据报的大小限制在64K内
    3.因无连接,是不可靠协议(对方可能不在,源依然再发,数据丢失)
    4.不需要建立连接(面向无连接),速度快
现实例子:1.寄包裹
         我去邮局寄一本书-->把书打包,写明寄往的地方(地址),某某人收(端口)
         -->但是寄往的地方(地址)和该人(端口)都不一定存在-->丢掉该包
         2.聊天/视频会议
  UDP优点:传输速度快
     缺点:可能丢包
二.TCP:(Transmission Control Protocol,传输控制协议)
 1.建立连接,形成传输数据通道
 2.在连接中进行大数据传输
 3.通过三次握手完成连接,是可靠协议
 4.必须建立连接,效率会稍低
 例如:打电话

三.Socket(直译:插座,空,套接字,端)
  通俗理解:
    Socket理解为港口,两个港口(A,B)想要进行通信
    通过船在A装货(数据),运送到B卸货.先有的港口,再有的船,最后进行通信.
    在通信之前必须两端先有Socket,有了Socket才能进行连接,之后有了数据传输的通路
  1.Socket就是为网络服务提供的一种机制
  2.通信的两端都有Socket
  3.网络通信其实就是Socket间的通信
  4.数据在两个Socket间通过IO传输

*/

4.UDP传输:

/*
UDP传输:DatagramSocket与DatagramPacket
DatagramSocket:
   此类表示用来发送和接收数据报包的套接字(Socket)。 
   在 DatagramSocket 上总是启用 UDP 广播发送。
DatagramPacket:
  此类表示数据报包.
  封装了源地址,目的地址,源端口,目的端口
*/
/*
通过UDP传输方式,将一段文字数据发送出去
1.建立udpscoket服务(类比先找邮局)
2.提供数据,并将数据封装到数据包中(邮寄的东西,目的地址等等打包)
3.通过scoket服务发送功能,将数据包发送出去
4.关闭资源.(会用到底层系统资源,需要释放)

*/

客户端:

package udp;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
class UDPSend{
  public static void main(String[] args)throws Exception{//在创建服务,发送都可能出现问题
     //1.创建UDP服务,通过DatagramSocket对象
     DatagramSocket ds=new DatagramSocket();
     //2.提供数据,并封成数据包:DatagramPacket(byte[] buf, int length, InetAddress address, int port) 
     byte[] buf="UDP".getBytes();//UDP以GBK编码
     InetAddress ia=InetAddress.getLocalHost();//发送到本地主机
     DatagramPacket dp=new DatagramPacket(buf,buf.length,ia,10000);
     //3.通过Socket将已有数据包发送出去
     ds.send(dp);
     //4.关闭资源
     ds.close();
  }
}

服务端:

class UDPReceive{
  public static void main(String[] args)throws Exception{
    //创建UDP Socket,建立端点
    DatagramSocket ds=new DatagramSocket(10000);
    //2.定义数据包,用于存储数据
    byte[] buf=new byte[1024];
    DatagramPacket dp=new DatagramPacket(buf,buf.length);
    //3.通过服务的receive方法收到数据存入数据包中
    ds.receive(dp);
    //4.通过数据包的方法获取其中的数据
    System.out.println(dp.getAddress().getHostAddress()+" "+
                       new String(buf,0,dp.getLength())+" "+ //将有效字节数以GBK解码成字符串,getLength在这里返回接收到的字节数据长度
                       dp.getPort());//发送方的主机IP地址+发送方发送的数据+发送方的端口
    //5.关闭资源
     ds.close();
  }
}
/*
注意:
1. 发送应用程序:
 DatagramSocket ds=new DatagramSocket();//系统随机分配一个端口给接收应用程序
 
 接收应用程序:
 DatagramSocket ds=new DatagramSocket(10000);//在这里给接收方应用程序一个标识10000
 //创建数据报套接字并将其绑定到本地主机上的指定端口
 //如果不指定数字标识(端口),它将接收不到,因为发送方指定了10000端口的应用程序去接收处理.
 //即使不指定,接收应用程序和发送应用程序均有数字标识(系统随机分配的)
 
2.ds.receive(dp);
 从此套接字接收数据报包。
  当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。
  数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 
  此方法在接收到数据报前一直阻塞。
  数据报包对象的 length 字段包含所接收信息的长度。
  如果信息比包的长度长,该信息将被截短。 
3.
  先运行发送端:发送端直接发送数据出去,可能丢包(接收方没运行),接收方接收到
  先运行接收端:如果没有收到数据,将一直堵塞.
4.
 new DatagramSocket(10000);
 new DatagramSocket(10000);
 不能把两个socket服务对象绑定到一个端口上
 否则引发Exception in thread "main" java.net.BindException
*/

需要在两个cmd下运行,因为发送端和接收端都是两个独立的可执行的程序,先运行哪一端均可,但是如果先运行发送端,它直接发送数据,这时候在开启接收端,接收不到已发送的数据,因此先运行接收端,在运行发送端即可:

UDPReceive

UDPSend

示意图:

UDP传输

5.将发送数据改为键盘录入:

/*
将发送的数据改为键盘录入
接收方改为循环接收
*/
package udp;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
class UDPSend2{
    public static void main(String[] args)throws Exception{
      DatagramSocket ds=new DatagramSocket();
      BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in));
      String data=null;
      while((data=bfr.readLine())!=null){
       if(data.equals("886"))
         break;
        byte[] buf=data.getBytes();
        DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.255"),10001);//主机IP地址:192.168.1.1~192.168.1.254都将收到
        ds.send(dp);
       }
       ds.close();
    }
}
class UDPReceive2{
  public static void main(String[] args)throws Exception{
   DatagramSocket ds=new DatagramSocket(10001);
   byte[] buf=new byte[1024];//发送的数据<=64K,也就是64*1024
   while(true){
    DatagramPacket dp=new DatagramPacket(buf,buf.length);
    ds.receive(dp); 
    System.out.println(dp.getAddress().getHostAddress()+" "+dp.getPort()+" "
                       +new String(buf,0,dp.getLength()));
   }
   
  } 
}

6.采用多线程将发送端与接收端在一个程序中运行:

/*
编写一个聊天程序
有收数据的部分,和发数据的部分
这两部分需要并发执行
->多线程
一个线程控制收,一个控制发
未使用多线程,启动了两个cmd命令窗口,两个进程,
这两个进程里面至少有一个线程,控制进程执行.
*/
/*对刚才代码,封装到线程里面*/
package udp;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.net.InetAddress;
class Send implements Runnable{
    private DatagramSocket ds;
    public Send(DatagramSocket ds){
     this.ds=ds;
    }
    public void run(){
     try{
      BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in));
      String data=null;
      while((data=bfr.readLine())!=null){
       if(data.equals("886"))
         break;
        byte[] buf=data.getBytes();
        DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.101"),
                                                      10002);
     ds.send(dp);
       }
       ds.close(); 
     }
     catch(Exception e){
      throw new RuntimeException("发送失败");
     }
    }
}
class Receive implements Runnable{
    private DatagramSocket ds;
    public Receive(DatagramSocket ds){
     this.ds=ds;
    }
    public void run(){
     try{
           byte[] buf=new byte[1024];//发送的数据<=64K,也就是64*1024
           while(true){
            DatagramPacket dp=new DatagramPacket(buf,buf.length);
            ds.receive(dp); 
            System.out.println(dp.getAddress().getHostAddress()+" "+dp.getPort()+" "
                               +new String(buf,0,dp.getLength()));
         }
     }
     catch(Exception e){
      throw new RuntimeException("接收失败");
     }
    }
}
class UDPTalk{
 public static void main(String[] args)throws Exception{
  new Thread(new Send(new DatagramSocket())).start(); 
  new Thread(new Receive(new DatagramSocket(10002))).start();
 }
}

UDPTalk

7.TCP传输

/*
TCP传输:
Socket和ServerSocket
1.建立客户端和服务器端
2.建立连接后,通过Socket中的IO流进行数据的传输
3.关闭Socket
4.同样,客户端与服务端是两个独立的应用程序
Socket:
    此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
ServerSocket:
    此类实现服务器套接字。服务器套接字等待请求通过网络传入。
    它基于该请求执行某些操作,然后可能向请求者返回结果。 
*/

示例:

客户端:

package tcp;
import java.net.Socket;
import java.net.ServerSocket;
import java.io.InputStream;
import java.io.OutputStream;

class Client{
 public static void main(String[] args)throws Exception{
  //1.创建客户端的scoket服务,指定目的的主机和端口
   Socket s=new Socket("192.168.1.101",10002);// Socket(String host, int port) 
    /*
     因为TCP是面向连接的,所以在建立socket服务时,
     就要有服务端存在,并连接成功.形成通路后,在该通道进行数据传输
   */
 //2.为了发送数据,应该获取socket流中的输出流.                                            
   OutputStream os=s.getOutputStream();
   os.write("TCP".getBytes());
   s.close();
 }
}

服务端:

/*
为了避免冲突(服务端把应该发送A的数据发送给了B)
在连接成功后,服务端通过客户端的流对象进行通信
1.建立服务端的Socekt服务,ServerSocket().
2.获取连接过来的客户端对象
  通过ServerSocket的accept方法,没有建立连接,此方法将一直堵塞
3.客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端
  对象的读取流读取发过来的数据
4.关闭服务端(一般不关闭,在一个客户端连接结束,还有其它客户端)
*/
class Server{
     public static void main(String[] args)throws Exception{
       //1.建立服务端的Socket服务
       ServerSocket ss=new ServerSocket(10002);//绑定10002端口
       //2.通过accept方法获取连接过来的客户端对象
       Socket s=ss.accept();
       String ip=s.getInetAddress().getHostAddress();//此套接字连接到的远程 IP 地址 即 客户端主机的IP地址
       int port=s.getPort();//返回此套接字连接到的远程端口 即 客户端的端口
       //3.获取客户端发送过来的数据,那么要使用客户端对象的读取流来读取数据
       InputStream is=s.getInputStream();
       byte[] buf=new byte[1024];
       int bytes=is.read(buf);
       System.out.println(ip+" "+port+" "+new String(buf,0,bytes));
       
       ss.close();//如果仅进行一次通讯,关闭服务端
      s.close();//关闭连接的客户端,不关浪费服务端资源
     }
}

/*
注意:
必须先运行服务端程序,在运行客户端程序
这是因为TCP通信之前,必须建立连接成功,如果不先开启服务端,客户端无法连接成功

*/

示意图:

TCP传输

8.服务端反馈信息:

/*
客户端给服务端发送数据,服务端收到后,给客户端反馈信息

*/
package tcp;
import java.net.Socket;
import java.net.ServerSocket;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.*;
class Client2{
    public static void main(String[] args)throws Exception{
      Socket s=new Socket("192.168.1.101",10002);
      //客户端向服务端发送数据
      OutputStream os=s.getOutputStream();
      os.write("服务端你好".getBytes());
      
      //客户端接收服务端发送(反馈)数据
      InputStream is=s.getInputStream();
      byte[] buf=new byte[1024];
      int bytes=is.read(buf);//read()为堵塞方法,如果流中没有数据,该方法一致等待,
                             //也就是说服务端未发送数据,一直堵塞
      System.out.println(new String(buf,0,bytes));
      s.close();
    }
}
class Server2{
    public static void main(String[] args)throws Exception{
     ServerSocket ss=new ServerSocket(10002);
     //服务端接收客户端发来的数据
      Socket s=ss.accept();
      String ip=s.getInetAddress().getHostAddress();
      InputStream is=s.getInputStream();
      byte[] buf=new byte[1024];
      int bytes=is.read(buf);
      System.out.println(ip+" "+new String(buf,0,bytes));
    
    //服务端向客户端发送数据
      OutputStream os=s.getOutputStream();
      os.write("我已收到".getBytes());
      
      s.close();//关闭客户端,防止浪费服务端资源
      ss.close();//关闭服务端
     
     }
  }

Server2

Client2

9.TCP练习:

/*
 将客户端发送的小写字母在服务端转换成大写后反馈给客户端
客户端:
  源:键盘录入
  目的:Socket输出流
  操作的是文本数据,使用字符流操作方便
服务端:
   源:Socket读取流
   目的:Socket写入流
  操作的是文本数据,使用字符流操作方便
*/
package tcp;
import java.net.Socket;
import java.net.ServerSocket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
class TransClient{
    public static void main(String[] args)throws Exception{
       Socket s=new Socket("192.168.1.101",10003);
       BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in));
       BufferedWriter bfwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//对Socket的输出流使用缓冲技术
     BufferedReader bfrIn=new BufferedReader(new InputStreamReader(s.getInputStream()));//读取服务器的反馈信息
       String line=null;
       while((line=bfr.readLine())!=null){
         if(line.equals("over"))
             break;
         bfwOut.write(line);
         bfwOut.newLine();
//如果不写入行终止符,导致服务端的bfrIn.readLine()一直堵塞 bfwOut.flush();//写入缓冲区的数据需要刷新
         System.out.println(bfrIn.readLine());//打印服务端发回的反馈信息
       }
       bfr.close();
       s.close();
    }
}
class TransServer{
   public static void main(String[] args)throws Exception{
     ServerSocket ss=new ServerSocket(10003);
     Socket s=ss.accept();
     String ip=s.getInetAddress().getHostAddress();
     System.out.println(ip+" Connect Success");
     BufferedReader bfrIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
     BufferedWriter bfwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
     String line=null;
     while((line=bfrIn.readLine())!=null){
         System.out.println(line);//打印客户端发过来的信息
         bfwOut.write(line.toUpperCase());
         bfwOut.newLine();
//如果不写入行终止符,导致客户端的bfrIn.readLine()方法一直堵塞
         bfwOut.flush();
      }
     s.close();
     ss.close();
   } 
}
/*
注意:
1.为什么客户端输入over,服务器端也结束?
   客户端的s.close();在Socket流中加了一个-1(结束标记)
   服务端的bfrIn.readLine()底层调用read方法,读到结束标记
   表示读到流末尾,循环结束
2.使用PrintWriter/PrintStream简化代码
           BufferedWriter bfwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//对Socket的输出流使用缓冲技术
         bfwOut.write(line);
          bfwOut.newLine();//如果不写入行终止符,导致服务端的bfrIn.readLine()一直堵塞
         bfwOut.flush();//写入缓冲区的数据需要刷新
  
       PrintWriter pw=new PrintWriter(Socket.getOutputStream(),true)//自动刷新
       pw.println(line.toUpperCase());
*/

10.TCP上传文件:

package tcp;
import java.net.Socket;
import java.net.ServerSocket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.PrintWriter;
import java.io.InputStreamReader;
import java.io.FileReader;
import java.io.FileWriter;
class TCPUpLoadClient{
   public static void main(String[] args)throws Exception{
    Socket s=new Socket("192.168.1.101",10003);  
    BufferedReader buf=new BufferedReader(new FileReader("C:\\Users\\ZhangHongQ\\Desktop\\Client.txt"));
    PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
    String line=null;
    while((line=buf.readLine())!=null)
      pw.println(line);
    s.shutdownOutput();
    BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
    System.out.println(bufIn.readLine());
    buf.close();
    s.close();
   }
}
class TCPUpLoadServer{
  public static void main(String[] args)throws Exception{
    ServerSocket ss=new ServerSocket(10003);
    Socket s=ss.accept();
    PrintWriter pw=new PrintWriter(new FileWriter("C:\\Users\\ZhangHongQ\\Desktop\\Server.txt"),true);
    BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
    String line=null;
    while((line=bufIn.readLine())!=null)
        pw.println(line);
    pw=new PrintWriter(s.getOutputStream(),true);
    pw.println("上传成功");
    s.close();
    ss.close();
  }
}
/*
以上程序运行发现客户端和服务端均等待:
客户端在bufIn.readLine()等待,
而服务端由于客户端pw.println(line);并未写入任何结束标记
导致bufIn.readLine()一直等待读入下一行
解决:在客户端读完文件后,加入s.shutdownOutput();语句:关闭客户端输出流
     相当于在OutputStream流末尾加入结束标记(-1)

*/
posted @ 2013-07-10 12:37  伊秋  阅读(560)  评论(0编辑  收藏  举报