黑马程序员 Java基础<十八>---> 网路编程

第一  概述

 

一、概述:

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

OSI参考模型(从上到下) --> TCP/IP参考模型

应用层 ------------------>
表示层   应用层
会话层------------------>
传输层------------------>传输层
网络层------------------>网际层
数据链路层
物理层------------------>主机至网络层

2、网络通讯要素:

1)IP地址:InetAddress

  .网络中设备的标识
  .不易记忆,可用主机名
  .本地回环地址:127.0.0.1  主机名:localhost
2)端口号
.用于标识进程的逻辑地址,不同进程的标识
.有效地址0~65535,其中0~1024系统使用或保留端口
3)传输协议:
.通讯规则
  .常见协议:TCP、UDP

①UDP(面向无连接)-->聊天、网络视频会议、步话机

.将数据及源和目的封装成数据包中,不需要建立连接

.每个数据包的大小限制在64k内

.因无连接,是不可靠的协议

.不需要建立连接,速度快

②TCP(面向连接)-->下载,打电话

.建立连接,形成传输数据的通道

.在连接中进行大数据量的传输

.通过三次握手完成连接,是可靠的协议

.必须建立连接,效率会稍低

注:三次握手:第一次本方发送请求,第二次对方确认连接,第三次本方再次发送确认信息告诉对方,这样双方就都知道了,从而才能建立连接

 

3、通信的步骤:

1)IP:找到需要通讯的IP地址

2)端口:数据要发送到对象指定应用程序,为标识这些应用程序,所以给这些网络应用程序都用数字标识,为方便称呼这个数字,叫做端口,即逻辑端口。每个网络程序都有自己唯一的标识端口。

3)定义通信规则,称之为协议。国际组织定义了通用协议,即TCP/IP。

注意:必须要有数字标识才能将数据发送到应用程序上。因为端口是明确数据需要有哪个应用程序来处理的标识。

看图:



二、网络模型:

1、对于TCP/IP协议,开发处于传输层和网际层

      应用层:FTP和HTTP协议等

      传输层:UDP和TCP等

      网际层:IP

三、网络通信要素:

IP地址:java中对应的是InetAddress类,存在于java.net包中。

InetAddress类:

1、无构造函数,可通过getLocalHost()方法获取InetAddress对象,此方法是静态的,返回此对象。

       InetAddress i = InetAddress.getLocalHost();

2、方法:

1)static InetAddress getByName(String host):在给定主机名的情况下获取主机的IP地址

2)String getHostAddress():返回IP地址字符串文本形式,以这个为主,即以IP地址为主。

3)String getHostName():返回IP地址主机名。

3、如何获取任意一台主机的IP地址对象:

 

public class IPDemo {
	public static void main(String[] args) throws IOException {
		InetAddress inetAddress = InetAddress.getLocalHost();
		System.out.println(inetAddress.toString());
		System.out.println(inetAddress.getHostAddress());
		System.out.println(inetAddress.getHostName());
		
//		InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
//		System.out.println("address: " + inetAddress.getHostAddress());
//		System.out.println("name: " + inetAddress.getHostName());
		
		InetAddress[] ia = InetAddress.getAllByName("www.baidu.com");
		for(int i=0; i<ia.length; i++){
			System.out.println("address: " + ia[i].getHostAddress());
			System.out.println("name: " + ia[i].getHostName());
		}
	}
}

 

 

第二  网络编程

一、概述:

Socket:

1、Socket就是为网络服务提供的一种机制

2、通讯的两端都必须有Socket

3、网络通讯其实就是Socket间的通讯

4、数据在两个Socket间通过IO传输

二、UDP传输:发送端和接收端是两个独立运行的程序

1、UDP传输的流程:

.DatagramSocke(创建Socket对象在发送端和接收端通讯)和DatagramPacket(发送端将数据封包,接收端将数据解包)

.建立发送端、接收端

.建立数据包

.调用Socket的发送和接收方法

.关闭Socket

 

2、方法:

1)创建 UDPSocket发送服务对象:DatagramSocket(),可不指定端口

2)创建 UDPSocket接收服务对象:DatagramSocket(int port)

3)发送:void send(DatagramPacket p)

4)接收:void receive(DatagramPacket p)

其中DatagramPacket:数据报包用来实现无连接包投递服务的,每条报文仅根据该包中包含的信息从一台机器路由到另一台机器中。

凡是带地址(InetAddress)的都是用于发送包的。

看实例:

 

/*
 * 需求:通过udp传输方式,将一段文字发送出去
 * 定义一个udp发送端
 * 思路:
 * 1、建立udp的socket服务
 * 2、提供数据,并将数据封装到数据包中
 * 3、通过socket服务的发送功能将数据发送出去
 * 4、关闭资源
 */
public class UDPSend {
	public static void main(String[] args) throws SocketException, Exception {
		// 1、建立udp服务,通过DategramSocket对象
		DatagramSocket ds = new DatagramSocket(8888);// 指定固定的端口,不指定系统随机分配

		// 2、确定数据,并封装成数据包DatagramPacket(byte[] buf, int length, InetAddress
		// address, int port)
		byte[] buf = "udp send message come on".getBytes();// 数据
		DatagramPacket dp = new DatagramPacket(buf, buf.length,
				InetAddress.getByName("192.168.229.1"), 1000);

		// 3、通过socket服务的send方法将数据包发送出去
		ds.send(dp);

		// 4、关闭资源
		ds.close();
	}
}

 

/*
 * 需求:定义一个应用程序,用于接收udp协议传输的数据并处理
 * 
 * 定义udp接收端口
 * 思路:
 * 1、定义udp的socket服务,通常会监听一个端口。其实就是给这个接收网络应用程序定义一个数字标识,
 * 明确哪些数据过来该应用程序可以处理
 * 2、定义一个数据包,用于存储接收到的字节数据,可以方便提取数据中的信息
 * 3、通过socket的receive方法将接收到的数据存入以定义好的数据包中
 * 4、通过数据包中特有功能,将不同的数据取出
 * 5、关闭资源
 */
public class UDPReceive {
	public static void main(String[] args) throws Exception {
		//1、建立udp的socket服务,并指定端点
		DatagramSocket ds = new DatagramSocket(1000);
		
		//2、定义数据包,用于存储数据
		byte[] buf = new byte[1024];
		DatagramPacket dp = new DatagramPacket(buf, buf.length);
		
		//3、通过socket服务的receive方法将接收到的数据存入数据包中
		ds.receive(dp);//阻塞式方法
		
		//4、通过数据包的方法获取其中的数据
		String ip = dp.getAddress().getHostAddress();//获取ip
		String data = new String(dp.getData(),0,dp.getLength());//获取数据
		int port = dp.getPort();//获取端口号
		System.out.println(ip+"::"+data+"::"+port);
		
		//5、关闭资源
		ds.close();
	}
}


在看一个获取键盘录入的发送端,服务端就不写了,都差不多:

 

 

/*
 * 发送键盘录入信息
 */
public class UDPSend2 {
	public static void main(String[] args) throws Exception {
		DatagramSocket ds = new DatagramSocket();
		BufferedReader bfr = new BufferedReader(
				new InputStreamReader(System.in));
		String line = null;
		byte[] buf = null;
		DatagramPacket dp = null;
		while ((line = bfr.readLine()) != null) {
			buf = line.getBytes();
			dp = new DatagramPacket(buf, buf.length,
					InetAddress.getByName("192.168.229.1"), 10000);//192.168.1.255广播地址,在这个频段里的所有ip都可以接收
			ds.send(dp);
			
			if("886".equals(line)){
				break;
			}
		}
		ds.close();
		buf.clone();
	}
}


练习:编写一个简单的聊天程序:

 

分析:

有收数据的部分,有发数据的部分,这两部分需要同时执行,那就需要多线程技术,一个线程控制接收,一个线程控制发。

因为收和发的动作不一致,所以要定义两个run方法,而且这个两个方法要封装到不同的类中。

/*
 编写一个聊天程序。
 有收数据的部分,和发数据的部分。
 这两部分需要同时执行。
 那就需要用到多线程技术。
 一个线程控制收,一个线程控制发。

 因为收和发动作是不一致的,所以要定义两个run方法。
 而且这两个方法要封装到不同的类中。
 */
public class UDPChatDemo {
	public static void main(String[] args) throws IOException {
		DatagramSocket sendSocket = new DatagramSocket();
		DatagramSocket ReceiveSocket = new DatagramSocket(10002);
		
		new Thread(new ChatSend(sendSocket)).start();
		new Thread(new ChatReceive(ReceiveSocket)).start();
	}
}
/*
 * 聊天的发送端
 */
public class ChatSend implements Runnable{
	
	private DatagramSocket ds;
	
	public ChatSend(DatagramSocket ds){
		this.ds = ds;
	}

	@Override
	public void run() {
		BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
		byte[] buf = null;
		String line = null;
		try {
			while((line=bfr.readLine()) != null){
				buf = line.getBytes();
				DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("192.168.1.255"), 10002);
				ds.send(dp);
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			ds.close();
			try {
				bfr.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
}
/*
 * 聊天,接收端
 */
public class ChatReceive implements Runnable{
	
	private DatagramSocket ds;
	
	public ChatReceive(DatagramSocket ds){
		this.ds = ds;
	}

	@Override
	public void run() {
		while(true){
			byte[] buf = new byte[1024];
			DatagramPacket dp = new DatagramPacket(buf, buf.length);
			try {
				ds.receive(dp);
				String ip = dp.getAddress().getHostAddress();
				String data = new String(dp.getData(),0,dp.getLength());
				int port = dp.getPort();
				System.out.println(ip + "::" + data + "::" + port);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}


三、TCP传输:

1、流程:

.Socket(客户端)和ServiceSocket服务端()

.建立客户端和服务端

.建立连接后,通过socket中的IO流进行数据的传输

.关闭socket

 

2、方法:

1)创建客户端对象: Socket(String host,int port),指定要接收的IP地址和端口号

2)创建服务端对象:ServerSocket(int port):指定接收的客户端的端口

3)Socket accept():侦听并接受到此套接字的连接,服务器用于接收客户端socket对象的方法

注:服务器没有socket流,也就没有读写操作的流,服务器是通过获取到客户端的socket流然后获取到其中的读写方法,对数据进行操作的,也正是因为这样服务器与客户端的数据操作才不会错乱

4)void shutdownInput():此套接字的输入流至于“流的末尾”

5)void shutdownOutput():禁用此套接字的输出流

6)InputStream getInputStream():返回此套接字的输入流

7)OutputStream getOutputStream():返回套接字的输出流

实例:

 

/*
 * 定义tcp的服务端
 * 
 * 1、建立服务端的socket服务,ServerSocket,并监听一个端口
 * 2、获取连接过来的客服端对象,通过ServerSokcet的 accept方法。没有连接就会等,所以这个方法阻塞式的。
 * 3、客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据。
 * 4、关闭服务端(可选)
 */
public class TCPServerDemo {
	public static void main(String[] args) throws IOException {
		// 建立服务端的socket服务,并监听一个端口
		ServerSocket ss = new ServerSocket(10003);

		// 通过accept方法获取连接过来的客户端对象
		Socket socket = ss.accept();

		String ip = socket.getInetAddress().getHostAddress();
		System.out.println(ip + "......connected");

		// 获取客户端发过来的数据,那么要使用客户端对象的读取流来获取数据
		InputStream in = socket.getInputStream();

		byte[] buf = new byte[1024];
		int len = in.read(buf);
		System.out.println(new String(buf, 0, len));

		socket.close();// 关闭客户端
		ss.close();// 关闭服务端,可选的操作
	}
}

 

/*
 * tcp分为客户端和服务端,客服端对象的对象是socket,服务端对应的是serversoceket
 * 
 * 客户端,在建立的时候就可以去连接指定的主机
 * 因为tcp是面向连接的,所以在建立socket时就需要有服务端存在
 * 并连接成功,形成通路,才能在该通道上传输数据
 */
public class TCPClientDemo {
	public static void main(String[] args) throws Exception {
		//1、创建客户端的socket服务,指定目的主机和端口
		Socket socket = new Socket("192.168.229.1", 10003);
		
		//2、获取socket的输出流,用于发送数据
		OutputStream out = socket.getOutputStream();
		//3、发送数据
		out.write("tcp client send message come on".getBytes());
		
		socket.close();
	}
}


示例二:下面是客户端和服务端互动的例子,建立一个文本转换机制,就是客户端把从键盘录入的数据发送给服务器,服务器将数据转为大写再发给客户端,分析在注释里面:

 

 

/*
 * 需求:建立一个文本转换服务器。
 * 客户端给服务端发送文本,服务单会将文本转成大写在返回给客户端。
 * 而且客户度可以不断的进行文本转换。当客户端输入over时,转换结束。
 * 
 * 分析:
 * 客户端
 * 既然是操作设备上的数据,那就可以使用IO技术,并按照io操作规律来思考
 * 源:键盘录入
 * 目的:网络设备,网络输出流
 * 而且操作的是文本数据,可以用字符流
 * 
 * 步骤:、
 * 1、建立服务
 * 2、获取键盘输入
 * 3、将数据发给客户端
 * 4、获取服务端返回的数据
 * 5、结束,关闭资源
 */
class TransClient {
	public static void main(String[] args) throws UnknownHostException,
			IOException {
		Socket s = new Socket(InetAddress.getLocalHost(), 10005);
		BufferedReader bfrb = new BufferedReader(new InputStreamReader(
				System.in));

		// 定义目的,将数据写入socket输出流,发给服务器
		OutputStream out = s.getOutputStream();
		OutputStreamWriter ow = new OutputStreamWriter(out);
		BufferedWriter bfw = new BufferedWriter(ow);

		// 定义读取流
		InputStream in = s.getInputStream();
		InputStreamReader is = new InputStreamReader(in);
		BufferedReader bfr = new BufferedReader(is);

		String line = null;
		while ((line = bfrb.readLine()) != null) {
			bfw.write(line);
			bfw.newLine();
			bfw.flush();

			// 读取数据
			String data = bfr.readLine();
			System.out.println(data);
		}

		bfrb.close();
		s.close();
	}
}

/*
 * 服务端: 源:socket读取流。 目的:socket输出流。
 */
class TransServer {
	public static void main(String[] args) throws IOException {
		ServerSocket ss = new ServerSocket(10005);
		Socket s = ss.accept();

		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip + "....connected");

		InputStream in = s.getInputStream();
		InputStreamReader isr = new InputStreamReader(in);
		BufferedReader bfr = new BufferedReader(isr);

		// 目的。socket输出流。将大写数据写入到socket输出流,并发送给客户端。
		BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(
				s.getOutputStream()));
		
		String line = null;
		while((line=bfr.readLine()) != null){
			bufOut.write(line.toUpperCase());
			bufOut.newLine();
			bufOut.flush();
		}
		s.close();
		ss.close();
	}
}

public class TCPTransText{
	public static void main(String[] args) {
		new TransClient();
		new TransServer();
	}
}

 


示例三:TCP复制文件

 

1、客户端:

源:硬盘上的文件;目的:网络设备,即网络输出流。若操作的是文本数据,可选字符流,并加入高效缓冲区。若是媒体文件,用字节流。

2、服务端:

源:socket读取流;目的:socket输出流。

3、出现的问题:

现象:

a.文件已经上传成功了,但是没有得到服务端的反馈信息。

b.即使得到反馈信息,但得到的是null,而不是“上传成功”的信息

原因:

a.因为客户端将数据发送完毕后,服务端仍然在等待这读取数据,并没有收到结束标记,就会一直等待读取。

b.上个问题解决后,收到的不是指定信息而是null,是因为服务端写入数据后,也需要刷新,才能将信息反馈给客服端。

解决:

a.方法一:定义结束标记,先将结束标记发送给服务端,让服务端接收到结束标记,然后再发送上传的数据。但是这样定义可能会发生定义的标记和文件中的数据重复而导致提前结束。

   方法二:定义时间戳,由于时间是唯一的,在发送数据前,先获取时间,发送完后在结尾处写上相同的时间戳,在服务端,接收数据前先接收一个时间戳,然后在循环中判断时间戳以结束标记。

  方法三:通过socket方法中的shutdownOutput(),关闭输入流资源,从而结束传输流,以给定结束标记。这里主要用这个方法。

/*
 * 客户端
 */
class TextClient {
	public static void main(String[] args) throws UnknownHostException,
			IOException {
		Socket s = new Socket(InetAddress.getLocalHost(), 10007);

		File file = new File("AwtDemo.java");

		BufferedReader bfr = new BufferedReader(new FileReader(file));

		// 将数据写入到socket流中
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);

		String line = null;
		while ((line = bfr.readLine()) != null) {
			out.println(line);
		}
		// 关闭客户端的输出流。相当于给流中加入一个结束标记-1.
		// 结束标记很重要,不然TCP中程序停不下来
		s.shutdownOutput();

		// 接收服务端发过来的数据
		BufferedReader bfrs = new BufferedReader(new InputStreamReader(
				s.getInputStream()));

		String data = bfrs.readLine();
		System.out.println(data);

		bfr.close();
		s.close();
	}
}

class TextServer {
	public static void main(String[] args) throws IOException {
		ServerSocket ss = new ServerSocket(10007);
		Socket s = ss.accept();// 接收客户端的socket流
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip + "....connected");

		//建立读取客户端数据的流
		BufferedReader bfr = new BufferedReader(new InputStreamReader(
				s.getInputStream()));
		//建立文件,关联流
		PrintWriter out = new PrintWriter(new FileWriter("server.java"),true);
		
		String line = null;
		while((line=bfr.readLine()) != null){
			out.println(line);
		}
		
		//建立给客户端回馈机制
		PrintWriter outToC = new PrintWriter(s.getOutputStream(),true);
		outToC.println("上传成功");
		
		out.close();
		s.close();
		ss.close();
	}
}

 

第三  实际应用

一、TCP并发执行请求

一)图片上传:

第一、客户端:

1、创建服务端点

2、读取客户端以后图片数据

3、通过Socket输出流将数据发给服务端

4、读取服务端反馈信息

5、关闭客户端

第二、服务端

对于客户端并发上传图片,服务端如果单纯的使用while(true)循环式有局限性的,当A客户端连接上以后,被服务端获取到,服务端执行具体的流程,这时B客户端连接就只有等待了,因为服务端还未处理完A客户端的请求,还有循环来回执行下须accept方法,所以暂时获取不到B客户端对象,那么为了可让多个客户端同时并发访问服务端,那么服务端最好就是将每个客户端封装到一个单独的线程,这样就可以同时处理多个客户端的请求。如何定义线程呢?只要明确每个客户端要在服务端执行的代码即可,将改代码存入到run方法中。

/*
 * 客户端
 */
class PicCilent2 {
	public static void main(String[] args) throws UnknownHostException,
			IOException {
		File file = new File(args[0]);
		Socket s = new Socket("192.168.229.1", 10010);
		// 读取文件数据
		BufferedInputStream bfr = new BufferedInputStream(new FileInputStream(
				file));
		// 将数据写到socket流中,传递给服务端
		BufferedOutputStream out = new BufferedOutputStream(s.getOutputStream());

		byte[] buf = new byte[1024];
		int len = 0;
		while ((len = bfr.read(buf)) != -1) {
			out.write(buf, 0, len);
		}
		s.shutdownInput();// 结束标记

		// 接收服务端反馈数据
		InputStream in = s.getInputStream();
		byte[] bufin = new byte[1024];
		int num = in.read(bufin);
		System.out.println(new String(bufin, 0, num));

		bfr.close();
		s.close();
	}
}

/*
 * 
 * 服务端:
 * 
 * 这个服务端有个局限性。当A客户端连接上以后。被服务端获取到。服务端执行具体流程。 这时B客户端连接,只有等待。
 * 因为服务端还没有处理完A客户端的请求,还有循环回来执行下次accept方法。所以 暂时获取不到B客户端对象。
 * 
 * 那么为了可以让多个客户端同时并发访问服务端。 那么服务端最好就是将每个客户端封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。
 * 
 * 如何定义线程呢?
 * 
 * 只要明确了每一个客户端要在服务端执行的代码即可。将该代码存入run方法中。
 */
class PicThread implements Runnable {

	private Socket s;

	PicThread(Socket s) {
		this.s = s;
	}

	@Override
	public void run() {
		int count = 1;
		String ip = s.getInetAddress().getHostAddress();
		try {
			System.out.println(ip + "....connected");

			InputStream in = s.getInputStream();

			File dir = new File("c:\\");

			File file = new File(dir, ip + "(" + (count) + ")" + ".jpg");

			while (file.exists())
				file = new File(dir, ip + "(" + (count++) + ")" + ".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);
			}

			OutputStream out = s.getOutputStream();

			out.write("上传成功".getBytes());

			fos.close();

			s.close();
		} catch (Exception e) {
			throw new RuntimeException(ip + "上传失败");
		}
	}

}

class Picserver2 {
	public static void main(String[] args) throws IOException {
		ServerSocket ss = new ServerSocket(10011);
		while (true) {
			Socket s = ss.accept();

			new Thread(new PicThread(s)).start();
		}
	}
}


二)客户端并发登陆:

 

客户端通过键盘录入用户名。服务端对这个用户名进行校验。如果该用户存在,在服务端显示xxx,已登陆。并在客户端显示 xxx,欢迎光临。如果该用户存在,在服务端显示xxx,尝试登陆。并在客户端显示 xxx,该用户不存在。最多就登录三次。

/*
 * 定义客户端
 */
class LoginClient {
	public static void main(String[] args) throws UnknownHostException,
			IOException {
		Socket s = new Socket(InetAddress.getLocalHost(), 10012);
		// 获取键盘输入
		BufferedReader bfr = new BufferedReader(
				new InputStreamReader(System.in));

		// 定义将数据写出
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);// true数据换行

		// 定义获取服务端数据
		BufferedReader bfrIn = new BufferedReader(new InputStreamReader(
				s.getInputStream()));

		// 因为最多登陆三次
		for (int i = 0; i < 3; i++) {
			String line = bfr.readLine();
			if (line == null) {
				break;
			}
			out.println(line);// 将数据写出

			// 读取服务端数据
			String info = bfrIn.readLine();
			System.out.println(info);
			if (info.contains("欢迎")) {
				break;
			}
		}

		bfr.close();
		s.close();
	}
}

/*
 * 建立多用户访问
 */
class UserThread implements Runnable {

	// 定义Socket
	private Socket s;

	public UserThread(Socket s) {
		this.s = s;
	}

	@Override
	public void run() {
		// 获取ip
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip);

		// 因为只允许三次登陆
		for (int i = 0; i < 3; i++) {
			try {
				// 建立读取流
				BufferedReader bfr = new BufferedReader(new InputStreamReader(
						s.getInputStream()));
				// 建立流给客户端反馈信息
				PrintWriter out = new PrintWriter(s.getOutputStream(), true);

				// 获取姓名
				String name = bfr.readLine();
				if (name == null) {
					break;
				}

				// 获取服务器中存在的用户信息
				BufferedReader bfrtofile = new BufferedReader(new FileReader(
						"user.txt"));
				// 开始读取信息
				String line = null;
				boolean flag = false;// 定义标记
				// 获取姓名
				while ((line = bfrtofile.readLine()) != null) {
					if (line.equals(name)) {
						flag = true;
						break;
					}
				}
				// 根据标记,做出相应的处理
				if (flag) {
					System.out.println(name + ",已登录");
					out.println(name + ",欢迎光临");
					break;
				} else {
					System.out.println(name + ",尝试登录");
					out.println(name + ",用户名不存在");
				}

				bfrtofile.close();

			} catch (IOException e) {
				throw new RuntimeException(ip + "校验失败" + e.toString());
			}
		}
		try {
			s.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

/*
 * 定义服务端
 */
class LoginServer {
	public static void main(String[] args) throws IOException {
		ServerSocket ss = new ServerSocket(10011);
		while (true) {
			Socket s = ss.accept();
			new Thread(new UserThread(s)).start();
		}
	}
}


二、其他访问:

1、浏览器

在浏览器地址栏输入你的IP和你的服务器的端口号,这就可以成功通过浏览器访问自己的服务器了。

2、telnet

telnet是windows提供的远程登录工具,可以连接服务器的任意一台主机,并在通过dos命令行配置服务器 访问方式:IP 端口号--> 192.168.229.1 端口号

2、 客户端:浏览器。
 服务端:Tomcat服务器。

启动tomcat服务器,至于启动前需要配置一些东西,我在这就不多说了,要是不知道就百度一下吧,很简单的。自己写写点数据用于服务器返回给客户端的数据,这里是html文件,自己写一个简单的html文件放在tomcat的webapps目录下就可以啦

3、客户端:自定义。(图形界面)
 服务端:Tomcat服务器。

重点说说这种方式,先看代码,这里加入了图形化界面:

 

public class MyIEByGUI {
	private Frame f;// 定义窗口
	private TextField text;// 定义输入框
	private Button btn;// 定义按钮
	private TextArea textArea;// 定义文本区域

	MyIEByGUI() {
		init();
	}

	// 初始化
	public void init() {
		// 定义控件
		f = new Frame("my IE");
		text = new TextField(45);
		btn = new Button("转到");
		textArea = new TextArea(50, 60);

		// 初始化控件
		f.setBounds(100, 100, 500, 600);
		f.setLayout(new FlowLayout());

		// 绑定控件
		f.add(text);
		f.add(btn);
		f.add(textArea);

		myEvent();

		f.setVisible(true);
	}

	// 定义事件
	private void myEvent() {
		// 给窗体添加事件
		f.addWindowListener(new WindowAdapter() {

			@Override
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}

		});

		// 给文本框定义键盘事件,Enter
		text.addKeyListener(new KeyAdapter() {

			@Override
			public void keyPressed(KeyEvent e) {
				try {
					if (e.getKeyCode() == KeyEvent.VK_ENTER)
						showIE();
				} catch (UnknownHostException e1) {
					e1.printStackTrace();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}

		});

		// 给按钮定义事件
		btn.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				try {
					showIE();
				} catch (UnknownHostException e1) {
					e1.printStackTrace();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		});
	}

	private void showIE() throws UnknownHostException, IOException {
		textArea.setText("");// 清空文本区域

		// 获取网址
		String url = text.getText();// http://192.168.229.1:8080/myweb/demo.html
		int index1 = url.indexOf("//") + 2;
		int index2 = url.indexOf("/", index1);
		String str = url.substring(index1, index2);// 获取IP和端口号
		String[] arr = str.split(":");
		String host = arr[0];// 获取ip
		int port = Integer.parseInt(arr[1]);// 获取端口号

		String path = url.substring(index2);// 获取目的地址

		// 建立socket服务,将数据发到服务端
		Socket s = new Socket(host, port);
		PrintWriter out = new PrintWriter(s.getOutputStream(), true);
		// 将http协议等写给服务器
		out.println("GET " + path + " HTTP/1.1");
		out.println("Accept: */*");
		out.println("Accept-Language: zh-CN");
		out.println("Host: 192.168.229.1:10013");
		out.println("Connection: closed");
		// 打印两个空行
		out.println();
		out.println();

		// 读取服务器返回的数据
		BufferedReader bufr = new BufferedReader(new InputStreamReader(
				s.getInputStream()));

		String line = null;
		while ((line = bufr.readLine()) != null) {
			textArea.append(line + "\r\n");
		}
		s.close();
	}

	public static void main(String[] args) {
		new MyIEByGUI();
	}
}

这里得到的返回数据就是带着http协议等一系列配置,然而浏览器得到的数据却把这些数据封装了,这是为什么呢?这是因为现在我们处在传输层,而不是应用层,也就是说没有再向上拆包而导致的,下面就介绍一个对象,就把我们的程序由传输层转到应用层。

 

 

三、URL和URLConnection

一)URL:

URI:范围更大,条形码也包含于此范围

URL:范围较小,即域名

1、方法:

1)构造函数:URL(String protocol,String host,int port,String file)

---> 依protocol,host,port,file创建URL对象

2)String getProtocol()    获取协议

3)String getHost()    获取主机名

4)int getPort()    获取端口号

5)String getFile()   获取URL文件名

6)String getPath()  获取此URL的路径部分

7)String getQuery()  获取此URL的查询部,客户端传输的特定信息

2、一般输入网址,是不带端口号的,此时可进行设置,在URL中写port,若port为-1,则分配一个默认的80端口,否则用自己定义的,如

int port = getPort();

if(port == -1)

 port = 80;

二)URLConnection:

1、方法:

1)URLConnection openConnection() --->  表示到URL所引用的远程对象的链接

2)InputStream getInputStream():获取输入流

3)OutputStream getOutputStream():获取输出流

下面就看优化后的山寨IE:

 

public class MyIEByGUI2 {

	private Frame f;// 定义窗口
	private TextField text;// 定义输入框
	private Button btn;// 定义按钮
	private TextArea textArea;// 定义文本区域

	MyIEByGUI2() {
		init();
	}

	// 初始化
	public void init() {
		// 定义控件
		f = new Frame("my IE 2");
		text = new TextField(45);
		btn = new Button("转到");
		textArea = new TextArea(50, 60);

		// 初始化控件
		f.setBounds(100, 100, 500, 600);
		f.setLayout(new FlowLayout());

		// 绑定控件
		f.add(text);
		f.add(btn);
		f.add(textArea);

		myEvent();

		f.setVisible(true);
	}

	// 定义事件
	private void myEvent() {
		// 给窗体添加事件
		f.addWindowListener(new WindowAdapter() {

			@Override
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}

		});

		// 给文本框定义键盘事件,Enter
		text.addKeyListener(new KeyAdapter() {

			@Override
			public void keyPressed(KeyEvent e) {
				try {
					if (e.getKeyCode() == KeyEvent.VK_ENTER)
						showIE();
				} catch (UnknownHostException e1) {
					e1.printStackTrace();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}

		});

		// 给按钮定义事件
		btn.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				try {
					showIE();
				} catch (UnknownHostException e1) {
					e1.printStackTrace();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		});
	}

	private void showIE() throws UnknownHostException, IOException {
		textArea.setText("");// 清空文本区域

		// 获取网址
		String urlpath = text.getText();// http://192.168.229.1:8080/myweb/demo.html
//		String ip = InetAddress.getByName(urlpath).getHostAddress();
		URL url = new URL(urlpath);
		URLConnection conn = url.openConnection();

		InputStream in = conn.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		textArea.setText(new String(buf, 0, len));
	}

	public static void main(String[] args) {
		new MyIEByGUI2();
	}
}

这样我们得到的数据就跟浏览器得到的数据一样了,遗憾的是我们不能对数据进行解析,不然我们就可以用我们自己的浏览器上网啦~~

 


 

小知识点

 

1、InetSocketAddress对象(IP+端口)

2、ServerSocket对象中的构造函数:

ServerSocket(int port,int backlog),其中的backlog表示队列的最大长度,即最多连入客户端的个数,即最大连接数。

3、在进行浏览器输入网址访问一台主机所做的操作:

如http://192.168.229.1:8080/myweb/demo.html,一般直接输入主机名:http://baidu.com等,那么如何通过主机名获取IP地址,从而连接到这台主机的呢?这就需要将主机名翻译成IP地址,即域名解析:DNS(存的是主机名和IP相对应的键值对)

在进行访问的时候,会现在本地的hosts文件(C:\WINDOWS\system32\drivers\etc\hosts)中找对应的映射,若有,则直接返回请求,若无,则到公网的映射列表即DNS中找对应的映射,找到后,将主机名对应的IP地址返回给本机,本机通过这个IP地址找到对应的服务器。

域名解析示意图:

host应用:

1、可屏蔽一些恶意网址,即将对应的映射关系写入hosts中,将IP地址改为本机的回环地址,那么会直接找到hosts,就不会将请求发送出去了。

2、不让软件更新,可以越过一些收费软件的限制。

3、把经常上的网站写入文件中,提高访问速率。


 

posted on 2013-09-10 19:19  新一  阅读(367)  评论(0编辑  收藏  举报

导航