[关闭]
@w460461339 2016-11-16T07:10:15.000000Z 字数 10116 阅读 757

Java学习Day15(网络编程 UDP/TCP)

Java基础


今天,哦不,是这几天断断续续把Java的网络编程给学习了= - 效率太低了。Anyway,开始吧。

1、网络编程概述

1.1网络编程层级关系

一共有七层,从上到下分别为:

应用层
表示层
会话层
传输层
网络层
数据链入口层
物理层

我们主要是在应用层层面进行编程。

1.2网络通信三要素

网络通信主要有三要素,分别为:IP地址,端口号以及协议。
IP地址是在网络中确认电脑的唯一地址,它帮助我们找到电脑;
端口号是应用程序在该电脑上的地址,它帮助我们在某电脑上找到特定的应用程序;
协议则是传输数据的方式,帮助我们的应用程序确认以哪种方式来理解数据。

1.2.1IP地址

网络中计算机的唯一标识。

计算机只能识别二进制的数据,所以我们的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地址

  1. import java.io.IOException;
  2. import java.net.InetAddress;
  3. public class MyInetDemo {
  4. /**
  5. * @param args
  6. */
  7. public static void main(String[] args) throws IOException{
  8. //以主机名字获取其InetAddress对象。
  9. //注意,它没有构造方法,也不是单一实例模式
  10. //仅能通过调用它的静态方法来创建它的实例
  11. InetAddress id=InetAddress.getByName("WuZifan");
  12. //获取主机的名字
  13. String name=id.getHostName();
  14. //获取主机IP地址
  15. String ipadd=id.getHostAddress();
  16. //输出
  17. System.out.println(name+"--"+ipadd);
  18. }
  19. }
1.2.2端口号

正在运行的程序的标识。
有效端口:0~65535,其中0~1024系统使用或保留端口。

1.2.3协议

通信的规则

UDP:
    把数据打包
    数据有限制
    不建立连接
    速度快
    不可靠

TCP:
    建立连接通道
    数据无限制
    速度慢
    可靠

举例:
    UDP:发短信
    TCP:打电话

2、UDP协议发送数据

首先,想清楚发送和接收数据需要确认的内容:

数据以及数据长度
IP地址以及端口号

在UDP中,发送和接收都需要通过DatagraSocket来进行(相当于电台);而数据都需要通过DatagramPacket来打包(相当于信封)。

2.1 基本发送功能实现

为了方便理解,先写发送端
发送端

  1. package net_02;
  2. import java.io.IOException;
  3. import java.net.DatagramPacket;
  4. import java.net.DatagramSocket;
  5. import java.net.InetAddress;
  6. public class MyUDPSend {
  7. //要抛IOEception
  8. public static void main(String[] args) throws IOException{
  9. //创建UDP的Socket对象
  10. DatagramSocket ds=new DatagramSocket();
  11. //封装要打包的数据
  12. //用这条语句:DatagramPacket(byte[] buf, int length, InetAddress address, int port)
  13. //要打包的数据
  14. byte[] bys="hello,world".getBytes();
  15. //获取长度
  16. int len=bys.length;
  17. //封装目标主机的IP
  18. InetAddress id=InetAddress.getByName("WuZifan");
  19. //封装端口
  20. int port=22222;
  21. //创建DatagramPacket对象
  22. DatagramPacket dp=new DatagramPacket(bys, len, id, port);
  23. //发送数据
  24. ds.send(dp);
  25. //关闭
  26. ds.close();
  27. }
  28. }

接收端

  1. package net_02;
  2. import java.io.IOException;
  3. import java.net.DatagramPacket;
  4. import java.net.DatagramSocket;
  5. public class MyUDPRec {
  6. public static void main(String[] args)throws IOException {
  7. //创建接收的Socket对象,并指定端口(这里和发送端不一样了!)
  8. DatagramSocket ds=new DatagramSocket(22222);
  9. //创建接收的Datagrampacket对象
  10. byte[] bys=new byte[1024];
  11. int len=bys.length;
  12. DatagramPacket dp=new DatagramPacket(bys, len);
  13. //接收数据
  14. //注意此时数据并不会放到之前的bys和len中,需要另外创建
  15. //字节数组和int变量,通过getData和getLength方法啦获得
  16. ds.receive(dp);
  17. //显示数据
  18. byte[] buf=dp.getData();
  19. int length=dp.getLength();
  20. String data=new String(buf,0,length);
  21. System.out.println(data);
  22. //关闭
  23. ds.close();
  24. }
  25. }
2.2 基于多线程的聊天室

基本思想使用线程将发送和接收端封装,使得可以进行持续的发送和接收
测试类

  1. package net_05;
  2. import java.io.IOException;
  3. import java.net.DatagramSocket;
  4. public class MyChat {
  5. /**
  6. * 任务:
  7. * 创建聊天室
  8. * 实现:
  9. * 创建发送端和接收端的datagramsocket对象
  10. * 创建多线程
  11. * 打开多线程
  12. */
  13. public static void main(String[] args) throws IOException{
  14. //创建datagramsocket对象
  15. //注意这里传给dsReve的端口号要和st里面的端口号一样
  16. DatagramSocket dsSend=new DatagramSocket();
  17. DatagramSocket dsReve=new DatagramSocket(22222);
  18. //创建多线程
  19. SendThread st=new SendThread(dsSend);
  20. ReveThread rt=new ReveThread(dsReve);
  21. Thread send=new Thread(st);
  22. Thread reve=new Thread(rt);
  23. //打开线程
  24. send.start();
  25. reve.start();
  26. }
  27. }

发送端

  1. package net_05;
  2. import java.net.DatagramPacket;
  3. import java.net.DatagramSocket;
  4. import java.net.InetAddress;
  5. import java.util.Scanner;
  6. public class SendThread implements Runnable{
  7. //创建发送端DatagramSocket对象
  8. private DatagramSocket ds;
  9. public SendThread(DatagramSocket ds){
  10. this.ds=ds;
  11. }
  12. //多线程要运行的程序
  13. @Override
  14. public void run() {
  15. //从键盘录入数据,并为打包进DatagramPacket做准备
  16. Scanner in=new Scanner(System.in);
  17. byte[] bys=new byte[1024];
  18. int len=0;
  19. while(true){
  20. //这里选择利用try-catch来处理异常
  21. try{
  22. String str=in.nextLine();
  23. //自定义结束符号
  24. if(str.equals("bye")){
  25. break;
  26. }else{
  27. //读取数据以及长度
  28. bys=str.getBytes();
  29. len=bys.length;
  30. //打包
  31. DatagramPacket dp=new DatagramPacket(bys, len,
  32. InetAddress.getByName("WuZifan"),22222);
  33. //发送
  34. ds.send(dp);
  35. }
  36. }catch(Exception e){
  37. e.printStackTrace();
  38. }
  39. }
  40. //注意这个关闭要放在while外面
  41. ds.close();
  42. }
  43. }

接收端

  1. package net_05;
  2. import java.net.DatagramPacket;
  3. import java.net.DatagramSocket;
  4. public class ReveThread implements Runnable{
  5. //创建接收端DatagramSocket对象
  6. private DatagramSocket ds;
  7. public ReveThread(DatagramSocket ds){
  8. this.ds=ds;
  9. }
  10. @Override
  11. public void run() {
  12. //创建接收端接收数据需要的包
  13. byte[] bys=new byte[1024];
  14. int len=bys.length;
  15. DatagramPacket dp=new DatagramPacket(bys, len);
  16. while(true){
  17. try{
  18. //获取数据包
  19. ds.receive(dp);
  20. //获取数据以及长度,并转为String
  21. byte[] bys2=dp.getData();
  22. int len2=dp.getLength();
  23. String str=new String(bys2, 0, len2);
  24. //输出发送主机名以及数据
  25. System.out.println(dp.getAddress().getHostName()+"--"+str);
  26. }catch(Exception e){
  27. e.printStackTrace();
  28. }
  29. }
  30. }
  31. }

这里需要注意的一点是,因为接收端不会关闭,所以在运行测试类的时候,哪怕输入了bye这个自定义结束符号,测试类也不会关闭,因为接收端的线程还在一直开启。

3、ICP协议发送数据

ICP和UDP除了是否需要确定接收以外,在代码层面上最大的不同就是,UDP利用DatagramPacket封装了数据,并直接利用DatagramSocket的send和receive方法来封装了发送和接收的方法,让我们不知道底层是什么;而ICP则很明确的告诉我们,他们是利用IO流来进行数据传输的。
这次我们先写发送端

3.1 ICP协议发送数据基本功能

发送端

  1. package net_03;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.InputStreamReader;
  6. import java.net.ServerSocket;
  7. import java.net.Socket;
  8. public class MyICPServer {
  9. /**
  10. * 服务器端创建步骤:
  11. * A:创建服务器端Socket对象(seversocket)
  12. * B:获取输入流,输出
  13. * C:关闭
  14. */
  15. public static void main(String[] args)throws IOException {
  16. // TODO Auto-generated method stub
  17. //创建服务器端socket对象
  18. ServerSocket ss=new ServerSocket(22222);
  19. //事件监听,在这一步程序阻塞,直到有数据传来
  20. Socket sk=ss.accept();
  21. //获取输入流,这里用高效的字符流获取
  22. BufferedReader br=new BufferedReader(new InputStreamReader(sk.getInputStream()));
  23. String str=br.readLine();
  24. System.out.println(str);
  25. //字节流接收并打印到控制台
  26. //byte[] bys=new byte[1024];
  27. //int len=0;
  28. //InputStream is=sk.getInputStream();
  29. //len=is.read(bys);
  30. //String str=new String(bys,0,len);
  31. //System.out.println(str);
  32. //关闭
  33. ss.close();
  34. }
  35. }

客户端

  1. package net_03;
  2. import java.io.BufferedWriter;
  3. import java.io.IOException;
  4. import java.io.OutputStream;
  5. import java.io.OutputStreamWriter;
  6. import java.net.InetAddress;
  7. import java.net.Socket;
  8. public class MyICPClient {
  9. /**
  10. * 客户端创建步骤:
  11. * A:创建发送端的Socket对象
  12. * B:获取输出流,写入数据
  13. * C:释放资源
  14. */
  15. public static void main(String[] args) throws IOException {
  16. // TODO Auto-generated method stub
  17. //创建发送端Socket对象,指定IP,以及端口号
  18. InetAddress id=InetAddress.getByName("WuZifan");
  19. Socket sc=new Socket(id,22222);
  20. //获取输出流对象(这里用高效的字符流接收)
  21. //创建方式:高效字符流对象=高效字符流(字符字节转换流(字节流))
  22. BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(sc.getOutputStream()));
  23. //写入数据
  24. bw.write("Hello,ICP");
  25. bw.flush();//这个刷新一定不要忘了,不然就读不到了= -
  26. //字节流方法写入数据
  27. //OutputStream os=sc.getOutputStream();
  28. //os.write("hello,ICP".getBytes());
  29. //关闭,因为bw依赖sc,直接关闭sc就好
  30. sc.close();
  31. }
  32. }
3.2 ICP协议发送数据之客户端键盘录入

这里的关键是如何处理多次录入的多条数据;在客户端较好解决,添加自定义结束字符,每次一行行的发送就好;在客户端,也是每次一行行读即可。

但是需要理解这个,就需要理解两个阻塞式方法,一个是ServerSocket的accept方法,一个就是字符/字节流的read或者readLine方法。accept()阻塞是为了等待连接信号,一旦连通了,就往下走;read()或者readLine()阻塞是为了等待数据的进入,一旦有数据进来,程序就往下走。

就是这样
客户端

  1. package net_04;
  2. import java.io.BufferedWriter;
  3. import java.io.IOException;
  4. import java.io.OutputStreamWriter;
  5. import java.net.InetAddress;
  6. import java.net.Socket;
  7. import java.util.Scanner;
  8. public class MyICPClient {
  9. public static void main(String[] args) throws IOException{
  10. //创建客户端Socket对象
  11. Socket sk=new Socket(InetAddress.getByName("WuZifan"),
  12. 22222);
  13. //键盘输入
  14. Scanner in=new Scanner(System.in);
  15. //封装输入流
  16. BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));
  17. String str=null;
  18. //持续输入数据
  19. while(true){
  20. str=in.nextLine();
  21. //直到输入bye时停止输入
  22. if(str.equals("bye")){
  23. break;
  24. }else{
  25. //注意write,newLine以及flush,三者缺一不可。
  26. bw.write(str);
  27. bw.newLine();
  28. bw.flush();
  29. }
  30. }
  31. //关闭
  32. sk.close();
  33. }
  34. }

服务器

  1. package net_04;
  2. import java.io.BufferedReader;
  3. import java.io.IOException;
  4. import java.io.InputStreamReader;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. public class MyICPServer {
  8. public static void main(String[] args) throws IOException{
  9. //创建服务器端Socket对象
  10. ServerSocket ss=new ServerSocket(22222);
  11. //监听
  12. //阻塞式方法,这个方法判断是否有客户端连接
  13. //一旦连接上,就往下继续进行(连接上和有数据过来是两个概念)
  14. Socket sk=ss.accept();
  15. //封装输入数据
  16. BufferedReader br=new BufferedReader(new InputStreamReader(sk.getInputStream()));
  17. String str=null;
  18. //这个readLine或者read在这里都是阻塞式方法
  19. //在没有数据过来时一直停着,当数据过来后才继续往下
  20. //当客户端输入bye时,没有数据继续录入,传入一个空行,结束
  21. while((str=br.readLine())!=null){
  22. System.out.println(str);
  23. }
  24. //关闭
  25. sk.close();
  26. }
  27. }
3.3 上传图片+反馈

上传文件需要注意的有两点:

1、最后记得刷新,以免数据丢失
2、记得shutdown输出流,告诉服务器上传结束

反馈主要就是服务器通过输入流写入,客户端通过输出流读取
客户端

  1. package net_06;
  2. import java.io.BufferedInputStream;
  3. import java.io.BufferedOutputStream;
  4. import java.io.BufferedReader;
  5. import java.io.FileInputStream;
  6. import java.io.IOException;
  7. import java.io.InputStreamReader;
  8. import java.net.InetAddress;
  9. import java.net.Socket;
  10. public class MyClientDemoR {
  11. /**
  12. * @param args
  13. */
  14. public static void main(String[] args) throws IOException{
  15. //创建客户端处Socket对象
  16. Socket sk=new Socket(InetAddress.getByName("WuZifan"), 22222);
  17. //获取输出流
  18. BufferedOutputStream bis=new BufferedOutputStream(sk.getOutputStream());
  19. //获取图片文件,并写入(这个需要再复习一下啊)
  20. //FileinputStream是InputStream的子类
  21. BufferedInputStream mypic=new BufferedInputStream(new FileInputStream("my.jpg"));
  22. byte[] bys=new byte[1024];
  23. int len=0;
  24. while((len=mypic.read(bys))!=-1){
  25. bis.write(bys,0,len);
  26. }
  27. //当之前读入到最后时,还有一部分数据在缓冲区,没有刷进流中
  28. //需要强制刷新一下,让最后的数据进入流
  29. bis.flush();
  30. //在读入数据时可以通过len是否为-1来判断读入是否结束
  31. //但在服务器端接收时却无法读到这个最后的信息。需要人为给一个信息,告诉服务器端我读完了
  32. //通过shutdown方法来告诉服务器,上传完成了
  33. sk.shutdownOutput();
  34. //获取输入流,并显示反馈
  35. BufferedReader br=new BufferedReader(new InputStreamReader(sk.getInputStream()));
  36. String str=br.readLine();//阻塞式
  37. System.out.println(str);
  38. //关闭
  39. mypic.close();
  40. sk.close();
  41. }
  42. }

服务器

  1. package net_06;
  2. import java.io.BufferedInputStream;
  3. import java.io.BufferedOutputStream;
  4. import java.io.BufferedWriter;
  5. import java.io.FileOutputStream;
  6. import java.io.IOException;
  7. import java.io.OutputStreamWriter;
  8. import java.net.ServerSocket;
  9. import java.net.Socket;
  10. public class MySeverDemoR {
  11. /**
  12. * @param args
  13. */
  14. public static void main(String[] args) throws IOException {
  15. //创建服务器端Socket对象
  16. ServerSocket ss=new ServerSocket(22222);
  17. Socket sk=ss.accept();
  18. //创建被输出对象(多复习啊!!)
  19. BufferedOutputStream newpic=new BufferedOutputStream(new FileOutputStream("new.jpg"));
  20. //获取输入流,并显示
  21. BufferedInputStream bis=new BufferedInputStream(sk.getInputStream());
  22. //读取,并写入文件
  23. byte[] bys=new byte[1024];
  24. int len=0;
  25. while((len=bis.read(bys))!=-1){//结束标志是-1,不是0!
  26. newpic.write(bys, 0, len);
  27. }
  28. //获取输出流,并给与回馈
  29. BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(sk.getOutputStream()));
  30. bw.write("收到了");
  31. bw.newLine();
  32. bw.flush();
  33. //关闭
  34. newpic.close();
  35. sk.close();
  36. }
  37. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注