@w460461339
2016-11-16T07:10:15.000000Z
字数 10116
阅读 757
Java基础
今天,哦不,是这几天断断续续把Java的网络编程给学习了= - 效率太低了。Anyway,开始吧。
一共有七层,从上到下分别为:
应用层
表示层
会话层
传输层
网络层
数据链入口层
物理层
我们主要是在应用层层面进行编程。
网络通信主要有三要素,分别为:IP地址,端口号以及协议。
IP地址是在网络中确认电脑的唯一地址,它帮助我们找到电脑;
端口号是应用程序在该电脑上的地址,它帮助我们在某电脑上找到特定的应用程序;
协议则是传输数据的方式,帮助我们的应用程序确认以哪种方式来理解数据。
网络中计算机的唯一标识。
计算机只能识别二进制的数据,所以我们的IP地址应该是一个二进制的数据。
但是呢,我们配置的IP地址确不是二进制的,为什么呢?
IP:192.168.1.100
换算:11000000 10101000 00000001 01100100
假如真是:11000000 10101000 00000001 01100100的话。
我们如果每次再上课的时候要配置该IP地址,记忆起来就比较的麻烦。
所以,为了方便表示IP地址,我们就把IP地址的每一个字节上的数据换算成十进制,然后用.分开来表示:
"点分十进制"
IP地址的组成:网络号段+主机号段
A类:第一号段为网络号段+后三段的主机号段
一个网络号:256*256*256 = 16777216
B类:前二号段为网络号段+后二段的主机号段
一个网络号:256*256 = 65536
C类:前三号段为网络号段+后一段的主机号段
一个网络号:256
IP地址的分类:
A类 1.0.0.1---127.255.255.254 (1)10.X.X.X是私有地址(私有地址就是在互联网上不使用,而被用在局域网络中的地址) (2)127.X.X.X是保留地址,用做循环测试用的。
B类 128.0.0.1---191.255.255.254 172.16.0.0---172.31.255.255是私有地址。169.254.X.X是保留地址。
C类 192.0.0.1---223.255.255.254 192.168.X.X是私有地址
D类 224.0.0.1---239.255.255.254
E类 240.0.0.1---247.255.255.254
两个DOS命令:
ipconfig 查看本机ip地址
ping 后面跟ip地址。测试本机与指定的ip地址间的通信是否有问题
特殊的IP地址:
127.0.0.1 回环地址(表示本机)
x.x.x.255 广播地址
x.x.x.0 网络地址
InetAddress获取本机IP地址
import java.io.IOException;
import java.net.InetAddress;
public class MyInetDemo {
/**
* @param args
*/
public static void main(String[] args) throws IOException{
//以主机名字获取其InetAddress对象。
//注意,它没有构造方法,也不是单一实例模式
//仅能通过调用它的静态方法来创建它的实例
InetAddress id=InetAddress.getByName("WuZifan");
//获取主机的名字
String name=id.getHostName();
//获取主机IP地址
String ipadd=id.getHostAddress();
//输出
System.out.println(name+"--"+ipadd);
}
}
正在运行的程序的标识。
有效端口:0~65535,其中0~1024系统使用或保留端口。
通信的规则
UDP:
把数据打包
数据有限制
不建立连接
速度快
不可靠
TCP:
建立连接通道
数据无限制
速度慢
可靠
举例:
UDP:发短信
TCP:打电话
首先,想清楚发送和接收数据需要确认的内容:
数据以及数据长度
IP地址以及端口号
在UDP中,发送和接收都需要通过DatagraSocket来进行(相当于电台);而数据都需要通过DatagramPacket来打包(相当于信封)。
为了方便理解,先写发送端
发送端
package net_02;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class MyUDPSend {
//要抛IOEception
public static void main(String[] args) throws IOException{
//创建UDP的Socket对象
DatagramSocket ds=new DatagramSocket();
//封装要打包的数据
//用这条语句:DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//要打包的数据
byte[] bys="hello,world".getBytes();
//获取长度
int len=bys.length;
//封装目标主机的IP
InetAddress id=InetAddress.getByName("WuZifan");
//封装端口
int port=22222;
//创建DatagramPacket对象
DatagramPacket dp=new DatagramPacket(bys, len, id, port);
//发送数据
ds.send(dp);
//关闭
ds.close();
}
}
接收端
package net_02;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class MyUDPRec {
public static void main(String[] args)throws IOException {
//创建接收的Socket对象,并指定端口(这里和发送端不一样了!)
DatagramSocket ds=new DatagramSocket(22222);
//创建接收的Datagrampacket对象
byte[] bys=new byte[1024];
int len=bys.length;
DatagramPacket dp=new DatagramPacket(bys, len);
//接收数据
//注意此时数据并不会放到之前的bys和len中,需要另外创建
//字节数组和int变量,通过getData和getLength方法啦获得
ds.receive(dp);
//显示数据
byte[] buf=dp.getData();
int length=dp.getLength();
String data=new String(buf,0,length);
System.out.println(data);
//关闭
ds.close();
}
}
基本思想使用线程将发送和接收端封装,使得可以进行持续的发送和接收
测试类
package net_05;
import java.io.IOException;
import java.net.DatagramSocket;
public class MyChat {
/**
* 任务:
* 创建聊天室
* 实现:
* 创建发送端和接收端的datagramsocket对象
* 创建多线程
* 打开多线程
*/
public static void main(String[] args) throws IOException{
//创建datagramsocket对象
//注意这里传给dsReve的端口号要和st里面的端口号一样
DatagramSocket dsSend=new DatagramSocket();
DatagramSocket dsReve=new DatagramSocket(22222);
//创建多线程
SendThread st=new SendThread(dsSend);
ReveThread rt=new ReveThread(dsReve);
Thread send=new Thread(st);
Thread reve=new Thread(rt);
//打开线程
send.start();
reve.start();
}
}
发送端
package net_05;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class SendThread implements Runnable{
//创建发送端DatagramSocket对象
private DatagramSocket ds;
public SendThread(DatagramSocket ds){
this.ds=ds;
}
//多线程要运行的程序
@Override
public void run() {
//从键盘录入数据,并为打包进DatagramPacket做准备
Scanner in=new Scanner(System.in);
byte[] bys=new byte[1024];
int len=0;
while(true){
//这里选择利用try-catch来处理异常
try{
String str=in.nextLine();
//自定义结束符号
if(str.equals("bye")){
break;
}else{
//读取数据以及长度
bys=str.getBytes();
len=bys.length;
//打包
DatagramPacket dp=new DatagramPacket(bys, len,
InetAddress.getByName("WuZifan"),22222);
//发送
ds.send(dp);
}
}catch(Exception e){
e.printStackTrace();
}
}
//注意这个关闭要放在while外面
ds.close();
}
}
接收端
package net_05;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ReveThread implements Runnable{
//创建接收端DatagramSocket对象
private DatagramSocket ds;
public ReveThread(DatagramSocket ds){
this.ds=ds;
}
@Override
public void run() {
//创建接收端接收数据需要的包
byte[] bys=new byte[1024];
int len=bys.length;
DatagramPacket dp=new DatagramPacket(bys, len);
while(true){
try{
//获取数据包
ds.receive(dp);
//获取数据以及长度,并转为String
byte[] bys2=dp.getData();
int len2=dp.getLength();
String str=new String(bys2, 0, len2);
//输出发送主机名以及数据
System.out.println(dp.getAddress().getHostName()+"--"+str);
}catch(Exception e){
e.printStackTrace();
}
}
}
}
这里需要注意的一点是,因为接收端不会关闭,所以在运行测试类的时候,哪怕输入了bye这个自定义结束符号,测试类也不会关闭,因为接收端的线程还在一直开启。
ICP和UDP除了是否需要确定接收以外,在代码层面上最大的不同就是,UDP利用DatagramPacket封装了数据,并直接利用DatagramSocket的send和receive方法来封装了发送和接收的方法,让我们不知道底层是什么;而ICP则很明确的告诉我们,他们是利用IO流来进行数据传输的。
这次我们先写发送端
发送端
package net_03;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class MyICPServer {
/**
* 服务器端创建步骤:
* A:创建服务器端Socket对象(seversocket)
* B:获取输入流,输出
* C:关闭
*/
public static void main(String[] args)throws IOException {
// TODO Auto-generated method stub
//创建服务器端socket对象
ServerSocket ss=new ServerSocket(22222);
//事件监听,在这一步程序阻塞,直到有数据传来
Socket sk=ss.accept();
//获取输入流,这里用高效的字符流获取
BufferedReader br=new BufferedReader(new InputStreamReader(sk.getInputStream()));
String str=br.readLine();
System.out.println(str);
//字节流接收并打印到控制台
//byte[] bys=new byte[1024];
//int len=0;
//InputStream is=sk.getInputStream();
//len=is.read(bys);
//String str=new String(bys,0,len);
//System.out.println(str);
//关闭
ss.close();
}
}
客户端
package net_03;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
public class MyICPClient {
/**
* 客户端创建步骤:
* A:创建发送端的Socket对象
* B:获取输出流,写入数据
* C:释放资源
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
//创建发送端Socket对象,指定IP,以及端口号
InetAddress id=InetAddress.getByName("WuZifan");
Socket sc=new Socket(id,22222);
//获取输出流对象(这里用高效的字符流接收)
//创建方式:高效字符流对象=高效字符流(字符字节转换流(字节流))
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(sc.getOutputStream()));
//写入数据
bw.write("Hello,ICP");
bw.flush();//这个刷新一定不要忘了,不然就读不到了= -
//字节流方法写入数据
//OutputStream os=sc.getOutputStream();
//os.write("hello,ICP".getBytes());
//关闭,因为bw依赖sc,直接关闭sc就好
sc.close();
}
}
这里的关键是如何处理多次录入的多条数据;在客户端较好解决,添加自定义结束字符,每次一行行的发送就好;在客户端,也是每次一行行读即可。
但是需要理解这个,就需要理解两个阻塞式方法,一个是ServerSocket的accept方法,一个就是字符/字节流的read或者readLine方法。accept()阻塞是为了等待连接信号,一旦连通了,就往下走;read()或者readLine()阻塞是为了等待数据的进入,一旦有数据进来,程序就往下走。
就是这样
客户端
package net_04;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
public class MyICPClient {
public static void main(String[] args) throws IOException{
//创建客户端Socket对象
Socket sk=new Socket(InetAddress.getByName("WuZifan"),
22222);
//键盘输入
Scanner in=new Scanner(System.in);
//封装输入流
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));
String str=null;
//持续输入数据
while(true){
str=in.nextLine();
//直到输入bye时停止输入
if(str.equals("bye")){
break;
}else{
//注意write,newLine以及flush,三者缺一不可。
bw.write(str);
bw.newLine();
bw.flush();
}
}
//关闭
sk.close();
}
}
服务器
package net_04;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class MyICPServer {
public static void main(String[] args) throws IOException{
//创建服务器端Socket对象
ServerSocket ss=new ServerSocket(22222);
//监听
//阻塞式方法,这个方法判断是否有客户端连接
//一旦连接上,就往下继续进行(连接上和有数据过来是两个概念)
Socket sk=ss.accept();
//封装输入数据
BufferedReader br=new BufferedReader(new InputStreamReader(sk.getInputStream()));
String str=null;
//这个readLine或者read在这里都是阻塞式方法
//在没有数据过来时一直停着,当数据过来后才继续往下
//当客户端输入bye时,没有数据继续录入,传入一个空行,结束
while((str=br.readLine())!=null){
System.out.println(str);
}
//关闭
sk.close();
}
}
上传文件需要注意的有两点:
1、最后记得刷新,以免数据丢失
2、记得shutdown输出流,告诉服务器上传结束
反馈主要就是服务器通过输入流写入,客户端通过输出流读取
客户端
package net_06;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
public class MyClientDemoR {
/**
* @param args
*/
public static void main(String[] args) throws IOException{
//创建客户端处Socket对象
Socket sk=new Socket(InetAddress.getByName("WuZifan"), 22222);
//获取输出流
BufferedOutputStream bis=new BufferedOutputStream(sk.getOutputStream());
//获取图片文件,并写入(这个需要再复习一下啊)
//FileinputStream是InputStream的子类
BufferedInputStream mypic=new BufferedInputStream(new FileInputStream("my.jpg"));
byte[] bys=new byte[1024];
int len=0;
while((len=mypic.read(bys))!=-1){
bis.write(bys,0,len);
}
//当之前读入到最后时,还有一部分数据在缓冲区,没有刷进流中
//需要强制刷新一下,让最后的数据进入流
bis.flush();
//在读入数据时可以通过len是否为-1来判断读入是否结束
//但在服务器端接收时却无法读到这个最后的信息。需要人为给一个信息,告诉服务器端我读完了
//通过shutdown方法来告诉服务器,上传完成了
sk.shutdownOutput();
//获取输入流,并显示反馈
BufferedReader br=new BufferedReader(new InputStreamReader(sk.getInputStream()));
String str=br.readLine();//阻塞式
System.out.println(str);
//关闭
mypic.close();
sk.close();
}
}
服务器
package net_06;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class MySeverDemoR {
/**
* @param args
*/
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
ServerSocket ss=new ServerSocket(22222);
Socket sk=ss.accept();
//创建被输出对象(多复习啊!!)
BufferedOutputStream newpic=new BufferedOutputStream(new FileOutputStream("new.jpg"));
//获取输入流,并显示
BufferedInputStream bis=new BufferedInputStream(sk.getInputStream());
//读取,并写入文件
byte[] bys=new byte[1024];
int len=0;
while((len=bis.read(bys))!=-1){//结束标志是-1,不是0!
newpic.write(bys, 0, len);
}
//获取输出流,并给与回馈
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));
bw.write("收到了");
bw.newLine();
bw.flush();
//关闭
newpic.close();
sk.close();
}
}