• Java基础之UDP协议和TCP协议简介及简单案例的实现


    写在前面的废话:马上要找工作了,做了一年的.net ,到要找工作了发现没几个大公司招聘.net工程师,真是坑爹呀。哎,java就java吧,咱从头开始学呗,啥也不说了,玩命撸吧,我真可怜啊。

    摘要:

    本片记载刚刚学习的网络编程的内容,网络编程也称 Socket 编程 、套接字编程。

    什么是Socket?

    用于描述ip地址和端口,是一个通信链的Handle。在Internet上的主机一般运行了多个服务软件,同时提供几种服务,每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket就是为了网络编程提供的一种机制,通信的两端都有socket,网络通信其实就是socket间的通信,数据在两个socket之间通过 IO 传输。(摘自黑马视频ppt).

    网络通信的三要素:

    IP地址:

    网络中设备的标识,也可以用主机名识别,但ip地址唯一,主机名不唯一;

    端口号:

    用于标识进程的逻辑地址,是不同进程的标识;

    传输协议:

    也即通信的规则,常见的协议由 UDP 协议 和 TCP协议;

    UDP协议和TCP协议

    (1)UDP

    UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据报的方法(百度百科截取)。

    百度上讲的有点复杂,不太容易懂,比较通俗的说法就是:UDP协议会把数据打包,然后扔给目标地址,但是这个包能不能扔的到目标机器上,就不管了,udp就只管扔。

    所以这种通信协议的优缺点很明显了,优点就是:速度快,效率高;缺点就是:安全性低,容易丢包

    (2)TCP

    传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议(摘自百度百科)。

    通俗说法:tcp协议只在已经确定通信双方都能联系上对方的时候才能进行通信:

         使用tcp协议时要先建立连接;

         建立连接的过程:三次握手,如下图,

     UDP协议的简单使用

     发送数据流程

    • 创建发送端socket对象;
    • 提供数据,并将数据封装到数据包中;
    • 通过socket服务的发送功能,将数据包发出去;
    • 释放资源;

    接收数据流程

    • 创建接收端socket对象;
    • 接收数据;
    • 解析数据;
    • 输出数据;
    • 释放资源;

    一个案例:

    创建两个控制台程序模拟发送端和接收端,使用udp发送端发送数据,接收端接收数据。

    发送端:

    SendDemo.java

    package com.cherish.Socket;
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    /*
     * 使用UDP协议发送数据
     *         创建发送端Socket对象
     *         创建数据并打包
     *         发送数据
     *         释放资源
     * 
     * DatagramSocket:此类表示用来发送和接受数据,基于UDP协议
     * 
     * DatagramSocket():
     * DatagramSocket(int port):
     * */
    public class SendDemo {
        public static void main(String[] args) throws IOException {
            //创建发送端Socket对象
            DatagramSocket ds = new DatagramSocket();
            //创建数据并打包  DatagramPacket表示数据包
            //数据 byte[] 设备地址ip 进程的地址 :端口号
            String s = "hello udp,i m coming";
            byte[] bys = s.getBytes();
            int length = bys.length; 
            InetAddress address = InetAddress.getByName("acer-pc");
            int port = 8888;
            DatagramPacket dp = new DatagramPacket(bys, length, address,port);
            //发送数据
            ds.send(dp);
            //释放资源
            ds.close();
            
        }
    }

    接收端:

    ReceiveDemo.java

    package com.cherish.Socket;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    
    /*
     * 使用UDP协议接受对象:
     *     创建接收端Socket对象
     *     接受数据
     *  解析数据
     *  输出数据
     *  释放资源
     * */
    public class ReceiveDemo {
        public static void main(String[] args) throws IOException {
            //创建接收端Socket对象,此处的端口号要跟发送端一致
            DatagramSocket ds = new DatagramSocket(8888);
            //接收数据
            byte[] bys = new byte[1024];
            DatagramPacket dp = new DatagramPacket(bys, bys.length);
            System.out.println("接受前");
            ds.receive(dp);
            System.out.println("接收后");
            //解析数据
            //InetAddress getAddress()
            InetAddress address = dp.getAddress();
            //byte[] getData
            byte[] data = dp.getData();
            int length = dp.getLength();
            //输出数据
            System.out.println("sender ----"+address.getHostAddress());
            System.out.println(new String(data));        
        }
    
    }

     运行结果:

    说明:

    (1)DatagramSocket类

    DatagramSocket() :创建实例,通常用于客户端编程,他并没有特定的监听端口,仅仅使用一个临时的。
    DatagramSocket(int port) :创建实例,并固定监听Port端口的报文。
    DatagramSocket(int port, InetAddress laddr) :这是个非常有用的构建器,当一台机器拥有多于一个IP地址的时候,由它创建的实例仅仅接收来自LocalAddr的报文。
    DatagramSocket(SocketAddress bindaddr) :bindaddr对象中指定了端口和地址。

    常用方法:

    receive(DatagramPacket p) :接收数据报文到p中。receive方法是阻塞的,如果没有接收到数据报包的话就会阻塞在哪里。
    send(DatagramPacket p) :发送报文p到目的地。
    setSoTimeout(int timeout) :设置超时时间,单位为毫秒。
    close() :关闭DatagramSocket。在应用程序退出的时候,通常会主动的释放资源,关闭Socket,但是由于异常的退出可能造成资源无法回收。所以应该在程序完成的时候,主动使用此方法关闭Socket,或在捕获到异常后关闭Socket。

    (2)DatagramPacket类

    DatagramPacket类用于处理报文,将字节数组、目标地址、目标端口等数据包装成报文或者将报文拆卸成字节数组。

    DatagramPacket(byte[] buf, int length, InetAddress addr, int port) :从buf字节数组中取出offset开始的、length长的数据创建数据对象,目标地址是addr,目标端口是port;
    DatagramPacket(byte buf[], int offset, int length, SocketAddress address) :从buf字节数组中取出offset开始的、length长的数据创建数据对象,目标地址是address;

    常用方法:

    getData() byte[] :从实例中取得报文中的字节数组编码。

    setData(byte[] buf, int offset, int length): 设置数据报包中的数据内容

    TCP协议的简单使用

     tcp客户端发送数据流程:

    • 创建发送端Socket对象(创建连接),Tcp的Socket对象与Udp的有所不同,需注意;
    •  获取输出流对象;
    •  发送数据;
    •  释放资源;

     tcp服务端接收数据流程:

    •  创建接收端Socket对象;
    • 监听(阻塞):如果建立连接失败,程序会卡在这里,不往下执行;
    • 获取输入流对象;
    •  获取数据;
    •  输出数据;
    •  释放资源;

     案例一:客户端发送数据,服务端接收数据;

    ClietDemo.java:

    package com.cherish.Socket;
    
    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    /*
     * 使用tcp协议发送数据
     *         创建发送端Socket对象(创建连接)
     *         获取输出流对象
     *         发送数据
     *         释放资源
     * */
    public class TcpClientDemo {
        public static void main(String[] args) throws IOException{
            //创建发送端Socket对象(创建连接)
            Socket s = new Socket(InetAddress.getByName("acer-pc"),8886); 
            //获取输出流对象
            OutputStream os = s.getOutputStream();
            //发送数据
            String str = "hello tcp , i m coming!";
            os.write(str.getBytes());
            //释放资源
            s.close();
        }
    }

    ServerDemo.java: 

    package com.cherish.Socket;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /*
     * 使用tcp协议接受对象
     *         创建接收端Socket对象
     *         监听(阻塞):如果建立连接失败,程序会卡在这里,不往下执行
     *         获取输入流对象
     *         获取数据
     *         输出数据
     *         释放资源
     * */
    public class TcpServerDemo {
        
        public static void main(String[] args) throws IOException {
            //创建接收端Socket对象
            ServerSocket ss = new ServerSocket(8886); 
            //监听
            Socket s = ss.accept();
            //获取输入流对象
            InputStream is = s.getInputStream();
            //获取数据
            byte[] bys = new byte[1024];
            int len; //用于存储读到的字节数
            len = is.read(bys);
            //输出数据
            String client = s.getInetAddress().getHostName();
            System.out.println(client+"发来的数据");
            System.out.println(new String(bys,0,len));
            //释放资源
            s.close();
            //ss.close(); //socket对象一般不释放,因为客户端不止一个,可能有多个客户端会发送数据        
        }    
    }

     运行结果:

     案例二:对案例一进行改进,服务端收到数据后,将数据由小写换成大写,然后返回给客户端:

    Client.java:

    package com.cherish.Socket.TcpDemo_2;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    public class Client {
        public static void main(String[] args) throws IOException {
            //创建发送端socket对象
            Socket s = new Socket(InetAddress.getByName("acer-pc"),8866);
            //获取输出流对象
            OutputStream os = s.getOutputStream();
            //写入数据
            String str = "hello tcp ,ahahahah!!!";
            os.write(str.getBytes());
            System.out.println("数据已发送");
            //获取输入流对象
            InputStream is = s.getInputStream();
            byte[] bys = new byte[1024];
            int len = is.read(bys);
            String backStr = new String(bys,0,len);
            System.out.println(backStr);
            s.close();        
        }
    }

     Server.java:

    package com.cherish.Socket.TcpDemo_2;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class Server {
        public static void main(String[] args) throws IOException {
            //创建服务端接收对象
            ServerSocket ss = new ServerSocket(8866);
            //监听 来自客户端的连接
            Socket s = ss.accept();
            //获取输入流对象
            InputStream is = s.getInputStream();
            //获取数据
            byte[] bys = new byte[1024];
            int len = is.read(bys);
            String str = new String(bys,0,len);
            //输出数据
            System.out.println(str);
            //转换数据
            String upperStr = str.toUpperCase();
            //获取输出流对象
            OutputStream os = s.getOutputStream();
            //写入数据到流中
            os.write(upperStr.getBytes());
            //释放资源
            s.close();
        }
    }

    运行结果:

    案例三:用tcp模拟一个登录功能,tcp客户端输入用户名和密码,然后发送给服务端,服务端根据输入结果返回登录或失败给客户端;

    LoginClient.java:

    package com.cherish.Socket.LoginCase;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    /*
     * 模拟用户登录案例
     * */
    public class LoginClient {
        public static void main(String[] args) throws IOException {
            //创建客户端socket对象
            Socket s = new Socket("acer-pc",8885);
            //获取用户名和密码
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("请输入用户名");
            String userName = br.readLine();
            System.out.println("请输入密码");
            String password = br.readLine();        
            //获取输出流对象
            PrintWriter out = new PrintWriter(s.getOutputStream(),true);
            //写出数据
            out.println(userName);
            out.println(password);
            //获取输入流对象
            BufferedReader serverBr = new BufferedReader(new InputStreamReader(s.getInputStream()));
            //获取服务端返回的数据
            String backStr = serverBr.readLine();
            System.out.println(backStr);
            //关闭资源    
            s.close();
        }
    }

     LoginServer.java:

    package com.cherish.Socket.LoginCase;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class LoginServer {
        public static void main(String[] args) throws IOException {
            //创建服务端socket对象
            ServerSocket ss = new ServerSocket(8885);
            //监听
            Socket s = ss.accept();
            //获取输入流对象
            BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            //获取用户名和密码
            String userName = br.readLine();
            String password = br.readLine();
            //判断用户名和密码是否正确
            boolean flag = false;
            if (userName.equals("cherish")&&password.equals("123")) {
                flag = true;
            }
            //获取输出流对象
            PrintWriter out = new PrintWriter(s.getOutputStream(),true);
            //返回判断信息
            if (flag) {
                System.out.println("成功");
                out.println("登录成功");
            }else {
                out.println("登陆失败");
            }
            //释放资源
            s.close();
            //ss.close();//服务端一般不关闭                
        }
    }

    运行结果:

     结语:本篇博客根据传智播客基础视频整理,记载比较简单,但大致能演示清楚udp和tcp的区别及其用法。个人学习java才不到4天时间,对于java的一些知识点不能讲解的很清楚,因此代码注释写的不是很详细,见谅见谅!哈哈!

    本片博客的代码我上传到百度云盘,需要的可自取:

    链接:https://pan.baidu.com/s/1-dTfG9GY5MBIAlNJg1v0mw 
    提取码:3dw6 
    复制这段内容后打开百度网盘手机App,操作更方便哦
  • 相关阅读:
    Array,prototype.toString.call()
    js 中的delete运算符
    c#连接sql数据库以及操作数据库
    ArrayList集合
    查找出数据表中的重复值
    C#中的List<string>泛型类示例
    C#中的List<string>泛型类示例
    C#中Convert.ToInt32、int.TryParse、(int)和int.Parse四者的区别
    C#获取文件夹下的所有文件的方法
    从本地文件夹中读取文本文档,并将所有的文档内容合并到一个文本中
  • 原文地址:https://www.cnblogs.com/CherishTheYouth/p/CherishTheYouth_2019_0716.html
Copyright © 2020-2023  润新知