目录
为什么需要网络编程
什么是网络编程
传输层的两个重要协议
什么是套接字Socket
分类
Java使用UDP协议?
?Java使用TCP协议?
两个协议的对比?
为什么需要网络编程
为什么需要网络编程?
——
丰富的网络资源
用户在浏览器中,打开在线视频网站,如优酷看视频,实质是通过网络,获取到网络上的一个视频资源。 与本地打开视频文件类似,只是视频文件这个资源的来源是网络。 相比本地资源来说,网络提供了更为丰富的网络资源:
?所谓的网络资源,其实就是在网络中可以获取的各种数据资源。 而所有的网络资源,都是通过网络编程来进行数据传输的
什么是网络编程
什么是网络编程
网络编程,指网络上的主机,通过
不同的进程
,以编程的方式实现
网络通信(或称为网络数据传输)
。
- 当然,我们只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数据, 也属于网络编程。
- 特殊的,对于开发来说,在条件有限的情况下,一般也都是在一个主机中运行多个进程来完成网络编程。 但是,我们一定要明确,我们的目的是提供网络上不同主机,基于网络来传输数据资源:
- 进程A:编程来获取网络资源
- 进程B:编程来提供网络资源
- 一般意义上的网络通信,一般站在应用层视角去探讨,所以一般的意义上的网络编程,多是在传输层上进行讨论
传输层的两个重要协议
- UDP User Datagram Protocol 用户报文协议 UDP没有做任何处理,保存网络的原生态,是不可靠,无连接,面向数据报文的一种协议
- TCP Transmission Control Protocol 传输控制协议 可靠,有连接,面向字节流的一种协议
- 都是传输层协议,都需要实现进程到进程的通信,都是站在应用层的角度
网络层的协议是IP协议
如何建立一条网络上(逻辑上)的通信线路
发送端和接收端
在一次网络数据传输时:
- 发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
- 接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
- 收发端:发送端和接收端两端,也简称为收发端。
注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
请求和响应
一般来说,获取一个网络资源,涉及到两次网络数据传输:
客户端和服务端
- 服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。
- 客户端:获取服务的一方进程,称为客户端。
对于服务来说
- 一般是提供:客户端获取服务资源
- 客户端保存资源在服务端
好比在银行办事:
- 银行提供存款服务:用户(客户端)保存资源(现金)在银行(服务端)
- 银行提供取款服务:用户(客户端)获取服务端资源(银行替用户保管的现金)
什么是套接字Socket
概念
Socket
套接字,是由系统提供用于网络通信的技术,是基于
TCP/IP
协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程
?
分类
流套接字
:使用传输层
TCP
协议
- 有连接
- 可靠传输
- 面向字节流
- 有接收缓冲区,也有发送缓冲区
- 大小不限
对于字节流来说,可以简单的理解为,传输数据是基于
IO
流,流式数据的特征就是在
IO
流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。
数据报套接字
:使用传输层
UDP
协议
- 无连接
- 不可靠传输
- 面向数据报
- 有接收缓冲区,无发送缓冲区
大小受限:一次最多传输
64k
对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如
100
个字节,必须一次发送,接收也必须一次接收100
个字节,而不能分
100
次,每次接收
1
个字节
Java数据报套接字通信模型
?对于一次发送及接收UDP数据报的流程如下:
?提供多个客户端的请求处理及响应,流程如下
Java使用UDP协议?
套接字的创建
DatagramSocket 构造方法
- ?UDP服务器(Server)采用一个固定端口,方便客服端进行通信,可能会有错误的风险,(该端口已经被其他进程占用)
- UDP客户端(Client),不需要采用固定端口(可以采用)
DatagramSocket 一些常用方法
- ?一旦通信的双方逻辑意义上有了通信线路,双方地位是平等的(谁都可以作为接收方,谁都可以作为发送方)
- 通信结束后,都需要进进行资源回收
- 对于接收方法,如果没有接收到一个数据报,进程会一直阻塞
数据报的创建
- 数据报是通信过程的数据抽象,就理解成通信过程中发送/接收一个信封
- 作为接收方,只需要提供存放数据的位置
- 作为发送方,将需要发送的数据,和要发送给谁(远端ip+端口)
数据包类的方法
- 对于服务器使用,来获得客户端的ip和port
- 对于接收者:拿到信的内容,对方进程发送的应用层的数据
?例子 基于UDP的翻译服务器和客户端
服务端
package com.lsc.net_demo.udp;
import com.lsc.net_demo.util.Log;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
public class TranslateServer {
private static final HashMap<String,String> map=new HashMap<>();
//公开的ip地址,就看进程在哪个ip上
//公开的port,需要在程序中指定
public static final int PORT=8888;
public static void main(String[] args) throws IOException {
Log.println("准备进行字典的初始化");
initMap();
Log.println("完成字典的初始化");
Log.println("准备创建UDPSocket,端口是"+PORT);
DatagramSocket socket=new DatagramSocket(PORT);
Log.println("UDP Socket创建成功");
//作为服务器,是被动的,循环的进行请求,响应周期的处理
//等待处理,处理并发送响应,直到永远
while (true){
//1接收请求
byte []buf=new byte[1024];//1024 代表我们最大接收数据大小
DatagramPacket receivePacket=new DatagramPacket(buf, buf.length);
Log.println("准备好接收DatagramPacket,最大大小为"+buf.length+"字节");
Log.println("开始接收请求");
socket.receive(receivePacket);//这个方法会阻塞,程序执行到这就不动了,除非有客户发送请求,才能继续
//2一旦走到此处,说明接收到了请求,拆信
//拆出对方的IP地址
InetAddress address=receivePacket.getAddress();
Log.println("对方的IP地址"+address);
//拆出对方的端口
int port=receivePacket.getPort();
Log.println("对方的端口"+port);
//拆出对方ip+端口
SocketAddress socketAddress=receivePacket.getSocketAddress();
Log.println("对象的完整地址"+socketAddress);
//拆出对方发送的数据,其实这个data就是刚才定义的buf数组
byte[]data =receivePacket.getData();
Log.println("刚接收的对象的数据"+ Arrays.toString(data));
//拆出接收到的数据的大小
int length=receivePacket.getLength();
Log.println("接收的数据大小(字节)"+length);
//3解析请求,意味着我们需要定义字节的应用层协议
//首先将字符集解码
String request=new String(data,0,length,"UTF-8");
String engWorld=request;
Log.println("请求(英文单词)"+engWorld);
//执行业务,进行翻译
String chiWorld=translate(engWorld);
//按照应用层协议,封装响应
String response=chiWorld;
//进行字符集编码 String->byte[]
byte []sendBuf=response.getBytes("UTF-8");
//发送响应
//作为发送方需要提供数据报的类
DatagramPacket sentPacket=new DatagramPacket(sendBuf,0,sendBuf.length,//要发送的数据
socketAddress//从请求中拆出的对象地址
);
Log.println("准备好发送DatagramPacket并发送");
socket.send(sentPacket);
Log.println("发送成功");
//本次请求-回应周期完成,继续下一次请求
}
// socket.close();
}
private static String translate(String engWorld) {
String chiWorld=map.getOrDefault(engWorld,"查无此单词");
return chiWorld;
}
private static void initMap() {
map.put("apple","苹果");
map.put("pear","梨子");
map.put("orange","橙子");
}
}
客户端
package com.lsc.net_demo.udp;
import com.lsc.net_demo.util.Log;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UserInputLoopClient {
public static void main(String[] args) throws IOException {
Scanner sc=new Scanner(System.in);
//1创建UDPSocket
Log.println("准备创建UDP socket");
DatagramSocket socket=new DatagramSocket();
Log.println("UDP socket创建结束");
System.out.println("请输入英文单词:");
while (sc.hasNextLine()) {
//2发送请求
String engWorld = sc.nextLine();
Log.println("英文单词是" + engWorld);
String request = engWorld;
byte[] bytes = request.getBytes("UTF-8");
//手段构造服务器的地址
//现在我们的客服端和服务端都在一台主机,所以使用127.0.0.1
InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
//端口使用Translate.PORT(8888)
DatagramPacket sentPacket = new DatagramPacket(bytes, 0, bytes.length,
loopbackAddress,//对方ip
TranslateServer.PORT//对方的端口
);
Log.println("准备发送请求");
socket.send(sentPacket);
Log.println("请求发送结束");
//3接收响应
byte[] buf = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
Log.println("准备接收响应");
socket.receive(receivePacket);
byte[] data = receivePacket.getData();
int len = receivePacket.getLength();
String response = new String(data, 0, len, "UTF-8");
String chiWord = response;
Log.println("翻译结果" + chiWord);
System.out.println("请输入英文单词:");
}
socket.close();
}
}
?Java使用TCP协议?
服务端的Socket建立
- 有连接(先拨号,拨通才能通信) 无连接 接发信(无脑发送,对方在不在都没关系)
- 因为TCP是有连接的,服务器使用TCP Socket(传入的端口就是要公开的端口,一般称为监听端口)
服务端ServerSocket的方法
- 服务器的Socket(客户端对象)是通过accept中获取来的,所以客户端Socket对象需要主机手动实例
- 挂电话,谁都可以挂
?客户端的Socket建立
- 一旦拿到socket对象拿到,双方是同时拿到的(电话是同时解通的),双方的地位就平等了,只需要分发送方和/接收方
?客户端的Socket方法
- 拿到对方的IP地址/getRemote()拿到对方的端口
- 输入流:站在进程角度,是输入流(背后对象就是网卡,网卡抽象出来的TCP连接),所以是给接收方使用的
- 输出流:所以是给发送方方使用的
?输入和输出流的使用
因为是基于字节流的,所以可以使用输入输出流
- 网络编程的输入输出流是站在进程的角度,所以输入是进程输入给别的对象,所以是给接收方使用,输出是别的东西输出给进程,所以是发送方使用
- 对于TCP来说,是基于字节流的,所以我们可以对输入流进行封装,封装成Scanner使用,对于输出流,先封装成OutputStream然后在封装成PrintWriter进行使用
- 对于输出的话,因为是要输出东西给进程, 所以需要刷新缓冲区,flush
TCP
中的长短连接
TCP
发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:
- 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数 据。
- 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以 多次收发数据
例子
- ?短连接,就是我每次说一句话,就挂断电话,如果想继续发信息,需要重新打电话
- 长连接,就是打通电话,我把我想说的全部都说了,一句接着一句(我说一句,回应一句)
?关于同时在线的服务器和客户端
- 短连接客户端和短连接服务器 支持同时在线,因为短连接是说一句就挂了,和服务器的连接也断了(其他客户端可以申请服务),所以可以支持多个连接
- 短链接客户端和长连接服务器,虽然是长连接服务器,但是是短连接客户端,客户端要求一次服务,就断开连接了(相当于我可以一直说话,但是我就是愿意说一句就挂断),跟前面的一种情况一致
- 长连接客户端和长连接服务器,就构成一个电话我可能会一直打,所以服务器一直被占用,其他长连接的 客户端就不能接收服务,不能支持同时在线,怎么解决呢?通过多线程解决,将提供服务的功能让子线程完成,主线程就只管服务端和客户端之间关系建立
例子 基于TCP翻译服务器和客户端(短链接)
服务器
package com.lsc.net_demo.tcp;
import com.lsc.net_demo.util.Log;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Scanner;
/**
* 短连接就算每次发送一个信息,就会挂掉电话
*/
public class TranslateServerShortConnection {
public static final int PORT = 8888;
public static void main(String[] args) throws Exception {
initMap();
ServerSocket serverSocket = new ServerSocket(PORT);
while (true) {
// 接电话
Log.println("等待对方来连接");
Socket socket = serverSocket.accept();//会阻塞 socket是来自客户端的连接
Log.println("有客户端连接上来了");
// 对方信息:
InetAddress inetAddress = socket.getInetAddress(); // ip
Log.println("对方的 ip: " + inetAddress);
int port = socket.getPort(); // port
Log.println("对方的 port: " + port);
SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); // ip + port
Log.println("对方的 ip + port: " + remoteSocketAddress);
// 读取请求
InputStream inputStream = socket.getInputStream();
Scanner scanner = new Scanner(inputStream, "UTF-8");
String request = scanner.nextLine(); // nextLine() 就会去掉换行符
String engWord = request;
Log.println("英文: " + engWord);
// 翻译
String chiWord = translate(engWord);
Log.println("中文: " + chiWord);
// 发送响应
String response = chiWord; // TODO: 响应的单词中是没有 \r\n
OutputStream outputStream = socket.getOutputStream();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");
PrintWriter writer = new PrintWriter(outputStreamWriter);
Log.println("准备发送");
writer.printf("%s\r\n", response);
writer.flush();
Log.println("发送成功");
// 挂掉电话 关闭在循环里面,说明每次发送一个信息就会关闭连接
socket.close();
Log.println("挂断电话");
}
// serverSocket.close();
}
private static final HashMap<String, String> map = new HashMap<>();
private static void initMap() {
map.put("apple", "苹果");
map.put("pear", "梨");
map.put("orange", "橙子");
}
private static String translate(String engWord) {
String chiWord = map.getOrDefault(engWord, "查无此单词");
return chiWord;
}
}
客户端
package com.lsc.net_demo.tcp;
import com.lsc.net_demo.util.Log;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class UserInputLoopConnectionClient {
//短连接
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("请输入英文单词");
if(!scanner.hasNextLine()){
break;
}
String engWord = scanner.nextLine();
//直接创建Socket 使用服务器IP+PORT
Log.println("准备创建socket(TCP连接)");
Socket socket = new Socket("127.0.0.1", TranslateServerShortConnection.PORT);
Log.println("创建socket(TCP连接)成功创建");
//发送请求
Log.println("英文" + engWord);
String request = engWord + "\r\n";
OutputStream os = socket.getOutputStream();
OutputStreamWriter osWriter = new OutputStreamWriter(os);
PrintWriter pw = new PrintWriter(osWriter);
Log.println("准备发生请求");
pw.print(request);
pw.flush();
Log.println("发送请求成功");
//等待响应接收
InputStream is = socket.getInputStream();
Scanner sc = new Scanner(is, "UTF-8");
Log.println("准备接收请求");
String chiWord = sc.nextLine();
Log.println("接收请求成功");
Log.println("中文" + chiWord);
socket.close();//每次发送一个信息,都会关闭连接 然后循环重新连接
}
}
}
长链接
服务器(带多线程解决不是同时在线问题)
package com.lsc.net_demo.tcp;
import com.lsc.net_demo.util.Log;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Scanner;
public class TranslateServerLongConnectionPool {
static class 专门负责处理接电话的人 implements Runnable{
Socket socket;
public 专门负责处理接电话的人(Socket socket){
this.socket=socket;
}
@Override
public void run() {
try {
// 对方信息:
InetAddress inetAddress = socket.getInetAddress(); // ip
Log.println("对方的 ip: " + inetAddress);
int port = socket.getPort(); // port
Log.println("对方的 port: " + port);
SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); // ip + port
Log.println("对方的 ip + port: " + remoteSocketAddress);
//初始化
//用来接收信息
InputStream inputStream = socket.getInputStream();
Scanner scanner = new Scanner(inputStream, "UTF-8");
//用来发送信息
OutputStream outputStream = socket.getOutputStream();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, "UTF-8");
PrintWriter writer = new PrintWriter(outputStreamWriter);
while (true) {
if(!scanner.hasNextLine()) {
Log.println("对方挂电话了");
//代表scanner背后的InputStream遇到了EOS(-1)
//代表客户端挂电话了
//所以服务端也挂电话
break;
}
// 读取请求
String request = scanner.nextLine(); // nextLine() 就会去掉换行符
String engWord = request;
Log.println("英文: " + engWord);
// 翻译
String chiWord = translate(engWord);
Log.println("中文: " + chiWord);
// 发送响应
String response = chiWord; // TODO: 响应的单词中是没有 \r\n
Log.println("准备发送");
writer.printf("%s\r\n", response);
writer.flush();
Log.println("发送成功");
}
// 挂掉电话
socket.close();
Log.println("挂断电话");
}catch (Exception e){
e.printStackTrace();
}
}
}
public static final int PORT = 8888;
public static void main(String[] args) throws Exception {
Log.println("启动长链接版本的TCP服务器");
initMap();
ServerSocket serverSocket = new ServerSocket(PORT);
while (true) {
// 接电话
Log.println("等待对方来连接");
Socket socket = serverSocket.accept();//会阻塞 socket是来自客户端的连接
Log.println("有客户端连接上来了");
Runnable task=new 专门负责处理接电话的人(socket);
//把任务交给专门的线程处理
new Thread(task).start();
}
// serverSocket.close();
}
private static final HashMap<String, String> map = new HashMap<>();
private static void initMap() {
map.put("apple", "苹果");
map.put("pear", "梨");
map.put("orange", "橙子");
}
private static String translate(String engWord) {
String chiWord = map.getOrDefault(engWord, "查无此单词");
return chiWord;
}
}
客户端
package com.lsc.net_demo.tcp;
import com.lsc.net_demo.util.Log;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class UserInputLoopLongConnectionClient {
public static void main(String[] args) throws IOException {
//长链接模式下,只拨通一次电话
//直接创建Socket 使用服务器IP+PORT
Scanner scanner=new Scanner(System.in);
Log.println("准备创建socket(TCP连接)");
Socket socket=new Socket("127.0.0.1",TranslateServerShortConnection.PORT);
Log.println("创建socket(TCP连接)成功创建");
while (true) {
System.out.print("请输入英文单词");
if(!scanner.hasNextLine()){
break;
}
String engWord = scanner.nextLine();
String request = engWord + "\r\n";
OutputStream os = socket.getOutputStream();
OutputStreamWriter osWriter = new OutputStreamWriter(os);
PrintWriter pw = new PrintWriter(osWriter);
Log.println("准备发生请求");
pw.print(request);
pw.flush();
Log.println("发送请求成功");
//等待响应接收
InputStream is = socket.getInputStream();
Scanner sc = new Scanner(is, "UTF-8");
Log.println("准备接收请求");
String chiWord = sc.nextLine();
Log.println("接收请求成功");
Log.println("中文" + chiWord);
}
socket.close();//只有在输入多个信息完毕之和,才会关闭连接
}
}
两个协议的对比?
|