TCP通信(四)

勿以恶小而为之,勿以善小而不为--------------------------刘备

劝诸君,多行善事积福报,莫作恶

上一章简单介绍了UDP通信实现学生咨询(三),如果没有看过,请观看上一章

TCP/IP 通信是非常重要的,需要掌握。

一. TCP 通信所用到的类

一.一 ServerSocket 服务器类

是服务器端所要用到的类。

一.一.一 构造方法

一.一.一.一 方法

方法 作用
ServerSocket​(int port) 绑定到指定的端口,创建服务器套接字

一.一.一.二 演示构造方法

 @Test
    public void conTest() throws Exception{

        //放置端口号, 监听9999端口
        ServerSocket serverSocket=new ServerSocket(9999);
    }

一.一.二 其他方法

方法 作用
Socket accept​() 阻塞式接收连接的那一个客户端对象
int getLocalPort​() 获取监听的那个端口

一.二 Socket 客户端类

一.二.一 构造方法

一.二.一.一 方法

方法 作用
Socket​(String host, int port) 传入主机名称包括ip地址 和端口号
Socket​(InetAddress address, int port) 传入地址对象和 端口号

一.二.一.二 演示构造方法

   @Test
    public void conTest() throws Exception{

        //第一种, 传入主机名和端口号。 注意,这个端口号是服务器的那个监听端口
        Socket socket=new Socket("localhost",9999);

        //第二种,传入地址对象

        InetAddress inetAddress=InetAddress.getByName("localhost");

        Socket socket1=new Socket(inetAddress,9999);

    }

一.二.二 其他方法

方法 作用
void close​() 关闭套接字
InputStream getInputStream​() 获取输入流
OutputStream getOutputStream​() 获取输出流

一.三 演示服务器和客户端

一.三.一 服务器端

 @Test
    public void serverTest() throws Exception{

        System.out.println("服务器启动中");

        ServerSocket serverSocket=new ServerSocket(9999);

        //阻塞式接收
        Socket client=serverSocket.accept();

        System.out.println("端口:"+serverSocket.getLocalPort());

        System.out.println("连接一个客户端程序");

    }

一.三.二 客户端

 @Test
    public void clientTest() throws Exception{
        System.out.println("启动客户端");
        Socket socket=new Socket("localhost",9999);
    }

一.三.三 测试运行

先运行服务器端, 查看控制台输出,

TCP通信(四)_第1张图片

线程一直在阻塞。

再运行客户端

TCP通信(四)_第2张图片

这个时候,再去查询一下服务器端的控制台

TCP通信(四)_第3张图片

会往下运行,接收到客户端的对象信息。

二. 演示各种 TCP 通信

TCP 通信的情况,与 UDP 通信的情况,基本是一样的。

二.一 演示单条字符串

二.一.一 客户端

 @Test
    public void client2Test() throws Exception{
        System.out.println("启动客户端,发送单条数据");
        Socket socket=new Socket("localhost",9999);
        //发送数据
        //客户端的输入
        OutputStream outputStream= socket.getOutputStream();

        //写入单条数据
       outputStream.write("两个蝴蝶飞,你好啊".getBytes());

       outputStream.close();

       socket.close();
    }

二.一.二 服务器端

 @Test
    public void server2Test() throws Exception{

        System.out.println("服务器启动中");

        ServerSocket serverSocket=new ServerSocket(9999);

        //阻塞式接收
        Socket client=serverSocket.accept();

        System.out.println("连接一个客户端程序");

        //得到输入流
        InputStream inputStream=client.getInputStream();

        byte[]  bytes=new byte[1024];

        //读取内容,放置到 bytes字节数组里面
        int len=inputStream.read(bytes);

        System.out.println("获取客户端传递过来的内容:"+new String(bytes,0,len));

    }

二.一.三 运行程序

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

TCP通信(四)_第4张图片

二.二 发送多条不同类型的数据

二.二.一 客户端

    @Test
    public void client3Test() throws Exception{
        System.out.println("启动客户端,发送多条不同类型的数据");
        Socket socket=new Socket("localhost",9999);
        //发送数据
        //客户端的输入
        OutputStream outputStream= socket.getOutputStream();

        DataOutputStream dataOutputStream=new DataOutputStream(outputStream);

        //发送字符串
        dataOutputStream.writeUTF("两个蝴蝶飞");
        //发送int 类型
        dataOutputStream.writeInt(24);
        //发送字符串
        dataOutputStream.writeUTF("一个快乐的程序员");

        dataOutputStream.flush();

        socket.close();
    }

二.二.二 服务器端

 @Test
    public void server3Test() throws Exception{

        System.out.println("服务器启动中");

        ServerSocket serverSocket=new ServerSocket(9999);

        //阻塞式接收
        Socket client=serverSocket.accept();

        System.out.println("连接一个客户端程序,接收多条数据");

        InputStream inputStream=client.getInputStream();

        DataInputStream dataInputStream=new DataInputStream(inputStream);

        //接收字符串
        String name=dataInputStream.readUTF();

        //接收 int 类型
        int age= dataInputStream.readInt();

        //接收 字符串 
        String desc=dataInputStream.readUTF();

        System.out.printf("姓名是:%s,年龄是:%d,描述是:%s",name,age,desc);

    }

二.二.三 运行程序

TCP通信(四)_第5张图片

二.三 发送对象数据

二.三.一 客户端

  @Test
    public void client3ObjectTest() throws Exception{
        System.out.println("启动客户端,发送多条对象数据");
        Socket socket=new Socket("localhost",9999);
        //发送数据
        //客户端的输入
        OutputStream outputStream= socket.getOutputStream();

        ObjectOutputStream objectOutputStream=new ObjectOutputStream(new BufferedOutputStream(outputStream));

        //放入对象
        Person person=new Person();
        person.setId(1);
        person.setName("两个蝴蝶飞");
        person.setSex('男');
        person.setAge(24);
        person.setDesc("一个快乐的程序员");

        objectOutputStream.writeObject(person);
        //放置日期

        objectOutputStream.writeObject(new Date());

        //一定不要忘记刷新
        objectOutputStream.flush();

        socket.close();
    }

二.三.二 服务器端

@Test
    public void server3ObjectTest() throws Exception{

        System.out.println("服务器启动中");

        ServerSocket serverSocket=new ServerSocket(9999);

        //阻塞式接收
        Socket client=serverSocket.accept();

        System.out.println("连接一个客户端程序,接收对象数据");

        InputStream inputStream=client.getInputStream();


        ObjectInputStream objectInputStream=new ObjectInputStream(new BufferedInputStream(inputStream));

        //读取数据

        Object obj1=objectInputStream.readObject();

        if(obj1 instanceof Person){

            Person person=(Person)obj1;

            System.out.println(person.toString());

        }else{
            System.out.println("接收格式有误");
        }

        Object obj2=objectInputStream.readObject();

        if(obj2 instanceof Date){

            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");

            System.out.println("日期:"+sdf.format((Date)obj2));


        }else{
            System.out.println("接收格式有误");
        }
    }

二.三.三 测试运行

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

TCP通信(四)_第6张图片

二.四 接收和响应客户端请求数据

二.四.一 客户端

 public static void main(String[] args) {
        try {
            client4Test();
			// 写多个 client5Test(), client6Test() 等
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void client4Test() throws Exception{
        System.out.println("启动客户端");

        BufferedReader bufferedReader=new BufferedReader(new InputStreamReader((System.in)));

        System.out.println("请输入用户名:");
        String userName=bufferedReader.readLine();
        System.out.println("请输入密码:");
        String password=bufferedReader.readLine();



        Socket socket=new Socket("localhost",9999);
        //发送数据

        //客户端的输入
        OutputStream outputStream= socket.getOutputStream();

        DataOutputStream dataOutputStream=new DataOutputStream(outputStream);

        //拼接请求参数
        String query="userName="+userName+"&password="+password;
        dataOutputStream.writeUTF(query);
        dataOutputStream.flush();


        //获取内容
        DataInputStream dataInputStream=new DataInputStream(socket.getInputStream());

        String content=dataInputStream.readUTF();
        System.out.println("接收服务器端返回的内容:"+content);

        dataInputStream.close();
        dataOutputStream.close();
        socket.close();
    }

二.四.二 服务器端

   @Test
    public void server4Test() throws Exception{

        System.out.println("服务器启动中");

        ServerSocket serverSocket=new ServerSocket(9999);

        //阻塞式接收
        Socket client=serverSocket.accept();
        System.out.println("连接一个客户端程序,响应数据");

        InputStream inputStream=client.getInputStream();

        DataInputStream dataInputStream=new DataInputStream(inputStream);

        String query=dataInputStream.readUTF();

        //拆分
        String[] qArr=query.split("&");


        String userName="";

        String password="";

        for(String str:qArr){

            String[] tempArr=str.split("=");

            if("userName".equals(tempArr[0])){

                userName=tempArr[1];
            }else if("password".equals(tempArr[0])){

                password=tempArr[1];
            }
        }

        String responseData="";

        System.out.printf("接收的用户名为:%s,密码为%s",userName,password);
        //返回数据
        if("两个蝴蝶飞".equals(userName)&&"1234".equals(password)){

            responseData="用户名和密码输入正确,登录成功";
        }else{
            responseData="用户名或者密码错误,登录失败";
        }

        DataOutputStream dataOutputStream=new DataOutputStream(client.getOutputStream());

        dataOutputStream.writeUTF(responseData);

        dataOutputStream.close();

        dataInputStream.close();

    }

二.四.三 测试运行

先运行服务器端

TCP通信(四)_第7张图片

再运行客户端, 输入错误的密码

TCP通信(四)_第8张图片

这时,查看服务器端

TCP通信(四)_第9张图片

再次重新测试, 输入正确的用户名和密码

TCP通信(四)_第10张图片

发现,服务器端可以接收客户端传递过来的数据,并且可以响应数据, 客户端也能够获取到响应的数据。

二.五 传输文件

IOUtils 仍然与第二章节的 工具类一致。

二.五.一 客户端

    public static void client5Test() throws Exception{
        System.out.println("启动客户端,发送文件");

        Socket socket=new Socket("localhost",9999);
        //发送数据

        //客户端的输入
        OutputStream outputStream= socket.getOutputStream();

        DataOutputStream dataOutputStream=new DataOutputStream(outputStream);


        String path="E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
                +File.separator+"129.png";

        //将文件转换成字节
        byte[] bytes= IOUtils.fileToByteArray(path);

        //写入图片
        dataOutputStream.write(bytes);

        dataOutputStream.flush();

        DataInputStream dataInputStream=new DataInputStream(socket.getInputStream());

        String content=dataInputStream.readUTF();
        System.out.println("接收服务器端返回的内容:"+content);

        dataInputStream.close();
        dataOutputStream.close();
        socket.close();
    }

二.五.二 服务器端

 @Test
    public void server5Test() throws Exception{

        System.out.println("服务器启动中");

        ServerSocket serverSocket=new ServerSocket(9999);

        //阻塞式接收
        Socket client=serverSocket.accept();
        System.out.println("连接一个客户端程序");

        InputStream inputStream=client.getInputStream();

        DataInputStream dataInputStream=new DataInputStream(inputStream);


        //图片大小为17k, 这儿接收时接收 20k
        byte[] bytes=new byte[1024*20];

        dataInputStream.read(bytes);


        //将字节转换成图片

        String path="E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
                +File.separator+"129Tcp.png";

        IOUtils.byteArrayToFile(bytes,path);


        String responseData="传输文件成功";


        DataOutputStream dataOutputStream=new DataOutputStream(client.getOutputStream());

        dataOutputStream.writeUTF(responseData);

        dataOutputStream.close();

        dataInputStream.close();

    }

二.五.三 测试运行

启动服务器端,再启动客户端, 客户端控制台打印输出

TCP通信(四)_第11张图片

查看文件系统

TCP通信(四)_第12张图片

图片,也可以正常的显示出来,没有破坏。

二.六 经典的 echo 程序

简单来说,就是响应数据 echo:客户端传递过来的数据

二.六.一 单次 echo

二.六.一.一 客户端

  public static void client6Test() throws Exception{
        System.out.println("启动客户端");

        BufferedReader bufferedReader=new BufferedReader(new InputStreamReader((System.in)));

        Socket socket=new Socket("localhost",9999);
        //发送数据

        //客户端的输入
        OutputStream outputStream= socket.getOutputStream();
        DataOutputStream dataOutputStream=new DataOutputStream(outputStream);


        String content=bufferedReader.readLine();

        //写入数据
        dataOutputStream.writeUTF(content);


        DataInputStream dataInputStream=new DataInputStream(socket.getInputStream());

        //读取数据,并且打印
        String respnseContent=dataInputStream.readUTF();
        System.out.println(respnseContent);

        dataInputStream.close();
        dataOutputStream.close();
        socket.close();
    }

二.六.一.二 服务器

  @Test
    public void server6Test() throws Exception{

        System.out.println("服务器启动中");

        ServerSocket serverSocket=new ServerSocket(9999);

        //阻塞式接收
        Socket client=serverSocket.accept();
        System.out.println("连接一个客户端程序");

        InputStream inputStream=client.getInputStream();

        DataInputStream dataInputStream=new DataInputStream(inputStream);

        String content=dataInputStream.readUTF();


        String responseData="echo:"+content;


        DataOutputStream dataOutputStream=new DataOutputStream(client.getOutputStream());

        dataOutputStream.writeUTF(responseData);

        dataOutputStream.close();

        dataInputStream.close();

    }

二.六.一.三 测试运行

先运行服务器端,再运行客户端

TCP通信(四)_第13张图片

二.六.二 多次echo

用while() 进行循环接收控制台传递过来的数据。

二.六.二.一 客户端

public static void client7Test() throws Exception{
        System.out.println("启动客户端,多次echo 程序");

        BufferedReader bufferedReader=new BufferedReader(new InputStreamReader((System.in)));

        Socket socket=new Socket("localhost",9999);
        //发送数据


        //客户端的输入
        OutputStream outputStream= socket.getOutputStream();
        DataOutputStream dataOutputStream=new DataOutputStream(outputStream);


        DataInputStream dataInputStream=new DataInputStream(socket.getInputStream());


        boolean isRun=true;
        while(isRun){
            //循环接收数据
            String content=bufferedReader.readLine();
            dataOutputStream.writeUTF(content);
            String respnseContent=dataInputStream.readUTF();
            System.out.println(respnseContent);

            if("bye".equalsIgnoreCase(content)||"quit".equalsIgnoreCase(content)){
                break;
            }
        }

        dataInputStream.close();
        dataOutputStream.close();
        socket.close();
    }

二.六.二.二 服务器端

  @Test
    public void server7Test() throws Exception{

        System.out.println("服务器启动中");

        ServerSocket serverSocket=new ServerSocket(9999);

        boolean isRun=true;

        while(isRun){

            //阻塞式接收
            Socket client=serverSocket.accept();
            System.out.println("连接一个客户端程序");

            InputStream inputStream=client.getInputStream();

            DataInputStream dataInputStream=new DataInputStream(inputStream);

            DataOutputStream dataOutputStream=new DataOutputStream(client.getOutputStream());

            boolean isBye=false;

            while(!isBye){

                String content=dataInputStream.readUTF();

                String responseData="";

                if("bye".equalsIgnoreCase(content)||"quit".equalsIgnoreCase(content)){
                    responseData="echo:欢迎下次再来";

                    isBye=true;

                }else{
                    responseData= "echo:"+content;
                }
                dataOutputStream.writeUTF(responseData);

            }

            dataOutputStream.close();

            dataInputStream.close();
        }
    }

二.六.二.三 测试运行

可以开多个客户端的控制台。 但 idea 默认是不能同时开启同一个程序的控制台的,需要进行设置。

老蝴蝶的 类 public 名称 是 SocketDemo

TCP通信(四)_第14张图片

TCP通信(四)_第15张图片

TCP通信(四)_第16张图片

先运行服务器,再运行 客户端的 main()方法, 打开一个控制台, 再运行 客户端的 main()方法,打开新的控制台

对于第一个控制台

TCP通信(四)_第17张图片

对于第二个控制台

TCP通信(四)_第18张图片

两个客户端都关闭了, 此时服务器仍然是 true, 死循环,接收客户端连接的状态

TCP通信(四)_第19张图片

连接一个客户端,就打印输出一下。

但是,发现有一个问题, 当客户端1 不关闭时,客户端2就无法响应。

客户端1

TCP通信(四)_第20张图片

客户端2

TCP通信(四)_第21张图片

需要用多线程去解决。

二.六.三 多线程 echo

二.六.三.一 关闭工具类

public class CloseUtils {

    public static void close(Closeable...closeables){

        for(Closeable closeable:closeables){

            if(null!=closeable){
                try {
                    closeable.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

二.六.三.二 客户端

public class Client {

    //发送者多线程
   static class Send implements  Runnable{

       private Socket client;
       private DataOutputStream dataOutputStream;

       private BufferedReader bufferedReader;

       //是否继续运行的标识
       private boolean isRunning;
       public Send(Socket client){

           this.client=client;

           this.isRunning=true;
           try {
               this.dataOutputStream=new DataOutputStream(client.getOutputStream());

               this.bufferedReader=new BufferedReader(new InputStreamReader(System.in));

           } catch (IOException e) {
               e.printStackTrace();
               isRunning=false;

               CloseUtils.close(client);
           }
       }
       @Override
        public void run() {
            while(this.isRunning){
                try {
                    //接收数据
                    String content=bufferedReader.readLine();
                    //发送数据
                    sendMsg(content);

                    if("bye".equalsIgnoreCase(content)||"quit".equalsIgnoreCase(content)){
                        //修改标识
                        stop();

                        CloseUtils.close(dataOutputStream);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
        }

        public void stop(){

           this.isRunning=false;
        }
        //发送数据
        public void sendMsg(String msg){
            try {
                dataOutputStream.writeUTF(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //接收者
    static class Receiver implements  Runnable{

        private Socket client;

        private DataInputStream dataInputStream;

        private boolean isRunning;
        public Receiver(Socket client){

            this.client=client;
            this.isRunning=true;
            try {
                this.dataInputStream=new DataInputStream(client.getInputStream());
            } catch (IOException e) {
                e.printStackTrace();
                this.isRunning=false;
                CloseUtils.close(client);
            }
        }

        @Override
        public void run() {
            while(this.isRunning){

                String content=readMsg();

                System.out.println(content);

                if("echo:欢迎下次再来".equalsIgnoreCase(content)){
                    stop();

                }
            }
            try {
                CloseUtils.close(this.dataInputStream,client.getOutputStream());
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        public void stop(){

            this.isRunning=false;
        }
        //接收数据
        public String readMsg(){

            String content="";
            try {
                content= dataInputStream.readUTF();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return content;
        }
    }
    public static void main(String[] args) {


        try {
            Socket socket=new Socket("localhost",9999);

            System.out.println("***************连接客户端**************");

            //客户端多线程运行发送和接收
            new Thread(new Send(socket)).start();

            new Thread(new Receiver(socket)).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



}

二.六.三.三 服务器端

public class Server {

    //客户连接类
    static class Channel implements  Runnable{

        private Socket client;

        private DataInputStream dataInputStream;

        private DataOutputStream dataOutputStream;

        private volatile boolean isRunning;

        public Channel(Socket client){

            this.client=client;
            //标识位
            this.isRunning=true;

            try {
                this.dataInputStream=new DataInputStream(client.getInputStream());
                this.dataOutputStream=new DataOutputStream(client.getOutputStream());
            } catch (IOException e) {
                e.printStackTrace();

                CloseUtils.close(dataInputStream,dataOutputStream);
            }

        }

        @Override
        public void run() {

            while(this.isRunning){

                String content=readMsg();

                String responseData="";

                if("bye".equalsIgnoreCase(content)||"quit".equalsIgnoreCase(content)){
                   responseData="echo:欢迎下次再来";
                    this.isRunning=false;

                }else{
                    responseData= "echo:"+content;
                }

                sendMsg(responseData);

            }

        }

        //服务器往客户端发送数据
        public void sendMsg(String msg){
            try {
                dataOutputStream.writeUTF(msg);
            } catch (IOException e) {
               // e.printStackTrace();
                CloseUtils.close(dataInputStream,dataOutputStream);
            }
        }
        //服务器读取客户端数据
        public String readMsg(){

            String content="";
            try {
                content= dataInputStream.readUTF();
            } catch (IOException e) {
                CloseUtils.close(dataInputStream,dataOutputStream);
                //e.printStackTrace();
            }
            return content;
        }
    }
    public static void main(String[] args) {

        try {
            ServerSocket serverSocket=new ServerSocket(9999);

            System.out.println("********服务器开启************");

            while(true){
                Socket client=serverSocket.accept();//多线程运行

                System.out.println("连接一个客户端");

                new Thread(new Channel(client)).start();

            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

二.六.三.四 测试运行

先运行服务器端,再运行创建客户端1,再运行创建客户端2
客户端1:

TCP通信(四)_第22张图片

现在客户端1 还没有断开链接

看一下客户端2, 发送数据

TCP通信(四)_第23张图片

是可以进行传递数据的。

要理解 多线程 echo 的应用,聊天室时会用得到。


谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!

你可能感兴趣的:(#,网络编程,TCP通信,TCP传输文件,TCP实现echo,多线程实现echo,Java网络编程)