JAVA Class24

学习内容:

1.网络通信协议

(1)TCP/IP协议:

TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层

链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。

网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。

传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。

应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。

客户端与服务端的三次握手:

第一次握手,客户端向服务器端发出连接请求,等待服务器确认。

第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。

第三次握手,客户端再次向服务器端发送确认信息,确认连接。

优缺点:每次连接都要经过三次握手,速度慢,优势在于可以保证传输数据的完整性。

(2)UDP协议:

UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。

优缺点:数据收发不确认,可能会造成数据的不完整,优势在于速度更快。

(3)IP与端口:

IP版本分为IPV4 IPV6,现阶段用户量最大的是IPV4,IP地址是网络上的客户端的身份标识,而端口是用来访问客户端的应用程序。形象的来说,IP地址就是小区名称,而端口则是门牌号。

(4)JAVA的IP地址封装:

InetAddress类,常用方法:

public class Test {

    public static void main(String[] args) {
        try {
            InetAddress inet = InetAddress.getLocalHost();
            System.out.println(inet.getHostAddress());//本机IP地址
            System.out.println(inet.getHostName());//本机名
            System.out.println(InetAddress.getByName("USER-20180226VT"));//在局域网内,根据主机名获取IP,返回一个InetAddress对象,参数可填主机名或者IP地址
        } catch (Exception e) {
            // TODO: handle exception
        }
        
    }

}

2.UDP协议通信:

收发信息的载体:DatagramPacket,常用构造方法有两个:

new DatagramPacket(byte数组,发送字节长度,InetAddress对象,整型端口号);//用于通过指定IP的指定端口号收发数据

new DatagramPacket(byte数组,发送字节长度);//不指定IP、端口号

常用方法:getAddress() 获取ItnetAddress对象 getPort()获取端口号 getLength()获取数据字节长度 getData()获取数据,返回字节数组

上述方法一般用于接收端,读取发送端的信息

收发信息的对象:DatagramSocket,常用构造方法:

new DatagramSocket() 不指定端口号

new DatagramSocket(整型端口号)  指定端口号

常用方法: send()发送数据 receive()接收数据 close() 关闭

public class UDPSend {

    public static void main(String[] args) throws IOException {
        Scanner s = new Scanner(System.in);
        DatagramSocket dgs = new DatagramSocket();//没指定端口号,JVM自动分配
        InetAddress inet = InetAddress.getByName("192.168.1.255");
        while(true) {
            System.out.println("请输入内容:");
            String mess = s.next();
            byte[] send =mess.getBytes();
            DatagramPacket dgp = new DatagramPacket(send,send.length,inet,8888);
            //注意这里的8888是信息发往的端口,发送用的端口是JVM分配的
            dgs.send(dgp);
        }
        
        //dgs.close();
    }

}

public class UDPReceive {

    public static void main(String[] args) throws IOException {
        DatagramSocket dgs = new DatagramSocket(8888);
        while(true) {
            byte[] receive = new byte[1024*64];
            DatagramPacket dgp = new DatagramPacket(receive,receive.length);
            dgs.receive(dgp);
            String ip = dgp.getAddress().getHostAddress();
            int port = dgp.getPort();
            int length = dgp.getLength();
            byte[]data = dgp.getData();
            String s = new String(data,0,length);
            System.out.printf("发送方地址为:%s 发送方端口号为:%d 数据长度为:%d 数据为:%s \n",ip,port,length,s);
        }
        
    }

}

3.TCP通信

由于TCP协议的三次握手机制,所以严格区分服务端和客户端,通过输出、输出字节流来进行数据收发

(1)服务端

ServerSocket 两种构造方式 无参不指定端口,有参指定端口

服务端无法通过Socket对象获取输入输出流,要通过accept()方法返回一个Socket对象 :Socket s = ss.accept();//返回的是客户端的Socket对象

这里要注意,如果用equals来判断客户端的Socket对象与服务端通过accept()对象返回的Socket,会返回false,虽然接收端口相同,但发送端口不同,例如

public class Test {

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(9008);
            Socket s = new Socket("127.0.0.1",9008);
            Socket ns = ss.accept();
            //不是同一个是对象:
            System.out.println("我是客户端"+s);
            //Socket[addr=/127.0.0.1,port=9008,localport=51515]
            System.out.println("我是服务端"+ns);
            //Socket[addr=/127.0.0.1,port=51515,localport=9008]
  }
}

(2)客户端

Socket 构造器均为有参,new Socket(String host, int port) new Socket(InetAddress address, int port),常用的时第一种

Socket通过getInputStream()、getOutputStream() 获取输入输出流

聊天小程序:

收发线程:

public class SendThread implements Runnable{
         private Socket s;
         public SendThread(Socket s){
            this.s = s;
        }
        public void run(){
            try {
                OutputStream os = s.getOutputStream();
                DataOutputStream dos = new DataOutputStream(os);
                while(true){
                    Scanner sc = new Scanner(System.in);
                    String str = sc.next();
                    dos.writeUTF(str);
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
             
        }
}

public class ReceiveThread implements Runnable{
    private Socket s;
    public ReceiveThread(Socket s) {
        this.s = s;
    }
    public void run() {
        try {
            InputStream is = s.getInputStream();
            DataInputStream dis = new DataInputStream(is);
            String ip = s.getInetAddress().getHostAddress();
            int port = s.getLocalPort();
            while (true) {
                String msg = dis.readUTF();
                System.out.println("来自:"+ip+"端口:"+port+"的消息:\n"+msg);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }
}

服务端与客户端分别开一个收发线程:

public class TCPClient {

    public static void main(String[] args) {
        try {
            Socket s = new Socket("127.0.0.1",8888);
            SendThread st = new SendThread(s);
            ReceiveThread rt = new ReceiveThread(s);
            new Thread(st).start();
            new Thread(rt).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
    }

}

public class TCPServer {

    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8888);
            System.out.println("服务器正在监听8888端口");
            Socket s = ss.accept();//返回的是客户端的Socket对象
            SendThread st = new SendThread(s);
            ReceiveThread rt = new ReceiveThread(s);
            new Thread(st).start();
            new Thread(rt).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

 多线程数据上传:

注意shutdownOutput这个方法,当socket关闭时也会有同样效果,如果客户端在上传数据后还要再接收服务端返回的信息,则必须使用该方法,最后在关闭socket,通知服务端数据传输完毕,客户端输出流关闭,自动flush,服务端读取的字节数最终被确定!此时while循环的-1条件可以成立,同时服务端的输入流也会随之关闭。

如果不使用该方法,服务端while循环-1条件会一直不满足,因为客户端的输入流一直在等待服务端传来的数据,socket也没法关闭,导致数据没法flush,而服务端一直在等待读取客户端传来的数据,双方等到海枯石烂...结果导致堵塞,所以在socket关闭前必须使用shutdownOutput这个方法,先关闭客户端的输出流,服务端得以接收到数据,然后服务端再把文字信息输出给客户端,客户端的输入流接收到信息并打印,最后socket关闭,整个流程执行完毕!

注意,如果客户端只是上传文件,然后不再接受服务端传来的消息,则可以直接关闭socket,这样输出流自动关闭!不必调用该方法

另外:如果不关闭socket,只是输出流手动flush,那么数据可以上传,但是服务端读取有问题,导致图片出现问题!

同理,shutdownInput也有类似的效果!

public class UploadThread implements Runnable{
    private Socket s;
     public UploadThread(Socket s){
        this.s = s;
    }
    public void run(){
        File f = new File("d:\\test\\1366768.jpg");
        try (FileInputStream fis = new FileInputStream(f);
                OutputStream os=s.getOutputStream();
                ){
            byte[] upload = new byte[(int)f.length()];
            fis.read(upload);
            os.write(upload);
            s.shutdownOutput();//关闭上传客户端的输出流,通知服务端客户端的数据传输完毕,服务端的输入流也随之关闭
            InputStream is = s.getInputStream();//不关闭,is在等待读取新的输出流的数据
            byte[] mess = new byte[1024];
            int len=is.read(mess);
            System.out.println(new String(mess,0,len));
        } catch (IOException e) {
                // TODO Auto-generated catch block
        e.printStackTrace();
        } finally {
            try {
                s.close();//关闭socket,所有流随之关闭
//如果在这里不关闭socket,服务端不在发消息,客户端也不接受,那么结果是输出流没关闭,没有上传
//如果输出流不关闭,只用flush,数据会全部上传,但是服务端读取不完整,图片有问题!
}
catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
public class ServerThread implements Runnable{
    private Socket s;
    public ServerThread(Socket s) {
        this.s = s;
    }
    Object o = new Object();
    public void run() {
        synchronized(o) {
            File f = new File("d:\\upload");
              String file =f.getPath()+File.separator+System.currentTimeMillis()+".jpg";
            try(FileOutputStream fos = new FileOutputStream(file);){
                InputStream is = s.getInputStream();
                byte[] read = new byte[1024];
                int a = 0;
                if(!f.exists()) {
                      f.mkdirs();
                  }
                while((a=is.read(read))!=-1) {
                    fos.write(read,0,read.length);
                }
                System.out.println("结束!");
                s.getOutputStream().write("上传成功".getBytes());
//注意这里,因为服务端又新开了一个输出流,导致客户端一定要关闭之前的输出流,终止输出,
//使得服务端循环的-1条件成立,以此通知服务端文件上传完了,
//与此同时服务端的输入流也随之关闭,
//而新增的这个输出流与客户端用来接收这个输出流的输入流会在socket关闭时关闭,
//如果没有这个输出流,客户端只是上传文件、之后不再接收服务端传来的消息,
//只需最后关闭socket就好!
} catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }

 

posted @ 2018-04-27 20:11  0==1&1==0  阅读(143)  评论(0编辑  收藏  举报