概念大纲:
1.套接字:一个类的对象,参数是目的ip和端口,用来建立连接
主要成员方法有发送和接收,socket这个单词就是一个标志
2.关于UDP(1)和TCP/IP(2)
(1)UDP协议:通过数据报包来传输
发送端
DatagramSocket和DatagramPacket
DatagramSocket类对象,指定自己的工作端口
DatagramPacket类对象,指定发送目的地ip和目的ip的工作端口
接收端
DatagramSocket和DatagramPacket
DatagramSocket类对象,指定自己的(接收)工作端口
DatagramPacket类对象,指定发送目的地ip和目的ip的工作端口
.getData()
.getOffset()
.getLength()
四个版本需求:(自动回复雏形)
(1)收发一个包
package com.cskaoyan.udp.v1;
import java.io.IOException;
import java.net.*;
/*
- 【1.】创建套接字Socket对象:指定发送端的工作端口
- 【2.】封装数据报包Packet对象:将要发送的数据(字节数组)、目的地ip、目的地工作端口号传入构造包方法
- 【3.】调用套接字Socket对象的发送方法
- 【4.】释放资源:套接字Socket对象调用close方法
*/
public class Sender {
public static void main(String[] args) throws IOException {
// 来自应用层的数据
String s = "hello udp";
// 建立udp的socket对象
DatagramSocket datagramSocket = new DatagramSocket(8888);
// 将要发送的数据封装成数据包
// DatagramPacket(byte[] buf, int offset,
// int length, InetAddress address, int port)
byte[] bytes = s.getBytes();
InetAddress targetIP = InetAddress.getByName("127.0.0.1");
int port = 9999;
DatagramPacket packet =
new DatagramPacket(bytes, 0, bytes.length, targetIP, port);
// send(DatagramPacket p)
// 从此套接字发送数据报包。
datagramSocket.send(packet);
// 释放资源
datagramSocket.close();
}
}
package com.cskaoyan.udp.v1;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
/*
- 【1.】创建套接字Socket对象:指定接收端工作端口号
- 【2.】缓存数据报包Packet对象:将要接收的数据(字节数组)、开始索引、结束索引传入构造缓存包方法
- 【3.】调用套接字Socket对象的接收方法:
- 非常繁琐:getData方法
- 【4.】新加控制台输出:记录接收日志,输出到控制台
- 【5.】释放资源:没有调用,原因未知
*/
public class Receiver2 {
public static void main(String[] args) throws Exception{
// 创建接收端的socket对象
DatagramSocket datagramSocket = new DatagramSocket(11111);
while (true) {
// 创建用于接收端的数据报包
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length);
// receive 并解析
datagramSocket.receive(packet);
byte[] data = packet.getData();
int offset = packet.getOffset();
int length = packet.getLength();
String s = new String(data, offset, length);
SocketAddress socketAddress = packet.getSocketAddress();
System.out.println("接收到了来自 " +
socketAddress + " 的消息: " + s);
}
// close
}
}
(2)while循环一直接收发
package com.cskaoyan.udp.v2;
import com.cskaoyan.utils.NetworkUtils;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Scanner;
/**
* @description: 发送端
* @author: 景天老师
**/
/*
要求重复发送
*/
/*
- 【1.】创建套接字Socket对象:指定发送端的工作端口 11111
- 【2.】创建控制台接收:直接通过System.in接收输入,作为传输数据字符串
- 【3.】封装数据报包Packet对象:目的地ip、目的地工作端口号、将要发送的数据(字节数组)传入构造包方法。注意:这里的getSenderPacket参数顺序跟之前是不一样的,之前是字节数组,要指定其实索引,而且在参数前几位;这里用字符串,在参数第三位
- 【4.】调用套接字Socket对象的发送方法
- 【5.】释放资源:没调用,原因未知
*/
public class Sender {
public static void main(String[] args) throws Exception{
// 创建发送端的socket对象
DatagramSocket datagramSocket = new DatagramSocket(11111);
Scanner scanner = new Scanner(System.in);
while (true) {
String s = scanner.nextLine();
// 封装数据
DatagramPacket senderPacket =
NetworkUtils.getSenderPacket("127.0.0.1", 12306, s);
// send
datagramSocket.send(senderPacket);
}
// close
}
}
package com.cskaoyan.udp.v2;
import com.cskaoyan.utils.NetworkUtils;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* NetworkUtils包工具类,在这里要使用
*/
/*
- 【1.】创建套接字Socket对象:指定接收端的工作端口 11111
- 【2.】创建接收数据报包Packet对象:没有参数,查看NetworkUtils方法
- 【3.】调用数据报包的解析方法,得到信息字符串s
- 【4.】释放资源:没调用,原因未知
*/
public class Receiver {
public static void main(String[] args) throws Exception{
// 创建用于接收的socket对象
DatagramSocket datagramSocket = new DatagramSocket(12306);
while (true) {
// 创建用于接收的数据报包
DatagramPacket receiverPacket = NetworkUtils.getReceiverPacket();
// 接收 并 解析 数据
datagramSocket.receive(receiverPacket);
String s = NetworkUtils.parsePacket(receiverPacket);
System.out.println(s);
}
// close
}
}
(3)发送端要可收可发,为了控制台获取反馈。接收端也是
package com.cskaoyan.udp.v3;
import com.cskaoyan.utils.NetworkUtils;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @description: 发送端
* @author: 景天老师
**/
/*
要求能够发送 也能够接收数据
*/
/**
* NetworkUtils包工具类,在这里要使用
*
* 发送端:
* 【1.】创建套接字对象
* 【2.】接收键盘输入
* 【3.】封装数据,发送
* 【4.】接收数据,解析
* 【5.】释放资源,数据报套接字关闭
*
*
*/
public class Sender {
public static void main(String[] args) throws Exception{
//【1.】创建用于发送的socket对象,指定自己的通信端口号 10000
DatagramSocket datagramSocket = new DatagramSocket(10000);
//【2.】接收键盘输入,用字节转字符包装,再用缓冲字符流包装
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
String line;//中介字符串声明在循环外
while ((line = br.readLine()) != null) {
//【3.】封装数据,发送
//调用构造方法,生成一个发送包,传入目的ip、目的ip主机的通信端口号、信息字符串
DatagramPacket senderPacket =
NetworkUtils.getSenderPacket("127.0.0.1", 10001, line);
//用套接字发送
datagramSocket.send(senderPacket);
//【4.】接收数据,解析
DatagramPacket receiverPacket = NetworkUtils.getReceiverPacket();//收到一个包
datagramSocket.receive(receiverPacket);//数据报套接字接收,给成员变量赋值
String s = NetworkUtils.parsePacket(receiverPacket);//解析出信息字符串
System.out.println(s);//输出
//结束匹配信号
if (line.equals("88")) {
break;
}
}
//【5.】释放资源,数据报套接字关闭
datagramSocket.close();
}
}
package com.cskaoyan.udp.v3;
import com.cskaoyan.utils.NetworkUtils;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Scanner;
/**
* @description: 接收端【控制台发送】
* @author: 景天老师
**/
/**
* NetworkUtils包工具类,在这里要使用
*
* 接收端:
* 【1.】创建套接字对象
* 【2.】接收数据报包,解析
* 【3.】生成发送包,发送
* 【4.】释放资源
*
*/
public class Receiver {
public static void main(String[] args) throws IOException {
//【1.】创建用于接收的socket对象,指定自己的通信端口号 10001
DatagramSocket datagramSocket = new DatagramSocket(10001);
//键盘录入【控制台输入信息,发送】
Scanner scanner = new Scanner(System.in);
while (true) {
//【2.】接收数据,解析
DatagramPacket receiverPacket = NetworkUtils.getReceiverPacket();
datagramSocket.receive(receiverPacket);//数据报套接字接收,给成员变量赋值
String s = NetworkUtils.parsePacket(receiverPacket);//解析出信息字符串
System.out.println(s);
//【3.】封装数据,发送
String sendMsg = scanner.nextLine();//控制台输入信息,一行信息字符串
//【4.】调用构造方法,生成一个发送包,传入目的ip、目的ip主机的通信端口号、信息字符串
DatagramPacket senderPacket =
NetworkUtils.getSenderPacket("127.0.0.1", 10000, sendMsg);
//用套接字发送
datagramSocket.send(senderPacket);
//结束匹配信号
if (s.equals("88")) {
break;
}
}
//【5.】释放资源,数据报套接字关闭
datagramSocket.close();
}
}
(4)自动回复:自己创建一个Resource Bundle文件,命名随意,我这里写的是 datadict.properties
创建完,自己写自动回复的键值对:
先启动接收端,再启动发送端,不然会报错。关闭的时候随意
package com.cskaoyan.udp.v4;
import com.cskaoyan.utils.NetworkUtils;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Scanner;
/**
* @description: 发送【实现自动回复功能】
* @author: 景天老师
**/
/*
自动回复的机器人
*/
/**
* NetworkUtils包工具类,在这里要使用
*
* 发送端:
* 【1.】创建套接字对象
* 【2.】接收键盘输入
* 【3.】封装数据,发送
* 【4.】接收数据,解析
* 【5.】释放资源,数据报套接字关闭
*
*
*/
public class Sender {
public static void main(String[] args) throws Exception{
//【1.】创建发送端socket对象,指定自己的通信端口号 12345
DatagramSocket datagramSocket = new DatagramSocket(12345);
//【2.】接收键盘输入
Scanner sc = new Scanner(System.in);//创建接收对象sc
while (true) {
String s = sc.nextLine();
//【3.】封装数据,发送
//调用构造方法,生成一个发送包,传入目的ip、目的ip主机的通信端口号、信息字符串
DatagramPacket senderPacket =
NetworkUtils.getSenderPacket("127.0.0.1", 54321, s);
//用套接字发送
datagramSocket.send(senderPacket);
//【4.】接收数据,解析
DatagramPacket receiverPacket = NetworkUtils.getReceiverPacket();//收到一个包
datagramSocket.receive(receiverPacket);//数据报套接字接收,给成员变量赋值
String receiveMsg = NetworkUtils.parsePacket(receiverPacket);//解析出信息字符串
//输出
System.out.println(receiveMsg);
//结束匹配信号
if (s.equals("86")) {
break;
}
}
//【5.】释放资源,数据报套接字关闭
datagramSocket.close();
}
}
package com.cskaoyan.udp.v4;
import com.cskaoyan.utils.NetworkUtils;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Properties;
/**
* @description: 接收端【实现自动回复功能】
* @author: 景天老师
**/
/**
* NetworkUtils包工具类,在这里要使用
*
* 接收端:
* 【1.】创建套接字对象
* 【2.】加载自动回复键值对表
* 【3.】接收数据报包
* 【4.】匹配字典
* 【5.】生成发送包
* 【6.】释放资源
*
*/
public class Receiver {
public static void main(String[] args) throws Exception{
//【1.】创建用于接收的socket对象,指定自己的通信端口号 54321
DatagramSocket datagramSocket = new DatagramSocket(54321);
//【2.自动回复库】创建properties对象
Properties properties = new Properties();
//load方法
properties.load(new InputStreamReader(
new FileInputStream("dataDict.properties"),"GBk"));
while (true) {
//【3.】接收数据,解析
DatagramPacket receiverPacket = NetworkUtils.getReceiverPacket();//收到一个包
datagramSocket.receive(receiverPacket);//数据报套接字接收,给成员变量赋值
//这一步是在做什么
String key = NetworkUtils.parsePacket(receiverPacket);//解析出键
//【4.匹配字典】
String responseMsg = properties.getProperty(key);//解析出值
System.out.println(responseMsg);//输出接收到的消息
if (responseMsg == null) {
//如果没有匹配上 返回一个默认的字符串
responseMsg = "正在忙,稍后回复";
}
//【5.】调用构造方法,生成一个发送包,传入目的ip、目的ip主机的通信端口号、信息字符串
DatagramPacket senderPacket =
NetworkUtils.getSenderPacket("127.0.0.1", 12345, responseMsg);
//用套接字发送
datagramSocket.send(senderPacket);
//结束匹配信号
if (key.equals("86")) {
break;
}
}
//【6.】释放资源,数据报套接字关闭
datagramSocket.close();
}
}
3.工具类:
一个老师写的工具类,提取了接收、发送的方法,方便调用
package com.cskaoyan.utils;
import java.net.*;
/**
* 网络的工具类,返回数据报包或者信息字符串
* 【1.】getSenderPacket
* 生成目的ip、目的ip通信端口、信息字符串的数据报包
* 【2.】getReceiverPacket
* 创建接收包
* 【3.】parsePacket
* 解析数据
*/
public class NetworkUtils {
public static DatagramPacket getSenderPacket(String targetIp, int port, String message) throws UnknownHostException {
// 创建用于发送的数据报包
byte[] bytes = message.getBytes();
InetAddress ip = InetAddress.getByName(targetIp);
DatagramPacket packet =
new DatagramPacket(bytes, 0, bytes.length, ip, port);//封装信息,生成数据报包
return packet;
}
// 创建用于接收的数据报包
public static DatagramPacket getReceiverPacket() {
byte[] bytes = new byte[1024];//空数组,用来接收字节数据
DatagramPacket packet =
new DatagramPacket(bytes, 0, bytes.length);
return packet;
}
// 解析数据
public static String parsePacket(DatagramPacket packet){
byte[] data = packet.getData();
int offset = packet.getOffset();
int length = packet.getLength();
String s = new String(data, offset, length);//拼接字符串信息
//SocketAddress socketAddress = packet.getSocketAddress();
return s;
}
}
(2)TCP/IP协议:通过io流来传输
客户端:Socket套接字
指定目的地ip和目的地工作端口号
服务端:ServerSocket套接字
指定自己的工作端口,调用监听方法来发现可能存在的连接请求,然后建立连接
三个版本需求:
(1)客户端、服务端没有反馈到控制台信息
package com.cskaoyan.tcp.v1;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
/**
* @description: 客户端
* @author: 景天老师
**/
/*
- 建立客户端的Socket服务,并明确要连接的服务器。
- 如果对象建立成功,就表明已经建立了数据传输的通道.就可以在该通道通过IO进行数据的读取和写入
- 根据需要从socket对象中获取输入,或输出流
- 向流中读取或写入数据
释放资源
*/
public class Client {
public static void main(String[] args) throws IOException {
// 建立客户端的Socket服务,并明确要连接的服务器。
// Socket(String host, int port)
Socket socket = new Socket("127.0.0.1", 8888);
// 如果对象建立成功,就表明已经建立了数据传输的通道.
// 就可以在该通道通过IO进行数据的读取和写入
// 想服务端发送数据
OutputStream out = socket.getOutputStream();
out.write("hello tcp".getBytes());
// 释放资源
socket.close();
}
}
package com.cskaoyan.tcp.v1;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @description: 服务端
* @author: 景天老师
**/
/*
- 创建Serversocket对象,在指定端口,监听客户端连接请求
- 收到客户端连接请求后,建立Socket连接
- 如果连接建立成功,就表明已经建立了数据传输的通道.就可以在该通道通过IO进行数据的读取和写入
- 从socket中根据需要获取输入,或输出流
根据需要向流中写入数据或从流中读数据
释放资源
*/
public class Server {
public static void main(String[] args) throws Exception{
// 创建Serversocket对象,在指定端口,监听客户端连接请求
ServerSocket serverSocket = new ServerSocket(8888);
// 收到客户端连接请求后,建立Socket连接
Socket socket = serverSocket.accept();
// 从socket中根据需要获取输入,或输出流
InputStream in = socket.getInputStream();
// 解析数据
byte[] bytes = new byte[1024];
int readCount = in.read(bytes);
String s = new String(bytes, 0, readCount);
System.out.println(s);
socket.close();
serverSocket.close();
}
}
(2)服务端向客户端给出反馈,使用输出流,向客户端的控制台输出信息
package com.cskaoyan.tcp.v2;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* @description:
* @author: 景天老师
**/
/*
客户端发送数据 服务端给出反馈
*/
public class Client {
public static void main(String[] args) throws Exception{
//【1.】建立客户端的Socket服务。参数:要连接的服务器ip地址,要连接的服务器的通信端口
//Socket(String host, int port)
Socket socket = new Socket("127.0.0.1", 8888);
//如果对象建立成功,就表明已经建立了数据传输的通道.
//就可以在该通道通过IO进行数据的读取和写入
//向服务端发送数据
//【2.】创建输出流
OutputStream out = socket.getOutputStream();
out.write("你好".getBytes());//写入
//【3.】接收来自服务器端的反馈
//获取输入流,套接字服务对象调用.getInputStream方法
InputStream in = socket.getInputStream();//这里接收的是什么,是服务器端的内容
byte[] bytes = new byte[1024];
int readCount = in.read(bytes);
String s = new String(bytes, 0, readCount);
System.out.println(s);
//【4.】释放资源
socket.close();
}
}
package com.cskaoyan.tcp.v2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @description:
* @author: 景天老师
**/
public class Server {
public static void main(String[] args) throws IOException {
//【1.】创建服务端Serversocket对象,指定自己的通信端口 11111
ServerSocket serverSocket = new ServerSocket(8888);
//【2.】监听连接请求
//监听到请求,就创建客户端套接字对象,存储监听到的套接字,后面调用套接字方法
Socket socket = serverSocket.accept();//之后用这个客户端套接字来调用收发
//【3.】用客户端套接字调用返回输入流方法,是服务器端读客户端内容到这个流
InputStream in = socket.getInputStream();
//读取中介:字节数组不能太小,也会有输入信息超出存储范围的情况
byte[] bytes = new byte[1024];
int readCount = in.read(bytes);
//【4.】拼接信息字符串
String s = new String(bytes, 0, readCount);
System.out.println(s);//服务器端会输出客户端控制台的信息
//【5.】给客户端一个反馈信息
//创建客户端的信息输出流
OutputStream out = socket.getOutputStream();
out.write("还行".getBytes());//按下回车
socket.close();//客户端关闭
serverSocket.close();//服务端关闭
}
}
改一下,写成while循环一直服务
package com.cskaoyan.tcp.v2;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @description:
* @author: 景天老师
**/
public class Server2 {
public static void main(String[] args) throws Exception{
//【1.】创建服务端Serversocket对象,指定自己的通信端口 11111
ServerSocket serverSocket = new ServerSocket(11111);
while (true) {
//为什么这里不用字节数组,因为是字符流,我们在控制台输入汉字
//byte[] bytes = new byte[1024];
//int readCount = in.read(bytes);
//String s = new String(bytes, 0, readCount);
//【2.】监听连接请求
//服务器端套接字一直用自己的 11111 端口监听
//监听到请求,就创建客户端套接字对象,存储监听到的套接字,后面调用套接字方法
Socket socket = serverSocket.accept();//之后用这个客户端套接字来调用收发
//【3.】用客户端套接字调用返回输入流方法,是服务器端读客户端内容到这个流
InputStream in = socket.getInputStream();
//最内层是字节向字符转换
//外层是缓冲字符流
//需要理解这种嵌套定义的父类子类关系:
//缓冲字符流,为了用读取行方法。参数是套接字返回的输入流
BufferedReader br = new BufferedReader(new InputStreamReader(in));
//字符串变量s接收读取到的行,向服务器端输入
String s = br.readLine();
//返回客户端ip地址字符串
InetAddress inetAddress = socket.getInetAddress();
//返回客户端通信端口
int port = socket.getPort();
//【4.】拼接信息字符串
System.out.println("接收到了来自" + inetAddress + ":" + port + "的消息: " + s);
}
}
}
(3)处理文件:需要确定两端的文件地址,然后还是处理字节流操作
package com.cskaoyan.tcp.v3;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.OutputStream;
import java.net.Socket;
/**
* @description:
* @author: 景天老师
**/
// 把客户端文件上传到服务器
public class Client {
public static void main(String[] args) throws Exception{
//【1.】创建Socket对象,目的ip,目的通信端口
Socket socket = new Socket("127.0.0.1", 11111);
//【2.】读取本地文件,注意地址
FileInputStream in = new FileInputStream("a.txt");
//【3.】获取输出流
OutputStream out = socket.getOutputStream();
byte[] bytes = new byte[1024];
int readCount;
while ((readCount = in.read(bytes)) != -1) {
out.write(bytes,0,readCount);
}
//【4.】释放资源
in.close();
socket.close();
}
}
package com.cskaoyan.tcp.v3;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @description: 服务器端接收文件
* @author: 景天老师
**/
public class Server {
public static void main(String[] args) throws Exception{
//【1.】创建ServerSocket对象,指定通信端口 11111
ServerSocket serverSocket = new ServerSocket(11111);
//【2.】监听连接请求
Socket socket = serverSocket.accept();
//【3.】创建输出流对象
FileOutputStream out = new FileOutputStream("b.txt");
//【4.】客户端套接字传入读取内容,输入流
InputStream in = socket.getInputStream();
int readCount;
byte[] bytes = new byte[1024];
while ((readCount = in.read(bytes)) != -1) {
//【5.】每读取1024字节到中间数组bytes成功,就把这些内容写入服务端输出流
out.write(bytes,0,readCount);
}
//【6.】释放资源
out.close();
socket.close();
serverSocket.close();
}
}