网络编程
一、网络模型
1、TCP/IP参考模型图

2、网络编程三要素
举例:
我想和林青霞说话了。肿么办?
A:我要找到林青霞。(电话号码等信息)
这就像一台电脑要和另一台电脑通信,必须要有对方的IP地址
B:对她说话,要对耳朵说。
这里的“耳朵”指的是端口号,一台计算机上的一个应用程序以端口号作为标识
C:我说什么呢?"I Love You"
但是,她没学过英语,听不懂。
我没必要说英语,说汉语就可以了:我爱你
这里我说汉语,林青霞才能听懂,这汉语是我和林青霞的沟通方式,而计算机相互交流,就得要一个规定,这个规定就是各种协议,就像国家的各种语言。
一句话:网络编程的三要素是IP地址、端口、协议
(1)先聊一聊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.254172.16.0.0---172.31.255.255是私有地址。169.254.X.X是保留地址。
C类 192.0.0.1---223.255.255.254192.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 网络地址
(2)端口号:
正在运行的程序的标识。比如QQ的端口号是4000,当然QQ的端口号不止一个。因为你一台电脑同时能登录的QQ号不止一个,如果没记错,一台电脑最多可以
登录10个QQ,因为QQ只有10个端口号。
有效端口:0~65535,其中0~1024系统使用或保留端口。
3、网络通讯过程
(1)找到对方 IP。
(2)数据要发到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行了标识。为了方便称呼这个数字,叫做端口。(逻辑端口)
4、UDP-TCP
UDP 特点:(面向无连接)(聊天)
(1)将数据及源和目的封装成数据包中,不需要建立连接。(封包,无连接)
(2)每个数据包的大小限制在64k内。(小数据)
(3)因无连接,是不可靠协议。(不可靠,丢数据)
(4)不需要建立连接,速度快。(速度快)
TCP 特点:(面向连接)(下载)
(1)建立连接,形成传输数据的通道。(传输,连接)
(2)在连接中进行大数据量传输。(大数据)
(3)通过三次捂手完成连接,是可靠协议。(可靠。在? 我在!我知道你在了)
(4)必须建立连接,效率会稍低。(速度慢)
二、网络通信基础类
1、InetAddress
JAVA提供了InetAddress类代表IP地址,InetAddress类下还有两个子类,Inet4Address,Inet6Address,它们分别代表Internet Protocol version 4(IPv4)地址和Internet Protocol version 6(IPv6)地址
InetAddress类没有构造器,而是提供了两个静态方法来获取InetAddress的对象
public static InetAddress getByName(String host)throws UnknownHostException
根据主机获取对应的InetAddress 对象
public static InetAddress[] getAllByName(String host)throws UnknownHostException
在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。 比如百度的主机不止一台,所以返回多个。
此外InetAddress还提供了一个isReachable的方法。
public boolean isReachable(int timeout) throws IOException
测试是否可以达到该地址。实现尽最大努力试图到达主机,但防火墙和服务器配置可能阻塞请求,使其在某些特定的端口可以访问时处于不可到达状态。
如果可以获得权限,则典型实现将使用 ICMP ECHO REQUEST;否则它将试图在目标主机的端口 7 (Echo) 上建立 TCP 连接。
package cn.wangjing.internet;
import java.io.IOException;
import java.net.InetAddress;
public class InetAddressTest {
public static void main(String[] args) throws IOException {
// 根据主机名来获取对应的InetAddress实例
InetAddress ip = InetAddress.getByName("www.baidu.com");
// 判断是否可达
System.out.println("baidu是否可达:" + ip.isReachable(2000));
// 获取该InetAddress实例的IP字符串
System.out.println(ip.getHostAddress());
// 根据原始IP地址来获取对应的InetAddress实例
InetAddress local = InetAddress.getByAddress(new byte[]{127,0,0,1});
System.out.println("本机是否可达:" + local.isReachable(5000));
// 获取该InetAddress实例对应的全限定域名
System.out.println(local.getCanonicalHostName());
}
}
上面程序简单示范了InetAddress类几个方法用法,InetAddress本身并不复杂,但是它代表了一个IP地址对象,是网络通信的基础,在网络通信中会大量使用该类。
2、URLDecoder 和URLEncoder
URLDecoder和URLEncoder用于完成普通字符串和application/x-www-form-urlencoded MIME 字符串之间的相互转换
在介绍application/x-www-form-urlencoded MIME 字符串之前,先使用www.google.com搜索关键字“黑马程序员”,当我们搜索的关键字包含中文时,这些关键字就变成了“乱码”,实际上这不是乱码,这就是所谓的application/x-www-form-urlencoded MIME字符串。当URL地址里包含非西欧字符的字符串时,系统会将这些非西欧字符串转换成我们所见的“乱码”。
那么编程过程中可能涉及将普通字符串和这种特殊字符串的相关转换,这就需要使用URLDecoder和URLEncoder类,其中:
URLDecoder类包含一个decode(String s,String enc)静态方法,它可以将看上去是乱码的特殊字符串转转成普通字符串。
URLEncoder类包含一个encode(String s,String enc)静态方法,它可以将普通字符串转换成application/x-www-form-urlencoded MIME字符串。
下面程序示范了如何将“乱码”转换成普通字符串,并示范了如何将普通字符串转换成application/x-www-form-urlencoded MIME字符串。
public class URLDecoderTest
{
public static void main(String[] args)
throws Exception
{
// 将application/x-www-form-urlencoded字符串
// 转换成普通字符串
// 其中的字符串直接从图17.3所示窗口复制过来
String keyWord = URLDecoder.decode(
"%B7%E8%BF%F1java", "GBK");
System.out.println(keyWord);
// 将普通字符串转换成
// application/x-www-form-urlencoded字符串
String urlStr = URLEncoder.encode(
"疯狂Android讲义" , "GBK");
System.out.println(urlStr);
}
}
3、使用URL
通常情况而言,URL可以由协议名、主机、端口和资源组成。即满足如下格式:
protocol://host:port/resourceName 例如如下的URL地址:
http://www.oneedu.cn/Index.htm URL类提供了多个构造器用于创建URL对象,一旦获得了URL对象之后,可以调用如下方法来访问该URL对应的资源:
String getFile():获取此URL的资源名。
String getHost():获取此URL的主机名。
String getPath():获取此URL的路径部分。
int getPort():获取此 URL 的端口号。
String getProtocol():获取此 URL 的协议名称。
String getQuery():获取此 URL 的查询字符串部分。
URLConnection openConnection():返回一个URLConnection对象,它表示到URL所引用的远程对象的连接。
InputStream openStream():打开与此URL的连接,并返回一个用于读取该URL资源的InputStream.
URL对象中前面几个方法都非常容易理解,而该对象提供的openStream()可以读取该URL资源的InputStream,通过该方法可以非常方便地读取远程资源--甚至实现多线程下载。
此外URL这个类在android程序设计中是非常常用的,所以得掌握。
二、网络程序编写案例
1、基于UDP协议编写一个数据接收和发送的程序
数据的发送端程序如下:
package cn.wangjing.internet;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
* 数据来自于键盘录入
* 键盘录入数据要自己控制录入结束。
*/
public class SendDemo {
public static void main(String[] args) throws IOException {
// 创建发送端的Socket对象
DatagramSocket ds = new DatagramSocket();
// 封装键盘录入数据
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("192.168.12.92"), 12345);
DatagramPacket dp = new DatagramPacket(bys, bys.length,
InetAddress.getByName("192.168.12.255"), 12345);
// 发送数据
ds.send(dp);
}
// 释放资源
ds.close();
}
}
数据的接收端程序如下:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/*
* 多次启动接收端:
* java.net.BindException: Address already in use: Cannot bind
* 端口被占用。
*/
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
// 创建接收端的Socket对象
DatagramSocket ds = new DatagramSocket(12345);
while (true) {
// 创建一个包裹
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
// 接收数据
ds.receive(dp);
// 解析数据
String ip = dp.getAddress().getHostAddress();
String s = new String(dp.getData(), 0, dp.getLength());
System.out.println("from " + ip + " data is : " + s);
}
// 释放资源(这里接收端应该一直开着等待接收数据,是不需要关闭)
// ds.close();
}
}
从上面的这个小Demo,看出网络编程其实并不难,只要记住网络编程的步骤,都能搞得定。
再次强调一下服务端与客户端编程的步骤
服务端: 1.创建接收端的Socket对象,2.创建一个包裹,3. 解析数据,4.释放资源(看情况)
客户端:1.创建发送端的Socket对象,2. 封装键盘录入数据,3.创建数据并打包,4.发送数据,5.释放资源
2、基于TCP协议编写一个数据接收和发送的程序
要求:数据从键盘录入,输入“886”,结束录入
基于TCP协议的和基于UDP的步骤基本差不多,但也有差别,请仔细看看下面的Demo。
接收端程序:
public class TcpReceive {
public static void main(String[] args) throws Exception {
//创建服务端Socket对象
ServerSocket ss=new ServerSocket(12345);
while(true){
//监听客户端发来的请求
Socket socket = ss.accept();
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line =null ;
while((line=br.readLine())!=null){
System.out.println(line);
}
OutputStream stream = socket.getOutputStream();
stream.write("数据已收到".getBytes());
stream.flush();
socket.close();
}
//关闭资源
//ss.close();
}
}
发送端:
public class TcpSend {
public static void main(String[] args) throws Exception {
Socket s = new Socket("wangjing-PC", 12345);
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line = br.readLine()) != null) {
if (line.equals("886"))
break;
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
//bw.close(); //bw不需要关闭了,因为bw是由s来的,所以s关闭了,bw就不用关闭了
//br.close();
s.close();
}
}
这就是基于TCP的发送与接收程序,可以看到基于TCP发送端的套接字是Socket,接收端的套接字的ServerSocket,而基于UDP协议的,无论是接收端还是发送端的套接字都DatagramSocket。同时发现基于TCP的比基于UDP的要简单一点,因为基于UDP的要对发送数据进行包裹,而TCP是不需要的。
注意:在程序运行的时候,一定要先运行服务端,因为TCP进行连接的时候要三次握手,如果服务端没有开启,客户端发起请求就会被拒绝,此时程序抛出Exception in thread "main" java.net.ConnectException: Connection refused: connect。
3、基于TCP协议编写一个文件上传程序
上传一个叫InetAddressDemo.java的文件到服务器,并且,上传成功之后,告诉客户端文件上传成功
客户端:
public class UploadClient {
public static void main(String[] args) throws IOException {
// 创建客户端Socket对象
Socket s = new Socket("wangjing-PC", 11111);
// 封装文本文件
BufferedReader br = new BufferedReader(new FileReader(
"InetAddressDemo.java"));
// 封装通道内流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
String line = null;
while ((line = br.readLine()) != null) { // 阻塞
bw.write(line);
bw.newLine();
bw.flush();
}
//自定义一个结束标记
//bw.write("over");
//bw.newLine();
<span style="white-space:pre"> </span> //bw.flush();
//Socket提供了一个终止,它会通知服务器你别等了,我没有数据过来了
s.shutdownOutput();
// 接收反馈
BufferedReader brClient = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String client = brClient.readLine(); // 阻塞
System.out.println(client);
// 释放资源
br.close();
s.close();
}
}
服务端:
public class UploadServer {
public static void main(String[] args) throws IOException {
// 创建服务器端的Socket对象
ServerSocket ss = new ServerSocket(11111);
// 监听客户端连接
Socket s = ss.accept();// 阻塞
// 封装通道内的流
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
// 封装文本文件
BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java"));
String line = null;
while ((line = br.readLine()) != null) { // 阻塞
// if("over".equals(line)){
// break;
// }
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();
}
}
4、基于TCP协议编写一个文件上传程序
在实际情况中,一个服务器对应了多个客户端,所下面就模拟一下。
思路:通过while循环可以改进一个服务器接收多个客户端。
* 但是这个是有问题的。
* 如果是这种情况,假设我还有张三,李四,王五这三个人分别执行客户端
* 张三:好好学习.avi(100M)256k
* 李四:天天向上.mp3(3M)1M
* 王五:ILoveJava.txt(1k)100M
现在张三的带宽小,上传的文件却很大,但是他又是第一上传,那么李四,王五肯定等的非常痛苦,所以服务端肯定需要采用多线程。
但是客户端都是一样的,下面贴出服务器端的代码。
服务端:
public class UploadServer {
public static void main(String[] args) throws IOException {
// 创建服务器Socket对象
ServerSocket ss = new ServerSocket(11111);
while (true) {
Socket s = ss.accept();
new Thread(new UserThread(s)).start();
}
}
}
public class UserThread implements Runnable {
private Socket s;
public UserThread(Socket s) {
this.s = s;
}
@Override
public void run() {
try {
// 封装通道内的流
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
// 封装文本文件
// BufferedWriter bw = new BufferedWriter(new
// FileWriter("Copy.java"));
// 为了防止名称冲突
String newName = System.currentTimeMillis() + ".java";
BufferedWriter bw = new BufferedWriter(new FileWriter(newName));
String line = null;
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();
} catch (IOException e) {
e.printStackTrace();
}
}
}
好的今天的博客就写到这,写的不对的地方还请指正!!!