黑马程序员---java网络详解

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


1. 网络参考模型


>> OSI参考模型:


· 7应用层:主要是一些终端应用,比如说FTP,WEB,QQ之类(可以理解为 我们在电脑屏幕上看到的东西就是终端应用)

· 6表示层:主要是对接收的数据进行解释、加密与解密、压缩与解压等(也就是将计算机可以是别的数据转换为人可以识别的数据:图片,声音等)

· 5会话层:通过传输层建立数据传输的通路,主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识,可以是IP地址、MAC地址、主机名)

· 4传输层:定义了一些传输数据的协议和端口号(端口号:传输端口与接收端口),如:TCP(传输控制协议,传输速率低,可靠性强,用于传输一些可靠性要求高,数据量大的数据),UDP(用户数据协议,最大传输64K数据,与TCP相反,如:QQ聊天的消息就是通过UDP来传输的)。主要将从下层接收的数据进行分段和传输,到达目的地地址后再进行重组。常把这一层的数据叫做段。

· 3网络层:主要将从下层接收到的数据(帧)进行IP地址的封装与解封,在这一层工作的设备是路由器(比如:要访问新浪网,就需要通过路由器来指引怎么能到达 新浪网)。常把这一层的数据称为 数据包

· 2数据链路层:主要将从物理层接收来的数据进行MAC地址(网卡地址,每块网卡在出厂时都会有一个全球唯一的地址)的封装与解封装,在这一层工作的设备是交换机,数据通过交换机来传输。这一层的数据通常称为 帧。

· 1物理层:主要定义物理设备标准,如:网线、光纤的接口类型,各种传输介质(无线、蓝牙、红外线)的传输速率等。它的主要作用是传输比特流 (就是先由本地的 1 0 转换为强弱电流,到达目的地后再转换为 1 0)。 这一层的数据叫做比特。


。举例: 有一个数据,从一端(发送端)发送到另一端(接收端):

         [发送]
· 在发送端先通过<应用层>(如QQ)给数据封装一个标记,标记表示是哪个应用发送出去的数据;

· 经过<应用层>封装后到达了<表示层>,<表示层>对数据进行解释,判断出发送出去的是什么数据(图片、文字...),同时添加一层标记,记录数据类型;

· 之后到达了<会话层>,通过<会话层>指定了数据要发送给谁(MAC、IP、主机名)

· 之后到达了<传输层>,通过<传输层>来判断要通过什么协议(TCP、IP)来发送数据,经过哪个端口发送,同时添加标记,记录端口号与传输协议


· 之后到达了<网络层>,将从<传输层>接收到的数据进行IP地址的封装,封装的IP地址表示要发送给哪个IP地址

· 之后到达了<数据链路层>,将从<网络层>接收到的数据进行MAC地址的封装,封装的MAC地址表示要发送给哪个MAC地址

· 之后到达了<物理层>,将数据转换为 模拟信号(强弱电流),通过物理介质(网线、蓝牙...)将数据发送出去

 [接收]
· 从发送端的物理介质(网线、蓝牙...)接收到了模拟信号,先将模拟信号转换为 1 0 数据

· 将转换后的数据从<数据链路层>到<应用层>一层一层的进行解析、解包,到达指定的应用,完成接收


>> TCP/IP参考模型:


· 4应用层:由OSI中的应用层、表示层、会话层这三层合并而成


· 3传输层:同OSI中的传输层一样


· 2网际层:同OSI中的网络层一样


· 1主机至网络层:由OSI中的数据链路层、物理层合并而成
 

2. DNS


。 DNS就是一个域名解析服务器,当要通过主机名访问一个网站时(如:www.sina.com.cn主机名,访问新浪),会通过本机设置的DNS地址对应的服务器来解析
主机名为www.sina.com.cn对应的IP地址,如果解析成功(根据主机名找到了对应的IP),那么可以访问该网站,如果没有找到对应的IP,则无法访问

      一般解析主机名时都会先根据本地hosts文件解析,如果想屏蔽一个网站(如:www.baidu.com),可以在hosts文件中配置 127.0.0.1 www.baidu.com
这样,在本地访问www.baidu.com的时候就不会访问到百度了



。 IP地址对应的java类:InetAddress,可以根据一个给定的主机名或者IP地址来获取一个InetAddress对象,通过获取的对象可以得到主机名、IP地址
对应的主机的信息



3. Socket


。 Socket就是为网络服务提供的一种机制,通信的两端都有Socket,网络通信其实就是Socket通信,数据在两个Socket之间通过IO传输



4. UDP


。 对于 DatagramPacket 的构造方法:如果构造方法的参数中有 InetAddress 、 SocketAddress,则表示该构造方法用于创建要发送的数据包
     如果没有这两项,则表示该构造方法用于创建要接收的数据包


<示例代码1:>

《发送端》




>>  创建UDP传输的发送端


1. 建立UDP的Socket服务(DatagramSocket)
2. 将要发送的数据封装在数据包中(DatagramPacket)
3. 通过UDP的Socket服务将数据包发送出去
4. 关闭Socket服务

package com.kingowe.upd;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * @author 孔超
 * 本类是UDP传输数据的发送端
 *
 */
public class UDPSend {

	public static void main(String[] args) throws IOException{
		System.out.println("发送端启动。。。。。");
 
		/*
		 *   DatagramSocket(int aPort) 
		 *	Constructs a UDP datagram socket which is bound to the specific port aPort on the localhost.
		 */
		DatagramSocket ds = new DatagramSocket(11111); // 【指定当前程序使用的端口号,不指定 编译器随机分配一个】	

		byte[] bs = new String("UDP测试,发送的数据!").getBytes();
		
		/*【将要发送的数据封装在数据包中,并指定端口号,该端口号为接收端 接收数据的端口号】*/
		DatagramPacket dp = new DatagramPacket(bs, bs.length, InetAddress.getByName("192.168.175.1"), 12345);

		ds.send(dp);	// 发送数据包
		ds.close();		
	}

}
《接收端》


>> 创建UDP传输的接收端


1. 建立UDP的Socket服务(DatagramSocket),因为要接收数据,必须明确一个端口号
2. 创建数据包(DatagramPacket),用于存储接收到的数据(数据包对象自己知道怎么解析这些数据)。
3. 使用Socket服务的receive()方法将接收到的数据存储到数据包中
4. 通过数据包中的方法解析这些数据
5. 关闭资源

</pre><pre name="code" class="java">package com.kingowe.upd;


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;


/**
 * @author 孔超
 * 本类是UDP传输数据的接收端
 *
 */
public class UDPReceive {


	public static void main(String[] args)throws IOException {
		System.out.println("接收端启动。。。。。");


		DatagramSocket ds = new DatagramSocket(12345);   // 【建立UDP的Socket服务,同时指定接收数据的端口号】
		
		byte[] bs = new byte[1024];
		// 【接收数据使用参数中没有InetAddress、SocketAddress的构造方法】
		DatagramPacket dp = new DatagramPacket(bs, bs.length);   
		
		ds.receive(dp);	// 该方法是阻塞式方法,如果没有接收到数据,程序会停在这里


		String data = new String(dp.getData());
		// 【数据包中封装的端口号,该端口号是发送端程序使用的端口号(注意与发送数据使用的端口号的区别)】
		int port = dp.getPort();	
		InetAddress iaddress = dp.getAddress();
		String hostName = iaddress.getHostName();
		String hostAddress = iaddress.getHostAddress();
		
		System.out.println("data:" + data);
		System.out.println("port:" + port);
		System.out.println("hostName:" + hostName);
		System.out.println("hostAddress:" + hostAddress);
		
		ds.close();	
	}
}

/*
  *   输出结果:
 *
 * data:UDP测试,发送的数据!
 * port:11111
 * hostName:PC201411121725
 * hostAddress:192.168.175.1
 */


5. TCP/IP

。 【注意】在客户端或服务器端读取数据时,如果缺少结束标记,程序就会死锁,如:在服务器端使用 BufferedReader 中的 readLine() 方法读取 客户端的OutputStream时,会读取一整行数据,而一行数据的结束标记为 \r\n , 当在客户端发送数据时 如果使用的是 print() 之类的方法,没有传递结束标记,那么在服务器端会一直读取,无法进行下一步操作


>> 创建TCP传输的客户端

<示例代码(上传文本到服务器):>

package com.kingowe.tcpupload;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

public class UploadClient {

	public static void main(String[] args)throws IOException {
		
		Socket socket = new Socket("192.168.175.1", 10010);
		
		OutputStream os = socket.getOutputStream();
		PrintWriter out = new PrintWriter(os, true);	
		/* 【这里第二个参数 true 表示的自动刷新,必须要刷新缓存,可以创建PrintWriter
		    时使用这种方式指定自动刷新,也可以每次写完一条数据之后使用 out.flush() 手动刷新】 */
		
		BufferedReader br = new BufferedReader(new FileReader("c:\\client.txt"));
		
		String line = "";
		while((line=br.readLine()) != null){
			
			out.println(line);
		}
		
//		//out.println("over"); // 传统方式,自己指定一个结束标记,当服务器端读到该标记时,表示文件结束,终止读取
		socket.shutdownOutput(); // 【【为socket流添加结束标记,如果不添加结束标记,在服务器端就会一直读取】】
		
		InputStream is = socket.getInputStream();
		BufferedReader read = new BufferedReader(new InputStreamReader(is));
		String result = read.readLine();	// 读取服务器返回的数据
		System.out.println(result);
		
		br.close();
		socket.close();
		
	}

}

>> 创建TCP传输的服务端

<示例代码(上传文本到服务器):>


package com.kingowe.tcpupload;


import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;


public class UploadServer {


	public static void main(String[] args)throws IOException {
		
		ServerSocket ss = new ServerSocket(10010);
		Socket s = ss.accept();		// 取得客户端连接进来的Socket对象
		
		InputStream is = s.getInputStream();
		BufferedReader br = new BufferedReader(new InputStreamReader(is));
		
		PrintWriter out = new PrintWriter(new FileWriter("c:\\server.txt"));	// 【<1>】
		
		String line = "";
		while((line=br.readLine()) != null){


//			//if(line.equals("over")){
//			//	break;
//			//}
//			//out.write(line);
			// 【使用 write()方法,为了不让格式乱掉,需要读取一行在写入时写入一个换行】
//			//out.newLine();	


			out.println(line);
		}


		out.flush();  //【 手动刷新,也可以在 <1> 处创建 PrintWriter 时,指定第二个参数为true(表示自动刷新)】	


		OutputStream os = s.getOutputStream();
		PrintWriter pw = new PrintWriter(new OutputStreamWriter(os));	
		pw.println("上传完毕!!!");
		pw.flush();   
		/* 【如果创建该 PrintWriter时没有添加第二个参数 true ,则这里必须手动刷新,否则上一句的
<span style="white-space:pre">			</span>输出流会保存在PrintWriter中,不会刷新到socket流中,指定自动刷新参数:
			new PrintWriter(new OutputStreamWriter(os), true);】 */
		
		out.close();
		s.close();
		ss.close();
	}


}

。【 以上代码注意点最重要的就三点】:

1. 输入、输出流的一行的结束标记(换行) 
2. 刷新缓冲区
3. 客户端文件上传完成后的结束标记



<示例代码(上传图片到服务器):>

	《客户端》

package com.kingowe.tcppicupload;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

/**
 * 上传图片到服务器程序的客户端
 * @author Administrator
 *
 */

public class PicClient {

	public static void main(String[] args)throws IOException {
		
		Socket s = new Socket("192.168.175.1", 10011);
		
		FileInputStream fis = new FileInputStream("C:\\01.jpg");	// 源文件流
		
		OutputStream os = s.getOutputStream();// 客户端的输出流,用于向服务器端输出数据
		
		byte[] b = new byte[1024];
		int len = 0;
		while((len=fis.read(b)) != -1){
			os.write(b, 0, len);
		}
		os.flush();
		s.shutdownOutput();	// 【记得写关闭标记】
		
		InputStream is = s.getInputStream();	// 客户端的输入流,用于读取服务器端返回的数据
		BufferedReader br = new BufferedReader(new InputStreamReader(is));
		String message = br.readLine();
		System.out.println(message);
		System.out.println("OK");	
		
		fis.close();
		s.close();	
	}

}
《服务器端》

package com.kingowe.tcppicupload;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 上传图片到服务器程序的服务器端
 * @author Administrator
 */

public class PicServer {

	public static void main(String[] args) throws IOException{
		
		ServerSocket ss = new ServerSocket(10011);
		Socket s = ss.accept();
		
		InputStream is = s.getInputStream();	// 服务器端的输入流,用于接收客户端传入的数据
		
		FileOutputStream fos = new FileOutputStream("C:\\00.jpg");
		
		byte[] b = new byte[1024];
		int len = 0;
		while((len=is.read(b)) != -1){
			fos.write(b, 0, len);
		}
		fos.flush();
		
		OutputStream os = s.getOutputStream();
		BufferedWriter pw = new BufferedWriter(new OutputStreamWriter(os));	
		pw.write("上传完毕!!!");
		pw.newLine();	// 【记得写入换行】
		pw.flush();	
					
		fos.close();
		s.close();
		ss.close();
	}

}
>> 以上程序改进:多客户端上传文件到服务器(使用线程)


《服务器端》

package com.kingowe.multithreadupload;

import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 上传图片到服务器程序的服务器端
 * @author Administrator
 */

public class PicServer {

	private static ServerSocket ss;

	public static void main(String[] args) throws IOException{
		
		ss = new ServerSocket(10011);
		int num = 1;
		
		while(true){
			
			Socket s = ss.accept();   // 因为该方法为阻塞式的方法,所以可以while(true)

			new Thread(new MyTask(s, num++)).start();	
			// 【这里,启动一个线程处理服务器端连接进来的客户端】
		}
	}

}

class MyTask implements Runnable{
	
	private Socket s;
	private int num;
	
	public MyTask(Socket s, int num){
		this.s = s;
		this.num = num;
	}
	
	@Override
	public void run() {	// 【在线程中像单客户端一样处理】
		
		try{
			InputStream is = s.getInputStream(); // 服务器端的输入流,用于接收客户端传入的数据
			
			FileOutputStream fos = new FileOutputStream("C:\\0" + num + ".zip");
			
			byte[] b = new byte[1024];
			int len = 0;
			while((len=is.read(b)) != -1){
				fos.write(b, 0, len);
			}
			fos.flush();
			
			OutputStream os = s.getOutputStream();
			BufferedWriter pw = new BufferedWriter(new OutputStreamWriter(os));	
			pw.write("上传完毕!!!");
			pw.newLine();
			pw.flush();	
						
			fos.close();
		
		}catch(IOException e){
			e.printStackTrace();
			
		}finally{
			try {
				s.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}	
	
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值