一.概述
计算机网络是指两台或更多计算机组成的网络,在同一网络中,任意两台计算机都可以直接通信,因为所有计算机都需要遵循同一协议。
互联网是网络的网络,即把很多计算机网络连接起来,形成一个全球统的网络。如果计算机网络各自的通讯协议不同,就无法将两个不同的网络连接起来形成互联网。因此,为了把计算机网络接入互联网,就必须使用TCP/IP协议。只有使用TCP/IP地计算机才能联入互联网,使用其他网络协议是无法联入互联网的。
定义: IP协议是一个分组交换协议,他不保证可靠运输,而TCP协议是传输控制协议,它是面向连接的协议,支持可靠传输和双向通信。TCP协议是建立在IP协议之上。
IP协议值负责发数据包,不保证顺序和正确性,而TCP协议复制控制数据包传输:在传输数据之前还需要建立连接,建立连接之后才能传输数据,传输完后还需要断开连接。
TCP之所以能够保证数据的可靠传输,是通过接受确认,超时重传这些机制实现的。而且TCP协议允许双向通信,即通信双方可同时发送和接受数据。
二.TCP协议
1.什么是Socket?
Socket是一个抽象概念,一个应用程序通过一个Socket来建立一个远程连接,而Socket内部通过一个TCP/IP协议把数据传输到网络。
Java提供的几个Socket相关的类封装了操作系统提供的接口:ServerSocket类,Socket类。
> 网络通信的过程:使用Socket进行网络编程时,本质就是两个进程之间的网络通信。其中一个进程必须充当服务器端,它会主动监听某个指定的端口,另一个进程充当客户端,它必须主动连接客户端的IP地址和指定端口,如果连接成功,服务器端何客户端就成功建立了连接,双方就可以随时发送和接收数据。
当Socket成功建立起服务器端和客户端的连接后:
·对服务器端来说,它的Socket是指定的IP地址和指定的端口号;
·对客户端来说。它的Socket是它所在计算机的一个IP地址和一个由操作系统分配的随机端口号。
2. 服务器端
Java提供了ServerSocket来实现对指定IP和指定端口的监听。
ServerSocket ss = new ServerSocket(6666);
过程:
如果ServerSocket监听成功,我们就使用一个无限循环来不断接收客户端的连接,ss.accept()表示每当有新的用户端连接,就会返回一个Socket实例,这个Socket实例就是用来和刚连接的客户端进行通信的。
如果没有客户端连接进来,accept()方法就会阻塞并一直等待。如果有多个客户端同时连接进来,ServerSocket会把连接扔到队列里,一个一个处理。对于Java程序而言,只需要通过while()循环不断调用 ServerSocket 的accept()就可以获取新的连接。
public class Server {
public static void main(String[] args) throws IOException {
// 监听指定端口
ServerSocket ss = new ServerSocket(6666);
System.out.println("server is running...");
// 无限循环:不断接受客户端的连接
while (true) {
// 服务器进入等待状态
// 当有客户端连接时,该方法返回客户端的Socket
Socket sock = ss.accept();
// 使用Socket流进行网络通信
// ...
System.out.println("connected from " + sock.getRemoteSocketAddress());
}
}
}
3.客户端
客户端通过 Socket sock = new Socket(“localhost”, 6666); 连接到指定服务器和端口。如果连接成功,将返回一个Soocket实例,用于后续通信。
public class Client {
public static void main(String[] args) throws IOException {
// 连接指定服务器和端口
Socket sock = new Socket("localhost", 6666);
// 使用Socket流进行网络通信
// ...
// 关闭
sock.close();
System.out.println("disconnected.");
}
}
4.Socket流
当Socket连接创建成功后,无论是服务器端还是客户端,我们都使用Socket实例进行网络通信。TCP是一种基于流的协议,因此使用InputStreanm和OutputStream来封装Socket的数据流,和普通IO流类似:
// 用于读取网络数据:
InputStream in = sock.getInputStream();
// 用于写入网络数据:
// 本地图片读取 ==> 通过输出流发送至服务器
// OutputStream:将读取到的本地图片流,发送至服务器
OutputStream out = sock.getOutputStream();
注意:写入网络时,必须调用flush()方法。如果不调用flush(),客户端和服务器都收不到数据,因为我们以流的形式写入数据时,并不是一写入就立刻发送到网络,而是先存入内存缓冲区,直到缓冲区满了以后,才会一次性真正发送到网络,可提高传输效率。如果缓冲区的数据很少,而我们又想强制把这些数据发送到网络,就必须调用flush()强制把缓冲区数据发送出去。
5.这是一个模拟对话的客户端和服务器端交流的代码的实现片段;
public class ChatClient {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// 客户端
while(true) {
// 创建Socket,连接服务器
try (
Socket client = new Socket("172.20.10.2",9966);
BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
) {
// 获取控制台的输入
String question = input.nextLine();
if(question.equals("over")) {
break;
}
// 发送问题至服务器
writer.write(question);
writer.flush();
// 暂时结束本次输出
client.shutdownOutput();
// 获取来自服务器的答案
String answer = reader.readLine();
System.out.println("【客户端】来自服务器的答案:" + answer);
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("Game over");
}
}
public class ChatServer {
public static void main(String[] args) {
Map<String, String> chatMap = new HashMap<String, String>(){
{
put("你好", "你好呀");
put("hi", "hi~");
put("hello", "哈喽");
put("吃了吗", "没呢,你呢");
put("孤勇者", "爱你孤身走暗巷");
put("有请潘周聃", "潘周聃,今年29岁,苏黎世理工大学.....");
put("很高兴认识你", "我也是哦");
}
};
// 服务器端
try (ServerSocket server = new ServerSocket(9966)) {
while(true) {
// 发生客户端连接
Socket client = server.accept();
// 获取该客户端的ip地址
String ipName = client.getInetAddress().getHostAddress();
//System.out.println("客户端" + ipName + "开始连接...");
try(BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));){
// 获取该客户端的提问
String question = reader.readLine();
System.out.println("【服务器】来自客户端" + ipName + "的提问:" + question);
// 获取该问题的答案
String answer = chatMap .get(question);
answer = answer == null ? "我不知道你在说什么" : answer;
// 将答案发送至客户端
writer.write(answer);
writer.flush();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
【运行结果】
在这里插入图片描述