目录
1.TCP流套接字:
使用传输层
TCP
协议,TCP,即
Transmission Control Protocol
(传输控制协议),传输层协议。
TCP
的特点(细节后续再学习):
- 有连接
- 可靠传输
- 面向字节流
- 有接收缓冲区,也有发送缓冲区
- 大小不限
对于字节流来说,可以简单的理解为,传输数据是基于IO
流,流式数据的特征就是在
IO
流没有关闭的情 况下,是无边界的数据,可以多次发送,也可以分开多次接收。
2.ServerSocket API
ServerSocket
是创建
TCP
服务端
Socket
的
API,
(1) ServerSocket 构造方法:
(2)ServerSocket普通方法
3.Socket API
Socket 是客户端
Socket
,或服务端中接收到客户端建立连接(
accept
方法)的请求后,返回的服务端 Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
(1)Socket 构造方法:
(2)Socket普通方法
4.1服务端代码
package network;
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.ExecutorService;
import java.util.concurrent.Executors;
public class TcpEchoServer {
private ServerSocket serverSocket = null;
public TcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
ExecutorService service = Executors.newCachedThreadPool();
while (true) {
// 通过 accept 方法, 把内核中已经建立好的连接拿到应用程序中.
// 建立连接的细节流程都是内核自动完成的. 应用程序只需要 "捡现成" 的.
Socket clientSocket = serverSocket.accept();
// processConnection(clientSocket);
// 此处不应该直接调用 processConnection, 会导致服务器不能处理多个客户端.
// 创建新的线程来调用更合理的做法.
// 这种做法可行, 不够好
// Thread t = new Thread(() -> {
// processConnection(clientSocket);
// });
// t.start();
// 更好一点的办法, 是使用线程池.
service.submit(new Runnable() {
@Override
public void run() {
processConnection(clientSocket);
}
});
}
}
// 通过这个方法, 来处理当前的连接.
public void processConnection(Socket clientSocket) {
// 进入方法, 先打印一个日志, 表示当前有客户端连上了.
System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
// 接下来进行数据的交互.
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
// 使用 try ( ) 方式, 避免后续用完了流对象, 忘记关闭.
// 由于客户端发来的数据, 可能是 "多条数据", 针对多条数据, 就循环的处理.
while (true) {
Scanner scanner = new Scanner(inputStream);
if (!scanner.hasNext()) {
// 连接断开了. 此时循环就应该结束
System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
break;
}
// 1. 读取请求并解析. 此处就以 next 来作为读取请求的方式. next 的规则是, 读到 "空白符" 就返回.
String request = scanner.next();
// 2. 根据请求, 计算响应.
String response = process(request);
// 3. 把响应写回到客户端.
// 可以把 String 转成字节数组, 写入到 OutputStream
// 也可以使用 PrintWriter 把 OutputStream 包裹一下, 来写入字符串.
PrintWriter printWriter = new PrintWriter(outputStream);
// 此处的 println 不是打印到控制台了, 而是写入到 outputStream 对应的流对象中, 也就是写入到 clientSocket 里面.
// 自然这个数据也就通过网络发送出去了. (发给当前这个连接的另外一端)
// 此处使用 println 带有 \n 也是为了后续 客户端这边 可以使用 scanner.next 来读取数据.
printWriter.println(response);
// 此处还要记得有个操作, 刷新缓冲区. 如果没有刷新操作, 可能数据仍然是在内存中, 没有被写入网卡.
printWriter.flush();
// 4. 打印一下这次请求交互过程的内容
System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
request, response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 在这个地方, 进行 clientSocket 的关闭.
// processConnection 就是在处理一个连接. 这个方法执行完毕, 这个连接也就处理完了.
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String process(String request) {
// 此处也是写的回显服务器. 响应和请求是一样的.
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
4.2客户端代码
package network;
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 TcpEchoClient {
private Socket socket = null;
public TcpEchoClient(String serverIp, int serverPort) throws IOException {
// 需要在创建 Socket 的同时, 和服务器 "建立连接", 此时就得告诉 Socket 服务器在哪里~~
// 具体建立连接的细节, 不需要咱们代码手动干预. 是内核自动负责的.
// 当我们 new 这个对象的时候, 操作系统内核, 就开始进行 三次握手 具体细节, 完成建立连接的过程了.
socket = new Socket(serverIp, serverPort);
}
public void start() {
// tcp 的客户端行为和 udp 的客户端差不多.
// 都是:
// 3. 从服务器读取响应.
// 4. 把响应显示到界面上.
Scanner scanner = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
PrintWriter writer = new PrintWriter(outputStream);
Scanner scannerNetwork = new Scanner(inputStream);
while (true) {
// 1. 从控制台读取用户输入的内容
System.out.print("-> ");
String request = scanner.next();
// 2. 把字符串作为请求, 发送给服务器
// 这里使用 println, 是为了让请求后面带上换行.
// 也就是和服务器读取请求, scanner.next 呼应
writer.println(request);
writer.flush();
// 3. 读取服务器返回的响应.
String response = scannerNetwork.next();
// 4. 在界面上显示内容了.
System.out.println(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
client.start();
}
}
4.3运行结果 (回显服务器)