• 网络编程——TCP协议和通信


    第1章 TCP通信

    TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象。

    区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。

    TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。

    JDK中提供了两个类用于实现TCP程序,一个是ServerSocket类,用于表示服务器端,一个是Socket类,用于表示客户端

    通信时,首先创建代表服务器端的ServerSocket对象,该对象相当于开启一个服务,并等待客户端的连接,然后创建代表客户端的Socket对象向服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。

    客户端Socket类向服务器的ServerSocket类请求连接,实现了数据同路连接,连接同路中,有一个对象建立完毕,这个对象就是IO流对象(字节流)

    1.1 ServerSocket

    通过前面的学习知道,在开发TCP程序时,首先需要创建服务器端程序。JDKjava.net包中提供了一个ServerSocket类,该类的实例对象可以实现一个服务器段的程序。通过查阅API文档可知,ServerSocket类提供了多种构造方法,接下来就对ServerSocket的构造方法进行逐一地讲解。

     

    使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上(参数port就是端口号)。

    接下来学习一下ServerSocket的常用方法,如表所示。

    ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收来自客户端的请求。当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求,accept()方法才会返回一个Scoket对象用于和客户端实现通信,程序才能继续向下执行。

    1.2 Socket

    讲解了ServerSocket对象可以实现服务端程序,但只实现服务器端程序还不能完成通信,此时还需要一个客户端程序与之交互,为此JDK提供了一个Socket类,用于实现TCP客户端程序。

    通过查阅API文档可知Socket类同样提供了多种构造方法,接下来就对Socket的常用构造方法进行详细讲解。

    使用该构造方法在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host接收的是一个字符串类型的IP地址。

    该方法在使用上与第二个构造方法类似,参数address用于接收一个InetAddress类型的对象,该对象用于封装一个IP地址。

    在以上Socket的构造方法中,最常用的是第一个构造方法。

    接下来学习一下Socket的常用方法,如表所示。

    Socket类的常用方法中,getInputStream()getOutStream()方法分别用于获取输入流和输出流。当客户端和服务端建立连接后,数据是以IO流的形式进行交互的,从而实现通信。

    接下来通过一张图来描述服务器端和客户端的数据传输,如下图所示。

    1.3 简单的TCP网络程序

    了解了ServerSocketSocket类的基本用法,为了让大家更好地掌握这两个类的使用,接下来通过一个TCP通信的案例来进一步学习。如下图所示。

     客户端代码:

    /**
     * 实现TCP客户端,连接到服务器
     * 和服务器实现数据交换
     * 实现TCP客户端程序类java.net.Socket
     *
     * 构造方法:
     *      Socket(String host, int port) 传递服务器IP和端口号
     *      注意:构造方法只要运行,就会和服务器进行连接,连接失败,抛出异常
     *
     * OutputStream getOutputStream() 返回套接字的输出流
     *      作用:将数据输出,输出到服务器
     * InputStream getInputStream() 返回套接字的输入流
     *      作用:从服务器端读取数据
     *
     * 客户端服务器数据交换,你虚使用套接字对象Socket中获取IO流,自己new的IO流,没用
     * Created by YuKai Fan on 2018/8/13.
     */
    public class TCPClient {
        public static void main(String[] args) throws IOException{
            //创建Socket对象,连接服务器
            Socket socket = new Socket("127.0.0.1", 8888);
            //通过客户端的套接字方法Socket的方法,获取字节输出流,将输入写向服务器
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("服务器OK".getBytes());
    
            //读取服务器发回的数据,使用socket套接字对象中的字节输入流
            InputStream inputStream = socket.getInputStream();
            byte[] data = new byte[1024];
            int len = inputStream.read(data);
            System.out.println(new String(data,0,len));
            socket.close();
        }
    }

    服务器端:

    /**
     * 实现TCP服务器程序
     * 表示服务器程序类java.net.ServerSocket
     *
     * 构造方法:
     *      ServerSocket(int port) 传递端口号
     *
     * 很重要的事情:必须要获取客户端的Socket对象
     *      Socket   accept()
     * 服务器可以获取到客户端的套接字对象
     * Created by YuKai Fan on 2018/8/13.
     */
    public class TCPServer {
        public static void main(String[] args) throws IOException {
            ServerSocket server = new ServerSocket(8888);
            //调用服务器套接字对象中的方法accept() 获取客户端套接字对象
            Socket socket = server.accept();
            //System.out.println(socket);
            //通过客户端套接字对象,Socket获取字节输入流,读取的是客户端发送来的数据
            InputStream inputStream = socket.getInputStream();
            byte[] data = new byte[1024];
            int length = inputStream.read(data);
            System.out.println(new String(data,0,length));
    
            //服务器向客户端回数据,字节输出流,通过客户端套接字对象获取字节输出流
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("收到,谢谢".getBytes());
            socket.close();
            server.close();
        }
    }

    TCP中的流对象过程:服务端有两个问题需要解决 (1)需要知道是哪个客户端输出流,(2)ServerSocket本身是没有流操作的,需要用到客户端类Socket的流操作

    1.4 文件上传案例

    目前大多数服务器都会提供文件上传的功能,由于文件上传需要数据的安全性和完整性,很明显需要使用TCP协议来实现。接下来通过一个案例来实现图片上传的功能。如下图所示。原图:文件上传.bmp

     要实现TCP通信需要创建一个服务器端程序和一个客户端程序,为了保证数据传输的安全性,首先需要实现服务器端程序。

    /**
     * 实现TCP图片上传客户端
     * 实现步骤:
     *      1.Socket套接字连接服务器
     *      2.通过Socket获取字节输出流,写入图片
     *      3.使用自己的流对象,读取图片数据源
     *          FileInputStream
     *      4.读取图片,使用字节输出流,将图片写到服务器
     *          采用字节数组进行缓冲
     *      5.通过Socket套接字获取字节输入流
     *          读取服务器发回来的上传成功
     *      6.关闭资源
     * Created by YuKai Fan on 2018/8/13.
     */
    public class TCPClientPic {
        public static void main(String[] args) throws IOException {
            Socket socket = new Socket("127.0.0.1",8000);
            //获取字节输出流,将图片写入服务器
            OutputStream out = socket.getOutputStream();
            //创建字节输入流,读取本机上的数据源图片
            FileInputStream fis = new FileInputStream("C:\Users\Public\Pictures\Sample Pictures\p_1508807882673.jpg");
            //开始读写字节数组
            int len = 0;
            byte[] data = new byte[1024];
            while ((len = fis.read(data)) != -1) {
                out.write(data, 0, len);
            }
            //给服务器写终止序列
            socket.shutdownOutput();
            //获取字节输入流,读取服务器上传成功
            InputStream in = socket.getInputStream();
            len = in.read(data);
            System.out.println(new String(data,0,len));
    
            fis.close();
            socket.close();
        }
    }

    完成了服务器端程序的编写,接下来编写客户端程序。

     

    /**
     * TCP图片上传服务器
     *      1.ServerSocket套接字对象,监听端口号8000
     *      2.方法accept()获取客户端连接对象
     *      3.客户端连接对象获取字节输入流,读取客户端发送图片
     *      4.创建File对象,绑定上传的文件夹
     *          判断文件夹是否存在
     *      5.创建字节输出流,数据目的File对象所在的文件夹
     *      6.字节流读取图片,字节流将图片写入到目的文件夹中
     *      7.将上传成功写回客户端
     *      8.关闭资源
     * Created by YuKai Fan on 2018/8/13.
     */
    public class TCPServerPic {
        public static void main(String[] args) throws IOException{
            ServerSocket server = new ServerSocket(8000);
            Socket socket = server.accept();
            //通过客户端连接对象,获取字节输入流,读取客户端图片
            InputStream in = socket.getInputStream();
            //将目的文件夹封装到File对象
            File upload = new File("d:\upload");
            if (!upload.exists()) {
                upload.mkdir();
            }
            //防止文件同名,重新定义文件名字
            //规则:域名+毫秒值+6位随机数
            String filename = "java"+System.currentTimeMillis()+new Random().nextInt(9999)+".jpg";
            //创建字节输出流,将图片写入目的文件夹中
            FileOutputStream fos = new FileOutputStream(upload + File.separator + filename);
            //读写字节数组
            byte[] data = new byte[1024];
            int len = 0;
            while ((len = in.read(data)) != -1) {//读到的是客户端的数据,永远也读不到-1,所以一直处于线程等待
                fos.write(data,0,len);
            }
            socket.getOutputStream().write("上传成功".getBytes());
            socket.close();
            server.close();
            fos.close();
        }
    }

     

    1.5文件上传案例多线程版本

     

    实现服务器端可以同时接收多个客户端上传的文件。

    我们要修改服务器端代码

     

    /*
     * 文件上传多线程版本, 服务器端
     */
    public class TCPServer {
        public static void main(String[] args) throws IOException {
            //1,创建服务器,等待客户端连接
            ServerSocket serverSocket = new ServerSocket(6666);
            
            //实现多个客户端连接服务器的操作
            while(true){
                final Socket clientSocket = serverSocket.accept();
                //启动线程,完成与当前客户端的数据交互过程
                new Thread(){
                    public void run() {
                        try{
                            //显示哪个客户端Socket连接上了服务器
                            InetAddress ipObject = clientSocket.getInetAddress();//得到IP地址对象
                            String ip = ipObject.getHostAddress(); //得到IP地址字符串
                            System.out.println("小样,抓到你了,连接我!!" + "IP:" + ip);
                            
                            //7,获取Socket的输入流
                            InputStream in = clientSocket.getInputStream();
                            //8,创建目的地的字节输出流   D:\upload\192.168.74.58(1).jpg
                            BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream("D:\upload\"+ip+"("+System.currentTimeMillis()+").jpg"));
                            //9,把Socket输入流中的数据,写入目的地的字节输出流中
                            byte[] buffer = new byte[1024];
                            int len = -1;
                            while((len = in.read(buffer)) != -1){
                                //写入目的地的字节输出流中
                                fileOut.write(buffer, 0, len);
                            }
                            
                            //-----------------反馈信息---------------------
                            //10,获取Socket的输出流, 作用:写反馈信息给客户端
                            OutputStream out = clientSocket.getOutputStream();
                            //11,写反馈信息给客户端
                            out.write("图片上传成功".getBytes());
                            
                            out.close();
                            fileOut.close();
                            in.close();
                            clientSocket.close();
                        } catch(IOException e){
                            e.printStackTrace();
                        }
                    };
                }.start();
            }
    
            //serverSocket.close();
        }
    }

     

     

  • 相关阅读:
    spring boot下载本地静态文件最实用
    非常实用的MySQL中if、ifnull函数以及case/when的使用
    java获取访问地址IP的简单方法
    Oracle数据库视图的创建以及使用
    http-post调用接口简单代码
    orale数据库to_char时间中英文转换
    java线程的简单实用
    java小数保留位数四舍五入
    二项式反演
    学习总结-后缀数组
  • 原文地址:https://www.cnblogs.com/FanJava/p/9470424.html
Copyright © 2020-2023  润新知