2.C/S模式的TCP程序

目录

TCP通信程序

1.TCP通信概述

2.客户端Socket代码实现

3.TCP通信的服务器端ServerSocket代码实现

4.TCP通信的文件上传

4.1.文件上传原理

4.2.文件上传分析图解

4.3.客户端代码

4.4.服务器代码

4.5.文件上传阻塞问题

4.6.文件上传优化问题

5.模拟BS服务器

学习网络通信,需要关注两个类,Socket和ServerSocket两个类来搭建服务器与客户端,以及客户端与服务器间通信通过各自的Socket。

TCP通信程序

1.TCP通信概述

  • 1.TCP通信程序:即使用TCP协议进行通信的java程序
  • 2.TCP通信能实现两台计算机之间的数据交互,通信的两端,要严格区分客户端与服务器端。TCP通信是面向连接的通信,客户端和服务器端必须经过3次握手,建立逻辑连接,才能通信。

两端通信时步骤:

  • 1.服务器端程序,需要事先启动,等待客户端的连接
  • 2.客户端主动连接服务器端,连接成功才能通信。服务器端不能主动连接客户端(只有客户端想访问才连接).
  • 服务器端先启动,服务器端不会主动的请求客户端,必须客户端请求服务器端,经三次握手客户端和服务器端就会建一个逻辑连接(通路).
  • 在这个逻辑连接中包含一个IO对象,客户端和服务器端就可以使用IO对象进行通信,通信的数据不仅是字符还有字节,因此这个IO对象是字节流对象。
  • 客户端和服务器端进行一次数据交互,需要4个IO流对象,客户端的字节输入输出流对象和服务器端的字节输入输出流对象(任意一端都有读取和写入)。

服务器端必须明确两件事:

   1.多个客户端同时和服务器端进行交互,服务器必须明确和哪个客户端进行的交互:

  • 在服务器端有一个方法叫server.accept(),可以获取到请求的客户端对象。

2.多个客户端同时和服务器端进行交互,服务器端就需要使用多个IO流对象

  • 服务器端是没有IO流的,但服务器可以获取到请求的客户端对象Socket,
  • 服务器端是使用每个客户端Socket中提供的IO流和客户端进行交互的。
  • 服务器使用客户端通过的字节输入流来读取客户端发送的数据,
  • 服务器使用客户端通过的字节输出流来给客户端回写数据。
  • 在服务器端使用server.accept()方法来获取请求的客户端对象Socket s1,Socket s2,然后服务器端根据获取到的客户端对象s1,s2分别与对应的客户端交互。

简单记:

  • 服务器使用客户端的流来和客户端交互的。

重点:

  • 1.客户端和服务器端是怎么进行交互的:需要使用IO流
  • 2.服务器端没有IO流,是借助客户端的流和客户端进行交互的。

2.客户端Socket代码实现

TCP通信的客户端Socket类代码实现:
1.TCP通信的客户端功能:

  • 向服务器发送连接请求,给服务器发送数据读取服务器回写数据

2. 表示客户端的类:

  • java.net.Socket:此类实现客户端套接字(也可就叫套接字)
  • 套接字:包含IP地址和端口号的网络单位,是两台机器间通信的端点。不妨认为套接字就是客户端,创建套接字就是创建客户端对象

3.Socket构造方法

  • Socket(String host,int port):创建一个流套接字并将其连接到指定主机host的指定端口号port上
  • 参数:
    • String host:服务器主机名称/服务器的ip地址
    • int port:服务器端口号

4.成员方法:

OutputStream getOutputStream():获取此套接字的输出流
InputStream getInputStream():获取此套接字的输入流
void close():关闭此套接字

5.Socket实现步骤:

  • 1.创建一个客户端对象Socket,构造方法绑定服务器的ip和端口号
  • 2.使用Socket对象中的方法getOutputStream获取网络字节输出流OutputStream对象
  • 3.使用网络字节输出流对象OutputStream对象中write方法,给服务器发送数据
  • 4.使用Socket对象中方法getInputStream获取网络字节输入流InputStream对象
  • 5.使用网络字节输入流InputStream对象中read方法,读取服务器回写的数据
  • 6.释放资源(Socket)

6.注意事项:

  • 1.客户端和服务器端进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
  • 2.当我们创建客户端对象Socket时候,就会去请求服务器和服务器3次握手建立连接通路,这时如果服务器没有启动,那么就会抛出异常java.net.ConnectException,如果服务器已经启动,那么就可以进行交互了
package demo1TCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1.创建一个客户端对象Socket,构造方法绑定服务器的ip和端口号
        Socket socket = new Socket("127.0.0.1",8888);//获取客户端对象
        //2.使用Socket对象中的方法getOutputStream获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();//获取网络流
        //3.使用网络字节输出流对象OutputStream对象中write方法,给服务器发送数据
        os.write("你好服务器".getBytes());
        //4.使用Socket对象中方法getInputStream获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //5.使用网络字节输入流InputStream对象中read方法,读取服务器回写的数据。
        byte[] bytes = new byte[1024];
        //因为客户端发送"你好服务器",使用字节数组读取一次就读完了,不需要循环
        int len = is.read(bytes);//len表示读取的有效字节数
        //使用new String()构造方法把字节数组化为字符串
        System.out.println(new java.lang.String(bytes,0,len));
        //6.释放资源(Socket)
        socket.close();
    }
}

3.TCP通信的服务器端ServerSocket代码实现

TCP通信的服务器端ServerSocket类代码实现:
1.TCP通信的服务器端功能:

  • 接收客户端请求,读取客户端发送的数据,给客户端回写数据

2.表示服务器的类:

  • java.net.ServerSocket:此类实现服务器套接字

3.服务器端构造方法:

  • ServerSocket(int port):创建绑定到特定端口的服务器套接字(服务器对象)

4.服务器端必须明确一件事情

  • 必须知道是哪个客户端请求的服务器所以可以使用accept方法获取到发出请求的客户端对象Socket
  • Socket accept():侦听并接收到此套接字的连接

5.服务器的实现步骤:

  • 1.创建服务器ServerSocket对象和系统要指定的端口号
  • 2.使用ServerSocket对象中accept方法,获取到发出请求的客户端对象Socket
  • 3.使用Socket对象中方法getInputStream方法获取网络字节输入流InputStream对象
  • 4.使用网络字节输入流对象中read方法,读取客户端发送的数据
  • 5.使用Socket对象中的方法getOutputStream获取网络字节输出流OutputStream对象
  • 6.使用网络字节输出流对象中的方法write,给客户端回写数据
  • 7.释放资源(Socket和ServerSocket)
package demo1TCP;

import com.sun.org.apache.xpath.internal.operations.String;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1.创建服务器ServerSocket对象和系统要指定的端口号
        ServerSocket server = new ServerSocket(8888);
        //2.使用ServerSocket对象中accept方法,获取到发出请求的客户端对象Socket
        Socket socket= server.accept();
        //3.使用Socket对象中方法getInputStream方法获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //4.使用网络字节输入流对象中read方法,读取客户端发送的数据
        byte[] bytes = new byte[1024];
        //因为客户端发送"你好服务器",使用字节数组读取一次就读完了,不需要循环
        int len = is.read(bytes);//len表示读取的有效字节数
        //使用new String()构造方法把字节数组化为字符串,本jdk不适合用String构造方法
        System.out.println(new java.lang.String(bytes,0,len));
        //5.使用Socket对象中的方法getOutputStream获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        // 6.使用网络字节输出流对象中的方法write,给客户端回写数据
        os.write("收到谢谢".getBytes());//以字节写入
        // 7.释放资源(Socket和ServerSocket)
        socket.close();
        server.close();
    }
}

4.在服务器中加入多线程

  • 先启动服务器,再启动客户端

  • 上述一个服务器只能与一个客户端交流,将来服务器肯定是和大量客户端交互,这就需要在服务器中加入多线程

 

4.TCP通信的文件上传

4.1.文件上传原理

  • 原理:客户端读取本地的文件,把文件上传到服务器,服务器再把上传的文件保存到服务器的硬盘上。文件下载原理与上传原理相反。

4.2.文件上传分析图解

4.3.客户端代码

将本地文件即根目录下的"Net\\待上传文件.txt"文件上传到服务器端(还是本台电脑)的根目录下Net\\upload文件夹

文件上传案例的客户端代码:

  • 1.读取本地文件 2.上传到服务器 3.读取服务器回写的数据

 明确:

  • 数据源(所要上传的文件,相对路径):Net\\待上传文件.txt
  • 目的地:服务器

 文件上传客户端步骤:

  • 1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
  • 2.创建一个客户端Socket对象,构造方法中绑定服务器ip和端口号
  • 3.使用Socket中方法getOutputStream获取网络字节输出流OutputStream对象
  • 4.使用本地字节输入流FileInputStream对象中方法read,读取本地文件
  • 5.使用网络字节输出流OutputStream对象中方法write,把读取到的文件上传到服务器
  • 6.使用Socket中的方法getInputStream获取网络字节输入流InputStream对象
  • 7.使用网络字节输入流对象中read方法读取服务器回写的数据
  • 8.释放资源(FileInputStream,Socket)
package demo2FIleUpload;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Demo01TCPClient {
    public static void main(String[] args) throws IOException {
        //1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("Net\\待上传本地文件.txt");
        //2.创建一个客户端Socket对象,构造方法中绑定服务器ip和端口号
        Socket socket = new Socket("127.0.0.1",8888);
        //3.使用Socket中方法getOutputStream获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //4.使用本地字节输入流FileInputStream对象中方法read,读取本地文件
        int len=0;//记录每次读取的有效字节数
        byte[] bytes = new byte[1024];
        while((len=fis.read(bytes))!=-1){
            //5.使用网络字节输出流OutputStream对象中方法write,把读取到的文件上传到服务器
            os.write(bytes,0,len);
        }
        //6.使用Socket中的方法getInputStream获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //7.使用网络字节输入流对象中read方法读取服务器回写的数据
        while ((len=is.read(bytes))!=-1){
            System.out.println(bytes);
        }
        //8.释放资源(FileInputStream,Socket)
        fis.close();
        socket.close();
    }
}

4.4.服务器代码

文件上传案例服务器端代码:

  • 1.读取客户端上传的文件 2.保存到服务器硬盘 3.给客户端回写数据

 明确:

  • 数据源:客户端上传的文件
  • 目的地:服务器上的硬盘:Net\\upload\\上传到服务器的文件.txt

 服务器端代码实现步骤:

  • 1.创建一个服务器ServerSocket对象,构造方法中绑定要指定的端口号
  • 2.使用ServerSocket对象中的accept方法,获取到发送请求的客户端Socket
  • 3.使用Socket对象中的getInputStream方法,获取网络字节输入流InputStream对象
  • 4.判断服务器硬盘上Net\\upload文件夹是否存在,不存在则创建
  • 5.创建一个本地字节输出流对象FileOutputStream,构造方法中绑定要输出的目的地
  • 6.使用网络字节输入流InputStream对象中的read方法,读取客户端上传文件
  • 7.使用本地字节输出流对象调用write方法把读取到的文件保存到服务器硬盘上
  • 8.使用Socket对象中getOutputStream方法,获取网络字节输出流对象OutputStream
  • 9.使用网络字节输出流OutputStream对象中write方法,给客户端回写"上传成功"
  • 10释放资源(FileOutputStream ,Socket)
package demo2FIleUpload;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Demo02TCPServer {
    public static void main(String[] args) throws IOException {
        //1.创建一个服务器ServerSocket对象,构造方法中绑定要指定的端口号
        ServerSocket server = new ServerSocket(8888);
        //2.使用ServerSocket对象中的accept方法,获取到发送请求的客户端Socket
        Socket socket = server.accept();
        //3.使用Socket对象中的getInputStream方法,获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //4.判断服务器硬盘上Net\\upload文件夹是否存在,不存在则创建
        File file = new File("Net\\upload");
        if (!file.exists()){
            file.mkdir(); //创建文件夹
        }
        //5.创建一个本地字节输出流对象FileOutputStream,构造方法中绑定要输出的目的地
        FileOutputStream fos = new FileOutputStream(file+"\\上传到服务器的文件.txt");
        //6.使用网络字节输入流InputStream对象中的read方法,读取客户端上传文件
        int len=0;
        byte[] bytes = new byte[1024];//创建数组,提供读取效率
        while ((len=is.read(bytes))!=-1){
            //7.使用本地字节输出流对象调用write方法把读取到的文件保存到服务器硬盘上
            fos.write(bytes,0,len);
        }
        //8.使用Socket对象中getOutputStream方法,获取网络字节输出流对象OutputStream
        //9.使用网络字节输出流OutputStream对象中write方法,给客户端回写"上传成功"
        socket.getOutputStream().write("上传成功".getBytes());
        //10释放资源(FileOutputStream ,Socket)
        fos.close();
        socket.close();
    }
}
//注意上述程序在先启动服务器端后再启动客户端后,虽然将目标文件上传服务器了,但是两个程序都没有关闭,被阻塞
//上述代码存在问题

4.5.文件上传阻塞问题

  • 注意上述程序在先启动服务器端后再启动客户端后,虽然将目标文件上传服务器了,但是两个程序都没有关闭,被阻塞。

原因在于:

  • read方法在没有读取到-1这个结束标记时,该方法会处于阻塞状态。
  • 在TCPClient客户端,当读取本地文件fis.read(bytes)时,while结束标记时读取到-1结束,
  • 但while循环遇到-1就截止了,是不会读取到-1标识,那么也就不会把结束标记写到服务器中,
  • 所以在TCPServer服务器端,当读取客户端传输来的文件时,由于文件中没有结束标记-1,
  • 因此服务器端就会被阻塞在该read读取客户端文件这个地方,这样导致服务器端后面的代码也不会被执行,
  • 那么也就无法给客户端回写"上传成功",这就导致了客户端在使用read方法读取服务器端回写的数据is.read(bytes)时读取不到数据和标记,客户端也进入阻塞状态了,资源也没有被释放。

解决方法:

  • 上传完文件,在客户端给服务器写一个结束标记,使用socket中shutdownOutput()方法。

文件上传阻塞问题的客户端代码修改:即在while循环后加上 socket.shutdownOutput();

package demo2FIleUpload;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;

public class Demo01TCPClient {
    public static void main(String[] args) throws IOException {
        //1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("Net\\待上传本地文件.txt");
        //2.创建一个客户端Socket对象,构造方法中绑定服务器ip和端口号
        Socket socket = new Socket("127.0.0.1",8888);
        //3.使用Socket中方法getOutputStream获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //4.使用本地字节输入流FileInputStream对象中方法read,读取本地文件
        int len=0;//记录每次读取的有效字节数
        byte[] bytes = new byte[1024];
        while((len=fis.read(bytes))!=-1){
            //5.使用网络字节输出流OutputStream对象中方法write,把读取到的文件上传到服务器
            os.write(bytes,0,len);
        }
        /**
         * 上述程序将本地文件上传到服务器后,由于while读取到-1标记就停止了,
         * 导致文件结束标记没有写入到服务器中,因此当服务器端读取文件时会由于
         * 读取不到-1标记而陷入阻塞等待状态。
         * 解决方法:上传完文件,在客户端给服务器写一个结束标记
         * void shutdownOutput():禁止次套接字的输出流
         * 对于TCP套接字,任何以前写入的数据都将被发送,并且后跟TCP的正常连接终止序列
         */
        socket.shutdownOutput();
        //6.使用Socket中的方法getInputStream获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //7.使用网络字节输入流对象中read方法读取服务器回写的数据
        while ((len=is.read(bytes))!=-1){
            System.out.println(bytes);
        }
        //8.释放资源(FileInputStream,Socket)
        fis.close();
        socket.close();

    }
}

4.6.文件上传优化问题

文件上传案例的优化问题:客户端没有优化地方,主要是服务端3处优化

package Demo03FileUpload;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Demo01TCPClient {
    public static void main(String[] args) throws IOException {
        //1.创建一个本地字节输入流FileInputStream对象,构造方法中绑定要读取的数据源
        FileInputStream fis = new FileInputStream("Net\\待上传本地文件.txt");
        //2.创建一个客户端Socket对象,构造方法中绑定服务器ip和端口号
        Socket socket = new Socket("127.0.0.1",8888);
        //3.使用Socket中方法getOutputStream获取网络字节输出流OutputStream对象
        OutputStream os = socket.getOutputStream();
        //4.使用本地字节输入流FileInputStream对象中方法read,读取本地文件
        int len=0;//记录每次读取的有效字节数
        byte[] bytes = new byte[1024];
        while((len=fis.read(bytes))!=-1){
            //5.使用网络字节输出流OutputStream对象中方法write,把读取到的文件上传到服务器
            os.write(bytes,0,len);
        }
        socket.shutdownOutput();
        //6.使用Socket中的方法getInputStream获取网络字节输入流InputStream对象
        InputStream is = socket.getInputStream();
        //7.使用网络字节输入流对象中read方法读取服务器回写的数据
        while ((len=is.read(bytes))!=-1){
            System.out.println(bytes);//暂先输出回写数据的字节数组的
        }
        //8.释放资源(FileInputStream,Socket)
        fis.close();
        socket.close();
    }
}

服务器端
文件上传案例的优化问题:

  • 优化1:自定义一个文件的命名规则:防止同名的文件被覆盖
  • 规则:域名+毫秒值+随机数(+后缀)
  • 下面是上传文件后的文件名java15829030825806972.txt,不容易重名

 

  • 优化2:让服务器一直处于监听状态(即死循环accept方法)
  • 有一个客户端上传文件,就保存一个文件。
  • 启动一次服务器端后,可以启动多次客户端不断上传文件,每次启动客户端也可以修改ip地址,访问不同服务器。

 

  • 优化3:使用多线程技术提高程序的效率
  • 有一个客户端上传文件,就开启一个线程,完成文件的上传,提高效率,
  • 避免多个客户端同时向同一个服务器上传文件。
package Demo03FileUpload;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;

public class Demo02TCPServer {
 public static void main(String[] args) throws IOException {
  //1.创建一个服务器ServerSocket对象,构造方法中绑定要指定的端口号
  ServerSocket server = new ServerSocket(8888);
  //2.使用ServerSocket对象中的accept方法,获取到发送请求的客户端Socket
     
  /**
   * 优化2:让服务器一直处于监听状态(即死循环accept方法)
   * 有一个客户端上传文件,就保存一个文件。
   * 启动一次服务器端后,可以启动多次客户端不断上传文件,每次启动客户端也可以
   * 修改ip地址,访问不同服务器。
   */
   while (true) {
      Socket socket = server.accept();
      /**
       * 优化3:使用多线程技术提高程序的效率。
       * 有一个客户端上传文件,就开启一个线程,完成文件的上传,提高效率,
       * 避免多个客户端同时向同一个服务器上传文件。
      */
      new Thread(new Runnable() {
        @Override
        //重写run方法设置线程任务为:上传文件
        //因为run方法源码是没有抛出异常的,所以重写的run方法也不能抛,只能tru..catch
        public void run() {
           try {
            //3.使用Socket对象中的getInputStream方法,获取网络字节输入流InputStream对象
              InputStream is = socket.getInputStream();
            //4.判断服务器硬盘上Net\\upload文件夹是否存在,不存在则创建
              File file = new File("Net\\upload");
              if (!file.exists()) {
                  file.mkdir(); //创建文件夹
              }
             /**
              * 优化1:自定义一个文件的命名规则:防止同名的文件被覆盖
              * 规则:域名+毫秒值+随机数(+后缀)
              */
              String fileName = "java" + System.currentTimeMillis() + 
                  new Random().nextInt(9999) + ".txt";
            //5.创建一个本地字节输出流对象FileOutputStream,构造方法中绑定要输出的目的地
            //FileOutputStream fos = new FileOutputStream(file+"\\上传到服务器的文件.txt");
              FileOutputStream fos = new FileOutputStream(file + "\\" + fileName);
             //6.使用网络字节输入流InputStream对象中的read方法,读取客户端上传文件
              int len = 0;
              byte[] bytes = new byte[1024];//创建数组,提供读取效率
              while ((len = is.read(bytes)) != -1) {
                 //7.使用本地字节输出流对象调用write方法把读取到的文件保存到服务器硬盘上
                 fos.write(bytes, 0, len);
              }
              //8.使用Socket对象中getOutputStream方法,获取网络字节输出流对象OutputStream
              //9.使用网络字节输出流OutputStream对象中write方法,给客户端回写"上传成功"
              socket.getOutputStream().write("上传成功".getBytes());
              //10释放资源(FileOutputStream ,Socket)
                fos.close();
                socket.close();
            } catch (IOException e) {
                 System.out.println(e);
            }
          }
        }).start();
         //server.close(); 服务器就不用关闭了
        }
    }
}

5.模拟BS服务器

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值