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 通信

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));

    }

二.一.三 运行程序

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

有图片

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

二.二.一 客户端

    @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);

    }

二.二.三 运行程序

有图片

二.三 发送对象数据

二.三.一 客户端

  @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("接收格式有误");
        }
    }

二.三.三 测试运行

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

有图片

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

二.四.一 客户端

 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();

    }

二.四.三 测试运行

先运行服务器端

有图片

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

有图片

这时,查看服务器端

有图片

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

在这里插入图片描述

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

二.五 传输文件

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();

    }

二.五.三 测试运行

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

有图片

查看文件系统

有图片

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

二.六 经典的 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();

    }
二.六.一.三 测试运行

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

有图片

二.六.二 多次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

有图片

有图片

有图片

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

对于第一个控制台

有图片

对于第二个控制台

有图片

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

有图片

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

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

客户端1

有图片

客户端2

有图片

需要用多线程去解决。

二.六.三 多线程 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:

有图片

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

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

有图片

是可以进行传递数据的。

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


谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

两个蝴蝶飞

你的鼓励,是老蝴蝶更努力写作的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值