个人简介
- 大家好,我是韩慧腾。一名正在努力学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) {
}
}
}