目录
一、ServerSocket API
1.1、ServerSocket构造方法
1.2、ServerSocket方法
二、Socket API
2.1、socket构造方法
2.2、socket方法
三、TCP 中的长短连接
四、示例
实现聊天室功能
五、存在的问题
一、ServerSocket API
ServerSocket 是创建TCP服务端Socket的API。
1.1、ServerSocket构造方法
方法签名 | 方法说明 |
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
1.2、ServerSocket方法
方法签名 | 方法说明 |
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户连接之后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
二、Socket API
Socket是客户端Socket,或服务端中接收到客户端建立连接(accpet方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,以及用来与对方收发数据的。
2.1、socket构造方法
方法签名 | 方法说明 |
Socket(String host,int port) | 创建一个客户端流套接字Socket,并与对应IP主机上,对应的端口的进程建立连接。 |
2.2、socket方法
方法签名 | 方法说明 |
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
三、TCP 中的长短连接
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接。
- 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说短连接只能一次收发数据
- 长连接:不关闭连接,一致保持连接状态,双方不停的收发数据,即是长连接。也就是说长连接能够多次收发数据。
对比以上长短连接,两者区别如下:
- 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要建立一次连接即可,之后的请求、响应都可以直接传输。相对于说建立连接、关闭连接也是需要耗时的,长连接效率比较高。
- 主动发送请求不同:短连接一般是客户端主动向服务端发起请求;而长连接不论是客户端还是服务端都可以向对方发起请求。
- 两者使用的场景不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接使用于客户端与服务端通信频繁的场景,如聊天室等。
四、示例
实现聊天室功能
- 客户端先接收键盘输入,循环接收客户端输入内容,发起聊天。
- 发送请求:用户输入的数据封装为输出流,发送到服务端 。
- 服务端接收并处理请求:接收请求数据,根据该请求数据来计算响应。
- 服务端返回响应:将响应数据封装为输出流,返回给客户端。
- 客户端接收响应:简单的打印输出所有的响应内容。
1、基于TCP的客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* 基于TCP的客户端
*/
public class TCPEchClient {
//生命Socket对象
public Socket socket;
//通过构造方法来初始化Socket,指定IP地址,端口
public TCPEchClient(String ServerIp, int port) throws IOException {
//根据服务器的IP与端口号创建Socket对象
this.socket=new Socket(ServerIp,port);
}
public void start(){
System.out.println("客户端已启动...");
try {
InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream();
//循环接收用户的输入
while(true){
System.out.print("->");
Scanner sc=new Scanner(System.in);
String request=sc.nextLine();
if(request==null||request.equals("")){
System.out.println("输入内容不能为空!!!");
continue;
}
//2、把用户的输入封装到输出流中,可以用PrintWriter简化操作
PrintWriter writer=new PrintWriter(outputStream);
//3、把数据发送出去(写数据)
writer.println(request);
//强制刷新缓冲区
writer.flush();
//4、接收服务器的响应
Scanner responseSc=new Scanner(inputStream);
//5、解析相应的数据
String response=responseSc.nextLine();
//6、打印日志
System.out.printf("request=%s,response=%s\n",request,response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
//初始化客户端
public static void main(String[] args) throws IOException {
TCPEchClient tcpEchClient=new TCPEchClient("127.0.0.1",9090);
tcpEchClient.start();
}
}
2、基于TCP的服务端
import tcp.TCPEchoSever;
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;
/**
* 基于TCP的服务端
*/
public class TCPEchServer {
//声明一个服务端的类
public ServerSocket socket;
//构造方法,初始化对象
public TCPEchServer(int port) throws IOException {
//校验端口
if(port<1||port>65535){
throw new RuntimeException("端口号建议在1025~65535之间");
}
//根据指定的端口号初始化Socket服务
this.socket=new ServerSocket();
}
public void start() throws IOException {
System.out.println("服务已启动等待客户端连接...");
//循环接收服务器连接
while(true){
//接收客户端请求
Socket clientSocket=socket.accept();
//接收到客户端连接之后,交给专门处理连接的方法。
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s %d]客户端已上线.\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
//所有的通信数据都在Socket对象中clientSocket,Tcp传输的数据是字节流的形式
//1、获取输入输出流
try {
InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream=clientSocket.getOutputStream();
//循环处理客户端发来的请求
while(true){
//Scanner简化字符串的获取
Scanner sc=new Scanner(inputStream);
//判断是否还有数据
if(!sc.hasNextLine()){
System.out.printf("[%s:%d] 客户端已下线.\n",
clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
//获取用户发来的请求
String request=sc.nextLine();
String response=process(request);
//把响应返回给客户端
PrintWriter writer=new PrintWriter(outputStream);
//强制刷新缓冲区
writer.flush();
System.out.printf("[%s:%d]request=%s,response=%d\n",clientSocket.getInetAddress().toString()
,clientSocket.getPort(),request,response);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
//关闭客户端连接
clientSocket.close();
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TCPEchoSever tcpEchoSever=new TCPEchoSever(9090);
tcpEchoSever.start();
}
}
3、封装响应数据
import java.io.IOException;
import java.util.Scanner;
public class TCPMsgServer extends TCPEchoSever {
public TCPMsgServer(int port) throws IOException {
super(port);
}
//实现一个聊天的功能
@Override
protected String process(String request) {
//1.打印发送方发来的消息
System.out.println("->"+request.toString());
//2.回复对方的消息
Scanner sc=new Scanner(System.in);
String response=sc.nextLine();
//3.返回消息
return response;
}
public static void main(String[] args) throws IOException {
TCPMsgServer tcpMsgServer=new TCPMsgServer(9090);
tcpMsgServer.start();
}
}
运行结果:
五、存在的问题
1、首先将客户端类对象设置为多实例创建状态,这样就可以创建出多个客户端对象
2、创建第二个客户端时,并没有按预想的连接服务端成功
3、出现这个现象的原因
4、如何去解决这个问题,实现多个客户端呢?这时就需要引入多线程来执行任务。
利用线程来执行不同的客户端任务,就需要创建一个线程服务端类,该类继承服务端类,重写start()方法。
import tcp.TCPEchoSever;
import java.io.IOException;
import java.net.Socket;
public class TCPThreadServer extends TCPEchoSever {
public TCPThreadServer(int port) throws IOException {
super(port);
}
@Override
public void start() throws IOException {
System.out.println("服务已启动,等待客户端连接.....");
//循环接收服务器的连接
while(true){
//接收客户端请求
Socket clientSocket = socket.accept();
//为每一个连接创建线程
Thread thread=new Thread(() ->{
//接收到客户端连接之后,交给专门处理连接的方法
try {
//在子线程中处理连接
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
});
//启动线程
thread.start();
}
}
public static void main(String[] args) throws IOException {
TCPThreadServer tcpThreadServer=new TCPThreadServer(9090);
tcpThreadServer.start();
}
}
运行结果:
5、手动创建线程太过麻烦,可以利用线程池来进行创建线程 ,还是跟上述操作差不过,重写start()方法
import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TCPThreadPoolServer extends TCPEchServer {
public TCPThreadPoolServer(int port) throws IOException {
super(port);
}
@Override
public void start() throws IOException {
System.out.println("服务已启动,等待客户端上线...");
//创建一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3,
10,
1,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10)); //阻塞队列可存10个服务器
//循环接收连接
while (true) {
//接收客户端请求
Socket clientSocket = socket.accept();
//把处理请求连接的操作,加入到线程池中
threadPool.submit(() -> {
try {
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
public static void main(String[] args) throws IOException {
TCPThreadPoolServer server=new TCPThreadPoolServer(9090);
server.start();
}
}