TCP流套接字
ServerSocket API
ServerSocket
是创建TCP服务端Socket的API。ServerSocket
构造方法 :
方法签名 | 方法说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
ServerSocket
方法:
方法签名 | 方法说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待(接受客户端的连接) |
void close() | 关闭此套接字 |
Socket accept()
既会给服务器用,也会给客户端用~
Socket API
Socket
构造方法:
方法签名 | 方法说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接 |
构造方法里面制定了IP和端口,构造方法就是在尝试和指定的服务器建立连接
Socket
方法:
方法签名 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址(对方的ip地址和端口) |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
通过 socket
可以获取到 两个流对象,分别用来读和写~~TCP
是面向字节流的,所以去读写 TCPsocket
本质上 和 读写文件 是一样的~
????TcpEchoSever
代码实现TCP
服务器:
package TCP;
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 TcpEchoSever {
//代码中会设计多个 socket 对象,使用不同的名字来区分,使用listenSeverSocke来进行监听
public ServerSocket listenSeverSocket = null;
public TcpEchoSever(int port) throws IOException {
listenSeverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
ExecutorService service = Executors.newCachedThreadPool();
while (true) {
//1. 先调用 appect 来 接受客户端的连接
Socket clientSocket = listenSeverSocket.accept();
//2.处理这个连接
//使用线程池保证可以同时处理多个客户端
service.submit(new Runnable() {
@Override
public void run() {
processConnect(clientSocket);
}
});
}
}
public void processConnect(Socket clientSocket) throws IOException {
System.out.println("客户端上线!");
//接下来处理客户端的请求
//拿到输入流输出流对象
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
//客户端可能会发多条数据
while(true) {
//1.读取请求并解析
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()) {
//读完了,连接断开
System.out.printf("客户端读完了:[%s:%d]",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
//读取到请求
String request = scanner.next();
//2. 根据请求计算响应
String response = process(request);
//3.把响应写回给客户端
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//刷新缓冲区确保数据确实是通过网卡发送出去了
printWriter.flush();
//打印日志
System.out.printf("[客户端ip地址: %s 客户端端口号: %d],请求: %s , 回应: %s \n", clientSocket.getInetAddress().toString(),
clientSocket.getPort(),request,response);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
clientSocket.close();
}
}
public String process(String requst) {
return requst;
}
public static void main(String[] args) throws IOException {
TcpEchoSever tcpEchoSever = new TcpEchoSever(9000);
tcpEchoSever.start();
}
}
listenSeverSocket
和 clientSocket
的区别:
- ❓为什么要使用多线程?
如果不使用多线程:
如果没有客户端建立连接,服务器就会阻塞到accept
~如果有一个客户端过来了,此时就会进入到
processConnection
方法~
但是连接建立了,如果客户端还没发消息,此时代码就会阻塞在hasNext
…
就无法第二次调用到accept
,也就无法处理第二个客户端了!!
- ❓ 为什么要关闭
socket
? 而前面的listenSocket
以及udp
程序中的socket
为什么就没有close
?
socket
也是一个文件
一个进程能够同时打开的文件个数,有上限!!! => PCB文件描述符表,不是无限的,要是满了就不能继续打开文件
listenSeverSocket
是在TCP
服务器程序中只有唯一一个对象~ 就不会把文件描述符占满(随着进程的退出,自动释放)
clientSocket
是在循环中,每个客户端连上来都要分配一个~
这个对象就会被反复的创建出实例,每创建一个,都要消耗一个 文件描述符~因此就需要能够把不再使用的
clientSocket
及时释放掉~
- ❓为啥
UDP
没这个问题,而TCP
有这个问题呢?
原因是
TCP
使用长连接这种连接方式~UDP
客户端直接发消息即可~ 不必专注于处理某一个客户端~TCP
建立连接之后,要处理客户端的多次请求,才导致无法快速的调用到accept
(长连接,导致专注于处理某一个客户端,务必要使用多线程)
如果TCP
每个连接只处理一个客户端的请求,也能够保证快速调用到accept
(短链接)
????TcpEchoClient
package TCP;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
public Socket socket = null;
public TcpEchoClient(String severIP,int severPort) throws IOException {
//和服务器建立连接,需要知道服务器在哪
socket = new Socket(severIP,severPort);
}
public void start() throws FileNotFoundException {
Scanner scanner = new Scanner(System.in);
try(OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream()) {
while (true) {
//1.从控制台读取数据,构造成一个请求
System.out.print("->");
String requestStr = scanner.next();
//2.将请求发给服务器
PrintWriter requestPrintWriter = new PrintWriter(outputStream);
requestPrintWriter.println(requestStr);
//如果不flush,可能导致请求没有真发出去
requestPrintWriter.flush();
//3.从服务器读取Tcp响应数据
Scanner response = new Scanner(inputStream);
String responseStr = response.next();
//4.把响应显示到界面上
System.out.println(responseStr);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9000);
tcpEchoClient.start();
}
}
- ❓为什么Scanner 也不用关闭?
scannner
这种关闭本质上是关闭了里面包含的InputStream
,scanner
自身是不会打开文件描述符的,而InputStream
已经在try
里面了,try
运行完毕会自动关闭~