Java-TCP通信(实现多发多收、群聊功能),BS通信源码

个人简介

  • 大家好,我是韩慧腾。一名正在努力学JAVA的大一小白,本文章为初学的笔记,希望各位多多指教。💙
  • 欢迎点赞+收藏+留言💜
  • 只要你跑得快,风声自然会盖过闲言碎语🧡

一、TCP通信

  • TCP是一种面向连接、安全、可靠、的传输数据的协议
  • 传输前,采用“三次握手”方式,点对点通信,是可靠的
  • 在连接中可进行大数据量的传输

TCP通信的基本原理:

  • 客户端怎么发,服务端就怎么收
  • 客户端如果没有消息,服务端会进入阻塞等待
  • Socket一方关闭或者出现异常,对方Socket也会失效或者出错

客户端/发送端:

 服务端/接收端:

package com.itheima.TCP;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author hanhan
 * date 2022/4/29 17:16
 * 努力已经来不及了,你得拼命
 * TCP通信客户端/发送端(一来一收)
 */
public class ClientDemo_0 {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端请求成功");
        //创建Socket通信管道请求与服务端的连接
        Socket s = new Socket(InetAddress.getLocalHost(),7777);
        //从Socket通信管道得到一个字节输出流,负责发送数据
        OutputStream os = s.getOutputStream();
        //把低级的流包装成打印流
        PrintStream p = new PrintStream(os);
        //发送消息
        p.println("我是TCP客户端");
        os.flush();
        //提醒:不要轻易关闭管道(s.close())
    }
}
package com.itheima.TCP;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author hanhan
 * date 2022/4/29 17:42
 * 努力已经来不及了,你得拼命
 * TCP通信服务端/接收端(一来一收)
 */
public class ServerDemo_0 {
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动成功");
        //注册端口
        ServerSocket s = new ServerSocket(7777);
        //调用accept方法,等待接收客户端的Socket连接请求,建立Socket管道
        Socket ss=s.accept();
        //从Socket通信管道中得到一个字节输入流
        InputStream i = ss.getInputStream();
        //把字节输入流包装成缓冲字符输入流
        BufferedReader b = new BufferedReader(new InputStreamReader(i));
        String s1;
        if((s1=b.readLine())!=null){
            System.out.println(ss.getRemoteSocketAddress()+"说:"+s1);
        }

    }
}

二、TCP通信——多发多收消息

package com.itheima.TCP;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author hanhan
 * date 2022/4/29 22:16
 * 努力已经来不及了,你得拼命
 * TCP通信客户端/发送端(多发多收)
 */
public class ClientDemo_01 {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端请求成功");
        Scanner sc = new Scanner(System.in);
        //创建Socket通信管道请求与服务端的连接
        Socket s = new Socket(InetAddress.getLocalHost(),7778);
        //从Socket通信管道得到一个字节输出流,负责发送数据
        OutputStream os = s.getOutputStream();
        //把低级的流包装成打印流
        PrintStream p = new PrintStream(os);
        while (true) {
            System.out.println("请输入弹幕:");
            String ss=sc.nextLine();
            //发送消息
            p.println(ss);
            os.flush();
        }
        //提醒:不要轻易关闭管道(s.close())
    }
}
package com.itheima.TCP;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author hanhan
 * date 2022/4/29 22:17
 * 努力已经来不及了,你得拼命
 * TCP通信服务端/接收端(多发多收)
 */
public class ServerDemo_01 {
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动成功");
        //注册端口
        ServerSocket s = new ServerSocket(7778);
        //调用accept方法,等待接收客户端的Socket连接请求,建立Socket管道
        Socket ss=s.accept();
        //从Socket通信管道中得到一个字节输入流
        InputStream i = ss.getInputStream();
        //把字节输入流包装成缓冲字符输入流
        BufferedReader b = new BufferedReader(new InputStreamReader(i));
        String s1;
        while (true) {
            if((s1=b.readLine())!=null){
                System.out.println(ss.getRemoteSocketAddress()+"说:"+s1);
            }
        }
    }
}

由于现在的服务端只有一个线程,只能与一个客户端进行通信

服务端同时接收多个客户端的消息

package com.itheima.TCP;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author hanhan
 * date 2022/4/29 23:33
 * 努力已经来不及了,你得拼命
 * 实现一个服务端接收多个客户端消息,服务端
 */
public class ServerDemo_02 {
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动成功");
            //注册端口
            ServerSocket s = new ServerSocket(7779);
            //定义一个死循环由主线程负责不断的接收客户端的Socket管道连接
            while (true) {
                //调用accept方法,等待接收客户端的Socket连接请求,建立Socket管道
                Socket ss=s.accept();
                System.out.println(ss.getRemoteSocketAddress()+"上线了");
                //每接收到一个客户端Socket管道,就交给一个独立的子线程负责读取消息
                new SeverDemo_02ReaderThread(ss).start();

        }
    }
    }
class SeverDemo_02ReaderThread extends Thread{
    private Socket socket;

    public SeverDemo_02ReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
//从Socket通信管道中得到一个字节输入流
            InputStream i = socket.getInputStream();
            //把字节输入流包装成缓冲字符输入流
            BufferedReader b = new BufferedReader(new InputStreamReader(i));
            String s1;
            while (true) {
                if ((s1 = b.readLine()) != null) {
                    System.out.println(socket.getRemoteSocketAddress() + "说:" + s1);
                }
            }
        } catch (IOException e) {
            System.out.println(socket.getRemoteSocketAddress()+"离线了!");
        }
    }
}

 

package com.itheima.TCP;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author hanhan
 * date 2022/4/29 23:32
 * 努力已经来不及了,你得拼命
 * 实现一个服务端接收多个客户端消息,客户端
 */
public class ClientDemo_02 {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端请求成功");
        Scanner sc = new Scanner(System.in);
        //创建Socket通信管道请求与服务端的连接
        Socket s = new Socket(InetAddress.getLocalHost(),7779);
        //从Socket通信管道得到一个字节输出流,负责发送数据
        OutputStream os = s.getOutputStream();
        //把低级的流包装成打印流
        PrintStream p = new PrintStream(os);
        while (true) {
            System.out.println("请输入弹幕:");
            String ss=sc.nextLine();
            //发送消息
            p.println(ss);
            os.flush();
        }
        //提醒:不要轻易关闭管
    }
}

三、TCP同时接收多个多个客户端消息(用线程池优化)

上述多线程实现存在的问题

  • 客户端与服务端 的线程模型是:N-N(即多少个客户端就创建多少线程,而线程又很耗费CPU资源)
  • 客户端存在的越多,系统越容易瘫痪

        

package com.itheima.TCP;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

/**
 * @author hanhan
 * date 2022/4/30 8:54
 * 努力已经来不及了,你得拼命
 * 接收多个客户端消息(线程池版本)
 */
public class ServerDemo_03 {
    //创建一个线程池对象
    private static ExecutorService pool=new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动成功");
        //注册端口
        ServerSocket s = new ServerSocket(7775);
        //定义一个死循环由主线程负责不断的接收客户端的Socket管道连接
        while (true) {
            //调用accept方法,等待接收客户端的Socket连接请求,建立Socket管道
            Socket ss=s.accept();
            System.out.println(ss.getRemoteSocketAddress()+"上线了");
            Runnable r = new ServerReaderRunnable(ss);
            pool.execute(r);
        }
    }
}
class ServerReaderRunnable implements Runnable{
    private Socket socket;

    public ServerReaderRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //从Socket通信管道中得到一个字节输入流
            InputStream i = socket.getInputStream();
            //把字节输入流包装成缓冲字符输入流
            BufferedReader b = new BufferedReader(new InputStreamReader(i));
            String s1;
            while (true) {
                if ((s1 = b.readLine()) != null) {
                    System.out.println(socket.getRemoteSocketAddress() + "说:" + s1);
                }
            }
        } catch (IOException e) {
            System.out.println(socket.getRemoteSocketAddress() + "离线了!");
        }
    }
}

 

package com.itheima.TCP;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author hanhan
 * date 2022/4/30 8:53
 * 努力已经来不及了,你得拼命
 * 接收多个客户端消息(线程池版本)
 */
public class ClientDemo_03 {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端请求成功");
        Scanner sc = new Scanner(System.in);
        //创建Socket通信管道请求与服务端的连接
        Socket s = new Socket(InetAddress.getLocalHost(),7775);
        //从Socket通信管道得到一个字节输出流,负责发送数据
        OutputStream os = s.getOutputStream();
        //把低级的流包装成打印流
        PrintStream p = new PrintStream(os);
        while (true) {
            System.out.println("请输入弹幕:");
            String ss=sc.nextLine();
            //发送消息
            p.println(ss);
            os.flush();
        }
        //提醒:不要轻易关闭管
    }
}

使用线程池的优势:

  • 服务端可以复用线程处理多个客户端,可以避免系统瘫痪
  • 适合客户端通信时长较短的场景

四、TCP通信实战案例——即时通信

即时通信含义:

  • 即时通信是指一个客户端的消息发出去,其他客户端可以接收到
  • 即时通信需要进行端口转发的设计思想
  • 服务端需要把在线的Socket管道存储起来
  • 一旦接收到一个消息要推送给其他管道

群聊:

package com.itheima.Case_;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

/**
 * @author hanhan
 * date 2022/4/30 10:15
 * 努力已经来不及了,你得拼命
 * 实战案例——即时通信(服务端)
 */
public class ServerDemo_00 {
    //定义一个静态的List集合存储当前全部在线的Socket管道
    public static List<Socket> all_Sockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动成功");
        //服务端注册端口
        ServerSocket ss = new ServerSocket(7771);
        while(true){
            //开始等待接收客户端的Socket管道
            Socket s = ss.accept();
            System.out.println(s.getRemoteSocketAddress()+"上线了");
            all_Sockets.add(s);
            //创建一个独立的线程来单独处理Socket管道
            new ServerDemo_00Thread(s).start();
        }
    }
}
class ServerDemo_00Thread extends Thread {
    private Socket socket;

    public ServerDemo_00Thread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
//从Socket通信管道中得到一个字节输入流
            InputStream i = socket.getInputStream();
            //把字节输入流包装成缓冲字符输入流
            BufferedReader b = new BufferedReader(new InputStreamReader(i));
            String s1;
            while ((s1 = b.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "说:" + s1);
                //把这个消息进行端口转发给全部客户Socket管道
                sendMessage(s1);

            }
        } catch (IOException e) {
            System.out.println(socket.getRemoteSocketAddress() + "离线了!");
            ServerDemo_00.all_Sockets.remove(socket);
        }
    }
private void sendMessage(String s1) throws IOException {
    for (Socket s : ServerDemo_00.all_Sockets) {
        OutputStream o = s.getOutputStream();
        PrintStream p = new PrintStream(o);
        p.println(s1);
        p.flush();
    }
}
}

 

package com.itheima.Case_;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

/**
 * @author hanhan
 * date 2022/4/30 9:52
 * 实战案例——即时通信()
 * 1.客户端发送消息
 * 2.客户端随时可能收到消息
 */
public class ClientDemo_00 {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动");
        //创建与服务端连接的Socket管道
        Socket s = new Socket(InetAddress.getLocalHost(), 7771);
        //创建一个独立的线程专门负责这个客户端的读消息
        new ClientReaderThread(s).start();
        //从Socket管道得到一个字节输出流管道
        OutputStream o = s.getOutputStream();
        //包装成高级的打印流
        PrintStream p = new PrintStream(o);
        Scanner sc=new Scanner(System.in);
        while (true){
            System.out.println("请输入:");
            String s1=sc.nextLine();
            p.println(s1);
            p.flush();
        }
    }
}
class ClientReaderThread extends Thread{
    private Socket socket;

    public ClientReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //从Socket通信管道中得到一个字节输入流
            InputStream i = socket.getInputStream();
            //把字节输入流包装成缓冲字符输入流
            BufferedReader b = new BufferedReader(new InputStreamReader(i));
            String s1;
            while (true) {
                if ((s1 = b.readLine()) != null) {
                    System.out.println("收到消息"+ s1);
                }
            }
        } catch (IOException e) {
            System.out.println("服务端把你踢出群聊");
        }
    }
}

五、BS架构模拟

之前的都是CS架构,客户端需要我们自己开发实现。而BS结构是浏览器访问服务端,不需要开发客户端。

注意:服务器必须给浏览器响应HTTP协议格式的数据 ,否则浏览器不识别

package com.itheima.BS;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * @author hanhan
 * date 2022/4/30 11:27
 * 努力已经来不及了,你得拼命
 */
public class ServerDemo_00 {
    public static void main(String[] args) throws UnknownHostException {
        System.out.println(InetAddress.getLocalHost());
        try {
            //注册一个端口
            ServerSocket s = new ServerSocket(9091);
            while (true){
                Socket ss=s.accept();
                new ServerDemo_00Thread(ss).start();

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class ServerDemo_00Thread extends Thread{
    private Socket socket;

    public ServerDemo_00Thread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //从Socket通信管道中得到一个字节输出流
            OutputStream i = socket.getOutputStream();
            //把输出流包装成打印输出流
            PrintStream p=new PrintStream(i);
            //必须响应HTTP协议格式数据,否则浏览器不认识消息
            p.println("HTTP/1.1 200 OK");
            p.println("Content-Type:text/html;charset=UTF-8");
            p.println();
            p.println("<span style='color:red;font-size:80px'>生日快乐</span>");
            p.close();
        } catch (IOException e) {

        }
    }
}

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

凌晨四点半sec

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值