Java基础:网络编程

第一部分:网络通信概述

一、网络通信三要素

1、IP

 IP是网络中计算机的标识,是唯一的。IP分四段(IPV4)组成,每一段都是一个字节,最大值为255。

        随着计算机的普及,公网IP已经远远不够,因此电信厂商将不同地区的IP通过子网掩码来划分,同一个小区的人就使用同一个公网IP地址;同时,四段IP不够用后,就出现了六段IP(IPV6),可以加入字母等。为了方便记忆,出现了主机名与IP的映射关系。

常用网段:192.168.*.*

广播地址:192.168.*.255(指定网段的广播地址)

本地回环地址:127.0.0.1,主机名:localhost。本机没有配置任何IP地址的情况下,本机默认的IP地址,可用于dos命令行测试网卡(ping 127.0.0.1)

2、端口号

用于标识进程的逻辑地址,不同进程的标识。

有效端口:0-65535,其中0-1024被系统使用或保留。

常见端口:

web服务:80

Tomcat服务器:8080

数据库:3306

3、传输协议

通信的规则。常见协议:TCP、UDP

二、网络模型

1、OSI参考模型


注意:

1)OSI参考模型中的传输层封装了TCP/UDP协议;网络层封装了IP协议。

2)物理层包括网线、光纤、无线。

3)数据封包与拆包

        数据从 A电脑应用程序 发送到 B电脑应用程序,会将数据从应用层到表示层依次封包,封装上每一层特有信息,最后经过物理层发送出去就是数据封包。

        B电脑应用程序接收到数据后由物理层到应用层依次拆包,拆掉每一层能识别的信息后,获取到发送的数据,这就是数据拆包。

2、TCP/IP参考模型


注意:

1)开发软件在网际层、传输层、应用层。

2)学习网络编程在网际层与传输层。

第二部分:IP(java.net)

|---- InetAddress

|---- Inet4Address

|---- Inet6Address

一、概述

        Java中提供了描述IP的对象:InetAddress,此类表示互联网协议 (IP) 地址。IP 地址是 IP 使用的 32 位或 128 位无符号数字,它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的。

二、方法

该类没有构造函数,而且有非静态方法存在,说明该类有静态方法返回本类对象。常用方法:

static InetAddress getLocalHost()        返回本地主机
String getHostAddress()        返回 IP 地址字符串(以文本表现形式)
String getHostName()        获取此 IP 地址的主机名
static InetAddress getByName(String host)        在给定主机名的情况下确定主机的 IP 地址
static InetAddress[] getAllByName(String host) 
          在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组

String toString()        主机名/字面值 IP 地址

注意:

1、主机名可以是机器名(如:www.baidu.com),也可以是IP地址的字符串表示形式

2、在使用getByName方法时,参数以IP地址为主,因为主机名需要解析。

三、示例

package day.day11;
import java.net.*;
import java.util.Arrays;
import java.util.List;
/*
 * 需求:获取ip演示
 */
public class IPDemo {
	public static void main(String[] args) throws Exception {
		//获取本机的IP
		InetAddress ip = InetAddress.getLocalHost();
		
		//获取IP地址与主机
		String host = ip.getHostName();
		String address = ip.getHostAddress();
		//输出本机的ip地址与主机
		System.out.println(host+" = "+address);
		
		//获取百度的所有IP(主机)
		InetAddress[] baiduIp = InetAddress.getAllByName("www.baidu.com");
		List<InetAddress> list = Arrays.asList(baiduIp);//数组变集合
		
		System.out.println(list);
	}
}

运行结果:


第三部分:TCP/UDP协议

一、UDP(面向无连接)

1、将数据及源和目的封装到数据报包中,不需要建立连接。

2、每个数据包的大小限制在64K以内。

3、因无连接,是不可靠协议。

4、不需要建立连接,速度快。但易丢包(数据)。

注意:

1)UDP传输不用确定对方在不在,直接发送数据,若不在,则数据包丢失。

2)UDP类似对讲机之间的通信,聊天、视频会议、桌面共享都是走UDP协议,数据的丢失不重要。

二、TCP(面向连接)

1、建立连接,形成传输数据的通道。
2、在连接中进行大数据量传输。
3、通过3次握手完成连接,是可靠协议。
4、必须建立连接,效率会降低。
注意:

1)3次握手:A确定B在不在;B在,反馈给A说在;A知道B已经在。

2)TCP类似打电话,下载走的就是TCP,因其不能丢失数据。

第四部分:UDP传输

|---- DatagramSocket(端点)
|---- DatagramPacket(数据报包)
一、概述

1、Socket就是为网络提供的一种机制。
2、通信的两端都有Sockt。
3、网络通信就是Socket之间的通信。
4、数据在两个Socket之间通过IO传输。
        由于传输协议不一样,每个传输协议都有自己不同的建立端点的方式。网络编程就是Socket编程。

二、基础类及方法

1、DatagramSocket
        此类表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。 

构造方法:

DatagramSocket()        构造数据报套接字并将其绑定到本地主机上任何可用的端口

DatagramSocket(int port)        创建数据报套接字并将其绑定到本地主机上的指定端口

常用方法:

void send(DatagramPacket p)        从此套接字发送数据报包 

void receive(DatagramPacket p)        从此套接字接收数据报包 

void close()        关闭此数据报套接字 

2、DatagramPacket
        此类表示数据报包。数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
构造方法:

DatagramPacket(byte[] buf, int length, InetAddress address, int port) 

          构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号 

DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) 

          构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号

DatagramPacket(byte[] buf, int length) 

          构造 DatagramPacket,用来接收长度为 length 的数据包

DatagramPacket(byte[] buf, int offset, int length) 

          构造 DatagramPacket,用来接收长度为 length 的包,在缓冲区中指定了偏移量

常用方法:

InetAddress getAddress()        返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的

int getPort()        返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的

byte[] getData()        返回数据缓冲区

int getLength()        返回将要发送或接收到的数据的长度

int getOffset()        返回将要发送或接收到的数据的偏移量

三、发送端

1、建立UDP的Socket服务:DatagramSocket(可以发送端指定端口)。
2、提供数据,并将数据、目的主机及端口封装到数据包中。
3、通过DatagramSocket服务的send(发送)功能,将数据包发出去。
4、关闭资源。

示例代码:

package demo;  
import java.io.IOException;  
import java.net.DatagramPacket;  
import java.net.DatagramSocket;  
import java.net.InetAddress;  
/* 
    需求:通过UDP传输方式,将一段文字发送出去 
*/  
public class UDPSendDemo{  
    public static void main(String[] args) throws IOException {  
        //1、建立UDP的Socket服务,即端点:DatagramSocket  
        DatagramSocket socket = new DatagramSocket();  
          
        //2、提供数据,并将数据、目的主机及端口封装到数据报包中  
        byte[] buf = "UDP 传输".getBytes();  
        DatagramPacket bag =   
                new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.104"),10000);  
          
        //3、通过DatagramSocket的send方法将数据发送出去  
        socket.send(bag);  
          
        //4、关闭资源  
        socket.close();  
    }  
} 
注意:数据已经发送出去,只是接收端没有开启,故数据包丢失。

四、接收端

1、创建UDP的Socket服务,即DatagramSocket,并监听指定的端口(不指定则系统随机分配一个)。

2、定义一个数据包,用于存储接收到的字节数据。(数据包对象中有更多功能可以提取字节数据中的不同数据信息,如端口、地址、数据)。

3、通过DatagramSocket服务的receive方法见收到的数据存储到已定义好的数据包中。

4、通过数据包对象的特有功能将这些不同的数据取出。

5、关闭资源。

示例代码:

package demo;  
/* 
 * 需求:定义一个程序,用于接收和处理UPD协议传输的数据 
 */  
import java.io.IOException;  
import java.net.DatagramPacket;  
import java.net.DatagramSocket;  
  
public class UDPReceiveDemo {  
    public static void main(String[] args) throws IOException{  
        //1、创建UDP的Sockt服务,建立端点,并监听指定的端口  
        DatagramSocket socket = new DatagramSocket(10000);  
          
        //2、定于数据包用于存储接收到的数据(借助数组)  
        byte[] buf = new byte[1024];  //缓冲区
        DatagramPacket bag = new DatagramPacket(buf,buf.length);  
          
        //3、通过DatagramSocket的receive方法将接收到的数据存放到数据包中  
        socket.receive(bag);  
          
        //4、通过数据包对象的方法取出数据包中的数据  
        String ip = bag.getAddress().getHostAddress();//ip  
        String data = new String(bag.getData(),0,bag.getLength());//数据  
        int port = bag.getPort();//端口  
          
        System.out.println(ip+"-"+port+":"+data);  
          
        //5、关闭资源  
        socket.close();  
    }  
} 
运行结果:(先开启接收端,再开启发送端)


注意:

1)网络应用程序无论是接收端还是发送端都需要有端口。(发送端也可以指定一个端口)

2)定义UDP接收端的Socket服务,通常会监听一个端口。其实就是给这个接收网络应用程序定义一个数字标识,方便明确哪些数据过来该应用程序可以处理。(不指定端口则系统会随机分配)

3)UDP接收端的receive方法是一个阻塞式方法,没有接收到数据就会一直等。

4)为了能够一直接收数据,可以将接收端的第2、3、4步定义在while(true)中,由于receive方法是阻塞式方法,因此其不会造成死循环的情况。

5)通过多线程技术让发送端与接收端在同一个界面中显示。

6)发送端与接收端是一个独立的应用程序。

7)一般用到java.net包,就会用到java.io包。

五、模拟聊天程序

package demo;
import java.io.*;
import java.net.*;
/*
 * 需求:模拟聊天程序
 */

//描述UDP发送端
class Send implements Runnable{
	private DatagramSocket socket;
	//构造时接收UDP的Socket
	Send(DatagramSocket socket) {
		this.socket = socket;
	}
	//发送端线程运行代码
	public void run(){
		try{
			//读取键盘录入
			BufferedReader bufr = 
					new BufferedReader(new InputStreamReader(System.in));
			String line = null;
			while((line=bufr.readLine())!=null){
				if("over".equals(line))
					break;

				//将键盘录入的数据封装到数据、目的主机及端口封装到数据报包中
				byte[] buf = line.getBytes();
				DatagramPacket bag = 
					new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.104"),10001);
				//将数据发送出去
				socket.send(bag);
			}
			//关闭资源
			socket.close();
		}catch(Exception e){
				throw new RuntimeException("发送失败");
		}
	}
}

//描述接收端
class Receive implements Runnable{
	private DatagramSocket socket;
	//构造时接收UDP的Socket
	Receive(DatagramSocket socket) {
		this.socket = socket;
	}
	//接收端线程运行代码
	public void run(){
		try{
			//循环接收数据(接收端一直开启)
			while(true){
				//将接收到的数据存到数据报包中
				byte[] buf = new byte[1024];
				DatagramPacket bag = new DatagramPacket(buf,buf.length);
				socket.receive(bag);
				
				//解析接收到的数据
				String ip = bag.getAddress().getHostAddress();
				String data = new String(bag.getData(),0,bag.getLength());
				
				System.out.println(ip+":"+data);
			}
			
		}catch(Exception e){
			throw new RuntimeException("接收失败");
		}
	}
}

public class ChatByUDP {
	public static void main(String[] args) throws Exception{
		//建立发送端的Socket服务
		DatagramSocket send = new DatagramSocket();
		//建立接收端的Socket服务,并指定监听的端口
		DatagramSocket rece = new DatagramSocket(10001);
		
		//分别开启发送端与接收端的线程(同时运行)
		new Thread(new Send(send)).start();
		new Thread(new Receive(rece)).start();
	}
}

运行结果:


注意:

1)如果将发送端封装的IP改为192.168.1.255,则表示向192.168.1.*这个网段中所有存活的机器发广播。(存活的机器的指定端口能接收到该数据)

2)该示例中readLine方法与receive方法均为阻塞式方法。


第五部分:TCP传输

|---- Socket(客户端)

|---- ServerSocket(服务端)

一、基础方法

1、Socket

        此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。

构造方法:

Socket()        通过系统默认类型的 SocketImpl 创建未连接端点 
Socket(String host, int port)        创建一个流套接字并将其连接到指定主机上的指定端口号
Socket(InetAddress address, int port)        创建一个流套接字并将其连接到指定 IP 地址的指定端口号 

常用方法:

void connect(SocketAddress endpoint)        将此套接字连接到服务器
InetAddress getInetAddress()        返回套接字连接的地址
InputStream getInputStream()        返回此套接字的输入流
OutputStream getOutputStream()        返回此套接字的输出流
int getPort()        返回此套接字连接到的远程端口
void close()        关闭此套接字
void shutdownInput()        此套接字的输入流置于“流的末尾”
void shutdownOutput()        禁用此套接字的输出流

2、ServerSocket

        此类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。

构造方法:

ServerSocket(int port)        创建绑定到特定端口的服务器端点

ServerSocket(int port, int backlog)         指定最大同时连接该服务端的客户端数

常用方法:

Socket accept()        侦听并接受到此服务端对象
void close()        关闭此套接字

二、客户端

1、创建客户端的Socket服务,因TCP是面向连接的,所以需指定连接的主机与端口。

        若创建成功,则其与服务端的通路就建立了,通路一建立就有了Socket流(网络流),Socket流中有输入流与输出流(通过getInputStream与getOutputStream方法获取)

2、获取Socket服务中的输出流(OutputStream),以发送数据。

3、关闭资源。

图解:


示例代码:

package demo;
import java.net.*;
import java.io.*;
/*
 * 需求:给服务端发送一个文本数据
 */
public class SocketDemo {
	public static void main(String[] args) throws IOException{
		//1、创建客户端的Socket端点,并监听指定的端口
		Socket socket = new Socket("192.168.1.104",10002);
		
		//2、获取Socket服务中的输出流(网络流),以发送数据
		OutputStream out = socket.getOutputStream();
		out.write("TCP 传输演示".getBytes());
		
		//3、关闭资源
		socket.close();
	}
}
注意:在没有开启服务端前开启客户端会发生ConnectException,因为TCP是面向连接的。

三、服务端

1、建立服务端的Socket服务:ServerSocket,并指定监听的端口。

2、获取连接过来的客户端对象。通过ServerSocket的accept方法完成。

3、客户端如果发送过来数据,服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发送过来的数据。

4、关闭服务端。(可选操作)

示例代码:

package demo;
import java.io.*;
import java.net.*;
/*
 * 需求:定义端点接收数据,并打印到控制台上
 */
public class ServerSocketDemo {
	public static void main(String[] args) throws IOException {
		//1、建立服务端的ServerSocket服务,并指定监听的端口
		ServerSocket serverSocket = new ServerSocket(10002);
		
		//2、获取连接过来的客户端对象
		Socket socket = serverSocket.accept();
		String ip = socket.getInetAddress().getHostAddress();//获取IP
		System.out.println(ip+"...connect");
		
		//3、获取客户端发送过来的数据,通过读取流获取
		InputStream in = socket.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println("data:"+new String(buf,0,len));
		
		//4、关闭资源
		socket.close();//关闭客户端,以免占用资源
		serverSocket.close();//关闭服务端,可选操作
	}
}

运行结果:(先启动服务端,再启动客户端)


注意:

1)服务端accept方法是一个阻塞式方法,没有接收到客户端对象就会一直等。

2)获取连接过来的客户端对象时先获取客户端的IP。

3)服务端可以关闭连接过来的客户端,中断网络流,以免占用服务端的资源。

4)服务端可以不用关闭,将服务端的2、3步定义在while(true)中,以方便客户端随时访问。(先启动服务端、再启动客户端)

5)服务端可以通过输出流向客户端发送信息;客户端可以通过输入流来获取服务端发送给客户端的信息。

四、流的结束标记

示例代码1:(定义大写转换服务器)

客户端:

package demo;
/*
 * 需求:客户度键盘录入数据,服务端实时返回大写数据
 * 思路:
 * 		1、操作的是文本数据,采用字符流,提高效率,加入缓冲区技术,且其提供了readLine方法等
 * 		2、注意结束标识,告知服务器,一行数据已经写完
 */
import java.net.*;
import java.io.*;
public class ToUpperCaseSocket {
	public static void main(String[] args) throws IOException {
		//创建客户端的Sockt服务,并指定目的主机的10003端口
		Socket socket = new Socket("192.168.1.104",10003);
		
		//读取键盘录入
		BufferedReader bufr =
				new BufferedReader(new InputStreamReader(System.in));
		String line = null;
	
		//获取Socket流对象(输入流与输出流)
		BufferedWriter bufOut = 
				new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		BufferedReader bufIn = 
				new BufferedReader(new InputStreamReader(socket.getInputStream()));
		
		//将键盘录入的数据发送到服务端、并获取服务端返回的数据
		while((line=bufr.readLine())!=null){
			if("over".equals(line))
				break;
			//向服务端发送数据(不包括\r\n)
			bufOut.write(line);
			//写入一行的结束标记
			bufOut.newLine();
			//将缓冲区中的数据刷到流中
			bufOut.flush();
			
			//获取服务端返回的信息并打印
			String info = bufIn.readLine();
			System.out.println(info);
		}
		
		//关闭键盘录入及网络流
		bufr.close();
		socket.close();
		bufOut.close();
	}
}

服务端:

package demo;
/*
 * 需求:客户度键盘录入数据,服务端实时返回大写数据
 * 思路:
 * 		1、操作的是文本数据,采用字符流,提高效率,加入缓冲区技术,且其提供了readLine方法等
 * 		2、注意结束标识,告知服务器,一行数据已经写完
 */
import java.io.*;
import java.net.*;
public class ToUpperCaseServer {
	public static void main(String[] args) throws IOException {
		//建立服务端的Socket服务,并监听10003端口
		ServerSocket serverSocket = new ServerSocket(10003);
		//获取客户端连接的Socket对象及ip
		Socket socket = serverSocket.accept();
		String ip = socket.getInetAddress().getHostAddress();
		System.out.println(ip+"...connect");
		
		//获取网络流、即Sockt流
		BufferedReader bufIn =
				new BufferedReader(new InputStreamReader(socket.getInputStream()));
		BufferedWriter bufOut = 
				new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		
		String line = null;
		//读取网络输入流中的数据
		while((line=bufIn.readLine())!=null){
			String message = line.toUpperCase();
			
			//将数据转成大写后返回给客户端,不包括\r\n
			bufOut.write(message);
			//写入一行的结束标记
			bufOut.newLine();
			//用到缓冲区就要刷新
			bufOut.flush();
		}
		
		//关闭客户端
		socket.close();
		//关闭网络流
		bufIn.close();
		bufOut.close();
	}
}
运行结果:


注意:上述代码中的输出流可以使用打印流替换。打印流有自动刷新功能及换行功能(自带结束标记)

示例代码2:(向服务器传送图片)

客户端:

package demo;
/*
 * 定义客户端,向服务器上传图片
 */
import java.net.*;
import java.io.*;
public class UploadPic {
	public static void main(String[] args) throws Exception {
		//连接服务端指定端口
		Socket socket = new Socket("192.168.1.104",10004);
		//将硬盘文件与字节输入流关联
		FileInputStream in = new FileInputStream("F:\\11.jpg");
		
		//获取网络输入流与输出流
		OutputStream bufOut = socket.getOutputStream();
		InputStream bufIn = socket.getInputStream();
		
		//读取图片数据,并将其写到网络输入流中
		byte[] buf = new byte[1024];
		int len = 0;
		while((len=in.read(buf))!=-1){
			bufOut.write(buf,0,len);
		}
		
		/* 由于图片数据写完后,while循环结束,但并没有向服务端写入结束标记,服务端的read方法会一直等待。
		 * 因此手动加入一个结束标记,告知服务端已到流的末尾
		 */
		socket.shutdownOutput();
		
		//读取服务端返回的数据
		byte[] tbuf = new byte[1024];
		int length = bufIn.read(tbuf);
		System.out.println(new String(tbuf,0,length));
		
		//关闭流资源
		socket.close();
		in.close();
	}
}
服务端:
package demo;
/*
 * 需求:将客户端发送到服务端的图片保存到硬盘中
 */
import java.net.*;
import java.io.*;
public class DownloadPic {
	public static void main(String[] args) throws IOException {
		//建立服务端
		ServerSocket serverSocket = new ServerSocket(10004);
		
		//获取连接的客户端对象
		Socket socket = serverSocket.accept();
		String ip = socket.getInetAddress().getHostAddress();
		System.out.println(ip+"...connect");
		
		//将字节输出流与硬盘文件关联,并新建一个该文件(不存在则新建)
		FileOutputStream out = new FileOutputStream("F:\\2.jpg");
		
		//获取网络流
		InputStream bufIn = socket.getInputStream();
		OutputStream bufOut = socket.getOutputStream();
		
		//将网络输入流中的数据写到硬盘文件中
		int len = 0;
		byte[] buf = new byte[1024];
		while((len=bufIn.read(buf))!=-1){
			out.write(buf,0,len);
		}
		//向客服端反馈信息
		bufOut.write("上传成功!".getBytes());
		
		//关闭流资源
		out.close();
		socket.close();
	}
}
运行结果:


注意:

1)TCP中程序不容易停止,因为需要结束标记,告知对方已到流的末尾。

2)可以自定义结束标记,如加入时间戳(时间的唯一性),通过DataInputStream与DataOutputStream完成。

3)readLine方法的结束标记是\r\n,在用该方法时要注意。

五、多线程并发访问服务端----服务器原理

        现实生活中,服务器是一直开启的,同时多个客户端可以同时访问服务器。可以通过多线程的方式实现:

示例代码1:(并发上传图片)

客户端:

package demo;
/*
 * 定义客户端,向服务器上传图片
 */
import java.net.*;
import java.io.*;
public class UploadPicByThread {
	public static void main(String[] args) throws Exception {
		//先对上传的文件进行一系列的判断
		if(args.length==0){
			System.out.println("请选择一个图片");
			return ;
		}
		String fileStr = args[0];
		
		File file = new File(fileStr);
		
		if(!file.exists()){
			System.out.println("文件不存在");
			return ;//结束主函数
		}
	
		if(!args[0].endsWith(".jpg")){
			System.out.println("只支持jpg格式的图片");
			return ;
		}
		
		if(file.length()>1024*1024*5){
			System.out.println("图片过大!");
			return ;
		}
		
		//符合要求后上传图片到服务端
		upload(file);
		
	}
	//上传图片
	public static void upload(File file) throws IOException{
		//客户端的Socket服务
		Socket socket = new Socket("192.168.1.104",10005);
		FileInputStream in = new FileInputStream(file);
		
		OutputStream bufOut = socket.getOutputStream();
		InputStream bufIn = socket.getInputStream();
		
		//向服务端上传图片
		byte[] buf = new byte[1024];
		int len = 0;
		while((len=in.read(buf))!=-1){
			bufOut.write(buf,0,len);
		}
		//定义结束标记
		socket.shutdownOutput();
	
		//接收服务端反馈
		byte[] tbuf = new byte[1024];
		int length = bufIn.read(tbuf);
		System.out.println(new String(tbuf,0,length));
		
		socket.close();
		in.close();
	}
}
服务端:
package demo;
/*
 * 需求:将客户端发送到服务端的图片保存到硬盘中
 */
import java.net.*;
import java.io.*;
public class DownloadPicByThread {
	public static void main(String[] args) throws IOException {
		//建立服务端
		ServerSocket serverSocket = new ServerSocket(10005);
		
		while(true){
			//每获取到新的客户端对象,就开启新的线程
			Socket socket = serverSocket.accept();
			new Thread(new Download(socket)).start();
		}
	}
}

//并发上传图片
class Download implements Runnable{
	private Socket socket;
	private int count = 1;
	Download(Socket socket){
		this.socket = socket;
	}
	
	public void run(){
		String ip = socket.getInetAddress().getHostAddress();
		System.out.println(ip+"...connect");
		
		FileOutputStream out = null;
		InputStream bufIn = null;
		OutputStream bufOut = null;
		try {
			//判断硬盘中文件是否存在,存在就重命名
			File path = new File("F:\\pic\\"+count+".jpg");
			while(path.exists()){
				path = new File("F:\\pic\\"+(count++)+".jpg");
			}
			out = new FileOutputStream(path);

			bufIn = socket.getInputStream();
			bufOut = socket.getOutputStream();
			
			//将网络输入流中的数据写到硬盘文件中
			int len = 0;
			byte[] buf = new byte[1024];
			while((len=bufIn.read(buf))!=-1){
				out.write(buf,0,len);
			}
			//向客服端反馈信息
			bufOut.write("上传成功!".getBytes());
			
			
		}catch (IOException e) {
			throw new RuntimeException("上传失败");
		}finally{
			try{
				if(out!=null)
					out.close();
			}catch(IOException e){
				throw new RuntimeException("关闭文件写入字节流失败");
			}
			try{
				socket.close();
			}catch(IOException e){
				throw new RuntimeException("关闭网络流字节流失败");
			}
		}
	}
}
运行结果:


示例代码2:(验证登陆用户名)

客户端:

package demo;
import java.net.*;
import java.io.*;
/*
 * 客户端通过键盘录入用户名
 * 服务端对这个用户名进行检验
 * 
 * 如果该用户存在,在服务端显示xxx,已登陆
 * 在服务端显示,xxx 欢迎光临
 * 
 * 如果该用户不存在,在服务端显示xxx,尝试登陆
 * 在客户端显示,xxx,该用户不存在
 * 
 * 最多就登陆3次
 */

//客户端
public class UserLoginClient {
	public static void main(String[] args) throws Exception{
		//客户端的Socket端点,指定目的主机与端口
		Socket socket = new Socket("192.168.1.104",10007);
		//读取键盘录入
		BufferedReader bufr = 
				new BufferedReader(new InputStreamReader(System.in));
		
		//获取网络流(Socket流)
		PrintWriter bufOut = new PrintWriter(socket.getOutputStream(),true);//自动刷新功能
		BufferedReader bufIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		
		for(int x=0;x<3;x++){
			//获取键盘录入数据
			String line = bufr.readLine();
			//没有输入任何数据(ctrl+c)
			if(line==null)
				break ;
			//将这行数据发送的服务端,ln表示换行(结束标记)
			bufOut.println(line);
			
			//获取服务端反馈信息
			String info = bufIn.readLine();
			System.out.println("info:"+info);
			if(info.contains("欢迎")){
				break;
			}
		}
		bufr.close();
		socket.close();//当键盘录入ctrl+c是,for循环结束,读到该句代码,给服务端发一个 -1(socket流)
	}
}

服务端:

package demo;
import java.io.*;
import java.net.*;
//多线程并发验证
class UserThread implements Runnable{
	private Socket socket;
	UserThread(Socket socket){
		this.socket = socket;
	}
	//线程运行代码
	public void run(){
		//获取IP
		String ip = socket.getInetAddress().getHostAddress();
		System.out.println(ip+"...connect");
		try{
			//验证
			for(int x=0;x<3;x++){
				//获取网络流
				BufferedReader bufIn = 
						new BufferedReader(new InputStreamReader(socket.getInputStream()));
				String name = bufIn.readLine();//客户端发的-1,readLine返回null,会打印null 尝试登陆
				//所以加一个判断判断是否为null的标记

				if(name==null){
					break;
				}
				//创建字符流与硬盘 用户信息文件 相关联
				BufferedReader bufi = new BufferedReader(new FileReader("F:\\userInfo.txt"));

				PrintWriter bufOut = new PrintWriter(socket.getOutputStream(),true);
				
				//定义标记用于验证用于信息
				boolean flag = false;
				//验证用户信息
				String line = null;
				while((line = bufi.readLine())!=null){
					if(line.equals(name)){
						//验证后存在,就更改标记
						flag = true;
						break;
					}
				}
				//向客户端反馈信息
				if(flag){
					bufOut.println(name+",欢迎光临");
					System.out.println(name+",登陆成功");
				}else{
					bufOut.println(name+",用户名不存在");//记得写ln啊
					System.out.println(name+",尝试登陆");
				}
			}
			
			socket.close();
		}catch(IOException e){
			throw new RuntimeException("登陆失败");
		}
	}
}

//服务端
public class UserLoginServer {
	public static void main(String[] args) throws Exception{
		//创建服务端的Socket对象,并监听指定端口
		ServerSocket serverSocket = new ServerSocket(10007);
		while(true){
			Socket socket = serverSocket.accept();
			//每获取到一个客户端对象就开启一个新线程,以达到并发验证的目的
			new Thread(new UserThread(socket)).start();
		}
	}

}
运行结果:



第六部分、浏览器与Tomcat服务器

一、浏览器

浏览器是一个客户端,可以访问服务端。

示例代码:

package com.itheima;
import java.net.*;
/*
 * 需求:自定义服务端让客户端访问
 * 		客户端:浏览器
 * 		服务端:自定义
 */
import java.io.*;
public class HtmlServer {
	public static void main(String[] args) throws Exception{
		//定义服务端的Socket端点
		ServerSocket serverSocket = new ServerSocket(11111);

		//接收客户端对象
		Socket socket = serverSocket.accept();
		String ip = socket.getInetAddress().getHostAddress();
		System.out.println(ip+"....connect");
		
		//通过网络流给客户端返回信息
		PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
		//html代码,能被浏览器解析
		out.println("<font color='red' size='7'>你好欢迎光临</font>");
		
		socket.close();

		serverSocket.close();
	}
}
运行结果:



二、telnet远程登录

telnet是Windows提供的一个远程登录命令,可以访问网络中的任意一台主机,连接完成后可以对这台主机进行命令式配置


注意:

从运行结果可以看出,浏览器可以解析HTML代码,而Telnet命令是将数据原样输出。

三、Tomcat服务器

1、Tomcat是一个服务器软件,纯Java编写。

2、能提供服务说明里面封装了ServerSocket。

3、Tomcat服务器可以读取自定义的资源

4、运行步骤:

1)通过运行bin\startup.bat启动Tomcat服务器

2)启动浏览器,并访问指定主机的tomcat服务器(端口:8080)


注意:访问服务器时要指定端口,否则连接不上。

访问自定义网页:

在 tomcat7.0\webapps 文件下新建一个文件夹myweb,在myweb下创建网页demo.html

<html>
	<body>
		<h1>这是我的主页</h1>
		<font size=5 color = red>欢迎光临</font>
		<div>
			盗梦空间</br>
			星际穿越</br>
			加勒比海盗</br>
		</div>
	</body>
</html>
在浏览器(客户端)中访问该网页:


浏览器收到的源码为:


注意:自定义的网页一定要存放到webapps文件夹中。


四、浏览器HTTP消息头

将浏览器访问服务器时发送的请求消息打印。

示例代码:

package com.itheima;
import java.net.*;
/*
 * 需求:输出客户端访问服务器时的请求信息
 */
import java.io.*;
public class HtmlServer {
	public static void main(String[] args) throws Exception{
		//定义服务端的Socket端点
		ServerSocket serverSocket = new ServerSocket(11111);

		//接收客户端对象
		Socket socket = serverSocket.accept();
		String ip = socket.getInetAddress().getHostAddress();
		System.out.println(ip+"....connect");
		
		//接收来自客户端发送的请求信息并打印
		InputStream in = socket.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);//一次读完
		System.out.println(new String(buf,0,len));
		
		//通过网络流给客户端返回信息
		PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
		out.println("<font color='red' size='7'>你好欢迎光临</font>");
		socket.close();
		serverSocket.close();
	}
}

运行结果:


浏览器请求消息头注解:
http://192.168.1.104:11111/myweb/demo.html(主机:端口号/资源路径/资源)
Http请求消息头: 

GET /myweb/demo.html HTTP/1.1(请求行:浏览器向服务器发送get请求,协议版本1.1)
Accept: text/html, application/xhtml+xml, */*(接收的数据)
Accept-Language: zh-CN(语言)
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6
.0)(用户信息)
Accept-Encoding: gzip, deflate(支持的封装形式:gzip压缩,将网页数据压缩后往浏览器发送)
Host: 192.168.1.104:11111(指定主机的指定端口,一台服务器可以装多台主机,一个主机可以装多个web应用程序)
DNT: 1
Connection: Keep-Alive(连接:保持存活)
(最后一行下面,有一个空行)
请求数据体(与请求数据头用空行隔开,代表哪段数据是头,哪段数据是内容)

五、自定义浏览器(传输层)

示例代码:(无图形化界面)

package day.day13;
import java.io.*;
import java.net.*;
/*
 * 需求:根据浏览器的请求消息头,自定义一个浏览器,访问tomcat服务器。(传输层)
 */
public class MyIE {
	public static void main(String[] args) throws Exception{
		//访问tomcat服务器
		Socket socket = new Socket("192.168.1.104",8080);
		
		PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
		//向服务器发送请求消息头
		out.println("GET /myweb/demo.html HTTP/1.1");
		out.println("Accept: */*");
		out.println("Accept-Language: zh-cn");
		out.println("Host: 192.168.1.104:8080");
		out.println("Connection: closed");//服务器数据发完就结束
		
		out.println();//一定要写空行
		out.println();//打两个空行将请求消息头与内容分开
		
		//获取服务器发来的数据
		BufferedReader bufIn = 
				new BufferedReader(new InputStreamReader(socket.getInputStream()));
		String line = null;
		while((line=bufIn.readLine())!=null){
			System.out.println(line);
		}
		
		socket.close();
	}
}
运行结果:


注意:相比浏览器,自定义IE多了应答消息头。

服务器应答消息头注解:

Tomcat服务器返回的应答消息头
Http应答消息头:

HTTP/1.1 200 OK(支持Http1.1版本;200代表响应状态码:成功(回馈)
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"180-1431137393196"(标记)
Last-Modified: Sat, 09 May 2015 02:09:53 GMT(上传修改时间)
Content-Type: text/html(html文本)
Content-Length: 180(文本大小:180字节)
Date: Sat, 09 May 2015 06:53:18 GMT(时间)
Connection: close
自定义浏览器:(图形化界面)
package day.day13;
/*
 * 需求:创建浏览器(图形化界面)(传输层)
 */
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

public class MyIEGUI
{
	//外部建立引用
	private Frame f;
	private Button b;
	private TextField tf;//
	private TextArea ta;

	MyIEGUI()
	{
		//对象初始化就具备图形化界面
		init();
	}

	public void init()
	{
		f = new Frame("我的浏览器");
		b = new Button("转到");
		
		tf = new TextField(49);//创建一个文本框
		ta = new TextArea(20,56);//创建一个文本区域 --- 行与列

		//对窗体进行基本设置
		f.setBounds(200,200,450,400);
		f.setLayout(new FlowLayout());//布局

		f.add(tf);
		f.add(b);
		f.add(ta);
		//添加事件
		myEvent();

		f.setVisible(true);
	}

	public void myEvent()
	{
		//在按钮事件源上添加活动监听
		b.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				try {
					method();
				} catch (Exception e1) {
					e1.printStackTrace();
				}
			}
		});

		//在文本框事件源上添加键盘监听
		tf.addKeyListener(new KeyAdapter()
		{
			//按下Enter键,运行method方法
			public void keyPressed(KeyEvent e)
			{
				if(e.getKeyCode()==KeyEvent.VK_ENTER)
					try {
						method();
					} catch (Exception e1) {
						e1.printStackTrace();
					}
			}
		});

		//在Frame窗体上添加窗体监听
		f.addWindowListener(new WindowAdapter()
		{
			public void windowClosing(WindowEvent e)
			{
				System.exit(0);
			}
		});
	}

	public void method() throws Exception 
	{
		//清空文本区域
		ta.setText("");
		String url = tf.getText();//http://192.168.1.104:8080/myweb/demo.html
		
		int index1 = url.indexOf("//")+2;
		int index2 = url.indexOf("/",index1);
		String str = url.substring(index1,index2);
		String path = url.substring(index2);
		 
		String[] arr = str.split(":");
		String host = arr[0];
		int port = Integer.parseInt(arr[1]);
		//访问服务器
		goToServer(host,port,path);
	}
	
    public void goToServer(String host, int port, String path) throws Exception {
		//访问tomcat服务器
		Socket socket = new Socket(host,port);//指定主机与端口
		PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
		
		out.println("GET "+path+" HTTP/1.1");//指定资源路径
		out.println("Accept: */*");
		out.println("Accept-Language: zh-cn");
		out.println("Host: 192.168.1.104:8080");
		out.println("Connection: closed");//服务器数据发完就结束
		
		out.println();//一定要写空行
		out.println();//打两个空行将请求消息头与内容分开
		
		//获取服务器发来的数据
		BufferedReader bufIn = 
				new BufferedReader(new InputStreamReader(socket.getInputStream()));
		String line = null;
		while((line=bufIn.readLine())!=null){
			//将服务器返回的数据设置到文本区域中
			ta.append(line+"\r\n");
		}
		socket.close();		
	}

	public static void main(String[] args) 
	{
		new MyIEGUI();
	}
}
运行结果:(传输层)



第七部分:URL类

一、概述

        类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。

        类URI也代表统一资源定位符,它比URL更强大,包括条形码。

二、方法

构造方法:

URL(String spec)        根据 String 表示形式创建 URL 对象
URL(String protocol, String host, int port, String file)        根据指定 protocol、host、port 号和 file 创建 URL 对象

常用方法:

String getProtocol()        获取此 URL 的协议名称
String getHost()        获取此 URL 的主机名(如果适用)
int getPort()        获取此 URL 的端口号

int getDefaultPort()        获取与此 URL 关联协议的默认端口号
String getPath()        获取此 URL 的资源路径部分
String getFile()        获取此 URL 的文件名(path+query)
String getQuery()        获取此 URL 的查询部分

注意:

1)在进行地址访问没有指定端口时,getPort()方法返回-1,则将端口号设置为80,因此上网时默认访问的端口都是80。

2)传输层协议(TCP)访问服务器时,会返回 响应头信息 与 正文主体。而浏览器在访问服务器时只返回正文主体,因其把符合应用层协议(HTTP)的消息部分(响应头)给解析(拆包)了,把正文部分显示在了主体区域内。

重点方法:

URL类中:

URLConnection openConnection() 
          返回一个 URLConnection 对象(子类HttpURLConnection对象),它表示到 URL 所引用的远程对象的连接

InputStream openStream() 
          打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream (先连接再获取输入流)

注意:

1)每次调用此 URL 的协议处理程序的 openConnection 方法都打开一个新的连接。也就是说,只要调用该openConnection()方法,就会去连接这台主机,获取与主机连接的对象(客户端对象Socket)。由于内部完成了封装动作,因此不用再写Socket流,且封装动作是带着Http协议封装的。(不用再写请求消息头部分)

2)之前用Socket流手动与服务器建立连接时,使用的是传输层的协议,因此所有操作都在传输层。而现在openConnection方法将Socket流带着Http协议封装到URLConnection的内部,操作就来到了应用层。

URLConnection类中:

InputStream getInputStream()        返回从此打开的连接读取的输入流(Socket流中的方法)
OutputStream getOutputStream()       返回写入到此连接的输出流(Socket流中的方法)

三、新浏览器(应用层)

示例代码:

package day.day13;
/*
 * 需求:创建浏览器(图形化界面)
 */
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

public class MyNewIEGUI
{
	//外部建立引用
	private Frame f;
	private Button b;
	private TextField tf;//
	private TextArea ta;

	MyNewIEGUI()
	{
		//对象初始化就具备图形化界面
		init();
	}

	public void init()
	{
		f = new Frame("我的浏览器");
		b = new Button("转到");
		
		tf = new TextField(49);//创建一个文本框
		ta = new TextArea(20,56);//创建一个文本区域 --- 行与列

		//对窗体进行基本设置
		f.setBounds(200,200,450,400);
		f.setLayout(new FlowLayout());//布局

		f.add(tf);
		f.add(b);
		f.add(ta);
		//添加事件
		myEvent();

		f.setVisible(true);
	}

	public void myEvent()
	{
		//在按钮事件源上添加活动监听
		b.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				try {
					method();
				} catch (Exception e1) {
					e1.printStackTrace();
				}
			}
		});

		//在文本框事件源上添加键盘监听
		tf.addKeyListener(new KeyAdapter()
		{
			//按下Enter键,运行method方法
			public void keyPressed(KeyEvent e)
			{
				if(e.getKeyCode()==KeyEvent.VK_ENTER)
					try {
						method();
					} catch (Exception e1) {
						e1.printStackTrace();
					}
			}
		});

		//在Frame窗体上添加窗体监听
		f.addWindowListener(new WindowAdapter()
		{
			public void windowClosing(WindowEvent e)
			{
				System.exit(0);
			}
		});
	}

	public void method() throws Exception 
	{
		ta.setText("");//清空文本区域
		URL url = new URL(tf.getText());//封装网址
		//获取URL连接对象
		URLConnection urlConnection = url.openConnection(); 

		//获取来自 服务器 的信息(已经拆包,只获取到正文主体)
		InputStream in = urlConnection.getInputStream();
		byte[] buf = new byte[1024];
		int len = 0;
		while((len=in.read(buf))!=-1){
			ta.append(new String(buf,0,len));
		}
	}

	public static void main(String[] args) 
	{
		new MyNewIEGUI();
	}
}
运行结果:(应用层)


注意:

1、浏览器在访问服务端时,带有请求消息头。服务器对请求消息头进行解析,并找到对应资源后,带着应答消息头将数据返回给浏览器。(使用自定义Socket流时,走的是传输层协议,会将所有的信息都展示出来)

2、使用URLConnection时,走的是应用层。传输层在收到数据后,会向上传递给应用层,应用层中有一个对象HttpURLConncetion,它会将数据包中的 头部分信息 用应用层的(Http)协议拆包,拆完后只将数据主体发给了显示区域。

3、浏览器中封装了许多解析引擎,能够解析HTML代码。

四、其他对象

SocktAddress    抽象类,封装了IP地址与端口号

Socket()需要与connect(SocketAddress endpoint)方法一起使用

ServerSocket(int port, int backlog)         backlog队列长度,同时在线连接数,一般最大50。

第八部分:域名解析

1、通过IP地址访问。

直接访问指定的主机。

2、通过主机名访问。

        先到本机的host文件(C:\Windows\System32\drivers\etc\hosts)中查找是否存在主机名与IP地址的映射关系,若存在,则拿到IP地址后访问;若不存在则到DNS服务器(该服务器中存放的是国内外知名网站的映射关系)中查找是否存在主机名与IP的映射关系,若存在则获取到IP访问该主机,不存在则可能超时导致访问失败。

3、hosst文件的作用

        可以通过getByName(String hostName)方法获取到主机的IP地址,将映射关系存放到本地,以提高上网的速度;也可以将一些垃圾网站的主机名与本地回环地址(127.0.0.1)映射,屏蔽掉这些网站;若不需要更新某些软件,可以将这些软件的主页与本地回环地址映射,不再更新该软件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值