目录
TCP两个核心类
服务端
1.用ServerSocker类创建对象并且手动指定端口号
2.accept阻塞连接服务端与客户端
3.给客户端提供处理业务方法
4.处理业务
整体代码
客户端
1.创建Socket对象,并连接服务端的ip与端口号
2.获取Socket流对象,写入数据,阻塞等待服务端响应
整体代码
jconsole用来监控java线程
TCP两个核心类
ServerSocket 服务器使用socket
accept没有参数 返回值是一个Socket对象
功能是等待服务器和客户端建立连接,建立成功后则会把这个连接获取到进程中。接下来就通过Scoket返回的对象来进行交互
Scoket服务器和客户端都使用socket
通过socket对象就可以进行发送接收数据
socket内部包含了输入流对象(接收) 输出流对象 (发送)
服务端
1.用ServerSocker类创建对象并且手动指定端口号
private ServerSocket serverSocket=null;
public Test1(int port) throws IOException {
serverSocket=new ServerSocket(port);
2.accept阻塞连接服务端与客户端
当我们用UDP那样连接客户端会出现一个问题
UDP是无连接的,他发送数据时直接用地址端口号发送
而TCP是有连接的,当我们用accept连接时,ProcessConnect方法会一直被循环执行单个客户端,而其他客户端要连接时,无法执行到accept,必须等待ProcessConnect方法执行结束,所以会被阻塞。简单来说就是当有一个客户端连接时,其他客户端必须等待连接的客户端断开连接才能连接,还是一个个来连接。
所以ProcessConnect方法我们直接交给线程去执行,这样其他客户端来连接时,直接让线程去执行处理客户端业务。
所以我们可以用到多线程
既然用到了多线程,就可以用出线程池,效率会更高。
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
//阻塞等待服务器与客户端建立连接
Socket socket=serverSocket.accept();
//若没线程 当一个客户端连接后 会一直在ProcessConnect内
//其他客户端连接时 必须要等待ProcessConnect结束,进入下一次循环,才能执行到Socketsocket=serverSocket.accept(); 才能连接下一个客户端
// // ProcessConnect(socket);
//所以执行处理客户端请求,我们让线程去干,这样就不会在一个线程内阻塞
// Thread thread=new Thread(()->{
// try {
// ProcessConnect(socket);
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// });
// thread.start();
//线程池
ExecutorService executorService= Executors.newCachedThreadPool();
executorService.submit(new Runnable() {
@Override
public void run() {
try {
ProcessConnect(socket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
3.给客户端提供处理业务方法
我们可以直接获取Socket内的流对象,直接写入读出即可
需要注意,写入数据时,因为时流对象接收,要\n等待其他结束,而我们输入数据时,流对象是不会写入\n的,所以我们可以在数据后自动添加\n 或者用println默认有个\n
public void ProcessConnect(Socket socket) throws IOException {
//实际他们交流数据是一个为socket类型的文件
System.out.printf("[地址:%s:端口号%d]建立连接成功\n",socket.getInetAddress().toString(),socket.getPort());
try (InputStream inputStream = socket.getInputStream();//获取socket内部的input流
OutputStream outputStream = socket.getOutputStream())//获取socket内部的output流
{
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
//长连接写法
while (true) {
if(!scanner.hasNext()){
System.out.printf("[地址:%s:端口号%d]断开连接",socket.getInetAddress().toString(),socket.getPort());
}
//从客户端接收数据
String resqust = scanner.next();
System.out.println("服务器接收数据");
//处理数据
String response = process(resqust);
System.out.println("服务器处理数据");
//因为这用流接收 我们按下换行是被scanner接收但并未添加到数据内 而服务器接收流收到的数据内
//是没有换行符的 就会一直阻塞 所以我们要在数据后再加个\n
//服务器发送数据
//printWriter.write(response+'\n');
//或者直接使用println自带\n
printWriter.println(response);
System.out.println("服务器发送数据");
//刷新缓冲区
printWriter.flush();
System.out.printf("[地址:%s:端口号:%d]接收数据:%s 响应数据:%s",socket.getInetAddress().toString(),socket.getPort(),resqust,response);
}
}
}
4.处理业务
这里是为了演示TCP连接,所以只写个简单回传
public String process(String s){
return s;
}
整体代码
package TestTCP;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//服务器
public class Test1 {
private ServerSocket serverSocket=null;
public Test1(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
//阻塞等待服务器与客户端建立连接
Socket socket=serverSocket.accept();
//若没线程 当一个客户端连接后 会一直在ProcessConnect内
//其他客户端连接时 必须要等待ProcessConnect结束,进入下一次循环,
// 才能执行到Socketsocket=serverSocket.accept(); 才能连接下一个客户端
// ProcessConnect(socket);
//所以执行处理客户端请求,我们让线程去干,这样就不会在一个线程内阻塞
// Thread thread=new Thread(()->{
// try {
// ProcessConnect(socket);
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// });
// thread.start();
//线程池
ExecutorService executorService= Executors.newCachedThreadPool();
executorService.submit(new Runnable() {
@Override
public void run() {
try {
ProcessConnect(socket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
//给当前客户端提供服务方法
public void ProcessConnect(Socket socket) throws IOException {
//实际他们交流数据是一个为socket类型的文件
System.out.printf("[地址:%s:端口号%d]建立连接成功\n",socket.getInetAddress().toString(),socket.getPort());
try (InputStream inputStream = socket.getInputStream();//获取socket内部的input流
OutputStream outputStream = socket.getOutputStream())//获取socket内部的output流
{
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
//长连接写法
while (true) {
if(!scanner.hasNext()){
System.out.printf("[地址:%s:端口号%d]断开连接",socket.getInetAddress().toString(),socket.getPort());
}
//从客户端接收数据
String resqust = scanner.next();
System.out.println("服务器接收数据");
//处理数据
String response = process(resqust);
System.out.println("服务器处理数据");
//因为这用流接收 我们按下换行是被scanner接收但并未添加到数据内 而服务器接收流收到的数据内
//是没有换行符的 就会一直阻塞 所以我们要在数据后再加个\n
//服务器发送数据
//printWriter.write(response+'\n');
//或者直接使用println自带\n
printWriter.println(response);
System.out.println("服务器发送数据");
//刷新缓冲区
printWriter.flush();
System.out.printf("[地址:%s:端口号:%d]接收数据:%s 响应数据:%s",socket.getInetAddress().toString(),socket.getPort(),resqust,response);
}
}
}
public String process(String s){
return s;
}
public static void main(String[] args) throws IOException {
Test1 t1=new Test1(8080);
t1.start();
}
}
客户端
1.创建Socket对象,并连接服务端的ip与端口号
private Socket socket=null;
public Test2() throws IOException {
socket=new Socket("127.0.0.1",8080);
2.获取Socket流对象,写入数据,阻塞等待服务端响应
public void start() throws IOException {
try(InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream())
{
while (true){
Scanner scanner=new Scanner(inputStream);
PrintWriter printWriter=new PrintWriter(outputStream);
Scanner scanner1=new Scanner(System.in);
System.out.println("输入数据>");
String requst=scanner1.next();
//因为这用流接收 我们按下换行是被scanner接收但并未添加到数据内 而服务器接收流收到的数据内
//是没有换行符的 就会一直阻塞 所以我们要在数据后再加个\n
//printWriter.write(requst+'\n');
//或者直接使用println自带\n
printWriter.println(requst);
//刷新缓冲区
printWriter.flush();
String response=scanner.next();
System.out.println(response);
}
}finally {
socket.close();
}
}
整体代码
package TestTCP;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
//客户端
public class Test2 {
private Socket socket=null;
public Test2() throws IOException {
socket=new Socket("127.0.0.1",8080);
}
public void start() throws IOException {
try(InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream())
{
while (true){
Scanner scanner=new Scanner(inputStream);
PrintWriter printWriter=new PrintWriter(outputStream);
Scanner scanner1=new Scanner(System.in);
System.out.println("输入数据>");
String requst=scanner1.next();
//因为这用流接收 我们按下换行是被scanner接收但并未添加到数据内 而服务器接收流收到的数据内
//是没有换行符的 就会一直阻塞 所以我们要在数据后再加个\n
//printWriter.write(requst+'\n');
//或者直接使用println自带\n
printWriter.println(requst);
//刷新缓冲区
printWriter.flush();
String response=scanner.next();
System.out.println(response);
}
}finally {
socket.close();
}
}
public static void main(String[] args) throws IOException {
Test2 t2=new Test2();
t2.start();
}
}
jconsole用来监控java线程
jconsole在路径 jdk/bin/jconsole.exe
例:
我们需要看util最后一局,可以看出线程是在next阻塞着。