黑马程序员----JAVA基础----网络编程

<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流!

一、网络参考模型:OSI参考模型和TCP/IP参考模型

1,TCP/IP参考模型由应用层、传输层、网际层和主机至网络层构成。

2,网络通讯要素:IP地址、端口号、传输协议

IP地址:网络中设备的标识。端口号:用于标识进程的逻辑地址,有效端口0~65535,其中0~1024为系统保留。

传输协议:通讯的规则,常见协议包括TCP(传输控制协议)、UDP(数据报文协议)

3,UDP特点:a,将数据及源和目的地址封装成数据包,不需要建立连接。UDP与邮局邮包裹类似,将物品,自己和对方地址

留下,但对方不一定能接到。b,数据包大小限制在64K内。c,因为无连接,不可靠。d,不需要连接,因此速度快。

TCP特点:建立连接,形成传输数据的通道,在连接中进行大量数据传输。经过三次握手完成连接,是可靠的协议。

必须建立连接,效率会降低。比如打电话、下载数据。

4,IP地址较为复杂,因此在Java中被封装成对象。

InetAddress类没有构造函数,包括Inet4Address和Inet6Address两个子类。

InetAddress提供了getLocalHost方法,可以获得本地主机的ip地址对象,通过对象调用不同的方法可以获得相应的信息。

此外,还提供了getByName方法,参数为主机名或ip地址的字符串形式,通过指定的主机名或IP地址获得ip地址对象

演示代码:

import java.net.InetAddress;
import java.net.UnknownHostException;

public class IPDemo {

	public static void main(String[] args) throws UnknownHostException {
		// 获取本地主机ip地址的对象
		InetAddress ip = InetAddress.getLocalHost();
		System.out.println(ip.getHostAddress()+" : "+ip.getHostName());
		// 获取局域网内其他主机的ip地址对象
		// 通过其他主机的ip或主机的字符串来获得相应的信息
		ip = InetAddress.getByName("USERMIC-JGS5KNU");
		ip = InetAddress.getByName("192.168.31.149");
		System.out.println(ip.getHostAddress());
		// 获取百度的ip地址对象
		ip = InetAddress.getByName("www.baidu.com");
		System.out.println(ip.getHostAddress());<span style="white-space:pre">	</span>// 119.75.218.70
	}

}
5,Socket(套接字)为网络服务提供的一种机制。通信的两端都有Socket,网络通信其实就是Socket间的通信。

数据在两个Socket间通过IO传输(比如港口)。

UDP传输,在java中将UDP封装成对象,DatagramSocket类是用来发送和接收数据报包的套接字。其中数据报包

在java中被封装成对象,用DatagramPacket类描述。

创建一个UDP发送端和UDP接收端

创建UDP发送端思路:先创建UDP的Sokcet服务,将要发送的数据(注意要转成byte格式)封装到数据包中,通过

UDP的Sokcet服务将数据发送(send)出去,关闭资源。

创建UDP接收端思路:先创建UDP的Socket服务,创建数据包对象,用于接收和存储数据(注意要明确使用哪个端口

来接收数据),使用Sokcet的receive方法(阻塞式)接收数据到数据包中,通过数据包对象解析数据,可以得到发送

端的ip、发送端口以及发送的内容等。最后关闭资源。

注意事项:a,发送端在创建Socket服务时,可以明确由哪个端口发送数据,也可以不明确。接收端在创建Socket服务时,

必须明确由哪个端口来接收数据,而且必须与发送端数据报包中封装的端口号一致。

b,发送端数据报包中封装的ip地址最后一位为255时,表示为广播,整个网段都可以接收到数据。

代码如下:

UDP发送端

public class UDPSendDemo {

	public static void main(String[] args) throws IOException {
		System.out.println("UDP发送端启动。。。。。。");
		/*
		 * 创建UDP传说的发送端
		 * 思路:1,建立UDP的Socket服务
		 * 		2,将要发送的数据封装到数据包中
		 * 		3,通过UDP的Socket服务将数据包发送出去
		 * 		4,关闭Socket服务
		 */
		// 建立DatagramSocket服务对象
		DatagramSocket ds = new DatagramSocket();
		// 将要发送的数据封装到数据包中
		String str = "UDP传输演示:哥们来了!";
		byte[] buf = str.getBytes();
		DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),10000);
		// 通过Socket服务将数据报包发送出去
		ds.send(dp);
		// 关闭资源
		ds.close();
	}

}
UDP接收端:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPReceiveDemo {

	public static void main(String[] args) throws IOException {
		System.out.println("UDP接收端启动......");
		/*
		 * 建立UDP接收端
		 * 思路:	1,建立Socket服务,因为是要接收数据,必须要明确用哪个端口来接收数据
		 * 		2,创建数据包对象,用于存储和接收数据,方便用数据包对象的方法解析这些数据
		 * 		3,使用Socket中的receive方法,将这些数据接收到数据包中
		 * 		4,通过数据包对象的方法解析数据包中的数据
		 * 		5,关闭资源
		 */
		// 建立Socket服务,因为是要接收数据,必须要明确用哪个端口来接收数据
		DatagramSocket ds = new DatagramSocket(10000);
		// 创建数据包对象
		byte[] buf = new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf,buf.length);
		// 使用Socket中的receive方法,将这些数据接收到数据包中
		ds.receive(dp);	// 阻塞式方法
		// 通过数据包对象的方法解析数据包中的数据,解析其中的数据、地址和端口等
		// 此处得到的ip为发送信息的主机的ip,端口是发送信息主机的端口,
		// 如果发送主机明确了发送端口,接收端就会显示固定的端口,如果没有明确端口
		// 接收端显示的端口是变化的,注意明确端口是在DatagramSocket的构造函数的参数列表中明确
		// 注意与DatagramPacket中的端口的区别。
		String ip = dp.getAddress().getHostAddress();
		int port = dp.getPort();
		String str = new String(dp.getData(),0,dp.getLength());
		System.out.println(ip+"---"+ port+"---"+str);
		
		
		
	}

}
UDP小练习:聊天程序

聊天程序:即能发送数据,又能接收数据,因此涉及到了多线程技术,启用两条线程,分别封装发送端和接收端。

代码如下:

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

public class ChatDemo {

	public static void main(String[] args) throws IOException {
		// 创建发送端Socket服务,可以明确发送端口,也可以不明确
		DatagramSocket send = new DatagramSocket();
		// 创建接收端Socket服务,必须明确端口号,即用哪个端口来接收数据,这个端口号
		// 与发送端数据包中封装的端口号是一致的。
		DatagramSocket rece = new DatagramSocket(10000);
		new Thread(new Send(send)).start();
		new Thread(new Receive(rece)).start();
	}

}

class Send implements Runnable{
	private DatagramSocket ds;
	// 创建构造函数,用于接收DatagramSocket类的对象
	public Send(DatagramSocket ds) {
		super();
		this.ds = ds;
	}
	@Override
	public void run() {
		try {
			// 创建高效字符输入流
			BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
			String line = null;
			// 由键盘录入数据,当录入字符串是”over“时,停止运行
			while((line= bufr.readLine())!=null){
				byte[] buf = line.getBytes();
				// 创建数据包对象,注意参数列表,当ip地址最后一位为255时,表示广播
				// 参数列表中的10000指定接收端用10000端口来接收数据,因此,在创建接收端
				// Socket服务时,必须明确端口号
				DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),10000);
				ds.send(dp);
				if("over".equals(line))
					break;
			}
			ds.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}
class Receive implements Runnable{
	private DatagramSocket ds;
	// 创建构造函数,用于接收DatagramSocket类的对象
	public Receive(DatagramSocket ds) {
		super();
		this.ds = ds;
	}

	@Override
	public void run() {
		// 只能用try/catch block
		try {
			while(true){
				byte[] buf = new byte[1024];
				// 定义数据包对象,用于接收和存储数据
				DatagramPacket dp = new DatagramPacket(buf,buf.length);
				ds.receive(dp);// 阻塞式
				// 用数据包对象解析所接收的内容,包括发送端ip、发送端口、以及内容
				String ip = dp.getAddress().getHostAddress();
				int port = dp.getPort();
				String text = new String(dp.getData(),0,dp.getLength());
				System.out.println(ip+"---"+port+" : "+text);
				// 当接收到的内容是”over“时,停止
				if(text.equals("over")){
					System.out.println(ip+"exist...");
					break;
				}
			}
		} catch (IOException e) {

		}
	}
	
}
二、TCP传输

1,Socket(客户端)和ServerSocket(服务端)

创建客户端思路:创建客户端Socket服务,使用的是Socket对象,建议在创建时就明确目的和端口;如果创建

连接成功,说明数据传输通道建立,这个通道称为socket流,是底层建立好的,可以提供getInputStream和

getOutputStream方法获取字节流,使用输出流,将数据写出,使用输入流获得服务端发送来的数据,关闭资源。

创建服务端思路:创建服务端Socket服务,必须对外提供一个端口,否则客户端无法连接;获取连接过来的客户端

对象s,通过s.getInputStream()获取客户端发送来的数据;通过s.getOutputStream()获取输出流,将数据发送到

客户端,最后关闭资源。注意,关闭时不仅要关闭服务端,还要关闭客户端,因为客户端不可控。

此外,要先启用服务端。

演示代码:

创建服务端:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo {

	public static void main(String[] args) throws IOException {
		
		/*
		 * TCP服务端建立:
		 * 1,创建TCP服务端Socket服务,使用的是Socket的对象
		 * 2,服务端必须对外提供一个端口,否则客户端无法连接。
		 * 3,获取链接过来的客户端对象
		 * 4,通过客户端对象获取socket流读取客户端发来的数据
		 * 5,关闭资源。注意:关客户端(客户端关闭资源不可控),关服务端
		 */
		// 创建服务端对象,对外提供一个端口
		ServerSocket ss = new ServerSocket(10002);
		// 获取链接过来的客户端对象
		Socket s = ss.accept();	// 阻塞式
		// 通过socket对象获取输入流,读取客户端发来的数据
		InputStream in = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		String text = new String(buf,0,len);
		String ip = s.getInetAddress().getHostAddress();
		int port = s.getPort();
		System.out.println(ip+"---"+port+" : "+text);
		
		// 使用客户端输出流给客户端发送数据
		OutputStream out = s.getOutputStream();
		out.write("收到".getBytes());
		// 关闭客户端及服务端
		s.close();
		ss.close();
	}

}


创建客户端:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class ClientDemo {

	public static void main(String[] args) throws IOException, IOException {

		/*
		 * TCP传输,客户端建立:
		 * 1,创建TCP客户端Sockt服务,使用的是Socket的对象,建议创建时就明确目的地址及端口
		 * 2,如果连接建立成功,说明数据传输通道已建立,这个通道称为Socket流,是底层建立好的,
		 * 	    既然是流,说明既能读又能写,想要获得输入或输出流对象,可以通过Socket获取
		 * 	    通过getInputStream()和getOutputStream()获取字节流
		 * 3,使用输出流,将数据写出。
		 * 4,关闭资源
		 */
		// 创建客户端Socket服务
		Socket socket = new Socket(InetAddress.getLocalHost(),10002);
		// 获取Socket流中的输出流
		OutputStream out = socket.getOutputStream();
		// 使用输出流,将指定的数据写出
		out.write("TCP演示".getBytes());
		
		// 读取服务端返回的数据
		InputStream in = socket.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		String text = new String(buf,0,len);
		System.out.println(text);
		// 关闭资源
		socket.close();
	}

}

2,TCP协议练习--上传文本和图片

a,上传文本思路:

客户端:创建Socket服务(服务端创建时最好指定服务端IP和端口),将要上传的文件进行流的关联,使用socket的输出流将文件数据发送到服务端,

文件发送完毕后要给服务端文件结束标记;通过socket输入流获得服务端的反馈信息,结束流,释放资源。

演示代码:

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

public class UploadClient {

	public static void main(String[] args) throws IOException, IOException {
		// 创建客服端Socket服务
		Socket socket = new Socket(InetAddress.getLocalHost(),10008);
		// 将要上传的文件进行封装
		BufferedReader bufr = new BufferedReader(new FileReader("demo.txt"));
		/*
		 * 将socket流的输出流封装到PrintWriter中,注意参数列表中要加上true,如果不加true,在写出
		 * 数据后,一定要调用flush方法,否则数据不会刷新到socket的输出流中。
		 */
		PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
		// 开始上传数据
		String line = null;
		while((line=bufr.readLine())!=null){
			// 此处要使用println方法,因为该方法带有换行标记。如果用print方法,在服务端的readLine方法由于
			// 没有读到换行标记,就会一直等待下去。
			out.println(line);
		}
		/*
		 * 为什么要结束输出流呢?为了告诉服务端一个结束标记,即文本文件已经上传完毕
		 * 如果没有该条语句,直接关闭socket流也可以
		 * 但是由于客户端的readLine方法在等待服务端的信息,为阻塞式方法,因此不会执行到socket流关闭的语句
		 * 因此服务端的while循环就无法结束
		 * 这样就造成客户端和服务端都在等的情况
		 */
		socket.shutdownOutput();	// 表示输出结束,
		// 接收服务端发来的信息
		BufferedReader bufrin = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		String strin = bufrin.readLine();
		System.out.println(strin);
		// 关闭流,释放资源
		bufr.close();
		socket.close();
	}

}


服务端:创建Socket服务(一定要对外提供一个访问端口!!!),获得客户端的输入流,用字符输出流关联目的文件,获得客户端输入流的数据,通过字

符输出流将数据写入目标文件;获取客户端输出流,将反馈信息发送给客户端,关闭流,释放资源,注意:一定要关闭客户端的流。

此外注意事项:阻塞式方法需要判断结束标记。

演示代码:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class UPloadServer {

	public static void main(String[] args) throws IOException {
		// 创建服务端Socket服务
		ServerSocket ss = new ServerSocket(10008);
		// 获得客户端的socket流
		Socket s = ss.accept();
		System.out.println(s.getInetAddress().getHostAddress()+"......connected");
		// 将客户端的输入流封装
		BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream()));
		// 将服务端的目的文件进行封装
		BufferedWriter bufw = new BufferedWriter(new FileWriter("server.txt"));
		// 接收客户端的数据,并写入目的文件中
		String line = null;
		/*
		 * 如果客户端没有传递文件结束标记,while循环无法结束
		 */
		while((line=bufr.readLine())!=null){
			bufw.write(line);
			bufw.newLine();
		}
		// 给服务端发送信息
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		out.println("upload success");	// 如果用print方法,客户端无法得到数据
		// 关闭流,释放资源,注意一定要关闭客户端的流
		bufw.close();
		s.close();
		ss.close();
	}

}

b,上传图片

上传图片与上传文本方法时一致的,不同的是上传图片需要用的是字节流。

客户端演示代码

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class UploadPhotoClient {

	public static void main(String[] args) throws UnknownHostException, IOException {
		// TODO Auto-generated method stub
		Socket socket = new Socket(InetAddress.getLocalHost(),8888);
		// 读取客户端要上传的文件
		FileInputStream fis = new FileInputStream("a.jpg");
		// 获取Socket输出流,将图片数据发送给服务端
		OutputStream out = socket.getOutputStream();
		
		byte[] buf = new byte[1024];
		int len=0;
		while((len=fis.read(buf))!=-1){
			out.write(buf, 0, len);
		}
		// 上传结束标识
		socket.shutdownOutput();
		// 读取服务端发回的内容
		InputStream in = socket.getInputStream();
		len = in.read(buf);
		String str = new String(buf,0,len);
		System.out.println(str);
		// 关闭流,释放资源
		fis.close();
		socket.close();
	}

}
服务端演示代码:

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

public class UploadPhotoServer {

	public static void main(String[] args) throws IOException {
		// 创建Socket服务
		ServerSocket ss = new ServerSocket(8888);
		// 获得客户端socket流对象
		Socket s = ss.accept();
		System.out.println(s.getInetAddress().getHostAddress()+"......connected");
		// 通过socket对象获得客户端输入流
		InputStream in = s.getInputStream();
		// 封装本地目的文件
		File dir = new File("d:\\photo");
		if(!dir.exists())
			dir.mkdirs();
		File file = new File(dir,"copy.jpg");
		FileOutputStream fos = new FileOutputStream(file);
		// 通过文件输出字节流向目标文件写入数据		
		byte[] buf = new byte[1024];
		int len = 0;
		while((len=in.read(buf))!=-1){
			fos.write(buf, 0, len);
			fos.flush();
		}
		// 获取客户端输出流,发送反馈信息
		OutputStream out = s.getOutputStream();
		out.write("上传成功".getBytes());
		// 关闭流,释放资源
		fos.close();
		s.close();
		ss.close();
	}

}

3,服务端处理多个客户端的请求

思路:将服务端处理客户端请求的内容封装起来,结合多线程技术,实现多个客户端的同时连接。

演示代码:

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

public class ThreadUploadTest {

	public static void main(String[] args) throws IOException {
		// 创建服务端Socket服务
		ServerSocket ss = new ServerSocket(8888);
		while(true){
			Socket s = ss.accept();		//阻塞式方法
			// 开启线程
			new Thread(new ThreadTask(s)).start();;
		}
		
	}

}
/*
 * 将服务端处理内容封装到一个类中,让该类实现Runnable接口
 */
class ThreadTask implements Runnable{
	
	private Socket s = null;

	public ThreadTask(Socket s) {
		super();
		this.s = s;
	}

	@Override
	// 将服务端处理内容封装到线程任务中
	public void run() {
		try {
			String ip = s.getInetAddress().getHostAddress();
			// 创建目的文件并完成与流的关联
			int count = 0;
			File dir = new File("d:\\photo");
			if(!dir.exists())
				dir.mkdirs();
			File file = new File(dir,ip+"copy.jpg");
			while(file.exists()){
				file = new File(dir,ip+"copy"+(++count)+".jpg");
			}
			FileOutputStream fos = new FileOutputStream(file);
			// 得到客户端的输入流
			InputStream in = s.getInputStream();
			// 读取客户端数据,并存入目的文件
			byte[]	buf = new byte[1024];
			int len = 0;
			while((len=in.read(buf))!=-1){
				fos.write(buf, 0, len);
			}
			// 得到客户端输出流,输出反馈信息
			OutputStream os = s.getOutputStream();
			os.write("上传成功".getBytes());
			// 关闭流,释放资源
			fos.close();
			s.close();
		} catch (IOException e) {
		
		}
	}
	
}

三、常见客户端和服务端

最常见的客户端:IE

最常见的服务端:Tomcat

Tomecat服务器对外提供了一个Servlet接口

1,模拟一个Tomcat服务器

使用IE浏览器访问模拟的服务器,演示代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class MyTomcat {

	public static void main(String[] args) throws IOException {
		// 创建服务端Socket,必须对外提供一个端口
		ServerSocket ss = new ServerSocket(9090);
		// 获得客户端socket流
		Socket s = ss.accept();
		System.out.println(s.getInetAddress().getHostAddress()+"......connected");
		// 通过客户端Socket对象获得客户端的输入流
		InputStream in = s.getInputStream();
		// 读取客户端信息
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		String text = new String(buf,0,len);
		System.out.println(text);
		// 给客户端一个信息
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		out.println("Welcom");
		// 关闭流,释放资源
		s.close();
		ss.close();
	}

}
效果如下:


2,网络结构

a,C/S   client/server

特点:该结构的软件,客户端和服务端都需要编写,开发成本高,维护较为麻烦。

好处:客户端在本地可以分担一部分运算

b,B/S browser/server

特点:该结构的软件,只开发服务端,不开发客户端,因为客户端直接由浏览器取代。开发成本低,维护简单。

缺点:所有运算都在服务端完成。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值