1 - 概述
1.1 - 网络编程的三要素
1、IP地址
2、端口
3、协议
1.2 - IP地址
IP地址就是设备(电脑、手机、pad、空调、冰箱...)在网络中的唯一标识(通过IP地址访问的设备)
-
IP地址两大类
-
IPV4:(主流的网络地址格式)
-
IPV6:(预计未来10年够呛能普及)
-
-
常用的DOS命令
-
ipconfig -all (查看当前计算机IP信息)
-
ping xxx.xxx.xxx.xxx (测试网络是否畅通,会给对方地址发送一个网络数据测试包,查看是否有响应)
-
netstat -ano (查看本机端口占用情况)
-
-
特殊IP地址
-
127.0.0.1 - 本地回环地址
-
1.3 - InetAddress
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Demo03 {
public static void main(String[] args) throws UnknownHostException {
//主机名
InetAddress address = InetAddress.getByName("DELL-3020");
System.out.println(address);
//外网地址
InetAddress address1 = InetAddress.getByName("10.50.20.230");
System.out.println(address1);
//本地回环地址
InetAddress address2 = InetAddress.getByName("127.0.0.1");
System.out.println(address2);
}
}
1.4 - 端口
端口是应用程序(进程/线程)在设备中的唯一标识
端口号:用两个字节的整数来表示(unsigned short:0 ~ 65535),其中0~1024之间的端口尽量不要使用,因为很多知名网商都会在这个去区间来选择端口号(被占用的几率大),建议在10000以上来选择使用。如果被占用,再换一个。
1.5 - 协议
计算机网络中,连接和通信的规则。
-
UDP
-
User Datagram Protocol 用户数据包协议
-
UDP协议是一种无连接的通信协议,即在数据的发送的时候,数的发送端和接收端不会建立所谓的逻辑连接,简单来说就当一台计算机向另一台计算机发送数据时,发送端不会确认接收端是否存在,同样接收端也不会向发送端反馈数据是否接收到。
-
-
UDP协议消耗的资源相对小,通信效率高,但是不安全。一般情况下使用在在线音频、视频的用处较多。
-
TCP
-
Transmission Control Protocol 传输控制协议
-
TCP协议是一种 面向连接/安全的数据通信协议,在数据传输之前,会在发送端和接收端各自创建一个逻辑连接,然后再进行数据的传输。
-
在数据传输之前会做“三次握手”
-
第一次:客户端会向服务器端发送连接请求,等待服务器确认
-
第二次:服务器会向客户端发送一个响应,通知客户端收到了请求并同意连接
-
第三次:客户端会再次向服务器端发送确认信息,确认最终的连接。
-
-
TCP释放资源之前会做“四次挥手”
-
第一次:客户端数据发送完成后,向服务器端发送连接释放请求
-
第二次:服务器端收到客户端发来的释放请求后,会通知服务器端内部相关进程,客户端已经释放向服务器端的连接
-
第三次:服务器端会向客户端发送同意连接释放执行
-
客户端收到服务器端连接释放指令后,向服务器端发送确认指令
-
-
2 - UDP通信
2.1 - 通信原理
UDP在通信的时候会在两端各自创建一个Socket(套接字)对象,但是这两个Socket只是用来发送和接收数据的对象,因此对于UDP通信的双方而言,没有所谓的客户端和服务器端的概念的。
2.2 - UDP发送
-
发送步骤
1、创建发送端Socket对象(DatagramSocket)
2、创建数据,并把数据打包(DatagramPacket)
3、调用发送方法(send)
4、关闭发送
import java.io.IOException; import java.net.*; public class UDPSend { public static void main(String[] args) throws IOException { //创建发送端Socket对象 DatagramSocket ds = new DatagramSocket(); //创建数据 byte[] bytes = "Hello UDP Send Message".getBytes(); int length = bytes.length; InetAddress address = InetAddress.getByName("127.0.0.1"); int port = 9958; //创建数据,并且将数据打包 DatagramPacket dp = new DatagramPacket(bytes, length, address, port); //调用发送数据方法 ds.send(dp); //关闭发送 ds.close(); } }
2.3 - UDP接收端
接收步骤
1、创建接收端Socket对象(DatagramSocket)
2、创建一个数据包,用于接收数据
3、调用接收方法
4、解析数据包
5、关闭接收
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPReceive {
public static void main(String[] args) throws IOException {
//1、创建发送端Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(9958);
//创建数据包
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
ds.receive(dp);
//解析数据包
//byte[] data = dp.getData();//通过数据包取出数据
int length = dp.getLength();//实际接收到的数据的长度
//通过数组取出数据
System.out.println(new String(bytes, 0, length));
//5、关闭接收
ds.close();
}
}
2.4 - 案例
需求:
UDP发送数据:数据来源于键盘输入,直接输入over的时候结束发送
UDP接收数据:因为不知道发送端发送多少条数据,所有采用死循环接收
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @Description #TODO 循环接收
*/
public class UDPReceive {
public static void main(String[] args) throws IOException {
//创建接收端对象
DatagramSocket ds = new DatagramSocket(1314);
while (true){
byte[] bytes = new byte[1024];
//创建接收数据的数据包
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
ds.receive(dp);//调用接收方法接收数据
//解析数据并打印
System.out.println("接收到 -" + new String(bytes,0,dp.getLength()));
}
}
}
-------------------------------------------
package demo05;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* @Description #TODO 发送键盘输入内容
*/
public class UDPSend {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();//创建发送端对象
String line;//来存储键盘输入的内容
while (true){
System.out.println("请输入要发送的数据:");
line = new Scanner(System.in).nextLine();//获取键盘输入
//判断是否是出口指令
if ("over".equals(line)){
break;//跳出循环
}
//如果不是over,则将数据发送出去
byte[] bytes = line.getBytes();//将发送的数据转换成byte数组
DatagramPacket dp = new DatagramPacket(bytes, bytes.length,
InetAddress.getByName("127.0.0.1"), 1314);
ds.send(dp);//发送数据包
}
//关闭发送对象
ds.close();
}
}
3 - TCP通信
3.1 - TCP通信原理
TCP通信是一种可靠的网络编程协议,它会在通信的两端各自创建一个Socket对象,从而在通信的两端形成虚拟的网络连接,一旦建立了这种网络连接,两端的程序就可以通过虚拟的网络的IO流来进行数据的通信。
3.2 - TCP的发送数据
-
发送步骤
1、创建客户端的Socket对象
2、获取输出流
3、写数据
4、释放资源
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
/**
* @Description #TODO
*/
public class TCPSend {
public static void main(String[] args) throws IOException {
// 1、创建客户端的Socket对象
Socket socket = new Socket("127.0.0.1", 9527);
// 2、获取输出流
OutputStream os = socket.getOutputStream();
// 3、写数据
os.write("Hello TCP Send Messages!~".getBytes());
// 4、释放资源
os.close();
socket.close();
}
}
3.3 - 服务器接收数据
-
接收数据步骤
1、创建服务器端的Socket对象(ServerSocket)
2、获取通信使用的Socket对象
3、获取输入流
4、读数据
5、处理数据
6、释放资源
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Description #TODO 服务器接收数据
*/
public class TCPReceive {
public static void main(String[] args) throws IOException {
// 1、创建服务器端的Socket对象(ServerSocket)
ServerSocket serverSocket = new ServerSocket(9527);
// 2、获取通信使用的Socket对象
Socket socket = serverSocket.accept();
// 3、获取输入流
InputStream is = socket.getInputStream();
// 4、读数据
byte[] bytes = new byte[1024];//用来存储读取的数据的容器
int length = is.read(bytes);//将数据读取到数组中,并返回数组中读取的实际数据的长度
// 5、处理数据
String data = new String(bytes, 0, length);//将byte数组转换成字符串
System.out.println("接收到客户端发来的消息:" + data);
// 6、释放资源
serverSocket.close();
socket.close();
is.close();
}
}
3.4 - 案例
-
客户端发送数据,并接收服务器的反馈
-
服务器端接收数据,并给出反馈
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @Description #TODO 客户端发送数据,并接收服务器端的反馈信息
*/
public class TCPSend {
public static void main(String[] args) throws IOException {
//先发送数据到服务器端
//创建socket对象
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9527);
//向服务器端发送数据,通过io流
OutputStream os = socket.getOutputStream();
os.write("今天是周日,是第二阶段最后一天可!~".getBytes());
//接收服务器端发送过来的反馈消息
InputStream is = socket.getInputStream();//获取输入流
byte[] bytes = new byte[1024];//创建容器,用来存储数据
int length = is.read(bytes);//通过输入流读取数据,并返回读取的数据的实际长度
String data = new String(bytes, 0, length);//将数据转换成字符串
System.out.println("接收到服务器端发来的反馈----" + data);
//释放资源
socket.close();
os.close();
is.close();
}
}
----------------------------------------------------------
package demo01;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Description #TODO 服务器接收数据,并给出反馈
*/
public class TCPReceive {
public static void main(String[] args) throws IOException {
//创建socket对象
ServerSocket serverSocket = new ServerSocket(9527);
Socket socket = serverSocket.accept();
//接收客户端发来的消息
InputStream is = socket.getInputStream();//获取输入流
byte[] bytes = new byte[1024];//创建容器,用来存储数据
int length = is.read(bytes);//将数据存到数组中,并返回数组中存储实际数据的长度
System.out.println("接收到客户端发来的信息:" + new String(bytes,0,length));
//给客户点发送反馈信息
OutputStream os = socket.getOutputStream();//获取输出流
os.write("服务器已经接收到数据".getBytes());
//释放资源
serverSocket.close();
socket.close();
is.close();
os.close();
}
}
-
通过TCP发送键盘内容,直到over结束
import java.io.*; import java.net.Socket; /** * @Description #TODO 客户端发送键盘输入的内容 */ public class ClientSendInput { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 9527);//创建通信的socket对象 //自己封装一个标准键盘输入 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //自己封装一个缓冲区字符输出流对象 // OutputStream os = socket.getOutputStream();//字节流 // OutputStreamWriter osw = new OutputStreamWriter(os);//字符流 // BufferedWriter bw = new BufferedWriter(osw);//缓冲区字符输出流 //当前效果,与上面三行同等 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); //获取用户的键盘输入 String line; while ((line = br.readLine()) != null){ //判断一个出口 if("over".equals(line)){ break; } //将数据写到输出流中 bw.write(line); bw.newLine(); bw.flush(); } //释放资源 socket.close(); br.close(); bw.close(); } } --------------------------------------------- package demo02; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * @Description #TODO 接收客户端的键盘输入的内容 */ public class ServerReceiveInput { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9527); Socket socket = serverSocket.accept(); //自己封装一个缓冲区字符输入流 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line; while ((line = br.readLine()) != null){ System.out.println("接收到客户端发来的:-" + line); } //释放资源 serverSocket.close(); socket.close(); } }
-
服务器端接收客户端发来的消息,并保存到文件中
import java.io.*;
import java.net.Socket;
/**
* @Description #TODO 客户端发送键盘输入内容给服务器端
*/
public class ClientSend {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 9527);//创建socket对象
//封装键盘输入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//自己封装输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//发送数据
String line;
String[] arr = {"张三","李四"};
int i = 1;
while ((line = br.readLine()) != null){
if("over".equals(line))//判断出口
break;
//如果不是over则把数据发送到服务器端
bw.write(arr[i % 2] + "说:" + line);
bw.newLine();
bw.flush();
i++;
}
//资源释放
br.close();
bw.close();
socket.close();
}
}
----------------------------------------------
package demo03;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @Description #TODO 服务器端接收并保存数据到文件中
*/
public class ServerReceive {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9527);
Socket socket = serverSocket.accept();
//字节封装输入流(获取客户端数据)和输出流(将数据写入到文件中)
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream("./myTest.txt")));
//获取客户端发来的消息并将消息写入到文件中
String line;
while ((line = br.readLine()) != null){
bw.write(line);//写一行
bw.newLine();//换一行
bw.flush();//刷新一行
}
serverSocket.close();
socket.close();
br.close();
bw.close();
}
}
```
-
模拟聊天室
import java.util.HashMap;
import java.util.Map;
/**
* 初始化用户类
*/
public class Global {
public static Map<Integer, String> portMap = new HashMap<>();
static {
portMap.put(9991, "佐伊");
portMap.put(9992, "狐狸");
portMap.put(9993, "拉克丝");
portMap.put(9994, "蛮族之王");
portMap.put(9995, "盲僧");
}
}
-------------------------------------------------------------------->
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Map;
public class Service {
/**
* 服务端,负责接收客户端的数据,再转发客户端发来的数据给各个客户端
* @param args
*/
public static void main(String[] args) {
try {
DatagramSocket dsSend = new DatagramSocket();
DatagramSocket dsReceive = new DatagramSocket(9999);
InetAddress address = InetAddress.getByName("127.0.0.1");
repeatData(dsSend, dsReceive, address,Global.portMap );
}catch (IOException e){
e.printStackTrace();
}
}
/**
* 转发数据
* @param dsSend 发送端Socket
* @param dsReceive 接收端Socket
* @param address 接收端host
* @param portMap 需要发送的端口号
*/
private static void repeatData(DatagramSocket dsSend, DatagramSocket dsReceive,InetAddress address,Map<Integer, String> portMap) {
try {
byte[] bytes = new byte[1024];
int i = 1;
while (true) {
DatagramPacket dpReceive = new DatagramPacket(bytes, bytes.length);
dsReceive.receive(dpReceive);
for (Integer port : portMap.keySet()) {
DatagramPacket dpSend = new DatagramPacket(bytes, dpReceive.getLength(), address, port);
dsSend.send(dpSend);
}
System.out.println("服务端转发了" + i++ + "数据");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
--------------------------------------------------------------------->
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class Client {
/**
* 客户端类
* 启动多个客户端,互相发送消息
* @param args
*/
public static void main(String[] args) {
int userPort = getUser() + 9990;
//发送线程
Thread sendThread = new Thread(Client::send, Global.portMap.get(userPort));
sendThread.start();
//接受线程
Thread receiveThread = new Thread(() -> receive(userPort), Global.portMap.get(userPort));
receiveThread.start();
}
/**
* 发送端数据
*/
public static void send(){
try {
//创建输入流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//创建发送socket
DatagramSocket ds = new DatagramSocket();
//获取控制台数据,发送到service
String line;
while ((line = br.readLine()) != null){
if(line.equals("exit")){
System.exit(0);
}
byte[] bytes = (Thread.currentThread().getName() + "&&" + line).getBytes();
DatagramPacket dp = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getByName("127.0.0.1"), 9999);
ds.send(dp);
}
}catch (IOException e){
e.printStackTrace();
}
}
/**
* 接收数据
* @param port
*/
public synchronized static void receive(int port){
try {
DatagramSocket ds = new DatagramSocket(port);
System.out.println("用户登录成功!~");
while (true){
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, 0, bytes.length);
ds.receive(dp);
String[] mesArr = new String(bytes, 0, dp.getLength()).split("&&");
String message = mesArr.length > 1 ? mesArr[1] : "";
if(!Thread.currentThread().getName().equals(mesArr[0]) && !message.equals("")){
System.out.println(mesArr[0] + "说----------->" + message);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
/**
* 获取选择客户序号
* @return
*/
public synchronized static int getUser(){
System.out.println("下面是客户列表");
int index = 1;
for (Integer port :Global.portMap.keySet()) {
System.out.println(index++ + " - " + Global.portMap.get(port));
}
do{
System.out.println("请选择几号客户端");
index = new Scanner(System.in).nextInt();
}while (index >5 || index<1 );
return index;
}
}