目录
1.TCP通信-快速入门
编写客户端代码
步骤:客户端发送消息
总结:
需求:服务端实现步骤
总结:
2.TCP通信-多发多收消息
案例:使用TCP通信实现:多发多收消息
总结:
*3.TCP通信-同时接受多个客户端消息[重点]
总结:
4.TCP通信-使用线程池优化
总结:
5.TCP通信实战案例-即时通信
总结:
6.TCP通信实战案例-模拟BS系统[了解]
总结:
TCP协议:
TCP是一种面向连接,安全、可靠的传输数据的协议
传输前,采用“三次握手”方式,点对点通信,是可靠的
在连接中可进行大数据量的传输
TCP通信模式演示:
Socket类
Socket类成员方法
需求:客户端实现步骤
1.创建客户端的Socket对象,请求与服务端的连接。
2.使用socket对象调用getOutputStream()方法得到字节输出流。
3.使用字节输出流完成数据的发送。
4.释放资源:关闭socket管道。
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
/**
目标:完成Socket网络编程入门案例的客户端开发,实现1发1收。
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
// 4、发送消息
ps.println("我是TCP的客户端,我已经与你对接,并发出邀请:约吗?");
ps.flush();
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
1.TCP通信的客户端的代表类是谁?
Socket类
public Socket(String host , int port)
2.TCP通信如何使用Socket管道发送、接收数据。
OutputStream getOutputStream():获得字节输出流对象(发)
InputStream getInputStream():获得字节输入流对象(收)
编写服务端代码、原理分析
步骤:服务端实现接收消息
ServerSocket(服务端)
ServerSocket类成员方法
创建ServerSocket对象,注册服务端端口。
调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
释放资源:关闭socket管道
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:开发Socket网络编程入门代码的服务端,实现接收消息
*/
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
if ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.TCP通信服务端用的代表类?
ServerSocket类,注册端口。
调用accept()方法阻塞等待接收客户端连接。得到Socket对象。
2.TCP通信的基本原理?
客户端怎么发,服务端就应该怎么收。
客户端如果没有消息,服务端会进入阻塞等待。
Socket一方关闭或者出现异常、对方Socket也会失效或者出错。
需求:使用TCP通信方式实现:多发多收消息。
具体要求:
可以使用死循环控制服务端收完消息继续等待接收下一个消息。
客户端也可以使用死循环等待用户不断输入消息。
客户端一旦输入了exit,则关闭客户端程序,并释放资源。
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
目标:实现多发和多收
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:开发Socket网络编程入门代码的服务端,实现接收消息
*/
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
while (true) {
// 2、必须调用accept方法:等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
本案例实现了多发多收,那么是否可以同时接收多个客户端的消息?
不可以的。
因为服务端现在只有一个线程,只能与一个客户端进行通信。
1.本次多发多收是如何实现的?
客户端使用循环反复地发送消息。
服务端使用循环反复地接收消息。
2.现在服务端为什么不可以同时接收多个客户端的消息?
目前服务端是单线程的,每次只能处理一个客户端的消息。
之前我们的通信是否可以同时与多个客户端通信,为什么?
不可以的
单线程每次只能处理一个客户端的Socket通信
如何才可以让服务端可以处理多个客户端的通信需求?
引入多线程。
同时处理多个客户端消息
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
目标:实现服务端可以同时处理多个客户端的消息。
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 7777);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
目标:实现服务端可以同时处理多个客户端的消息。
*/
public class ServerDemo2 {
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 3、开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
本次是如何实现服务端接收多个客户端的消息的。
主线程定义了循环负责接收客户端Socket管道连接
每接收到一个Socket通信管道后分配一个独立的线程负责处理它。
目前的通信架构存在什么问题?
客户端与服务端的线程模型是: N-N的关系。
客户端并发越多,系统瘫痪的越快。
引入线程池处理多个客户端消息
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
拓展:使用线程池优化:实现通信。
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 6666);
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
目标:实现服务端可以同时处理多个客户端的消息。
*/
public class ServerDemo2 {
// 使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(300,
1500, 6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2)
, Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(6666);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 任务对象负责读取消息。
Runnable target = new ServerReaderRunnable(socket);
pool.execute(target);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
本次使用线程池的优势在哪里?
服务端可以复用线程处理多个客户端,可以避免系统瘫痪。
适合客户端通信时长较短的场景。
即时通信是什么含义,要实现怎么样的设计?
即时通信,是指一个客户端的消息发出去,其他客户端可以接收到。
之前我们的消息都是发给服务端的。
即时通信需要进行端口转发的设计思想。
即时通信-端口转发
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
拓展:即时通信
客户端:发消息的同时,随时有人发消息过来。
服务端:接收消息后,推送给其他所有的在线socket
*/
public class ClientDemo1 {
public static void main(String[] args) {
try {
System.out.println("====客户端启动===");
// 1、创建Socket通信管道请求有服务端的连接
// public Socket(String host, int port)
// 参数一:服务端的IP地址
// 参数二:服务端的端口
Socket socket = new Socket("127.0.0.1", 9999);
// 马上为客户端分配一个独立的线程负责读取它收到的消息
new ClientReaderThread(socket).start();
// 2、从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os = socket.getOutputStream();
// 3、把低级的字节流包装成打印流
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
// 4、发送消息
ps.println(msg);
ps.flush();
}
// 关闭资源。
// socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class ClientReaderThread extends Thread{
private Socket socket;
public ClientReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "收到了: " + msg);
}
} catch (Exception e) {
System.out.println("服务端把你踢出去了~~");
}
}
}
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
目标: 即时通信
*/
public class ServerDemo2 {
public static List onLineSockets = new ArrayList<>();
public static void main(String[] args) {
try {
System.out.println("===服务端启动成功===");
// 1、注册端口
ServerSocket serverSocket = new ServerSocket(9999);
// a.定义一个死循环由主线程负责不断的接收客户端的Socket管道连接。
while (true) {
// 2、每接收到一个客户端的Socket管道,交给一个独立的子线程负责读取消息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+ "它来了,上线了!");
// 把当前客户端管道Socket加入到在线集合中去
onLineSockets.add(socket);
// 3、开始创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.Socket;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 3、从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 5、按照行读取消息
String msg;
while ((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + "说了:: " + msg);
// 把这个消息发给当前所有在线socket
sendMsgToAll(msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
// 从在线集合中抹掉本客户端socket
ServerDemo2.onLineSockets.remove(socket);
}
}
private void sendMsgToAll(String msg) {
try {
// 遍历全部的在线 socket给他们发消息
for (Socket onLineSocket : ServerDemo2.onLineSockets) {
// 除了自己的socket,其他socket我都发!!
if(onLineSocket != socket){
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println(msg);
ps.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
即时通信是什么含义,要实现怎么样的设计?
即时通信,是指一个客户端的消息发出去,其他客户端可以接收到
即时通信需要进行端口转发的设计思想。
服务端需要把在线的Socket管道存储起来
一旦收到一个消息要推送给其他管道
之前的客户端都是什么样的
其实就是CS架构,客户端实需要我们自己开发实现的。
BS结构是什么样的,需要开发客户端吗?
浏览器访问服务端,不需要开发客户端。
HTTP响应数据的协议格式:就是给浏览器显示的网页信息
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
了解:BS-浏览器-服务器基本了解。
引入:
之前客户端和服务端都需要自己开发。也就是CS架构。
接下来模拟一下BS架构。
客户端:浏览器。(无需开发)
服务端:自己开发。
需求:在浏览器中请求本程序,响应一个网页文字给浏览器显示
*/
public class BSserverDemo {
// 使用静态变量记住一个线程池对象
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) {
try {
// 1.注册端口
ServerSocket ss = new ServerSocket(8080);
// 2.创建一个循环接收多个客户端的请求。
while(true){
Socket socket = ss.accept();
// 3.交给一个独立的线程来处理!
pool.execute(new ServerReaderRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// 浏览器 已经与本线程建立了Socket管道
// 响应消息给浏览器显示
PrintStream ps = new PrintStream(socket.getOutputStream());
// 必须响应HTTP协议格式数据,否则浏览器不认识消息
ps.println("HTTP/1.1 200 OK"); // 协议类型和版本 响应成功的消息!
ps.println("Content-Type:text/html;charset=UTF-8"); // 响应的数据类型:文本/网页
ps.println(); // 必须发送一个空行
// 才可以响应数据回去给浏览器
ps.println("《最牛的149期》 ");
ps.close();
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了!!!");
}
}
}
1.TCP通信如何实现BS请求网页信息回来呢?
客户端使用浏览器发起请求(不需要开发客户端)
服务端必须按照浏览器的协议规则响应数据。
2.浏览器使用什么协议规则呢?
HTTP协议(简单了解下)