JAVA 网络编程——TCP 三次握手 四次挥手 文件从客户端传输到服务器

Tcp通信原理

TCP通信协议是一种可靠的网络协议,他在通信的两端各建立一个Socket对象

通信之前要保证连接已经建立

通过Socket产生IO流来进行网络通信

TCP发送数据步骤

1.创建客户端的Socket对象(Socket)与指定服务端链接

Socket(String host,int port)

2.获取输出流,写数据

OutputStream getOutputStream()

3.释放资源 close()

代码:

public static void main(String[] args) throws IOException {
    //创建Socket对象
    Socket socket =new Socket("127.0.0.1",9999);
    OutputStream ost=socket.getOutputStream();
    //将数据写到服务器
    ost.write("hellp".getBytes());
    //释放资源
    ost.close();
    socket.close();

}

TCP接收数据

步骤:

1.创建服务器端的Socke对象(ServerSocket)

ServerSocket(int port)

2.监听客户链接,返回一个Socket对象 

Socket accept()

3.获取输入流,读数据,并把数据显示在控制台

InputStream getInputStream()

4.释放资源

void close()

代码:

public class Server {
    public static void main(String[] args) throws IOException {
        //创建服务器对象
        ServerSocket serverSocket = new ServerSocket(9999);
        // 服务端调用accept()接收并返回一个Socket对象
//        accept会一直等待连接
        Socket accept = serverSocket.accept();
        //获得输入流对象
        InputStream is = accept.getInputStream();
        int b;
        while ((b = is.read()) != -1) {
            //强转回字符
            System.out.println((char) b);
        }

    }
}

TCP原理分析

JAVA 网络编程——TCP 三次握手 四次挥手 文件从客户端传输到服务器_第1张图片

三次握手

1.客户端向向服务器发送链接请求,等待服务器缺人

2.服务器向客户端返回一个响应,告诉客户端收到请求

3.客户端向服务器再次发出缺人信息,连接建立

四次挥手

1.客户端发送取消连接请求

2.服务器向客户端返回响应表示收到客户端取消请求,并将最后的数据处理完毕

3.服务器向客户端发出确认取消信息

4.客户端再次向服务器发出确认取消信息,连接取消

TCP通信程序练习

1.

客户端:发送数据,接收服务器反馈

服务器:接收数据,给出反馈

客户端:

public class Client {
    public static void main(String[] args) throws IOException {
        //创建Socket对象
        Socket socket =new Socket("127.0.0.1",9999);
        OutputStream ost=socket.getOutputStream();
        //将数据写到服务器
        ost.write("hello".getBytes());
        //关闭输出流但是不影响socket,否则server的Reader会阻塞,一直等
        socket.shutdownOutput();

        BufferedReader br =new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while ((line= br.readLine())!=null){
            System.out.println(line);
        }
        //释放资源\
        br.close();
        ost.close();
        socket.close();

    }
}

服务端:

public class Server {
    public static void main(String[] args) throws IOException {
        //创建服务器对象
        ServerSocket serverSocket = new ServerSocket(9999);
        // 服务端调用accept()接收并返回一个Socket对象
//        accept会一直等待连接
        Socket accept = serverSocket.accept();
        //获得输入流对象
        InputStream is = accept.getInputStream();
        int b;
        while ((b = is.read()) != -1) {
            //强转回字符
            System.out.println((char) b);
        }
        System.out.println("看看是否这里阻塞了");
        //涉及到中文,我们把字节流转换成字符流
        BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("我很好");
        bw.newLine();
        bw.flush();

        bw.close();
        is.close();
        accept.close();
        serverSocket.close();
    }
}

2.客户端发送文件到服务端

客户端:

public class Client {
    public static void main(String[] args) throws IOException {
        //创建Socket对象
        Socket socket =new Socket("127.0.0.1",9999);
        //创建本地文件输入流
        BufferedInputStream bf=new BufferedInputStream(new FileInputStream("test\\image\\1.jpg"));
        //创建网络输出流
        OutputStream ost=socket.getOutputStream();
        BufferedOutputStream bos =new BufferedOutputStream(ost);
        //将数据写到网络

        int b;
        while ((b=bf.read())!=-1){
            bos.write(b);
        }

        //关闭输出流但是不影响socket,否则server的Reader会阻塞,一直等
        socket.shutdownOutput();

        BufferedReader br =new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while ((line= br.readLine())!=null){
            System.out.println(line);
        }
        //释放资源
        br.close();
        socket.close();

    }
}

服务端:

public class Server {
    public static void main(String[] args) throws IOException {
        //创建服务器对象
        ServerSocket serverSocket = new ServerSocket(9999);
        // 服务端调用accept()接收并返回一个Socket对象
//        accept会一直等待连接
        Socket accept = serverSocket.accept();
        //获得网络输入流对象
        InputStream is = accept.getInputStream();
        //转换成缓冲流
        BufferedInputStream bis =new BufferedInputStream(is);

        //创建本地输出流
        BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("test\\image\\4.jpg"));
        //读取网络流的内容并写到新文件中
        int b;
        while ((b = bis.read()) != -1) {
          bos.write(b);
        }
        System.out.println("看看是否这里阻塞了");
        //涉及到中文,我们把字节流转换成字符流
        BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("传送完毕");
        bw.newLine();
        bw.flush();


        bos.close();
        accept.close();
        serverSocket.close();
    }
}

关于关流,网络上的流只需要关闭socket就可以了 

本地的流需要手动关一下

文件上传服务器优化

1.

刚才编写的服务器,客户端一旦完成文件传输,服务器就关闭了

但是有时候客户端需要多次传输,或者有多个客户端需要传输

那么我们在服务器端直接加一个循环进去:

public class Server {
    public static void main(String[] args) throws IOException {
        //创建服务器对象
        ServerSocket serverSocket = new ServerSocket(9999);
        // 服务端调用accept()接收并返回一个Socket对象
//        accept会一直等待连接
        while (true) {
            Socket accept = serverSocket.accept();
            //获得网络输入流对象
            InputStream is = accept.getInputStream();
            //转换成缓冲流
            BufferedInputStream bis =new BufferedInputStream(is);

            //创建本地输出流
            BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("test\\image\\4.jpg"));
            //读取网络流的内容并写到新文件中
            int b;
            while ((b = bis.read()) != -1) {
              bos.write(b);
            }
            System.out.println("看看是否这里阻塞了");
            //涉及到中文,我们把字节流转换成字符流
            BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            bw.write("传送完毕");
            bw.newLine();
            bw.flush();


            bos.close();
            accept.close();
        }
        //服务器不关闭
//        serverSocket.close();
    }
}

2.  第二次或者多次上传文件的时候,会把第一次的文件给覆盖。

那么我们可以在写文件的时候,将文件名设置成随机就可以了

但是用Random方法显然不符合要求

UUID:一个表示不可变的通用唯一标识符的类

静态方法:.randomUUID()

随机生成一个uuid对象

然后再调用.toString()方法就可以转换成字符串

将文件名改成 UUID.randomUUID().toString();就可以了

3.设置while true循环的时候虽然可以传输多个文件,但是无法同时处理多个客户端的请求

我们可以利用多线程

修改后的Server:

public class Server {
    public static void main(String[] args) throws IOException {
        //创建服务器对象
        ServerSocket serverSocket = new ServerSocket(9999);
        // 服务端调用accept()接收并返回一个Socket对象
//        accept会一直等待连接
        while (true) {
            Socket accept= serverSocket.accept();
            //当接收到socket对象就开一条线程 并且将accept传递过去
            ServerThread st= new ServerThread(accept);
           new Thread(st).start();
        }
        //服务器不关闭
//        serverSocket.close();
    }
}

创建的线程类:

public class ServerThread implements Runnable{
    private final Socket accept;

    public ServerThread(Socket accept) {
       this.accept=accept;
    }

    @Override
    public void run() {
        BufferedOutputStream bos=null;

        try {

            //获得网络输入流对象
            InputStream is = accept.getInputStream();
            //转换成缓冲流
            BufferedInputStream bis =new BufferedInputStream(is);

            //创建本地输出流
          bos=new BufferedOutputStream(new FileOutputStream("test\\image\\"+ UUID.randomUUID().toString() +".jpg"));
            //读取网络流的内容并写到新文件中
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
            System.out.println("看看是否这里阻塞了");
            //涉及到中文,我们把字节流转换成字符流
            BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
            bw.write("传送完毕");
            bw.newLine();
            bw.flush();

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                assert bos != null;
                bos.close();
                accept.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

4.当我们使用多线程后, 虽然可以让服务器处理多个客户端请求,但是资源消耗量太大

直接在Server创建一个线程池

public class Server {
    public static void main(String[] args) throws IOException {
        //创建服务器对象
        ServerSocket serverSocket = new ServerSocket(9999);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                10,//线程池最大容量
                60,//临时线程空闲时间
                TimeUnit.SECONDS,//临时线程空闲时间的单位
                new ArrayBlockingQueue<>(5),//允许阻塞的个数
                Executors.defaultThreadFactory(),//创建线程的方式
                new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
        );
        // 服务端调用accept()接收并返回一个Socket对象
//        accept会一直等待连接
        while (true) {
            Socket accept = serverSocket.accept();
            //当接收到socket对象就开一条线程
            ServerThread st = new ServerThread(accept);
//            new Thread(st).start();
            //将线程丢进线程池
            pool.submit(st);
        }
        //服务器不关闭
//        serverSocket.close();
    }
}

你可能感兴趣的:(网络,tcp/ip,网络协议)