1.网络编程
概述:就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换
网络模型
概述:计算机网络之间以何种规则进行通信,就是网络模型研究问题
网络模型一般是指:
1.OSI(Open System Interconnection开放系统互联)参考模型
- 应用层
- 表示层
- 会话层
- 传输层
- 网络层
- 数据链路层
- 物理层
2.TCP/IP模型
- 应用层
- 传输层
- 网络层
- 网络接口层
网络编程三要素
- IP地址:网络中计算机的唯一标识
- 端口: 正在运行的程序的标识,有效端口为0-65535,其中0-1024被系统使用或保留
- 协议:通信的规则,分为UDP协议(不安全,不可靠,速度快,不建立连接,把数据打包,数据有限制)和TCP协议(安全,可靠,速度相对较慢,建立通道传输数据,通过三次握手完成连接)
socket(网络套接字)
概述:网络上具有唯一标识的ip地址和端口号组合在一起才能构成唯一能识别的标识符套接字
socket原理:
- 通信的两端都有socket
- 网络通信其实就是socket间的通信
- 数据在两个socket间通过IO传输
2.网络编程在java中的使用
InetAddress(代表互联网协议(IP)协议)
如果一个类没有构造方法,有三种情况:
- 成员全部是静态的
- 单例模式
- 类中有静态方法返回该类的对象
而我们的InetAddress类就是属于第三种情况,他通过静态方法返回本类的对象
InetAddress的成员方法:
- public static InetAddress getName(String host):根据主机名或者ip地址的字符串得到IP地址对象
- public String getHostName():获得主机对象的主机名
- public String getHostAddress():获得主机对象的IP地址
代码示例:
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Demo1 {
public static void main(String[] args) throws UnknownHostException {
//通过静态方法获得InetAddress对象
InetAddress address = InetAddress.getByName("DESKTOP-JOEJMG5");
//获取主机名和主机ip地址
String name = address.getHostName();
String ip = address.getHostAddress();
System.out.println(name + "---" + ip);
}
}
UDP协议发送接受数据
UDP发送数据的步骤:
- 创建发送端Socket对象
- 创建数据,并把数据打包
- 调用socket对象的发送方法发送数据
- 释放资源
代码实现:
//发送数据
//SendDemo.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建UPD的socket对象
DatagramSocket ds = new DatagramSocket();
//创建数据
byte[] bys = "你好,约么?".getBytes();
//数据的长度
int length = bys.length;
//IP地址对象
InetAddress address = InetAddress.getByName("127.0.0.1");
//端口号
int port = 10086;
//把数据打包
DatagramPacket dp = new DatagramPacket(bys, length, address, port);
//发送数据包
ds.send(dp);
//释放资源
ds.close();
}
}
UDP协议接受数据的步骤:
- 创建接收端Socket对象
- 创建一个数据包(接受容器)
- 调用Socket对象的接受方法接受数据
- 解析数据,并显示在控制台
- 释放资源
代码示例:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建一个接收端socket对象,开的是10086端口
DatagramSocket ds = new DatagramSocket(10086);
//创建一个数据包(接受容器)
byte[] bys = new byte[1024];
int length = bys.length;
DatagramPacket dp = new DatagramPacket(bys, length);
//调用socket对象的接受方法接受数据
ds.receive(dp);//阻塞式
byte[] bys2 = dp.getData();//获取数据缓冲区
int len = bys2.length;//获取数据的实际长度
InetAddress address = dp.getAddress();//获得InetAddress对象
String ip = address.getHostAddress();//获取ip地址
String result = new String(bys2, 0, len);
System.out.println(result);
System.out.println("ip:" + ip);
//释放资源
ds.close();
}
}
补充:发送端和接收端要想通信,就得先开启接收端,再开启发送端,但是没有接收端,发送端开启也不会报错,因为UDP不保证数据可以被接收到
多线程实现UDP聊天室代码实现:
//ChatRoom.java
import java.io.IOException;
import java.net.DatagramSocket;
public class ChatRoom {
public static void main(String[] args) throws IOException {
DatagramSocket dsSend = new DatagramSocket();
DatagramSocket dsReceive = new DatagramSocket(12306);
SendThread st = new SendThread(dsSend);
ReceiveThread rt = new ReceiveThread(dsReceive);
Thread t1 = new Thread(st);
Thread t2 = new Thread(rt);
t1.start();
t2.start();
}
}
//SendThread.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class SendThread implements Runnable {
private DatagramSocket ds;
public SendThread(DatagramSocket ds){
this.ds = ds;
}
@Override
public void run() {
try{
//封装键盘录入对象
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = br.readLine()) != null){
if("886".equals(line)){
break;
}
byte[] bys = line.getBytes();
//封装数据包
DatagramPacket dp = new DatagramPacket(bys, bys.length,
InetAddress.getByName("127.0.0.1"), 12306);
//发送数据
ds.send(dp);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
//ReceiveThread.java
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReceiveThread implements Runnable {
private DatagramSocket ds;
public ReceiveThread(DatagramSocket ds){
this.ds = ds;
}
@Override
public void run() {
try{
while(true){
//创建一个包(接受容器)
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//接受数据
ds.receive(dp);
//获取ip值
String ip = dp.getAddress().getHostAddress();
//获取发送过来的数据
String s = new String(dp.getData(), 0, dp.getLength());
System.out.println("from " + ip + " data is:" + s);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
TCP协议发送接受数据
TCP发送数据的步骤:
- 创建发送端的Socket对象:如果这步成功了,说明连接已经建立了,因为如果没有连接成功,是会报错的
- 获取输出流,写数据
- 释放对象
代码示例:
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class SendDemo2 {
public static void main(String[] args) throws IOException {
//创建socket对象
Socket s = new Socket("127.0.0.1", 8006);
//获取输出流,写数据
OutputStream os = s.getOutputStream();
os.write("你好!约吗".getBytes());
//释放资源
s.close();
}
}
TCP接收数据的步骤:
- 创建接收数据的Socket对象
- 监听客户端连接,返回一个对应的Socket对象
- 获取输入流,读取数据显示在控制台
- 释放资源
代码示例:
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ReceiveDemo2 {
public static void main(String[] args) throws IOException {
//创建接收端socket对象
ServerSocket ss = new ServerSocket(8006);
//监听客户端连接,返回一个对应的socket对象
Socket s = ss.accept();//阻塞式
//获取输入流,读取数据到控制台
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String str = new String(bys, 0, len);
System.out.println(str);
//释放资源
s.close();
}
}
补充:TCP发送端启动前,必须要有接受端,不然就会报错,因为TCP是可靠的,保证信息被接收到的
TCP上传文本文件并给出反馈代码案例:
//SendDemo3.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class SendDemo3 {
public static void main(String[] args) throws IOException {
//创建socket对象
Socket s = new Socket("127.0.0.1", 8007);
//封装文本文件
BufferedReader br = new BufferedReader(new FileReader("text1.txt"));
//封装通道内流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line = null;
while((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();
}
//Socket提供了一个终止功能,通知服务器别等了,我没有数据过去了
s.shutdownOutput();
//接收反馈
BufferedReader brReceive = new BufferedReader(new InputStreamReader(s.getInputStream()));
String receive = brReceive.readLine();//阻塞
System.out.println(receive);
//释放资源
bw.close();
br.close();
s.close();
}
}
//ReceiveDemo3.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class ReceiveDemo3 {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8007);
Socket s = ss.accept();//阻塞式
//封装通道内的流
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//封装文本文件
BufferedWriter bw = new BufferedWriter(new FileWriter("text2.txt"));
String line = null;
/*
* 读取文本文件是可以以null作为结束信息的,但是呢,通道内不能这样结束信息的,
* 所以发送端需要使用shutdownOutput()方法通知我们说已经终止发送数据了,不然就会一直阻塞
*/
while((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
bw.flush();
}
//给出反馈
BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bwServer.write("文件上传成功");
bwServer.newLine();
bwServer.flush();
//释放资源
bw.close();
s.close();
}
}
补充:通过while循环可以使得以上的服务端(接受端)接收多个客户端(发送端),但是这个是有问题的,因为当有多个客户端连接时,单线程的服务端就需要一个一个处理,等待,不符合实际情况,所以,我们想要让服务器接受多个客户端,就必须得使用多线程
更多了解请参考:https://www.cnblogs.com/linhaifeng/articles/5937962.html