java网络编程
1.1网络通信的模型
OSI将计算机网络体系结构(architecture)划分为以下七层:
- 物理层: 将数据转换为可通过物理介质传送的电子信号 相当于邮局中的搬运工人。
- 数据链路层: 决定访问网络介质的方式。在此层将数据分帧,并处理流控制。本层指定拓扑结构并提供硬件寻址,相当于邮局中的装拆箱工人。
- 网络层: 使用权数据路由经过大型网络 相当于邮局中的排序工人。
- 传输层: 提供终端到终端的可靠连接 相当于公司中跑邮局的送信职员。
- 会话层: 允许用户使用简单易记的名称建立连接 相当于公司中收寄信、写信封与拆信封的秘书。
- 表示层: 协商数据交换格式 相当公司中简报老板、替老板写信的助理。
- 应用层: 用户的应用程序和网络之间的接口。
1.2 TCP/UDP通信协议
TCP:稳定协议,需要经历三次握手,四次挥手(断开连接需进行的确认),效率低下,例如打电话
UDP:不稳定协议,由一方就可以直接发送,不需要在乎对方是否接收到,例如发短信,DDOS攻击
1.3 UDP实现聊天功能(TCP通信需建立连接,百度即可)
UDP不需要对方回应,但是需要知道对面的地址
UDP有关的java类:
-
DatagramPacket 类:建立一个信息包,负责将要发送或接收的信息包装成一个“包裹”
-
DatagramSocket类:代表着使用UDP通信的n台主机,主机之间可以来回发送消息或接收消息
1.3.1通信展示
其中一台主机(以发送消息为主)
package bin; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; /** * 发送信息包的类不需要等待,直接发送 * @author 86155 * */ public class UdpDemo { public static void main(String[] args) throws IOException { //建立一个socket对象用来负责发送消息 DatagramSocket socket = new DatagramSocket(); //获取目标电脑的ip InetAddress targetIp = InetAddress.getByName("localhost"); //设置端口号 int port = 8010;//设置端口号只会通过这个端口进行通信,不会监听端口和占用端口号 //建立要发送的信息包 String msg = "我是UDP发送给你的消息"; //建立发送的信息包的对象:参数说明:1、发送的信息字节数组 2、发送的信息长度 3、目标ip地址 4、目标端口号 DatagramPacket packet = new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,targetIp,port); //发送信息包 socket.send(packet); System.out.println("已发送消息,我不管对面是否能接收到"); //关闭流 socket.close(); } }
-
控制台输出的消息:已发送消息,我不管对面是否能接收到
-
控制台直接结束
下一台主机(负责接收信息为主)
package bin; import java.net.DatagramPacket; import java.net.DatagramSocket; /** * 在建立一个信息包,可以发送数据,也可以接收数据 * @author 86155 * */ public class UpdDemo2 { public static void main(String[] args) throws Exception { //这里使用带端口号的类,可以接收数据 DatagramSocket socket = new DatagramSocket(8010); //建立一个信息包,既可以发送数据,也可以接收数据 byte[] b= new byte[1024]; DatagramPacket packet = new DatagramPacket(b, 0,b.length); //这里的packet负责接收数据包 socket.receive(packet);//将会阻塞等待接收 //将包装好的信息包拆开 System.out.println("客户端"+packet.getSocketAddress()+"发送的信息:"+new String(packet.getData()));//带端口号 System.out.println("客户端"+packet.getAddress()+"发送的信息:"+new String(packet.getData()));//不带端口号 socket.close(); } }
-
控制台输出的消息:客户端/127.0.0.1:50492发送的信息:我是UDP发送给你的消息;客户端/127.0.0.1发送的信息:我是UDP发送给你的消息
-
控制台直接结束
1.3.2 实现循环接收发送
发送信息的类:
package bin;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 发送信息包的类不需要等待,直接发送
* @author 86155
*
*/
public class UdpDemo {
public static void main(String[] args) throws IOException {
//建立一个socket对象用来负责发送消息
DatagramSocket socket = new DatagramSocket();
//获取目标电脑的ip
InetAddress targetIp = InetAddress.getByName("localhost");
//设置端口号
int port = 8010;//设置端口号只会通过这个端口进行通信,不会监听端口和占用端口号
while(true) {
//建立要发送的信息包
//Scanner scan = new Scanner(System.in);与下面实例等价
BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in));
String msg = buffer.readLine();
//建立发送的信息包的对象:参数说明:1、发送的信息字节数组 2、发送的信息长度 3、目标ip地址 4、目标端口号
DatagramPacket packet = new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,targetIp,port);
//发送信息包
socket.send(packet);
System.out.println("已发送消息:"+msg);
if(msg.trim().equals("bye")) {
break;
}
}
//关闭流
socket.close();
}
}
-
输入:你好 输出:已发送消息:你好
-
控制台不结束,继续等待输入
package bin; import java.net.DatagramPacket; import java.net.DatagramSocket; /** * 在建立一个信息包,可以发送数据,也可以接收数据 * @author 86155 * */ public class UpdDemo2 { public static void main(String[] args) throws Exception { //这里使用带端口号的类,可以接收数据 DatagramSocket socket = new DatagramSocket(8010); while(true) { //建立一个信息包,既可以发送数据,也可以接收数据 byte[] b= new byte[1024]; DatagramPacket packet = new DatagramPacket(b, 0,b.length); //这里的packet负责接收数据包 socket.receive(packet);//将会阻塞等待接收 //将包装好的信息包拆开 String data = new String(packet.getData()); System.out.println("客户端"+packet.getAddress()+"发送的信息:"+data);//不带端口号 if(data.trim().equals("bye")) { break; } } socket.close(); } }
- 输入:客户端/127.0.0.1发送的信息:你好 输出:无
- 服务端继续等待接收
1.3.3 聊天系统最终版
思路:一个发送消息类(SendMsg),一个接收消息类(AcceptMsg),这两个类分别继承Runnable接口,通过两个线程的不断监听实现消息的发送和接收。再通过两个people类来测试通信过程
SendMsg类:
package com.hr.threads;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class SendMsg implements Runnable {
private DatagramSocket socket = null;
private DatagramPacket packet = null;
private int port;//要发送到那个端口号
private String address;//目标ip
public SendMsg(int port,String address) {
this.port = port;
this.address = address;
}
@Override
public void run() {
try {
Scanner scan = new Scanner(System.in);
socket = new DatagramSocket();
InetAddress targetIp = InetAddress.getByName(address);
while(true) {
//包装信息包,将信息包打散成字节
String msg = scan.nextLine();
packet = new DatagramPacket(msg.getBytes(), 0,msg.getBytes().length,targetIp,this.port);
//发送
socket.send(packet);
if(msg.trim().equals("bye")) {
break;
}
}
socket.close();
scan.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
AcceptMsg类:
package com.hr.threads;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class AcceptMsg implements Runnable {
private DatagramSocket socket = null;
private DatagramPacket packet = null;
private int port;//要采用那个端口号接收信息
public AcceptMsg(int port) {
this.port = port;
}
@Override
public void run() {
try {
socket = new DatagramSocket(this.port);
while(true) {
//接收信息包
byte[] b =new byte[1024];
packet = new DatagramPacket(b,0,b.length);//规定一次性读取的字节数
socket.receive(packet);
//获取信息包里面的信息
String msg = new String(packet.getData(),0,packet.getLength());
InetAddress ip = packet.getAddress();
//信息展示区域
System.out.println(ip+"发来消息:"+msg);
if(msg.trim().equals("bye")) {
break;
}
}
socket.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
people1:
package com.hr.people;
import com.hr.threads.AcceptMsg;
import com.hr.threads.SendMsg;
public class People1 {
public static void main(String[] args) {
new Thread(new SendMsg(8010,"localhost")).start();
new Thread(new AcceptMsg(8009)).start();
}
}
people2:
package com.hr.people;
import com.hr.threads.AcceptMsg;
import com.hr.threads.SendMsg;
public class People2 {
public static void main(String[] args) {
new Thread(new SendMsg(8009,"localhost")).start();
new Thread(new AcceptMsg(8010)).start();
}
}
总结
通过测试,两个程序均能实现发送和接收消息,如果结合图形化界面,需要知道两台电脑的ip地址和通信端口号,然后将两端程序代码放入两台电脑当中,复杂过程需深入解决
新的知识点
- 代表一个电脑的类:DatagramSocket
- 代表一个信息包的类:DatagramPacket
- 发送消息时需要知道对方的ip地址和进行通信的端口号,ip地址通过 InetAdress ip = InetAdress.getByName(adress);获取
- 接收消息只需要表明自己是以那个端口号进行连接通信的即可
- 发送消息需要将信息包装成一个packet,接收消息需要将信息包打散读取,并告知一次读取的字节数
更新
1.4 通过java代码访问网络资源并下载
思路:获取到网络上的某个资源的url地址,将url进行解析,通过java的io流技术将资源下载到本地
1.4.1 URL类一览
package com.hr.threads;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
public class URLDemo {
public static void main(String[] args) throws IOException {
//伪造的url地址
URL url = new URL("http://localhost:8080/sms/mytest.txt?username=zhangsan&pwd=123456");
System.out.println(url.getAuthority());// localhost:8080
// System.out.println(url.getContent());// 需要一个能够正确链接到资源的地址
System.out.println(url.getDefaultPort()); //80
System.out.println(url.getFile());// /sms/mytest.txt?username=zhangsan&pwd=123456
System.out.println(url.getHost());// localhost
System.out.println(url.getPath());// /sms/mytest.txt
System.out.println(url.getPort());// 8080
System.out.println(url.getProtocol());// http
System.out.println(url.getQuery());// username=zhangsan&pwd=123456
System.out.println(url.getRef());// null
System.out.println(url.getUserInfo());// null
}
}
1.4.2 URL获取网络资源
package com.hr.threads;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
public class URLDemo {
public static void main(String[] args) throws IOException {
//1、获取到要下载资源的url地址
URL url = new URL("https://m10.music.126.net/20210206123341/3842575a88f8be7258cfc1779b91e496/yyaac/015a/0"
+ "55d/525a/60ea8f22932761f95da8e0df17c5d319.m4a");
//获取到资源名称
int last = url.getPath().lastIndexOf("/");
String resource = url.getPath().substring(last);
//2、连接到资源,将资源根据协议转换成相应的对象
URLConnection openConn = url.openConnection();
HttpURLConnection conn = (HttpURLConnection) openConn;
//3、将这个资源放入输入流中(放入本地内存中)
InputStream is = conn.getInputStream();
//4、通过io输出流将资源下载到本地
FileOutputStream fis = new FileOutputStream("E:\\"+resource);
byte[] b = new byte[1024];
int len= -1;
while((len = is.read(b))!= -1) {
fis.write(b,0,len);
}
//5、关闭资源
fis.close();
is.close();
conn.disconnect();
}
}
注意:不要将资源放在C盘中,因为java程序没有写入C盘中的权限,所以放在桌面会报错