TCP协议往期回顾:
TCP是一种面向连接,安全、可靠的传输数据的协议
传输前,采用“三次握手”方式,点对点通信,是可靠的
在连接中可进行大数据量的传输
注意:在java中只要是使用java.net.Socket类实现通信,底层即是使用了TCP协议
TCP通信的基本原理:
客户端怎么发,服务端就应该怎么收
客户端如果没有消息,服务端会进入阻塞等待
Socket一方关闭或者出现异常,对方Socket也会失效或者出错
注意:要先启动服务端,再启动客户端,不然会导致数据丢失!
实现一发一收
客户端:
import java.io.*;
import java.net.Socket;
public class TCP_客户端 {
//一发一收
public static void main(String[] args) throws Exception {
System.out.println("-----客户端启动-----");
//创建Socket通信管道请求与服务器的连接
/*
public Socket(String host, int port)
参数一、服务器的IP地址
参数二、服务器的端口
*/
Socket socket=new Socket("127.0.0.1",6666);
//从Socket通信管道中得到一个字节输出流,负责发送数据
OutputStream out=socket.getOutputStream();
//把低级的字节流包装成打印流
PrintStream ps=new PrintStream(out);
//发送消息
ps.println("你好啊");
//刷新
ps.flush();
}
}
服务端:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
class TCP_服务端{
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动-----");
//注册端口
ServerSocket serverSocket=new ServerSocket(6666);
//必须调用accept方法,等待接收客户端的Socket连接请求,建立Socket通信管道
Socket socket=serverSocket.accept();
//从Socket通信管道中得到一个字节输入流
InputStream is=socket.getInputStream();
//把字节输入流包装成缓冲字节输入流进行消息的接收
BufferedReader br=new BufferedReader(new InputStreamReader(is));
//按照行读取消息
String line;
if((line=br.readLine())!=null){
System.out.println("接收到了来自"+socket.getRemoteSocketAddress()+"的消息:"+line);
}
}
}
实现多发多收、服务端同时接收多个客户端的消息:
如何实现的?
主线程定义了循环负责接收客户端Socket管道连接
每接收到一个Socket通信管道后分配一个独立的线程负责处理它
客户端:
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TCP_多发多收 {
//一发一收
public static void main(String[] args) throws Exception {
System.out.println("-----客户端启动-----");
//创建Socket通信管道请求与服务器的连接
/*
public Socket(String host, int port)
参数一、服务器的IP地址
参数二、服务器的端口
*/
Socket socket=new Socket("127.0.0.1",6666);
//从Socket通信管道中得到一个字节输出流,负责发送数据
OutputStream out=socket.getOutputStream();
//把低级的字节流包装成打印流
PrintStream ps=new PrintStream(out);
Scanner sc=new Scanner(System.in);
while (true){
System.out.println("请输入你要发送的消息:");
String line=sc.nextLine();
//发送消息
ps.println(line);
//刷新
ps.flush();
}
}
}
服务端:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
//实现可以同时接收多个客户端
class TCP_多发多收_服务端{
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动-----");
//注册端口
ServerSocket serverSocket=new ServerSocket(6666);
//定义一个死循环由主线程负责不断的接收客户端的Socket通信管道连接
while (true){
//每接收一个客户端的Socket通信管道,交给一个独立的子线程负责读取信息
Socket socket=serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"上线了");
//开始创建独立线程处理Socket
new ServerMyThread(socket).start();
}
}
}
服务端线程类:
import java.io.*;
import java.net.Socket;
//创建服务端线程类
class ServerMyThread extends Thread{
private Socket socket;
public ServerMyThread(Socket socket){
this.socket=socket;
}
public void run(){
try {
//从Socket通信管道中得到一个字节输入流
InputStream is=socket.getInputStream();
//把字节输入流包装成缓冲字节输入流进行消息的接收
BufferedReader br=new BufferedReader(new InputStreamReader(is));
//按照行读取消息
String line;
while ((line=br.readLine())!=null){
System.out.println("接收到了来自"+socket.getRemoteSocketAddress()+"的消息:"+line);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress()+"下线了");
}
}
}
由于上述的多发多收,存在着多一个任务就要新建一个线程的缺陷,
于是我们采用线程池优化:
客户端与之前一样:
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.*;
public class TCP_多发多收 {
//一发一收
public static void main(String[] args) throws Exception {
System.out.println("-----客户端启动-----");
//创建Socket通信管道请求与服务器的连接
/*
public Socket(String host, int port)
参数一、服务器的IP地址
参数二、服务器的端口
*/
Socket socket = new Socket("127.0.0.1", 6666);
//从Socket通信管道中得到一个字节输出流,负责发送数据
OutputStream out = socket.getOutputStream();
//把低级的字节流包装成打印流
PrintStream ps = new PrintStream(out);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入你要发送的消息:");
String line = sc.nextLine();
//发送消息
ps.println(line);
//刷新
ps.flush();
}
}
}
服务端:
这里我设置的线程池,核心线程3个,意味着同时可以管3个线程,但从第四个开始,由于设置的是3个任务队列,所以第4、5、6都不会被处理,直到第7个任务出现,才会开始设置临时线程,随机处理任务队列中的任务。但如果任务队列和临时线程一直存在,由于我设置的最大线程数为5个,任务队列最多存在3个任务,所以最多同时8个任务。当第9个任务出现时,会被拒绝。
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
//实现可以同时接收多个客户端(线程池优化)
class TCP_多发多收_服务端 {
//使用一个静态变量记住一个线程池对象
//缺陷:并发能力有限,不适合用于大型互联网项目,但局域网等小型项目还是可以的,
// 将核心线程数量和线程池可支持的最大线程数量设置大一点就行了,前提是你的服务器带的动
private static ExecutorService pools = new ThreadPoolExecutor(3, 5,
6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());//核心线程3个,意味着同时可以管三个线程,
// 但第四个开始,由于我这里设置的是3个任务队列,所以第4、5、6都不会被处理,直到第7个任务出现,才会开始设置临时线程,随机处理任务队列中的任务
//但如果任务队列和临时线程一直存在,由于我这里设置的最大线程数为5个,任务队列最多存在3个任务,所以最多同时8个任务。当第9个任务出现时,会被拒绝
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动-----");
//注册端口
ServerSocket serverSocket = new ServerSocket(6666);
//定义一个死循环由主线程负责不断的接收客户端的Socket通信管道连接
while (true) {
//每接收一个客户端的Socket通信管道,交给一个独立的子线程负责读取信息
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上线了");
//将任务交给线程池,任务对象负责读取消息
pools.execute(new ServerRunnable(socket));
}
}
}
线程实现类:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
class ServerRunnable implements Runnable {
private Socket socket;
public ServerRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//从Socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
//把字节输入流包装成缓冲字节输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//按照行读取消息
String line;
while ((line = br.readLine()) != null) {
System.out.println("接收到了来自" + socket.getRemoteSocketAddress() + "的消息:" + line);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "下线了");
}
}
}
使用线程池的优势:
服务端可以复用线程处理多个客户端,可以避免系统瘫痪
适合客户端通信时长较短的场景
缺陷:
并发能力有限,不适合用于大型互联网项目,但局域网等小型项目还是可以的,将核心线程数量和线程池可支持的最大线程数量设置大一点就行了,前提是你的服务器带的动。