19.1 Socket基础
socket(套接字) , 各种开发语言提供出来专门用于网络编程的API(接口),在传输层上的协议进行的编程tcp udp , 通过端口号来进行通信,实现点对点之间的通信 (服务端的Socket , 客户端的Socket)
ServerSocket 服务器套接字
Socket 套接字 (客户端使用它)
数据的传输使用的是流Stream。
创建一个服务器Socket对象,一定要先启动服务
代码语言:javascript复制//new ServerSocket(1314); 指定一个用于通信的端口
ServerSocket server=new ServerSocket(1314);
accept() 阻塞,等待客户端的连接 返回值是一个Socket对象
代码语言:javascript复制OutputStream outputStream = socket.getOutputStream(); //向外发送信息时使用输出流
代码语言:javascript复制InputStream inputStream = accept.getInputStream(); //向内接收信息时使用输入流
代码语言:javascript复制public class Server {
public static void main(String[] args) {
try {
//创建一个服务器对象
ServerSocket server=new ServerSocket(1314);
//阻塞等待客户端的连接,只要不再阻塞就代表有客户端连接成功
Socket accept = server.accept();
//就可以使用socket获取输入流来读取发送过来的信息
InputStream inputStream = accept.getInputStream();
Reader reader=new InputStreamReader(inputStream);
BufferedReader bufferedReader=new BufferedReader(reader);
System.out.println("客户端说:");
while (true){
String s = bufferedReader.readLine();
if(s==null){
break;
}
System.out.print(s);
}
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码语言:javascript复制import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
//创建Socket对象,只要对象创建成功就可以连接上服务器
Socket socket=new Socket("192.168.40.33",1314);
//连接上了服务器后就可能发送消息了,使用Socket中的输出流
OutputStream outputStream = socket.getOutputStream();
PrintWriter out=new PrintWriter(outputStream);
out.println("你好,服务器");
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
19.2 模拟tomcat
HTTP响应报文与HTTP请求报文相似,HTTP响应也由4个部分组成: 1、状态行 协议/版本 200 响应编码 OK响应文本 2、响应头(Response Header) 3、空行 4、响应正文
代码语言:javascript复制package com.qf.server;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyTomcat {
public static void main(String[] args) {
try {
ServerSocket serverSocket=new ServerSocket(8080);
while (true) {
Socket accept = serverSocket.accept();
//浏览器的响应流
OutputStream outputStream = accept.getOutputStream();
String html = "<!DOCTYPE html>n"
"<html>n"
"t<head>n"
"tt<meta charset="utf-8">n"
"tt<title></title>n"
"tt<script src="https://code.jquery.com/jquery-3.6.0.js"></script>n"
"tt<script>n"
"ttvar names=["李鹏","王廷瑞","陈思源","胥富瀚","秦浠凯","谢成乾","颜志华","梅澳","李斯浩","周鑫","陈浩","张正凯","卢东川","胡亮忠","陈东","杨皓","向科豪","倪瑞东","周鹏","罗一淘","黄黉广","刘洪海","李承煜","李小龙","唐博","杨茂林","陈宇","王赟","王昊","张孝轩", "勾越","龙泉","陈鸿","殷钟森","苟涛","官锐","张方灿","李宁博"]n"
"ttvar time;n"
"tt$(function(){n"
"ttt$("button:eq(0)").click(function(){n"
"ttttn"
"tttt time=setInterval("scrollNames()",10);n"
"ttttn"
"ttttsetTimeout("stopTime()",parseInt(Math.random()*10000));n"
"ttt})n"
"ttt$("button:eq(1)").click(function(){n"
"ttttstopTime();n"
"ttt});n"
"tttn"
"tt})n"
"ttvar index=0;n"
"ttfunction scrollNames(){n"
"tttn"
"tttif(index>=names.length){n"
"ttttindex=0;n"
"ttt}n"
"ttt$("div").html(names[index]);n"
"tttindex ; n"
"tt}n"
"ttn"
"ttfunction stopTime(){n"
"tttclearInterval(time);n"
"tt}n"
"tt</script>n"
"t</head>n"
"t<body style="text-align: center;">n"
"tt<div style="border: 1px solid red;width: 200px;height: 70px;font:bold 24px '微软雅黑';margin: 10px auto;line-height: 70px;"></div>n"
"tt<button>抽奖</button>n"
"tt<button>停止</button>n"
"t</body>n"
"</html>n";
StringBuffer headers = new StringBuffer("HTTP/1.1 200 OKrn");
headers.append("Content-Type:text/html;charset=utf8rn");
headers.append("Content-Length:" html.getBytes().length "rn");
headers.append("rn");
headers.append(html);
PrintStream printStream = new PrintStream(outputStream);
printStream.println(headers);
printStream.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
19.3 多客户端通信
代码语言:javascript复制//服务器
package com.qf.server;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyServerListSocket2 {
private static volatile Map<String,Socket> socketMap=new ConcurrentHashMap<>();
private static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
/*
1、接收客户端的连接
2、将客户端的连接对象存入到map中
3、服务向客户端发信息,客户端也能向服务器发信息
4、信息中带有客户端的特殊的请求信息
*/
try {
//建立服务器
ServerSocket server=new ServerSocket(10005);
//接收客户端的连接
getClient(server);
//因为现在是控制台应用程序,我们要进行程序的流程控制让整个程序变得更合理,所以使用了do...while if的结构来进行控制
int menu;
do{
System.out.println("1、发送信息");
System.out.println("2、退出");
menu=input.nextInt();
if(menu==1){
//发送信息给客户端
sendInfo();
}else {
System.out.println("服务关闭");
break;
}
}while (true);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 这里是向客户端发送消息
*/
private static void sendInfo(){
//首先打印客户端列表,确定要向谁发消息
for (String k:socketMap.keySet()) {
System.out.println(k);
}
//让用户进行输入确定要发送的客户端对象
System.out.println("请输入要发送的客户端:");
String ip=input.next();
//根据输入的ip来确定客户端对象
Socket socket = socketMap.get(ip);
System.out.println("请输入要发送的信息:");
String info=input.next();
try {
//真正发送消息的方法
sendClientInfo(socket, info);
} catch (IOException e) {
e.printStackTrace();
}
}
//这里是发送消息的方法 因为发送消息可以会在多个地上进行使用
private static void sendClientInfo(Socket socket, String info) throws IOException {
OutputStream outputStream = socket.getOutputStream();
PrintStream printStream=new PrintStream(outputStream);
printStream.println(info);
printStream.flush();//如果不进行flush数据会传输失败
}
/**
* 监听客户端发送过来的信息
* @param socket
*/
private static void revserClientInfo(Socket socket){
//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
//有一个客户连接上来就用线程监听一个客户是否发消息
executorService.submit(()->{
try {
//如果要不断的接收人家发来的消息这里要使用循环
while (true) {
InputStream inputStream = socket.getInputStream();
Reader reader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(reader);
//如果客户端没有发消息这里的读取会阻塞
String s = bufferedReader.readLine();
//如果发送过来的信息是约定的特殊消息
if ("java2203abc123...!!!".equals(s)) {
//这里要向客户端发送整个客户端列表
Set<String> clients = socketMap.keySet();
sendClientInfo(socket, clients.toString());
} else {//否则客户端发过来的就是普通消息,普通消息就展示
System.out.println(socket.getInetAddress().getHostAddress() ":" s);
}
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* 实时监听获取客户端的子线程
* @param server
*/
private static void getClient(ServerSocket server) {
//在线程中去循环获取客户端信息
new Thread(()->{
while (true){
try {
//获取客户端连接对象 A B
Socket accept = server.accept();
//监听客户发过来的信息
revserClientInfo(accept);
//将客户端连接对象存入到map中
socketMap.put(accept.getInetAddress().getHostAddress(),accept);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
代码语言:javascript复制//客户端代码
package com.qf.client;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class ListClient1 {
public static void main(String[] args) {
try {
//与服务器建立连接
Socket socket=new Socket("127.0.0.1",10005);
//只要与服务器建立上了连接后就可以去监听服务器发送过来的消息了
RevserServerInfo(socket);
//客户端也进行了流程控制
Scanner input=new Scanner(System.in);
int menu;
do {
System.out.println("1、查看客户端列表");
System.out.println("2、退出");
menu=input.nextInt();
if(menu==1){
//向服务发送请求获取客户端列表信息
OutputStream outputStream = socket.getOutputStream();
PrintStream printStream=new PrintStream(outputStream);
//这里发什么信息(这是一种约定这种约定只是客户端和服务器之间的约定)发什么数据不重要,重要的是这个数据要据有一定的唯一性
//java2203abc123...!!!
printStream.println("java2203abc123...!!!");
printStream.flush();
//以上客户端已经向服务发送完成请求获取整个客户端列表的信息
}else {
System.out.println("程序结束");
break;
}
}while (true);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 监听服务器发送过来的消息
* @param socket
*/
private static void RevserServerInfo(Socket socket) {
//监听服务器消息的代码写到了一个子线程中
Thread thread=new Thread(()->{
//同样使用了一个死循环,这里目的是为了可以重复的去接收服务发送过来的消息
while (true){
InputStream inputStream = null;
try {
inputStream = socket.getInputStream();
Reader reader=new InputStreamReader(inputStream);
BufferedReader bufferedReader=new BufferedReader(reader);
String s = bufferedReader.readLine();
System.out.println("服务器说:" s);
} catch (IOException e) {
e.printStackTrace();
}
}
});
//设置接收消息的子线程为守护线程
thread.setDaemon(true);
thread.start();
}
}
19.4 心跳机制
心跳机制 主机和从机之间一种状态检测机制,从机在固定的频率上向主机发送特殊的信息,来告诉主机我还活着,主机也是在固定的频率上来接收从机发送过来的心跳信息,如果接收不到,一次两次没关系,有一个阈值如果超过了这个阈值,主机将认为从机已经死掉。
代码语言:javascript复制//客户端发送
socket.sendUrgentData(255); //发送一个紧急数据 发送什么不重要,两边约定好,目的是为了告诉服务器,我还活着
代码语言:javascript复制//服务器设置
//判断允许接收紧急数据是否已经开启,如果没有开启
if(!socket.getOOBInline()) {
//要允许服务器接收紧急数据
socket.setOOBInline(true);
}
19.5 心跳机制的基础案例
客户端
代码语言:javascript复制package test;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
public class Client {
public static void main(String[] args) {
try {
Socket socket=new Socket("127.0.0.1",1315);
int i=0;
//写了一个死循环,循环发送
while (true){
//发送数据
socket.sendUrgentData(255);
//这里的打印只是为了让你们看到循环在运行没有实际的意义
System.out.println(i );
//发送心跳的频率
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器
代码语言:javascript复制package test;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(1315);
//Socket socket --> 客户端那边的Socket
Socket socket = serverSocket.accept();
if(!socket.getOOBInline()) {
//要允许服务器接收紧急数据
socket.setOOBInline(true);
}
//服务约定的规定是9秒如果接收不到客户端的心跳数据就认为客户端已经掉线
//计数器,循环是三秒执行一次,不出意外(没读到数据),循环三count也就 3次就到了9秒,就可以认为客户端离线
int count=0;
while (true){
//这里循环接收
try {
//服务器接收客户端发送的紧急数据 还是使用InputStream.
InputStream inputStream = socket.getInputStream();
//判断是判断当前获取这个流中有没有数据存在
if(inputStream.available()>0) {
//在这里读取心跳数据
int heart = inputStream.read();
//判断当前读到的数据是不是约定的心跳数据
if(heart==255){
//这里的打印只为了让你们能看到效果,没有实际意义
System.out.println(heart);
//只要读到数据计数清0
count=0;
}
}
count ;
if(count>3){
System.out.println("客户端已离线");
}
Thread.sleep(3000);
}catch (SocketException ex){
System.out.println("客户端已离线");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
19.6 将心跳放入线程
主线程被占用,心跳机制的东西应该放到子线程中去,不要占用主线程,因为程序还有接收正常的信息和发送正常的信息,心跳机制应该为其他线程服务,守护线程
代码语言:javascript复制package test;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Currency;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(1315);
//Socket socket --> 客户端那边的Socket
Socket socket = serverSocket.accept();
Thread heart=new Thread(()->{
try {
heartRead(socket);
} catch (SocketException e) {
e.printStackTrace();
}
});
heart.setDaemon(true);
heart.start();
//程序走到这里要接收键盘的输入,主要为了阻塞main线程不结束
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void heartRead(Socket socket) throws SocketException {
if(!socket.getOOBInline()) {
//要允许服务器接收紧急数据
socket.setOOBInline(true);
}
int count=0;
while (true){
//这里循环接收
try {
InputStream inputStream = socket.getInputStream();
if(inputStream.available()>0) {
int heart = inputStream.read();
if(heart==255){
System.out.println(heart);
count=0;
}
}
count ;
if(count>3){
System.out.println("客户端已离线");
}
Thread.sleep(3000);
}catch (SocketException ex){
System.out.println("客户端已离线");
}
catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
代码语言:javascript复制package test;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
public class Client {
public static void main(String[] args) {
try {
Socket socket=new Socket("127.0.0.1",1315);
Thread heart=new Thread(()->{
try {
sendHeart(socket);
} catch (IOException e) {
e.printStackTrace();
}
});
heart.setDaemon(true);
heart.start();
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void sendHeart(Socket socket) throws IOException {
int i=0;
while (true){
socket.sendUrgentData(255);
System.out.println(i );
//循环发送 代表发送一个紧急数据 随便
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
缓冲区域的刷新
代码语言:javascript复制//发送代码
public void write(OutputStream outputStream,String info){
PrintWriter out=new PrintWriter(outputStream);
out.println(info);
//缓冲区的刷新很重要,如果不刷,信息发送失败
out.flush();
}
19.7 心跳机制和正常数据通信的综合使用
代码语言:javascript复制//服务器代码
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Scanner;
public class Server {
//计数器
private static volatile int count=0;
//当客户端下线时控制其他线程不再执行
private static volatile boolean flat=true;
public static void main(String[] args) throws IOException {
ServerSocket serverSocket=new ServerSocket(3333);
Socket socket=serverSocket.accept();
Thread heart=new Thread(() -> {
try {
heartRead(socket);
} catch (IOException e) {
e.printStackTrace();
}
});
//设置心跳为守护线程
heart.setDaemon(true);
heart.start();
new Thread(() -> { read(socket); }).start();
new Thread(() -> {
try {
write(socket);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
public static void read(Socket socket) {
//使用flat作为判断条件,控制循环在某个时刻可以退出
while (flat){
try {
InputStream inputStream = socket.getInputStream();
if (inputStream.available() > 1) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
//如果发送的信息没有rn读取会阻塞 ,因为readLine() 读取一行,一行的标准就是看末尾有没有rn
String info = reader.readLine();
//对读取的数据进行判断,区分出心跳数据还是正常数据
if (!"heart-1".equals(info)) {
//正常路数直接显示
System.out.println("客户端说:" info);
} else {
//心跳数据将计数器清0
count = 0;
}
}
}catch (SocketException ex){
//如果在读取数据时出现Socket异常,代表客户端断开连接了
System.out.println("客户端已离线");
//将flat设置为false退出线程中的循环
flat=false;
break;
}catch (IOException e) {
e.printStackTrace();
}
}
}
public static void write(Socket socket) throws IOException {
Scanner input = new Scanner(System.in);
while (flat){
String info=input.nextLine();
OutputStream outputStream = socket.getOutputStream();
PrintWriter writer=new PrintWriter(new OutputStreamWriter(outputStream));
writer.println(info);
writer.flush();
}
}
public static void heartRead(Socket socket) throws SocketException {
//这里while(true) 不影响,因为hearRead()方法是在一个守护线程
while (true){
//这里循环接收
try {
Thread.sleep(3000);
InputStream inputStream = socket.getInputStream();
if(inputStream.available()>0) {
BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
//读取心跳数据
String heart = reader.readLine();
//有可能读取到正常数据
if ("heart-1".equals(heart)) {
//是心跳数据计数器清0
count = 0;
continue;
}else {
//也有可能读取到正常数据,正常数据正常显示
System.out.println("客户端说:" heart);
}
}
//3秒内没有接收到客户端的任何数据计数器 1
count ;
if(count>3){
//当三次没有接收到信息的时候判定客户端下线
System.out.println("客户端已离线");
//flat设置为false
flat=false;
break;
}
}catch (SocketException ex){
//读取数据过程中出现Socket异常也代表客户端断线
System.out.println("客户端已离线");
//flat设置为false
flat=false;
break;
}catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
代码语言:javascript复制//客户端代码
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.Scanner;
public class Client {
private static volatile boolean flat=true;
public static void main(String[] args) throws IOException {
Socket socket=new Socket("127.0.0.1",3333);
new Thread(() -> {
try {
read(socket);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
write(socket);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
Thread heart=new Thread(() -> {
try {
sendHeart(socket);
} catch (IOException e) {
e.printStackTrace();
}
});
heart.setDaemon(true);
heart.start();
}
public static void write(Socket socket) throws IOException {
Scanner input = new Scanner(System.in);
while (flat){
String info = input.nextLine();
OutputStream outputStream = socket.getOutputStream();
//PrintWriter
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream));
//writer.println 会在传递的信息末尾加 rn(一行信息结束的标志)
writer.println(info);
writer.flush();
}
}
public static void read(Socket socket) throws IOException {
try {
while (flat) {
InputStream inputStream = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String info = reader.readLine();
System.out.println("服务器说:" info);
}
}catch (SocketException ex){
System.out.println("服务器已离线");
flat=false;
}catch (IOException e) {
e.printStackTrace();
}
}
public static void sendHeart(Socket socket) throws IOException {
int i=0;
while (true){
//循环发送 代表发送一个紧急数据 随便
try {
Thread.sleep(3000);
OutputStream outputStream = socket.getOutputStream();
PrintWriter writer=new PrintWriter(new OutputStreamWriter(outputStream));
writer.println("heart-1");
writer.flush();
}catch (SocketException ex){
System.out.println("服务器已离线");
flat=false;
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}