2022Java学习笔记七十九(网络编程:TCP通信,TCP通信:多个客户端消息【重点】,追踪客户端的上线和下线功能、线程池优化)
一、TCP通信快速入门
TCP协议回顾: 1、TCP是一种面向连接,安全、可靠的传输数据的协议 2、传输前,采用“三次握手”方式,点对点通信,是可靠的 3、在连接中可进行大数据量的传输
构造器和常用API 二、TCP客户端发送消息 示例代码
package com.zcl.d12_tcpDaemo;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ColientDemo {
public static void main(String[] args) {
System.out.println("-------- 客户端启动成功 --------");
try {
Socket socket = new Socket("127.0.0.1",7777);
OutputStream ops = socket.getOutputStream();
PrintStream ps = new PrintStream(ops);
ps.println("我对您发起邀请");
ps.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、服务端代码编写
示例代码
package com.zcl.d12_tcpDaemo;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) {
System.out.println("-------- 服务端启动成功 --------");
try {
ServerSocket serverSocket = new ServerSocket(7777);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
if ((msg = br.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
需要严格遵守一发一收的原则,如果客户端不是使用一行消息来发送的化,而服务端使用的是接收一行数据就会报错,因为客户端不是发送一行服务端就接收到不完整的消息 如果客户端只发送一条行数据,而服务端使用循环while()接收数据也会报错,因为客户端已经完成了一次消息发送已经关掉了,服务器还在接收数据就会报错
三、TCP通信:多发多收案例
在上面的代码基础上分别个客户端和服务端添加反复循环就可以了
客户端代码修改
while (true) {
System.out.println("请输入需要发送的消息:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
服务端代码修改
while ((msg = br.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg);
}
现在写好的服务端目前自能同时一收一个客户端的消息,原因是:目前的服务端是单线程的,每次只能处理一个客户端的消息
四、实现同时接收多个客户端消息【重点】 引用多线程 实现代码 1、客户端的代码不要动 2、修改服务端的代码
package com.zcl.d13_socket4;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args) {
System.out.println("-------- 服务端启动成功 --------");
try {
ServerSocket serverSocket = new ServerSocket(7777);
while (true) {
Socket socket = serverSocket.accept();
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3、创建一个多线程的ServerReaderThread类实现Thread类
package com.zcl.d13_socket4;
import java.io.BufferedReader;
import java.io.IOException;
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 {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
多客户端接收消息就可以完成了
关于如何追踪客户端的上线和下线功能
1、在服务端的while循环里面通过socket的IP地址可以知道哪台客户端上线了 // 判断可客户端谁上线了 System.out.println(socket.getRemoteSocketAddress()+"他上线了"); 2、在定义的ServerReaderThread多线程类里面,将捕获catch中最终IP地址判断下线,如果客户端报错了服务端就会给那个客户端报错
System.out.println(socket.getRemoteSocketAddress() + "他下线了");
在定义的ServerReaderThread多线程类 五、TCP通信:线程池优化 1、客户端代码不需要动 2、修改服务端代码
package com.zcl.d14_scoket5;
import com.zcl.d13_socket4.ServerReaderThread;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
public class ServerDemo {
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) {
System.out.println("-------- 服务端启动成功 --------");
try {
ServerSocket serverSocket = new ServerSocket(6666);
while (true) {
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress()+"他上线了");
Runnable target = new ServerReaderRunnable(socket);
pool.execute(target);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
添加了ExecutorService 线程池对象,通过线程池提交客户端发起的信息,在线程池排队
3、创建ServerReaderRunnable任务对象实现Runnable接口
需要重写构造器接收客户端发送对象
package com.zcl.d14_scoket5;
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 {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg);
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "他下线了");
}
}
}
使用线程池的优势在哪里 1、服务器端可以复用线程池处理多个客户端,可以避免系统瘫痪 2、适合客户端通信时长较短的场景
|