网络编程套接字,是操作系统给应用程序提供的一组API。
这组API,叫做 socket API。
socket 可以视为是应用层 和 传输层 的 桥梁.应用层 和 传输层 之间,交互数据就是靠的 socket API。
在上篇网络初识博文中,我们在讲封装的时候,就提到过 传输层的协议有两种 TCP 和 UDP 。
故,socket API 也有对应的两组。由于我们的 TCP 和 UDP 协议,差别非常很大。
因此,这两组 API 差别也很大。
在正式介绍 这些 API 之前,我们先来了解 TCP 与 UDP 的 区别。
可以怎么去理解:
有链接:像打电话比如说:现在我们要打电话给某个朋友。
输入号码,按下手机拨号键。
手机开始发出 嘟嘟嘟 声音,开始等待对方接听,
而且,我们拨号之后,并不是马上就能接通的!
必须要等待 对方接听之后,我们才能与其交流。
之所以说:有链接 就像 打电话一样,是因为 打电话,必须要接通了之后,才能交流;没有接通,双方就无法交流。
有连接的意思:就是在两者确认建立联系后,就可以开始交互了。
无连接:发微信
不需要接通,直接就能发数据。
发微信,我们都知道:发送信息的时候,是不需要对方在线或者回复,按下回车,立马就能加个信息发送出去,不过 对方 看没看见这条消息,我们是不确定的 。
这种情况,就叫做无连接。
所以 TCP,就是要求双发先建立连接,连接好了,才能进行传数据。
而 UDP,直接传输数据,不需要双方建立连接。
可靠传输:发送方 知道 接收方 有没有接收到数据
注意!不要理解错了。
可靠传输,不是说数据发送之后,对方100% 就能收到。
你代码写得再好,也刚不住挖掘机把你家网线挖断了。
网线都断了,你能把数据发出去才有鬼。
可靠传输,不是说传输数据百分百成功,关键还得看这里面是否能感知到 传输数据成功了。
关于可靠传输,还有一种错误理解。
可靠传输,就是“安全传输”。这种说法也是一个典型的错误。
可靠 和 安全 是 两码事!!!!
安全,指的是 数据在传输过程,不容易被黑客窃取,不容易被篡改。
可靠,指的是 数据发给对方,发送方能知道接收方有没有收到数据。
总得来说:
可靠,就是我们对于自己发送的信息,心里有点数。
心里没底,就是不可靠。
面向字节流:数据是以字节为单位,进行传输的。
这个就非常类似于 文件操作中的文件内容相关的操作中的字节流。
网络传输也是一样!
假设,现有100个字节的数据。
我们可以一直发完。
也可以 一次发 10个字节,发送十次。
也可以 一次发 2 个字节,发送50次。
…
面向数据报:
以数据报为单位,进行传输。一个数据报都会明确大小。
一次 发送/接收 必须是 一个 完整的数据报。
不能是半个,也不能是一个半,必须是整数个。
在代码中,这两者的区别是非常明显的!
全双工 对应的是 半双工。
全双工:一条链路,双向通信。
举个例子:间谍
通常抓到一个间谍,都会对其进行拷问。
说:你的上级是谁?平时是怎么联系的?
间谍:我和他认识,知道彼此身份,并且有相互联系的方式。
他是xxx,联系方式xxxxxx。所以别再打我,作用不大,因为我都会说。
半双工:一条链路,单向通信。
举个例子:间谍
通常抓到一个间谍,都会对其进行拷问。
说:你的上级是谁?平时是怎么联系的?
间谍:我和上级是单向通信的,他联系到我,我联系不到他。所以别再打我,作用不大。
TCP 和 UDP 都是全双工。
半双工理解即可。
以上,是 TCP 和 UDP 直观上的区别。
细节上海域很多很多的东西。
这个后面的博文中都会讲到。
【就是说:网络这一块,你们需要连接看,这几篇博客耦合性极强!】
UDP socket 比 TCP 更简单。
我们先从简单的开始。
1、DatagramSocket(数据报套接字)
2、DatagramPacket(数据报 的数据包)TCP 和 UDP 协议中,只有 UDP 是面向数据报的。
那么 DatagramScoket 和 DatagramPacket 这两类,从名字就能看出来(Datagram-数据报),是关于UDP协议的类。
方法签名 | 方法说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacketp) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
什么是回显服务?
回显服务 - Echo Server简单来说,我说什么,你回什么。
就像回声,重复着我们说过话。
回显服务,就是这样的。
我们发送什么样子的数据,它就给我们返回一个同样的数据。
也就是说:根据我们请求的内容数据,来返回一个具有相同数据的响应。
这样的程序属于最简单的网络编程中的程序。
因为不涉及到任何的业务逻辑,就只是通过 socket API 进行单纯的数据转发。
我通过这个程序,来向大家演示 API 的使用。
准备工作,在项目中创建一个 package 包。
在所创建的包底下,创建两个类,分别是 UdpEchoServer(udp 回显服务器),UdpEchoClient(udp 回显客户端)
回到,服务器类这一边,下面开始实现了。
需要注意的是:端口号可以是自己创建的,也可以是系统分配的。
当前这个写法,就属于自己分配的。
至于系统分配的,到后面也会跟你们讲的。
知识点:关于服务器 和 客户端的定义,在MySQL的 初步认识中 的 细谈MySQL 中讲了。
知识点:文件操作:输出型参数 在 文件内容相关的操作中讲到了。
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//“五元组”
//站在服务器的角度:
//源IP:服务器程序所在主机的IP
//源端口:手动指定的端口【服务器绑定的端口】
//目的IP:包含在收到的数据报中【客户端的IP】
//目的端口:包含在收到的数据报中【客户端的端口】
//协议类型:UDP
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 datagramPacket= new DatagramPacket(new byte[1024],1024);
socket.receive(datagramPacket);//为了接收数据,需要先准备好一个空的DatagramPacket对象,有receive来填充数据
// 将 datagramPacket 解析成一个 String
String request = new String(datagramPacket.getData(),0,datagramPacket.getLength(),"UTF-8");
//2、根据请求计算响应(由于咱们这是一个回显服务,这一步就可以省略了)
String response = process(request);
//3、把响应写回客户端
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
datagramPacket.getSocketAddress());
socket.send(responsePacket);
// 打印具体 IP、端口、请求、响应
System.out.printf("[%s:%d] request: %s,response: %s\n",
datagramPacket.getAddress().toString(),// 客户端IP
datagramPacket.getPort(),// 客户端端口号
request,//请求
response);// 响应
}
}
// 由于是回显服务,所以响应就和请求一样
//但是实际上,对于一个真实的服务器来说,这个过程(根据请求计算响应),是最复杂的!
// 为了实现这个过程,可能需要几万行,甚至几十万行代码。。。
private static 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;
//站在客户端的角度:
//源IP:本机IP
//源端口:系统分配的端口
//目的IP:服务器IP
//目的端口:服务器的端口
//协议类型:UDP
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
//此处的 serverPort 是服务器的端口
// 服务器启动的时候,不需要 socket来指定窗口,客户端自己的端口是系统随机分配的。
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
// 启动客户端
public void start() throws IOException {
Scanner sc = new Scanner(System.in);
while(true){
//1、先从控制台读取用户输入的字符串
System.out.println("->");
String request = sc.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 {
//由于服务器 和 客户端 在同一个机器上,所以使用的 IP,仍然是 127.0.0.1,如果是在不同的机器上,这里IP就需要更改了。
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
这才是我们服务器正常的运行状态,同时处理多个客户端发送的请求。
拓展:
通常情况下,一个服务器,是要同时给多个客户端提供服务的。
但是也有特殊情况下,一个服务器只给一个客户端提供服务。典型例子就是在分布式系统中,两个节点之间的交互。
一个节点视为服务器
另一个及诶单视为客户端
这种就属于是一个专属的情况,它不能给其他人随便乱请求。
给大家讲一个真实案例
清华大学知道吧?
娃哈哈知道吧?
娃哈哈老板的女儿当时就在清华大学读书。
为了让自己的女儿吃上家乡菜,专门就在清华门口开了个饭店。。。
这才是壕无人性的 一对一服务。
当然现在其他人也可以吃。
写一个翻译程序(英译汉)
请求是一些简单的英文单词。
响应是 英文单词 对应的 中文翻译。
客户端不变,把服务器代码进行调整。
主要是调整 process 方法。
其他步骤都是一样的。
关键的逻辑就是“根据想求来处理响应”
知识点:多态,继承
import network.UdpEchoServer;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
// 创建一个类 UdpDictionaryServer 来表示 字典服务器
// 因为代码的逻辑几乎是一样,且所有的办法都是public的
// 所以我们在这里就直接继承,就可以使用内部所有的方法,并且可以进行重写操作。
public class UdpDictionaryServer extends UdpEchoServer {
HashMap<String,String> map = new HashMap<>();//利用HashMap 来构建词库
public UdpDictionaryServer(int port) throws SocketException {
super(port);
map.put("cat","小猫");
map.put("dog","小狗");
map.put("pig","佩奇");
}
@Override
public String process(String request) {
// 如果查询的单词在“词库”中存在,就返回其 键值对/对应的中文,
//反之,如果查询的单词在 “词库”中 不存在,返回 没有对应的词。
return map.getOrDefault(request,"没有对应的词义");
}
public static void main(String[] args) throws IOException {
UdpDictionaryServer dictionaryServer = new UdpDictionaryServer(9090);
dictionaryServer.start();
}
}
一个服务器,最关键的逻辑就是“根据想求来处理响应”!
什么样的请求,得到什么样的响应。
这是我们一个服务器要完成的一个最最关键的事情。
通过这个东西,才能让我们的程序真正帮我们解决一些实际问题。
这一点,大家要体会我们 服务器-客户端 的交互过程。
之所以,网络编程 是一个 服务器-客户端的结构,是因为 有些工作,我们希望让服务器完成一些工作,既然要完成这样的工作,就得有输入(请求),也有输出(响应)。
从输入到输出,从请求到响应的这个过程,这就是服务器要完成的基本工作。MySQL 也是 服务器-客户端,这样的结构。
输入/请求:SQL语句
输出/响应:可能是一个临时表,可能是 返回一个影响的行数、
再比如:打开一个网页,输入/请求 一个 奥特曼
这个和前面是TCP是一样,就不讲了。
package network;
import java.io.IOException;
import java.net.ServerSocket;
public class TcpEchoServer {
// listen 的 中文意思是 监听
// 但是,在Java socket 中是体现不出来 “监听”的含义
// 之所以这么叫,其实是 操作系统原生的 API 里有一个操作叫做 listen
// 而 ServerSocket 确实起到了一个监听的效果
// 所以,取个 listenSocket 的名字
private ServerSocket listenSocket = null;
public TcpEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
}
知识点:文件操作
知识点:多线程中讲过进程是系统分配资源的单位,我忘记在那张图里讲过了。。
你们直接把 多线程的文章都看了吧。
package network;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
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){
//由于 TCP 是有连接的、因此,不能一上来就读取数据,需要先建立连接
// accept 就是在“接电话”,接电话的前提是:有人给你打电话【有客户端发送请求】
Socket clientSocket = listenSocket.accept();
processConnection(clientSocket);// 处理连接成功的客户端请求
}
}
private void processConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s,%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),// 获取客户端IP地址
clientSocket.getPort());//获取客户端端口
//接下来,就可以来处理请求 和 响应
// 这里的针对 TCP socket 的读写 和 文件操作 的读取一模一样!
try(InputStream inputStream = clientSocket.getInputStream()){
try(OutputStream outputStream = clientSocket.getOutputStream()){
Scanner sc = new Scanner(inputStream);
// 循环处理每个请求,分别返回响应
while(true){
//1、读取请求
// 如果没有下一个结果,直接结束循环。
if(!sc.hasNext()){
System.out.printf("[%s:%d] 客户端断开连接!\n",clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
//此处使用 Scanner 更方便
// 如果不用 Scanner,而使用原生的 InputStream 的 read 也是可以的。
// 但是很麻烦!它需要构造一个 字节数组 来存储 read 读取的数据。
// read 还会返回字节的个数,如果为-1,即为没有后续数据了,读完了。
String request = sc.next();
//2、根据请求,计算响应
String response = process(request);
//3、将响应返回给客户端
//为了方便起见,可以使用 PrintWriter 把 OutputStream 包裹一下
PrintWriter printWriter =new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();// 刷新缓冲区。
// 如果没有这个 flush,可能 客户端就不能第一时间看到响应的结果
System.out.printf("[%s:%d] request:%s,response:%s\n",clientSocket.getInetAddress(),// IP
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 {
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
}
}
public void start(){
System.out.println("和进服务器连接成功!");
Scanner sc = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream()){
try (OutputStream outputStream = socket.getOutputStream()){
while(true){
//1、从控制台读取字符串
System.out.println("->");
String request = sc.next();
//2、根据读取的自妇产,构造请求,把请求发送给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);// 看似是一个输出语句,其实已经将数据写到服务器里面去了
printWriter.flush();// 记得 立即刷新缓冲区,确保 服务器 第一时间 感知到 请求。
//3、从服务器读取响应,并解析
Scanner scanner = new Scanner(inputStream);
String response = scanner.next();
//4、把结果显示到控制台上。
System.out.printf("request:%s,response:%s\n ",request,response);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
package network;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
// 用普通的 Socket 即可,不用 ServerSocket 了
private Socket socket = null;
//此处也不用手动给客户端指定端口号,由系统自动分配(隐式)
public TcpEchoClient(String serverIP,int serverPort) throws IOException {
// 其实这里是可以给定端口号的,但是这里给了之后,含义是不同的。
// 这里传入的 IP 与 端口号 的 含义: 表示的不是自己绑定,而是表示 和 这个IP 端口 建立连接
socket = new Socket(serverIP,serverPort);// 这里表示 与 IP 为serverIP的主机上的 端口为9090的程序,建立连接。
}
public void start(){
System.out.println("和进服务器连接成功!");
Scanner sc = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream()){
try (OutputStream outputStream = socket.getOutputStream()){
while(true){
//1、从控制台读取字符串
System.out.println("->");
String request = sc.next();
//2、根据读取的自妇产,构造请求,把请求发送给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);// 看似是一个输出语句,其实已经将数据写到服务器里面去了
printWriter.flush();// 记得 立即刷新缓冲区,确保 服务器 第一时间 感知到 请求。
//3、从服务器读取响应,并解析
Scanner scanner = new Scanner(inputStream);
String response = scanner.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代码已将跑起来了还是此处还存在一个很严重的问题!!!!
当前的服务器,同一时刻只能处理一个客户端连接。
作为一个服务器应该给很多客户端提供服务,而这里只能处理一个客户端,这显然是不科学的。
知识点:多线程基础篇
package network;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
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){
//由于 TCP 是有连接的、因此,不能一上来就读取数据,需要先建立连接
// accept 就是在“接电话”,接电话的前提是:有人给你打电话【有客户端发送请求】
Socket clientSocket = listenSocket.accept();
Thread t = new Thread(()->{
try {
processThreadConnection(clientSocket);// 处理连接成功的客户端请求
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
}
}
private void processThreadConnection(Socket clientSocket) throws IOException {
System.out.printf("[%s,%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),// 获取客户端IP地址
clientSocket.getPort());//获取客户端端口
//接下来,就可以来处理请求 和 响应
// 这里的针对 TCP socket 的读写 和 文件操作 的读取一模一样!
try(InputStream inputStream = clientSocket.getInputStream()){
try(OutputStream outputStream = clientSocket.getOutputStream()){
Scanner sc = new Scanner(inputStream);
// 循环处理每个请求,分别返回响应
while(true){
//1、读取请求
// 如果没有下一个结果,直接结束循环。
if(!sc.hasNext()){
System.out.printf("[%s:%d] 客户端断开连接!\n",
clientSocket.getInetAddress().toString(),
clientSocket.getPort());
break;
}
//此处使用 Scanner 更方便
// 如果不用 Scanner,而使用原生的 InputStream 的 read 也是可以的。
// 但是很麻烦!它需要构造一个 字节数组 来存储 read 读取的数据。
// read 还会返回字节的个数,如果为-1,即为没有后续数据了,读完了。
String request = sc.next();
//2、根据请求,计算响应
String response = process(request);
//3、将响应返回给客户端
//为了方便起见,可以使用 PrintWriter 把 OutputStream 包裹一下
PrintWriter printWriter =new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();// 刷新缓冲区。
// 如果没有这个 flush,可能 客户端就不能第一时间看到响应的结果
System.out.printf("[%s:%d] request:%s,response:%s\n",
clientSocket.getInetAddress(),// IP
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 {
TcpThreadEchoServer server = new TcpThreadEchoServer(9090);
server.start();
}
}
此时我们才真正完成了 TCP 代码。
知识点:多线程基础篇
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 TcpThreadPool {
private ServerSocket listenSocket = null;
public TcpThreadPool(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
ExecutorService pool = Executors.newCachedThreadPool();
while(true){
Socket clientSocket = listenSocket.accept();
pool.submit(new Runnable() {
@Override
public void run() {
processConnection(clientSocket);
}
});
}
}
private void processConnection(Socket clientSocket) {
System.out.printf("[%s:%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
try (InputStream inputStream = clientSocket.getInputStream()){
try (OutputStream outputStream = clientSocket.getOutputStream()){
Scanner sc = new Scanner(inputStream);
while (true){
if(!sc.hasNext()){
System.out.printf("[%s:%d] 客户端断开连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
break;
}
String request = sc.next();
String response = process(request);
PrintWriter printWriter= new PrintWriter(outputStream);
printWriter.println(response);
printWriter.flush();
System.out.printf("[%s:%d] request:%s response:%s\n",
clientSocket.getInetAddress(),
clientSocket.getPort(),
request,
response);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpThreadPool pool = new TcpThreadPool(9090);
pool.start();
}
}
package network;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient {
// 用普通的 Socket 即可,不用 ServerSocket 了
private Socket socket = null;
//此处也不用手动给客户端指定端口号,由系统自动分配(隐式)
public TcpEchoClient(String serverIP,int serverPort) throws IOException {
// 其实这里是可以给定端口号的,但是这里给了之后,含义是不同的。
// 这里传入的 IP 与 端口号 的 含义: 表示的不是自己绑定,而是表示 和 这个IP 端口 建立连接
socket = new Socket(serverIP,serverPort);// 这里表示 与 IP 为serverIP的主机上的 端口为9090的程序,建立连接。
}
public void start(){
System.out.println("和进服务器连接成功!");
Scanner sc = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream()){
try (OutputStream outputStream = socket.getOutputStream()){
while(true){
//1、从控制台读取字符串
System.out.println("->");
String request = sc.next();
//2、根据读取的自妇产,构造请求,把请求发送给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);// 看似是一个输出语句,其实已经将数据写到服务器里面去了
printWriter.flush();// 记得 立即刷新缓冲区,确保 服务器 第一时间 感知到 请求。
//3、从服务器读取响应,并解析
Scanner scanner = new Scanner(inputStream);
String response = scanner.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 和 UDP,它们无论是 API 代码,还是协议底层的工作过程,都是差异巨大的。
不是单纯的 “把流转换成数据报”就可以的,这两者之间毫无关系。
我们需要明白:
一次通信,需要用到五元组:源IP,源端口,目的IP,目的端口,协议类型。
如果协议类型不匹配,那么两者之间是无法通信的。
通信双方必须使用同一种协议,才能顺利的进行通信。