黑马程序员--java基础复习之网络编程

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

       还记得以前在学校的时候,学习网络编程,用UDP写个聊天室,然后在教室里通过局域网接收同学们的消息,然后也向其他人发送消息,玩得不亦乐乎。如今,再次拾起旧日情怀,重新学习网络编程的知识,只是时光变迁,已是七年之后的事了,那时是学的C#,而现在却是Java,那时正值青春年少,羞涩旧少年,如今年岁渐长,已为人夫人父。唏嘘。。。。。
好了,闲话少叙,上面只是个人的一点感叹而已,人得向前看。。。。。下面进入正题

当我们要写某个友人寄东西时,应该知道那人的具体地址,比如具体到四楼410,可这还不够,因为这里同可能不止一个人,所以还要加上收件人。而当我们在网络上要联系哪个人时,要保证网络之间能够畅通,就是要给友人寄东西一样,要确保邮件能送到,网络也是一样。我们寄东西通常选择快递或邮局,比如中通,顺风,快递之间动作不一样,比如顺风一般三天内必到,甚至隔天就到,而其他快递就慢多了。。。而网络之间也是一样,这就有了网络协议和端口的概念。

两台电脑之间要通讯:
1、找到对方   IP
2、数据要发送到对方指定的应用程序上。为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识。
为了方便称呼这个数字,叫做端口。逻辑端口。

3、定义通信规则。这个通讯规则称为协议。国际组织定义了通用协议:TCP/IP

如下:

而一个网络应用程序通常会对应一个或多个标识,即接口

所以,从上面可以看出,网络通信有三大要素
网络通信三要素:IP地址,端口号,传输协议

一、IP地址
        a、它是网络中的设备标识
        b、不易记忆,可用主机名表示,两者存在映射关系
        c、本机回环地址:127.0.0.1,主机名为:localhost。
IP地址:java中对应的是InetAddress类,存在于java.net包中。
InetAddress类:
     (一)无构造函数,可通过getLocalHost()方法获取InetAddress对象,此方法是静态的,返回本类对象。
                  InetAddress i = InetAddress.getLocalHost();
     (二)方法:
                1)static InetAddress getByName(String host):获取指定主机的IP和主机名。(最好用ip地址去获取,主机名需要解析)
                2)static InetAddress[] getAllByName(String host):在给定主机名的情况下,根据系统上配置的名称服务返回IP地址所组成的数组。返回对象不唯一时,用此方法。
                3)String getHostAddress():返回IP地址字符串文本形式,以IP地址为主。
                4)String getHostName():返回IP地址主机名。
     (三)如何获取任意一台主机的IP地址对象:
                1)功能:返回InetAddress对象
                2)对于任意主机,需要指定传入主机名的参数
注意:如果IP地址和对应的主机名,这种映射关系没有在网络上,就不会解析成功,返回的还是指定的IP。

二、端口号
用于标识进程的逻辑地址,不同进程的标识
有效端口:0~65535,其中0-1024系统使用或保留端口

三、传输协议
通讯的规则
常见协议:TCP,UDP

代码:
import java.net.*;
class IpDemo
{
	public static void main(String[] args) throws Exception
	{
		//通过InetAddress类的静态方法获取对象
		InetAddress i=InetAddress.getLocalHost();
		
		//获取主机的IP地址
		System.out.println(i.getHostAddress());
		//获取主机名
		System.out.println(i.getHostName());
		System.out.println(i.toString());
		
		
		//获取任意一台主机
		InetAddress ia=InetAddress.getByName("www.baidu.com");
		sop("IpAddress:"+ia.getHostAddress());//获取主机地址
		sop("Name:"+ia.getHostName());//获取主机名
		
		InetAddress[] ias=InetAddress.getAllByName("www.baidu.com");//获取所有主机名
		for(InetAddress ad:ias)
		{
			sop(ad.toString());
		}
	}
	
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}


其中,端口范围是0~65535,其中0~1024一般被系统程序保留。

网络参考模型:



一般来说开发处于传输层和网际层,应用层为:FTP和HTTP协议等,传输层为:UDP和TCP等,网际层为:IP。
        通常用户操作的是应用层,而编程人员需要做的是传输层和网际层,用户在应用层操作的数据,经过逐层封包,最后到物理层发送到另一个模型中,再进行逐层解包,如图:





传输协议(TCP和UDP)

UDP特点:
A、将数据及源和目的封装成数据包中,不需要建立连接
B、每个数据包的大小限制在64K内
C、因无连接,是不可靠协议
D、不需要建立连接,速度快。

TCP特点:
A、建立连接,形成传输数据的通道。
B、在连接中进行大数据量传输
C、通过三次握手完成连接,是可靠协议
D、必须建立连接,效率稍低


说到UDP和TCP,就不能不说另一个对象:Socket

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


下面我们先来说UDP
从UDP的特点可以看到,是将数据封装成包,那么,这里就涉及到了两个类,DatagramSocket和DatagramPacket

DatagramSocket:用来发送和接收数据报包的套接字。
DatagramPacket:此类表示数据报包。

而接收端要将数据发送出去,客户端要接收数据,都会用到DatagramSocket的两个方法
void send(DatagramPacket p)         从此套接字发送数据报包。 
 void receive(DatagramPacket p)      从此套接字接收数据报包。 

而数据报包的定义:DatagramPacket(byte[] buf, int length, InetAddress address, int port)      构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号,其常见方法有:
byte[] getData()      返回数据缓冲区。 
int getLength()   返回将要发送或接收到的数据的长度。 
int getPort()     返回某台远程主机的端口号 
InetAddress getAddress()   返回某台机器的 IP 地址 

如下:
import java.net.*;

/*
需求:通过UDP传输方式,将一段文字数据发出去
思路:
1、建立UDP的Socket 服务
2、提供数据,并将数据封装到数据包中
3、通过Socket服务的发送功能,将数据包发出去
4、关闭资源
*/

class UdpSend
{
	public static void main(String[] args) throws Exception
	{
		method_Send();
	}
	
	public static void method_Send() throws Exception
	{
		//1、创建UDP服务,通过DatagramSocket对象
		DatagramSocket ds=new DatagramSocket();
		                                                                                                                                   
		//2、确定数据,并封装成数据包。DatagramPacket(byte[] byte,int length,InetAddress address,int port)
		byte[] byt="UDP~我来了。。。".getBytes();  //将字符串转换成字节数据
		DatagramPacket dp=new DatagramPacket(byt,byt.length,InetAddress.getByName("127.0.0.1"),10000);
		
		//3、通过Socket服务,将已有的数据包发送出去。通过Send 方法
		ds.send(dp);
		//4、关闭资源
		ds.close();
	}
}

/*
需求:定义一个应用程序,用于接收UDP协议传输的数据并处理。

定义UDP的接收端
思路:
1、定义udp的Socket服务。通常会监听一个端口,其实就是给这个接收网络应用程序定义数字标识,
	方便于明确哪些数据过来该应用程序可以处理。
2、定义一个数据包,用于存储接收到的字节数据。
3、通过Socket服务的receive方法将收到的数据存入已定义好的数据包中。
4、通过数据包对象的特有功能。将这些不同的数据取出。打印在控制台上。
5、关闭资源
*/

class UdpRece
{
	public static void main(String[] args) throws Exception
	{
		method_Receive();
	}
	public static void method_Receive() throws Exception
	{
		//1、创建udp  的Socket服务。建立端点
		DatagramSocket ds=new DatagramSocket(10000);
		while(true)
		{
			//2、定义数据包,用于存储数据
			byte[] byt=new byte[1024];
			DatagramPacket dp=new DatagramPacket(byt,byt.length);
			
			//3、通过服务的receive方法将接收到的数据存入数据包中
			ds.receive(dp);
			
			//4、通过数据包的方法获取其中的数据
			String ip=dp.getAddress().getHostAddress();
			String data=new String(dp.getData(),0,dp.getLength());
			
			//获取发送端的端口
			int port=dp.getPort();
			
			System.out.println(ip+":"+port+"--->"+data);
		}
		//ds.close();
	}
}


从上面的代码里可以看出,发送端是将数据准备好,然后封装到数据报包中,再通过UDP的Socket服务(DatagramSocket)将这个数据包发送出去。而在接收端,是准备好一个空的数据包,然后接收数据,并将数据封装到数据报包中,并通过数据包对象将数据取出来,包括发送端主机名,IP地址,端口,及数据。

再看下面两个UDP的例子
/*
创建一个发送端和一个接收端,其他,发送端从键盘发送数据
*/

import java.net.*;
import java.io.*;
class MyUdpSend
{
	public static void main(String[] args)
	{
		method_Send();
	}
	//发送端
	public static void method_Send()
	{
		BufferedReader br=null;
		DatagramSocket ds=null;
		try
		{
			//创建一个Socket服务
			 ds=new DatagramSocket();	
			 //从键盘接收数据
			br=new BufferedReader(new InputStreamReader(System.in));
			
			byte[] byt=new byte[1024];
			String line=null;
			while((line=br.readLine())!=null)
			{
				if("over".equals(line))
					break;
				//将字符串转换成字节数组
				byt=line.getBytes();
				//创建数据包对象
				DatagramPacket dp=
				new DatagramPacket(byt,byt.length,InetAddress.getByName("127.0.0.1"),10001);
				//将准备好的数据包发送出去
				ds.send(dp);
			}
		}
		catch(Exception e)
		{
			sop(e.toString());
		}
		finally
		{
			try
			{
				if(br!=null)
					br.close();
			}
			catch(Exception e)
			{
				sop(e.toString());
			}
			finally
			{
				try{
					if(ds!=null)
						ds.close();
				}
				catch(Exception e)
				{
					sop(e.toString());
				}
			}			
		}
		
		
	}
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}

class MyUdpRece
{
	public static void main(String[] args)
	{
		method_Rece();
	}
	public static void method_Rece()
	{
		
		try
		{
			//接收端的Socket服务
		   DatagramSocket ds=new DatagramSocket(10001);
						
			while(true)
			{
				byte[] byt=new byte[1024];
				//定义一个空的数据包,用于存入接收的数据
				DatagramPacket dp=new DatagramPacket(byt,byt.length);
				ds.receive(dp);//接收数据
				//根据数据包对象获取发送端的IP地址和数据
				String ip=dp.getAddress().getHostAddress();
				String data=new String(dp.getData(),0,dp.getLength());
				int port=dp.getPort();//获取端口
				
				sop(ip+":"+port+"-->"+data);
			}
			
		}
		catch(Exception e)
		{
			sop(e.toString());
		}
		
	}
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}
结果:

另一个例子(创建一个UDP聊天室)  唉,可惜就我一人,没法试验。。。难现当年场景。。。。。
/*
实现一个聊天室功能,使用UDP和多线程
*/

import java.net.*;
import java.io.*;

class ChatDemo
{
	public static void main(String[] args) throws Exception
	{
		DatagramSocket ds=new DatagramSocket(8080);
		DatagramSocket ds1=new DatagramSocket(10005);
		
		new Thread(new Send(ds)).start();
		new Thread(new Send(ds1)).start();
	}
}

/*
发送端:
1、定义一个发送类,实现Runnable接口
2、构造函数接收一个DatagramSocket对象
3、重写run方法,在run方法里,定义数据包,将数据封装进数据包里,定义一个读取流,从键盘获取数据
4、通过Socket服务将数据包发送出去
5、关闭资源
*/

class Send implements Runnable
{
	private DatagramSocket ds;
	Send(DatagramSocket ds)
	{
		this.ds=ds;
	}
	public void run()
	{
		BufferedReader br=null;
		try
		{
			//从键盘接收数据
			 br=new BufferedReader(new InputStreamReader(System.in));
			//准备数据,从键盘接收数据
			String line=null;
			byte[] byt=new byte[1024];
			while((line=br.readLine())!=null)
			{
				byt=line.getBytes();
				DatagramPacket dp=new DatagramPacket(byt,byt.length,InetAddress.getByName("127.0.0.1"),10005);
				ds.send(dp);
			}
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			try
			{
				if(br!=null)
					br.close();
			}
			catch(Exception E)
			{
				System.out.println(E.toString());
			}
		}
		
		
	}
}

/*
接收端
1、定义接收类,实现 Runnable接口
2、构造函数接收一个DatagramSocket对象
3、重写run方法,定义一个DatagramPacket数据包对象,定义一个字节数组,用于存放接收的数据
4、通过Socket服务接收数据,并将数据存入数据包中
5、关闭资源
*/
class Rece  implements Runnable
{
	private DatagramSocket ds;
	Rece(DatagramSocket ds)
	{
		this.ds=ds;
	}
	public void run()
	{
		try
		{
			byte[] byt=new byte[1024];
			DatagramPacket dp=new DatagramPacket(byt,byt.length);
			//接收数据
			ds.receive(dp);
			
			String ip=dp.getAddress().getHostAddress();
			int port=dp.getPort();
			String data=new String(dp.getData(),0,dp.getLength());
			System.out.println(ip+":"+port+"--->"+data.toUpperCase());
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}	
	}
}



TCP传输

A、Socket和 ServerSocket
B、建立客户端和服务端
C、建立连接后,通过Socket中的IO流进行数据的传输。
D、关闭Socket
同样,客户端和服务端是两个独立的应用程序


客户端
通过查阅Socket对象,发现在该对象建立时,就可以去连接指定主机。因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功。形成通路后,在该通道进行数据的传输。

服务端
1、建立服务端的Socket服务.ServerSocket,并监听一个端口
2、获取连接过来的客户端对象。通过ServerSocket的accept方法,没有连接就会等, 所以这个方法是阻塞式的。
3、客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流,来读取发过来的数据,并打印在控制台上。
4、关闭服务端(可选)  关闭客户端对象


如下:
/*
客户端
通过查阅Socket对象,发现在该对象建立时,就可以去连接指定主机。
因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,
并连接成功。形成通路后,在该通道进行数据的传输。

步骤:
1、创建Socket服务,并指定要连接的主机和端口。

*/
import java.io.*;
import java.net.*;

class TcpClient
{
	public static void main(String[] args)
	{
		Socket s=null;
		try
		{
			//创建客户端的Socket服务。指定目的主机和端口
		 s=new Socket("127.0.0.1",10003);
		
		//为了发送数据,应该获取Socket流中的输出流
		OutputStream os=s.getOutputStream();
		
		os.write("TCP,Im Coming".getBytes());
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			try{
				if(s!=null)
					s.close();
			}
			catch(Exception e)
			{
				System.out.println(e.toString());
			}
		}				
	}
}

/*
服务端
1、建立服务端的Socket服务.ServerSocket,并监听一个端口
2、获取连接过来的客户端对象。通过ServerSocket的accept方法,没有连接就会等,
	所以这个方法是阻塞式的。
3、客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流
	来读取发过来的数据,并打印在控制台上。
4、关闭服务端(可选)  关闭客户端对象
*/
class TcpServer
{
	public static void main(String[] args)
	{
		try
		{
			//建立服务端Socket服务,并监听一个端口
			ServerSocket ss=new ServerSocket(10003);
			//通过accept方法获取连接过来的客户端对象
			Socket s=ss.accept();
			
			//获取客户端信息
			String ip=s.getInetAddress().getHostAddress();
			System.out.println(ip+"....connected");
			//获取客户端发送过来的数据,要使用客户端对象的读取流来读取数据
			byte[] byt=new byte[1024];
			InputStream is=s.getInputStream();
			int leng=is.read(byt);
			System.out.println(new String(byt,0,leng));
								
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			
		}
	}
}

Tcp建立连接需要经过三次握手,但上面只经过了两次。

客户端请求连接,服务端建立连接后,服务端就获取了客户端的Socket对象,这样,服务端就能使用客户端的Socket对象的输入输出流。

其流程如下                    1、  客户端Socket   ---------------------------连接-------------------------------------->服务端ServerSocket
2、服务端监听端口----------------------------建立连接-------------------------------->客户端
3、服务端监听端口-----------------------------发送反馈信息-------------------------->客户端


在上面的三步中,服务端要首先开启,监听端口,创建ServerSocket服务,然后客户端创建Socket服务,创建Socket流对象,连接服务器,同时,获取Socket流的输出流对象,将数据发送到服务端。
在第2步中,服务端通过ServerSocket服务的accept()方法,获取客户端的Socket流对象。然后获取Socket的读取流,读取客户端发送至的数据,然后通过Socket 的输出流,将反馈信息发送至客户端
第3步,通过Socket 流的读取流,来接收服务端发送过来的反馈信息。最后关闭资源。


如下图:





来看一个练习:
/*
需求:建立一个文本转换服务器
客户端给服务端发送文本,服务端会将文本转成大写再返回给客户端。
而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束

分析
客户端:
既然是操作设备上的数据,那么就可以使用IO技术,并按照IO的操作规律来思考。
源:键盘录入
目的:网络设备,网络输出流
而且操作的文本数据,可以选择字符流

步骤:
1、建立服务。
2、获取键盘录入
3、将数据发给服务端
4、获取服务端返回的大写数据
5、结束,关闭资源

都是文本数据,可以使用字符流进行操作,同时为提高效率,加入缓冲
*/

import java.io.*;
import java.net.*;

class TransClient
{
	public static void main(String[] args) throws Exception
	{
		Socket s=new Socket("127.0.0.1",10005);
		//源,从键盘获取录入的数据
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		
		//建立一个客户端 Socket 的输出流(定义目的,将数据写入到Socket输出流,发给服务端)
		BufferedWriter bufOut=
			new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		
		//定义一个Socket读取流,读取服务端返回的大写信息
		BufferedReader bufIn=
			new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		String line=null;
		//从键盘读取
		while((line=br.readLine())!=null)
		{
			if("over".equals(line))
				break;
			bufOut.write(line);  //将数据发到服务端去
			bufOut.newLine();
			bufOut.flush();
			
			
			String str=bufIn.readLine();  //读取服务端反馈回的数据信息
			System.out.println("Server:"+str);
		}
		
		br.close();
		s.close();
				
	}
}

/*
服务端
源:Socket读取流
目的:Socket输出流
都是文本,装饰
*/

class TransServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss=new ServerSocket(10005);
		//获取客户端的Socket对象
		Socket s=ss.accept();
		
		String ip=s.getInetAddress().getHostAddress();
		System.out.println(ip+"....connected");
		
		//获取客户端对象的读取流,读取Socket读取流中的数据
		BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		//目的。Socket输出流,将大写数据写入到Socket输出流,并发送给客户端
		BufferedWriter bufOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		
		String line=null;
		// 获取客户端发送过来的数据
		while((line=bufIn.readLine())!=null)
		{
			System.out.println(line);
			bufOut.write(line.toUpperCase());
			bufOut.newLine();
			bufOut.flush();
			
		}
		
		s.close();
		ss.close();
	}
}

结果:


再看下面一个例子,里面涉及了程序阻塞问题
两个方法:
void shutdownOutput() 禁用此套接字的输出流 
 void shutdownInput() 此套接字的输入流置于“流的末尾”。
/*
客户端

思路:
1、创建Socket服务,并指定主机的地址和端口
2、既然是上传文件,那么要创建文件读取流,用来读取源文件,将其作为要发送到服务端的数据
3、获取Socket流的输出流,然后将数据发到服务端
4、关闭客户端
*/
import java.net.*;
import java.io.*;

class UploadClient
{
	public static void main(String[] args)  throws Exception
	{
		Socket s=new Socket("127.0.0.1",10008);
		
		//准备数据
		FileInputStream fis=new FileInputStream("D:\\构造最全的Java面试题.pdf");
		//获取Socket服务的输出流
		OutputStream out=s.getOutputStream();
		//获取Socket服务的输入流,用于读取服务端的反馈信息
		BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
		//InputStream in=s.getInputStream();
		
		byte[] byt=new byte[1024];
		int len=0;
		while((len=fis.read(byt))!=-1)
		{
			out.write(byt,0,len); //将数据发送到服务端
		}
		s.shutdownOutput(); 
		
		//接收服务端发送过来的反馈信息
		/*
		byte[] by=new byte[1024];
		int leng=0;
		leng=in.read(by);
		*/
		String str=bufIn.readLine();
		System.out.println(str);
		//System.out.println(new String(by,0,leng));
		fis.close();
		s.close();
	}
}

/*
服务端
思路:
1、创建服务端的Socket服务,并监听端口
2、获取客户端对象的输入流
3、创建文件输出流,用于存储文件数据
4、通过对象的输入流读取客户端发送过来的数据,再通过文件输出流保存为文件。
5、获取客户端对象的输出流,输出反馈信息到客户端
5、关闭文件输出流对象资源和客户端
*/
class UploadServer
{
	public static void main(String[] args) throws Exception
	{
		ServerSocket ss=new ServerSocket(10008);
		Socket s=ss.accept(); //获取客户端对象
		//获取客户端信息
		String ip=s.getInetAddress().getHostAddress();
		System.out.println(ip+"...connected");
		
		//定义文件目的地
		FileOutputStream fos=new FileOutputStream("E:\\构造最全的Java面试题.pdf");
		//获取客户端对象的读取流
		BufferedInputStream bufIn=new BufferedInputStream(s.getInputStream());
		//获取客户端对象的输出流,用于给客户端发送反馈信息
		BufferedWriter bufOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		//OutputStream out=s.getOutputStream();
		byte[] byt=new byte[1024];
		int len=0;
		while((len=bufIn.read(byt))!=-1)
		{
			fos.write(byt,0,len);
		}
		bufOut.write("上传成功!");
		bufOut.flush();
		//out.write("上传成功".getBytes());
		fos.close();
		s.close();
	}
}

如果上面代码中没有s.shutdownOutput() 这句,那么当客户端读完数据,完成发送后,但是服务器那边没有读到表示到达末尾的-1,所以依然会循环读取,那么,就不会执行下面的发送反馈信息给客户端,而客户端在执行到in.read()时进行阻塞状态,等待服务端发送反馈信息。而在这里添加了这句以后,当客户端完成发送数据后,通过shutdownOutput()往服务端发送了一个-1,告诉服务端已发完数据,这时服务端的while语句就会读取到-1,完成循环,发送反馈信息。




从上面的代码里,我们可以看出,上面的代码只能一个一个地上传文件,当A客户端连接服务端,被服务端获取到,服务端执行具体流程。这时B客户端如果连接,只有等待。因为服务端还没有处理完A客户端的请求,还有循环回来执行下次accept方法,所以暂时获取不到B客户端对象。A上传文件成功后,B才能连上服务器,而做不到A和B同时连接服务端,同时上传文件。
那么为了让多个客户端同时并发访问服务端,那么服务端最好就是将每个客户端封装一个单独的线程中,这样,就可以同时处理多个客户端请求。

代码:
import java.net.*;
import java.io.*;


class PicThread implements Runnable
{
	Socket s;
	PicThread(Socket s)
	{
		this.s=s;
	}
	//复写run方法
	public void run()
	{
		String ip=s.getInetAddress().getHostAddress();  //获取客户端IP地址
		try
		{
			//对上传的文件保存名称作设置
			System.out.println(ip+"....connected");
			int num=1;
			File file=new File("临时文件\\"+ip+"("+num+")"+".jpg");
			while(file.exists())
				file=new File("临时文件\\"+ip+"("+(num++)+")"+".jpg");
			
			//定义一个输出流,用于接收数据,保存为文件
			BufferedOutputStream bufos=new BufferedOutputStream(new FileOutputStream(file));
			//获取客户端对象的读取流
			BufferedInputStream bufIn=new BufferedInputStream(s.getInputStream());
			
			byte[] byt=new byte[1024];
			int len=0;
			while((len=bufIn.read(byt))!=-1)
			{
				bufos.write(byt,0,len);  
			}
			
			//将反馈信息发送至客户端
			BufferedOutputStream bufOut=new BufferedOutputStream(s.getOutputStream());
			
			bufOut.write("上传成功!".getBytes());
			bufOut.flush();
			bufos.close();
			s.close();
		}
		catch(Exception e)
		{
			System.out.println(ip+"上传失败");
		}
		
		
		
	}
}

/*
服务端
思路:1、创建服务端Soceket服务,并监听端口
2、通过accept方法获取客户端对象,获取客户端对象的读取流
3、定义一个输出流,用于保存客户端发送过来的数据
4、获取客户端对象的输出流,将反馈信息发送给客户端
5、关闭客户端,关闭输出流
*/
class PicServer
{
	public static void main(String[] args) throws Exception
	{
		//文件从控制台输入地址来获取
		
		
		//创建服务端Socket服务,并监听端口
		ServerSocket ss=new ServerSocket(10009);
		while(true)
		{
			Socket s=ss.accept();
			
			new Thread(new PicThread(s)).start();
		}
		
		
	}
}

/*
客户端

思想:
1、创建客户端的Socket服务,并指定主机和端口
2、获取客户端服务的输出流
3、准备数据,上传图片,使用文件输出流
4、将准备好的数据发送至服务端
5、通过客户端服务的读取流,获取服务端的反馈信息
6、关闭资源
*/
class PicClient
{
	public static void main(String[] args) throws Exception
	{
		//对客户端上传的文件作限制(格式,大小,类型)
		if(args.length!=1)
		{
			System.out.println("请选择一个jpg格式的图片");
			return;
		}
		
		File file=new File(args[0]);
		if(!(file.exists()&&file.isFile()))
		{
			System.out.println("该文件有问题,要么不存在,要么不是文件");
			return;
		}
		if(!file.getName().endsWith(".jpg"))
		{
			System.out.println("图片格式错误,请重新选择");
			return;
		}
		if(file.length()>1024*1024*5)
		{
			System.out.println("文件过大,请重新选择!");
			return;
		}
		
		Socket s=new Socket("127.0.0.1",10009);
		BufferedOutputStream bufOut=new BufferedOutputStream(s.getOutputStream());
		
		BufferedInputStream bufis=new BufferedInputStream(new FileInputStream("client.jpg"));
		
		byte[] byt=new byte[1024];
		int len=0;
		while((len=bufis.read(byt))!=-1)
		{
			bufOut.write(byt,0,len); //将数据发送至服务端
		}
		s.shutdownOutput();//数据已发送完毕
		
		BufferedInputStream bufIn=new BufferedInputStream(s.getInputStream());
		byte[] by=new byte[100];
		int leng=bufIn.read(by);
		String str=new String(by,0,leng);
		System.out.println(str);
	}
}


练习:客户端并发登录
/*
客户端通过键盘录入用户名
服务端对这个用户名进行校验

如果该用户存在,在服务端显示XXX,已登录。
并在客户端显示XXX,欢迎光临。

如果该用户不存在,在服务端显示XXX,尝试登录
并在客户端显示XXX,该用户不存在。

最多就登录三次。
*/

import java.io.*;
import java.net.*;

/*
客户端
1、创建一个Socket服务,并确定主机和端口
2、通过Socket流,获取输出流对象
3、通过键盘读取输入的用户名
4、获取Socket流的读取流,读取服务端发过来的反馈信息
5、关闭客户端资源

*/
class UserLogin
{
	public static void main(String[] args)
	{
		method();
	}
	public static void method()
	{
		try
		{
			Socket s=new Socket("127.0.0.1",10007);
			//接收键盘输入的用户名
			BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
			//获取客户端Socket流的输出流对象
			BufferedWriter bwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
			
			//获取客户端Socket流的读取流对象
			BufferedReader brIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
			
			for(int i=0;i<3;i++)
			{
				String line=null;
				line=br.readLine();
				if(line==null)
					break;
				bwOut.write(line);  //将用户名发送到服务端
				bwOut.newLine();
				bwOut.flush();
				
				
				String str=brIn.readLine();
				
				System.out.println(str);
				if(str.contains("欢迎"))
					break;
			}
			
			br.close();
			s.close();
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}
	}
}

/*
服务端
1、创建Socket服务,并监听端口
2、创建一个读取流对象,读取用户名数据文件
3、获取客户端Socket流对象,并获取其读取流,读取客户端发送至的数据
4、验证用户名是否存在,并反馈相关信息
5、关闭客户端资源
*/

class ServerThread implements Runnable
{
	private Socket s;
	ServerThread(Socket s)
	{
		this.s=s;
	}
	public void run()
	{
		BufferedReader br=null;
		try{
			
			
			String ip=s.getInetAddress().getHostAddress();
			System.out.println(ip+"...connected");
			for(int i=0;i<3;i++)
			{
			//获取用户名列表文件
			 br=new BufferedReader(new FileReader("UserList.txt"));
			//获取客户端Socket的读取流
			BufferedReader brIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
			
			//获取客户端Socket的输出流,用于给客户端发送反馈信息
			BufferedWriter bwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
			
			
				String userName=brIn.readLine();
				if(userName==null)
					break;
				
				String line=null;
				boolean flag=false;
				while((line=br.readLine())!=null)
				{
					System.out.println(line+"::"+userName);
					if(userName.equals(line))
					{
						flag=true;
						break;
					}
				}
				if(flag)
				{
					System.out.println(userName+",已登录!");
					bwOut.write(userName+",欢迎光临!");
					bwOut.newLine();
					bwOut.flush();
					
					break;
				}
				else
				{
					System.out.println(userName+",尝试登录!");
					bwOut.write(userName+",用户不存在!");				
					bwOut.newLine();					
					bwOut.flush();
					
				}
			}
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			try{
				if(s!=null)
				{
					s.close();
					br.close();
				}					
			}
			catch(Exception e)
			{
				System.out.println(e.toString());
			}
		}
	}
}
class Server
{
	public static void main(String[] args)
	{
		
		method();
	}
	public static void method()
	{
	
		try{
			ServerSocket ss=new ServerSocket(10007);
		
			while(true)
			{
				//获取客户端Socket 流对象				
				Socket s=ss.accept();
				//创建线程
				new Thread(new ServerThread(s)).start();
			}
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}			
	}
}

结果:









自定义服务端(以浏览器作为客户端)

代码:
/*
自定义服务端
*/

import java.net.*;
import java.io.*;
class ServerDemo
{
	public static void main(String[] args) throws Exception
	{
		//创建服务端Socket服务,并监听端口
		ServerSocket ss=new ServerSocket(11000);
		//获取客户端的Socket流对象
		Socket s=ss.accept();
		
		String ip=s.getInetAddress().getHostAddress();
		System.out.println(ip);
		
		//BufferedReader brIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
		
		BufferedWriter bwOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		
		bwOut.write("客户端你好");
		bwOut.newLine();
		bwOut.flush();
		
		s.close();
		ss.close();
	}
}

结果:




URL类

类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。

其常见方法有:

String getProtocol() 获取此 URL 的协议名称。 
String getHost()  获取此 URL 的主机名(如果适用)。 
 String getPath()    获取此 URL 的路径部分。 
 int getPort()       获取此 URL 的端口号。 
 String getQuery()     获取此 URL 的查询部分。

注:一般输入网址,是不带端口号的,此时可进行获取,通过获取网址返回的port,若port为-1,则分配一个默认的80端口,如
        int port = getPort();
        if(port == -1)
              port = 80;
2、URLConnection
方法:
        1)URLConnection openConnection();//用URL调用此方法,返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。
        2)InputStream getInputStream();//获取输入流
        3)OutputStream getOutputStream();//获取输出流

代码:
import java.net.*;
class UrlDemo
{
	public static void main(String[] args)
	{
		URL url=null;
		try
		{
			
			url=new URL("http://www.letv.com/ptv/vplay/20075539.html");
			//获取协议名称
			sop("getProtocol:"+url.getProtocol());
			//获取主机名
			sop("getHost:"+url.getHost());
			//获取路径部分
			sop("getPath:"+url.getPath());
			//获取端口
			sop("getPort:"+url.getPort());
			//获取查询部分
			sop("getQuery:"+url.getQuery());
			
			sop("getFile:"+url.getFile());
		}
		catch(Exception e)
		{
			sop(e.toString());
		}
		 
		
		
	}
	
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}

结果:




练习,使用URL对象的openStream()方法,将指定URL对象的内容读取到一个文件中

InputStream openStream()     打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。

import java.net.*;
import java.io.*;
class URLConnectionDemo
{
	public static void main(String[] args)
	{
		URL url=null;
		InputStream in=null;
		BufferedOutputStream bos=null;
		try
		{
			
			url=new URL("http://www.letv.com/ptv/vplay/20075539.html");
			
			URLConnection conn=url.openConnection();
			System.out.println(conn);
			
			//获取一个一个URLConnection的读取流
			in=conn.getInputStream();
			//将读取的数据存入一个TXT文件
			bos=new BufferedOutputStream(new FileOutputStream("url.txt"));
			
			byte[] byt=new byte[1024];
			int len=0;
			while((len=in.read(byt))!=-1)
			{
				bos.write(byt,0,len);
				bos.flush();
			}
			
			
		}
		catch(Exception e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			try
			{
				if(bos!=null)
					bos.close();
			}
			catch(Exception e)
			{
				System.out.println(e.toString());
			}
		}		
		 
	}
}


结果:




小知识: ServerSocket
构造函数
ServerSocket(int port, int backlog)           利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号
这里的backlog参数:backlog - 队列的最大长度。即连接到服务器端的客户端最大个数


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

       如输入http://61.135.169.125,可以直接连接此ip的主机,而我们一般是输入主机名:http:/www.baidu.ocm(百度主机对应的ip地址就是:61.135.169.125),那么此时浏览器做了神马操作呢?

       也就是说如何通过主机名获取IP地址,从而连接到这台主机呢?这就需要将主机名翻译成IP地址,即域名解析:DNS

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


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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值