一、网络编程基础
为什么需要网络编程?
用户在浏览器中,打开在线视频网站,如优酷看视频,实质是通过网络,获取到网络上的一个视频资源。
与本地打开视频文件类似,只是视频文件这个资源的来源是网络。
相比本地资源来说,网络提供了更为丰富的网络资源:
所谓的网络资源,其实就是在网络中可以获取的各种数据资源。
而所有的网络资源,都是通过网络编程来进行数据传输的。
1.1、什么是网络编程
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)。
当然,我们只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程。
特殊的,对于开发来说,在条件有限的情况下,一般也都是在一个主机中运行多个进程来完成网络编程。
但是,我们一定要明确,我们的目的是提供网络上不同主机,基于网络来传输数据资源:
- 进程A:编程来获取网络资源
- 进程B:编程来提供网络资源
1.2、网络编程中的基本概念
发送端和接收端
在一次网络数据传输时:
- 发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
- 接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
- 收发端:发送端和接收端两端,也简称为收发端。
注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
请求和响应
一般来说,获取一个网络资源,涉及到两次网络数据传输:
- 第一次:请求数据的发送
- 第二次:响应数据的发送。
好比在快餐店点一份炒饭:
先要发起请求:点一份炒饭,再有快餐店提供的对应响应:提供一份炒饭
客户端和服务端
- 服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。
- 客户端:获取服务的一方进程,称为客户端。
对于服务来说,一般是提供:
客户端获取服务资源
客户端保存资源在服务端
好比在银行办事:
- 银行提供存款服务:用户(客户端)保存资源(现金)在银行(服务端)
- 银行提供取款服务:用户(客户端)获取服务端资源(银行替用户保管的现金)
常见的客户端服务端模型
最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:
- 客户端先发送请求到服务端
- 服务端根据请求数据,执行相应的业务处理
- 服务端返回响应:发送业务处理结果
- 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)
二、Socket套接字
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。
2.1、Socket套接字分类
Socket套接字主要针对传输层协议划分为如下三类:
- 1、流套接字:使用传输层TCP协议
TCP,即Transmission Control Protocol(传输控制协议),传输层协议。
以下为TCP的特点(细节后续再学习)
有连接
可靠传输
面向字节流
有接收缓冲区,也有发送缓冲区
大小不限
对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的情
况下,是无边界的数据,可以多次发送,也可以分开多次接收。
- 2、数据报套接字:使用传输层UDP协议
- UDP,即User Datagram Protocol(用户数据报协议),传输层协议。
以下为UDP的特点(细节后续再学习):
无连接
不可靠传输
面向数据报
有接收缓冲区,无发送缓冲区
大小受限:一次最多传输64k
对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一
次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节
- 3、原始套接字
原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。
我们不学习原始套接字,简单了解即可。
接下来先来了解TCP和UDP两种协议
2.2、TCP 与 UDP 的 区别
有连接 和 无连接
可以怎么去理解:
有链接:像打电话比如说:现在我们要打电话给某个朋友。
输入号码,按下手机拨号键。
手机开始发出 嘟嘟嘟 声音,开始等待对方接听,
而且,我们拨号之后,并不是马上就能接通的!
必须要等待 对方接听之后,我们才能与其交流。
之所以说:有链接 就像 打电话一样,是因为 打电话,必须要接通了之后,才能交流;没有接通,双方就无法交流。
有连接的意思:就是在两者确认建立联系后,就可以开始交互了。
无连接:发微信
不需要接通,直接就能发数据。
发微信,我们都知道:发送信息的时候,是不需要对方在线或者回复,按下回车,立马就能加个信息发送出去,不过 对方 看没看见这条消息,我们是不确定的 。
这种情况,就叫做无连接。
所以 TCP,就是要求双发先建立连接,连接好了,才能进行传数据。
而 UDP,直接传输数据,不需要双方建立连接。
可靠传输 和 不可靠传输
可靠传输:发送方 知道 接收方 有没有接收到数据
注意!不要理解错了。
可靠传输,不是说数据发送之后,对方100% 就能收到。
你代码写得再好,也刚不住挖掘机把你家网线挖断了。
网线都断了,你能把数据发出去才有鬼。
可靠传输,不是说传输数据百分百成功,关键还得看这里面是否能感知到 传输数据成功了。关于可靠传输,还有一种错误理解。
可靠传输,就是“安全传输”。这种说法也是一个典型的错误。
可靠 和 安全 是 两码事!!!!安全,指的是 数据在传输过程,不容易被黑客窃取,不容易被篡改。
可靠,指的是 数据发给对方,发送方能知道接收方有没有收到数据。
不可靠传输:发送方 不知道 接收方有没有接收到数据
总得来说:
可靠,就是我们对于自己发送的信息,心里有点数。
心里没底,就是不可靠。
面向字节流 与 面向数据报
面向字节流:数据是以字节为单位,进行传输的。
这个就非常类似于 文件操作中的文件内容相关的操作 中的字节流。
网络传输也是一样!
假设,现有100个字节的数据。
我们可以一直发完。
也可以 一次发 10个字节,发送十次。
也可以 一次发 2 个字节,发送50次。
…
面向数据报:
以数据报为单位,进行传输。一个数据报都会明确大小。
一次 发送/接收 必须是 一个 完整的数据报。
不能是半个,也不能是一个半,必须是整数个。
在代码中,这两者的区别是非常明显的!
全双工
全双工 对应的是 半双工
全双工:一条链路,双向通信。
举个例子:间谍
通常抓到一个间谍,都会对其进行拷问。
说:你的上级是谁?平时是怎么联系的?
间谍:我和他认识,知道彼此身份,并且有相互联系的方式。
他是xxx,联系方式xxxxxx。所以别再打我,作用不大,因为我都会说。
半双工:一条链路,单向通信。
举个例子:间谍
通常抓到一个间谍,都会对其进行拷问。
说:你的上级是谁?平时是怎么联系的?
间谍:我和上级是单向通信的,他联系到我,我联系不到他。所以别再打我,作用不大
TCP 和 UDP 都是全双工。
半双工理解即可。
三、UDP数据报套接字编程
3.1、DatagramSocket API
DatagramSocket API
DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。
DatagramSocket 构造方法
方法签名 | 方法说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端) |
DatagramSocket(intport) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端) |
DatagramSocket 方法
方法签名 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
3.2、DatagramPacket API
DatagramPacket 是 UDP Socket 发送和接收的数据报。
DatagramPacket 构造方法
方法签名 | 方法说明 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length) |
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) | 构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号 |
DatagramPacket 方法
方法签名 | 方法说明 |
---|---|
InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。
3.3、InetSocketAddress API
InetSocketAddress ( SocketAddress 的子类 )构造方法:
方法签名 | 方法说明 |
---|---|
InetSocketAddress(InetAddress addr, int port) | 创建一个Socket地址,包含IP地址和端口号 |
3.4、示例一:写一个最简单的客户端服务器程序【回显服务】
什么是回显服务?
回显服务 - Echo Server
简单来说,我说什么,你回什么。就像回声,重复着我们说过话。
回显服务,就是这样的。
我们发送什么样子的数据,它就给我们返回一个同样的数据。
也就是说:根据我们请求的内容数据,来返回一个具有相同数据的响应。
这样的程序属于最简单的网络编程中的程序。
因为不涉及到任何的业务逻辑,就只是通过 socket API 进行单纯的数据转发。
我通过这个程序,来向大家演示 API 的使用。
准备工作
服务器部分
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
/**
* Created with Intellij IDEA.
* Description:
* User: ryy
* Date: 2023-05-02
* Time: 16:28
*/
public class UdpEchoServer {
//进行网络编程,首先要准备 socket 实例
private DatagramSocket socket = null;
//port 是服务器的端口号
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
//启动服务器
public void start() throws IOException {
System.out.println("启动服务器!");
//UDP是不需要建立连接的,直接接收客户端发来的数据即可。
while (true){
//1、读取客户端发来的请求
//为了接受数据,我们需要先准备好一个空的DatagramPacket对象.
//然后,由receive 来填充数据。
DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
socket.receive(requestPacket);
//把 DatagramPacket 对象构造出一个字符串
String request = new String(requestPacket.getData(),0, requestPacket.getLength(),"UTF-8");
//2、根据请求计算响应(由于咱们这是一个回显服务,这一步就可以省略了)
String response = process(request);
//3、把响应写回客户端
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
// 打印具体 IP、端口、请求、响应
System.out.printf("[%s:%d] request: %s,response: %s\n",
requestPacket.getAddress().toString(),// 客户端IP
requestPacket.getPort(),// 客户端端口号
request,//请求
response);// 响应
}
}
//由于是 回显服务,响应和请求是一样的
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
运行效果
客户端部分
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
/**
* Created with Intellij IDEA.
* Description:
* User: ryy
* Date: 2023-05-02
* Time: 16:28
*/
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String ip, int port) throws SocketException {
//此处的 serverPort 是服务器的端口
// 服务器启动的时候,不需要 socket来指定窗口,客户端自己的端口是系统随机分配的。
socket = new DatagramSocket();
serverIP = ip;
serverPort = port;
}
//启动客户端
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
//1、先从控制台上读取用户输入的字符串
System.out.println("用户输入字符:");
String request = scanner.next();
//2、把用户输入的内容,构造成一个 UDP 并发送
// 构造的请求包括两个部分
// 1)数据的内容 -》 request
// 2)要发给谁,服务器的 IP + 端口号
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);
//3、从服务器读取响应数据,并解析
DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
socket.receive(responsePacket);
String response = new String(responsePacket.getData(),0,responsePacket.getLength(),"UTF-8");
//4、把解析的内容显示到控制台
System.out.printf("[%s:%d] request: %s,response: %s\n",
serverIP,// 服务器IP
serverPort,// 服务器端口
request,//请求
response);// 响应
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
客户端 和 服务器交互效果
这才是我们服务器正常的运行状态,同时处理多个客户端发送的请求。
再来写一个简单程序:就是上面代码的基础上,带上点业务逻辑
写一个翻译程序(英译汉)
请求是一些简单的英文单词。
响应是 英文单词 对应的 中文翻译。
客户端不变,把服务器代码进行调整。
主要是调整 process 方法。
其他步骤都是一样的。
关键的逻辑就是“根据想求来处理响应”
package network;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
/**
* Created with Intellij IDEA.
* Description:
* User: ryy
* Date: 2023-05-02
* Time: 22:02
*/
// 创建一个类 UdpDictionaryServer 来表示 字典服务器
// 因为代码的逻辑几乎是一样,且所有的办法都是public的
// 所以我们在这里就直接继承,就可以使用内部所有的方法,并且可以进行重写操作。
public class UdpDictionaryServer extends UdpEchoServer{
private Map<String,String> map = new HashMap<>();
public UdpDictionaryServer(int port) throws SocketException {
super(port);
map.put("dog","小狗");
map.put("cat","小猫");
map.put("duck","小鸭子");
}
@Override
public String process(String request) {
// 如果查询的单词在“词库”中存在,就返回其 键值对/对应的中文,
//反之,如果查询的单词在 “词库”中 不存在,返回 没有对应的词。
return map.getOrDefault(request,"该词没有翻译!");
}
public static void main(String[] args) throws IOException {
UdpDictionaryServer server = new UdpDictionaryServer(9090);
server.start();
}
}
代码运行结果
总结
一个服务器,最关键的逻辑就是“根据想求来处理响应”!
什么样的请求,得到什么样的响应。
这是我们一个服务器要完成的一个最最关键的事情。
通过这个东西,才能让我们的程序真正帮我们解决一些实际问题。
这一点,大家要体会我们 服务器-客户端 的交互过程。
之所以,网络编程 是一个 服务器-客户端的结构,是因为 有些工作,我们希望让服务器完成一些工作,既然要完成这样的工作,就得有输入(请求),也有输出(响应)。
从输入到输出,从请求到响应的这个过程,这就是服务器要完成的基本工作。
四、TCP流套接字编程
TCP 和 UDP 的差别很大!
在 TCP API 中,也是涉及到两个核心的类
4.1、ServerSocket API
4.1.1、ServerSocket 构造方法
ServerSocket 是创建TCP服务端Socket的API。
方法签名 | 方法说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
4.1.2、ServerSocket 方法
方法签名 | 方法说明 |
---|---|
Socketaccept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() | 关闭此套接字 |
4.3、Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
4.3.1、Socket 构造方法
方法签名 | 方法说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接 |
4.3.2、Socket 方法
方法签名 | 方法说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
4.4、回显服务器 - TCP版
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;
/**
* Created with Intellij IDEA.
* Description:
* User: ryy
* Date: 2023-05-02
* Time: 22:31
*/
public class TcpEchoServer {
// listen 的 中文意思是 监听
// 但是,在Java socket 中是体现不出来 “监听”的含义
// 之所以这么叫,其实是 操作系统原生的 API 里有一个操作叫做 listen
// 而 ServerSocket 确实起到了一个监听的效果
// 所以,取个 listenSocket 的名字
private ServerSocket listenSocket = null;
public TcpEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
//启动服务器
public void start() throws IOException {
System.out.println("启动服务器!");
while (true){
//注意,UDP 是无连接,所以一上来就发送数据报
//而TCP是有连接的,要先建立连接,连接好之后才能发送数据
//accept 就是用来建立连接的,如果没有客户端建立连接,那服务器就会阻塞
//accept 返回了一个Socket 对象,称为 clientSocket ,后续和客户端的沟通都是通过 clientSocket 来完成的
//换句话说,ServerSocket 就干了一件事, 建立连接
Socket clientSocket = listenSocket.accept();
//处理连接之后的客户端的请求
processConnection(clientSocket);
}
}
private void processConnection(Socket clientSocket) {
//先打印IP 和 端口号
System.out.printf("[%s : %d] 客户端建立连接!",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//接下来就是处理响应和请求
//此处 处理 TCP Socket 文件读写 和 普通文件的读写是一样的
try(InputStream inputStream = clientSocket.getInputStream()){
try (OutputStream outputStream = clientSocket.getOutputStream()){
//循环处理每个请求
Scanner scanner = new Scanner(inputStream);
while (true){
if(!scanner.hasNext()){
System.out.printf("[%s : %d] 客户端断开连接!",clientSocket.getInetAddress().toString()
,clientSocket.getPort());
break;
}
//此处使用 Scanner 更方便
// 如果不用 Scanner,而使用原生的 InputStream 的 read 也是可以的。
// 但是很麻烦!它需要构造一个 字节数组 来存储 read 读取的数据。
// read 还会返回字节的个数,如果为-1,即为没有后续数据了,读完了。
String request = scanner.next();
//2、根据请求 计算响应
String response = process(request);
//3、把响应返回给客户端
//为了方便 用 PrintWriter 把 outputStream 包裹一下
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.print(response);
//刷新缓冲区
printWriter.flush();
System.out.printf("[%s : %d] request -》 %s response -》 %s\n",clientSocket.getInetAddress().toString()
,clientSocket.getPort(),request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
//此处用来关闭文件
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
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;
/**
* Created with Intellij IDEA.
* Description:
* User: ryy
* Date: 2023-05-03
* Time: 21:36
*/
public class TcpEchoClient {
// 用普通的 Socket 即可,不用 ServerSocket 了
private Socket socket = null;
//此处也不用手动给客户端指定端口号,由系统自动分配(隐式)
public TcpEchoClient(String serverIP,int serverPort) throws IOException {
// 其实这里是可以给定端口号的,但是这里给了之后,含义是不同的。
// 这里传入的 IP 与 端口号 的 含义: 表示的不是自己绑定,而是表示 和 这个IP 端口 建立连接
// 这里表示 与 IP 为serverIP的主机上的 端口为9090的程序,建立连接。
socket = new Socket(serverIP,serverPort);
}
private void start(){
System.out.println("和服务器建立连接成功!");
Scanner scanner = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream()){
try (OutputStream outputStream = socket.getOutputStream()){
while (true){
//客户端还是4个步骤
//1、从控制台上输入字符串
System.out.println("请输入请求:");
String request = scanner.next();
//2、把字符串构造成请求,然后发送请求给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);
printWriter.flush();
//3、服务器响应请求,并解析
Scanner scaResponse = new Scanner(inputStream);
String response = scaResponse.next();
//4、将响应显示到控制台上
System.out.printf("request:%s,response:%s\n",request,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();
}
}
此是TCP版本的回显服务写完了,但是还会出现一些问题,UDP版本的可以多个客户端与服务器建立连接,但是TCP就不行,下面我们看一下出现的问题
多线程版本的回显服务器程序
客户端代码和上面一样,只需要改一下服务器里面的代码
当前的服务器,同一时刻只能处理一个客户端连接。
作为一个服务器应该给很多客户端提供服务,而这里只能处理一个客户端,这显然是不科学的。
public class TcpThreadEchoServer {
// listen 的 中文意思是 监听
// 但是,在Java socket 中是体现不出来 “监听”的含义
// 之所以这么叫,其实是 操作系统原生的 API 里有一个操作叫做 listen
// 而 ServerSocket 确实起到了一个监听的效果
// 所以,取个 listenSocket 的名字
private ServerSocket listenSocket = null;
public TcpThreadEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
//启动服务器
public void start() throws IOException {
System.out.println("启动服务器!");
while (true){
//注意,UDP 是无连接,所以一上来就发送数据报
//而TCP是有连接的,要先建立连接,连接好之后才能发送数据
//accept 就是用来建立连接的,如果没有客户端建立连接,那服务器就会阻塞
//accept 返回了一个Socket 对象,称为 clientSocket ,后续和客户端的沟通都是通过 clientSocket 来完成的
//换句话说,ServerSocket 就干了一件事, 建立连接
Socket clientSocket = listenSocket.accept();
//处理连接之后的客户端的请求
//改进方法 - 就是在客户端建立连接的时候,创建一个线程执行这里的 processConnection 方法
Thread t = new Thread(() -> {
processConnection(clientSocket);
});
t.start();
}
}
private void processConnection(Socket clientSocket) {
//先打印IP 和 端口号
System.out.printf("[%s : %d] 客户端建立连接!\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
//接下来就是处理响应和请求
//此处 处理 TCP Socket 文件读写 和 普通文件的读写是一样的
try(InputStream inputStream = clientSocket.getInputStream()){
try (OutputStream outputStream = clientSocket.getOutputStream()){
//循环处理每个请求
Scanner scanner = new Scanner(inputStream);
while (true){
if(!scanner.hasNext()){
System.out.printf("[%s : %d] 客户端断开连接!\n",clientSocket.getInetAddress().toString()
,clientSocket.getPort());
break;
}
//此处使用 Scanner 更方便
// 如果不用 Scanner,而使用原生的 InputStream 的 read 也是可以的。
// 但是很麻烦!它需要构造一个 字节数组 来存储 read 读取的数据。
// read 还会返回字节的个数,如果为-1,即为没有后续数据了,读完了。
String request = scanner.next();
//2、根据请求 计算响应
String response = process(request);
//3、把响应返回给客户端
//为了方便 用 PrintWriter 把 outputStream 包裹一下
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//刷新缓冲区
printWriter.flush();
System.out.printf("[%s : %d] request -》 %s response -》 %s\n",clientSocket.getInetAddress().toString()
,clientSocket.getPort(),request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
//此处用来关闭文件
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadEchoServer server = new TcpThreadEchoServer(9090);
server.start();
}
}
效果图