黑马程序员---网络编程

-----------android 培训 java培训 、java学习型技术博客、期待与您交流!---------


网络编程

     

      网络模型

      

            OSI参考模型                     TCP/IP参考模型

        


      网络通讯要素

                 

            IP地址,端口号,传输协议

 

      网络传输步骤


            1.   找到对方IP


            2.   数据要发送到对方指定的应用程序上,为了表示这些应用程序。


                  给这些网络应用程序进行表示为了方便称呼,这个数字叫做端口(逻辑端口)


            3.   定义通信规则,这个通信规则称为协议,国际组织定义通用协议TCP\IP

 

      IP地址


            它是网络中设备的识别。


            不易记忆,可用主机名表示,两者存在映射关系。


                  本机回环地址127.0.0.1,主机名 localhost

 

            Java中用来表示IP地址的类,java.net.InetAddress


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


                  例:InetAddress  i  =  InetAddress.getLocalHost();

 

                  IntAddress类中方法:


                        static  InetAddress  getByName(String  host)


                              通过主机名,或IP地址的字符串形式,获得IP地址对象。


                        String getHostAddress()


                              返回IP地址字符串文本形式


                        String getHostName()


                              返回IP地址主机名。

        

            获取IP地址代码示例

/*
获取IP地址方法示例
*/
import java.net.*;

class  IPDemo
{
	public static void main(String[] args) throws Exception
	{
		//获取本机IP地址对象
		InetAddress ip = InetAddress.getLocalHost();

		//将此IP地址输出
		sop(ip.toString());

		//获取IP地址字符串表现形式
		sop(ip.getHostAddress());

		//获取此IP地址对应的主机名
		sop(ip.getHostName());

		
	}
	//输出方法
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}


                  运行结果




      端口号

      

            端口号用于表示进程的逻辑地址,不同进程的标识。

 

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

 

      传输协议


            即通信的规则,常见协议:TCP  UDP

                   

                  UDP


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


                        每个数据包的大小限制在64K之内


                        因无连接,所以是不可靠协议。


                        不需要建立连接,速度快


                              如:聊天,视频通话,桌面共享。

 

                        特点


                              1.   面向无连接


                              2.   数据会被封包


                              3.   不可靠


                              4.   速度快

 

                  TCP


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


                        在连接后进行大数据传输。


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


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


                              如:下载

 

                        三次握手


                              我——>>他——>>我——>>传


                                    第一次本方向对法发送请求,对法同意,并告诉我方他同意了,我方开始传输数据。

 

            个人理解:UDP像对讲机,不管你在不在线,我说我的。


                               TCP像电话,你接了,跟我说“喂”了,我才能跟你说我要说的事儿。

      

      网络传输


            Socket


                        它是为网络服务提供的一种机制。通信两端都有Socket,才能建立服务


                              网络通信其实就是Socket间的通信,数据在两个Socket之间通过IO传输。

            

            UDP传输

                        

                        DatagramSocket  与  DatagramPacket

 

                        传输步骤


                              1.   建立发送端,接收端


                              2.   建立数据包


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


                              4.   关闭Socket

 

                              发送端与接收端是两个独立的运行程序。

 

                        下面根据需求来学习UDP传输

 

                              需求:通过UDP传输方式,将一段文字数据发送出去。

 

                                    思路


                                          1.   建立UDPSocket服务,通过DatagramSocket对象。


                                                例:DatagtamSocket   ds =  new  DatagramSokcet();


                                                也可把一个数字作为参数传入构造函数中,设置端口号。是发送端的端口号。


                                          2.   提供数据,并将数据封装到数据包中


                                                通过DatagramPacket的构造函数


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


                                                参数传入:数据包,数据包长度,目的地址,目的端口号。


                                                                    数据包的长度小于等于buf.length。


                                                                   目的地址对象可以通过Inetaddress.getByName("IP地址的字符串表现形式")获得。


                                          3.   通过Socket服务的发送功能,将数据包发出去


                                                通过DatagramSocket中的方法


                                                void send(DatagramPcket  P)


                                          4.   关闭资源


                                                ds.close();

 

                              需求:通过UDP传输方式,接受一段文字数据


                                         接收数据通常会监听一个端口,其实就是给这个接收网络应用程序定义数字标识(端口)


                                         方便于明确哪些数据过来,该应用程序可以处理

 

                                    思路


                                          1.   建立UDPSocket服务,监听端口


                                                例:DatagtamSocket  ds = newDatagramSokcet(端口号);


                                          2.   定义一个数据包,用于要存储接收到的字节数据。


                                                      数据包对象中有很多功能可以提取字节数组中的不同数据信息。


                                                      先定义一个字节数组用于接收数据。在定义数组时担心1024不够装,就定义64k大小的数组即可,因为数据包最大为64k


                                                通过数据包的构造函数 DatagramPacket(byte [] buf  ,  int length);


                                                例:DatagramPacket  dp = new  DatagramPacket(buf , buf.length);


                                          3.   通过Socket服务的receive方法,将接收到的数据存入已定义好的数据包中


                                                void receive(DatagramPacket  p)     是阻塞式方法。


                                                例:ds.receive(dp);


                                          4.   通过数据包对象的特有功能,获取数据包中数据。


                                                如:获取IP。


                                                              先通过InetAddress  getAddress()方法,获取发送数据的机器的IP地址对象。


                                                              然后使用getHostAddress()方法,获取IP地址的字符串形式。


                                                        获取数据内容


                                                              发送的是字符串,所以可以用String(byte[] , int offser , int length)的构造函数,将字节数组转成字符串类型。


                                                              使用byte[] getData()方法返回数据缓冲区,也就是字节数组。


                                                              void  setLength(int length)方法返回数据包的长度。再通过String的构造函数获取数组的字符串形式。


                                                        获取端口


                                                            通过int  getPort()方法,获取发送端的端口号。


                                          5.   关闭资源


                                                ds.close();

 

                        发送和接收我们都学习完了,下面来发送接收数据。

/*
UDP发送和接收数据示例

在这里为了看起来方便,我们将发送和接收代码写在了一起
一起编译,但分开运行,通过再DOS命令行中输入start开启一个新的Dos命令行窗口
先运行接收代码,让它开着,再运行发送,要不会发生数据丢失。
*/
import java.net.*;

//发送数据类
class UdpSend
{
	public static void main(String[] args) throws Exception
	{
		//1.建立UDP服务,在这里设置端口号,是发送端的端口号
		DatagramSocket ds = new DatagramSocket(6666);

		//2.提供数据,将数据封装到数据包中。
		//将字符串转成byte类型数组存储,这就是要传输的数据。
		byte[] buf = "你好我是潘机智".getBytes();
		//通过DatagramPacket的构造方法,将数据封装到数据包中
		DatagramPacket dp =
			new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.1.3"),10000);

		//3.通过Socket服务的发送功能,将数据包发出去
		ds.send(dp);

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

//接收数据类
class UdpRece
{
	public static void main(String[] args)throws Exception
	{
		//1.建立Udp服务,指定端点。
		DatagramSocket da = new DatagramSocket(10000);

		//2.定义接收数据的数据包
		//先定义个数组
		byte[] buf = new byte[1024];
		//定义数据包
		DatagramPacket dp = new DatagramPacket(buf,buf.length);

		//3.通过Socket的receive方法,将接收到的数据存入定义好的数据包中
		da.receive(dp);

		//4.通过数据包对象的特有功能,获取数据包中数据
		//获取发来数据的主机的IP地址
		String ip = dp.getAddress().getHostAddress();
		//获取内容
		String data = new String(dp.getData() , 0 , dp.getLength());
		//获取端口号,发送端的端口号。
		int port = dp.getPort();

		//输出信息
		System.out.println("发送数据的主机IP地址:"+ip);
		System.out.println("发送的内容:"+data);
		System.out.println("发送端端口号:"+port);
	}
}


                              运行结果



 

                        UDP练习,编写一个聊天程序

/*
Udp传输练习

既然Udp服务的有一个应用是聊天,那么我们现在就来写一个聊天程序

有收数据的部分,和发数据的部分。 
这两部分需要同时执行。 
就使用到多线程技术。 
一个线程控制收数据,一个线程控制发数据。 
 
因为读取数据的read方法和把数据存入数据包中的receive方法都是阻塞式方法
所以收,发数据要方法不同的run方法中,避免发生死锁线程永远等待的情况
*/
import java.io.*;
import java.net.*;

//发送端线程
class UdpSend implements Runnable
{
	//定义Socket服务的引用
	private DatagramSocket ds;
	//定义构造函数,接收DatagramSocket对象
	UdpSend(DatagramSocket ds)
	{
		this.ds = ds;
	}

	//复写run方法
	public void run()
	{
		//处理异常
		try
		{
			//建立键盘录入
			BufferedReader buf = 
				new BufferedReader(new InputStreamReader(System.in));

			//定义一个String类型变量,用于存储键盘录入的信息
			String line = null;

			//因为是聊天,不会一句就结束,所以创建循环。
			while((line=buf.readLine())!=null)
			{	
				//将键盘录入的字符串转成字节数组
				byte[] b = line.getBytes();

				//将字节数组存入数据包中,指定要发送的主机和端口号
				DatagramPacket dp = 
					new DatagramPacket(b,b.length,InetAddress.getByName("192.168.1.3"),10001);

				//将数据包发送出去
				ds.send(dp);
				//判断键盘;录入的是否是bye
				if("bye".equals(line))
				{
					//如果是,就结束循环,键盘不再录入数据
					break;
				}
				
			}
			//关闭资源
			ds.close();
		}
		catch (Exception e)
		{
			throw new RuntimeException("发送端出现异常");
		}
	}
}

//接收线程
class UdpRece implements Runnable
{
	//定义Socket服务的引用
	private DatagramSocket ds;
	//定义构造函数,接收DatagramSocket对象
	UdpRece(DatagramSocket ds)
	{
		this.ds = ds;
	}

	//复写run方法
	public void run()
	{
		//处理异常
		try
		{
			//因为是接收端,所以一直处于接收的状态所以要无线循环
			while(true)
			{
				//定义一个数组,用于存储数据
				byte[] b = new byte[1024];
				//定义一个数据包
				DatagramPacket dp = new DatagramPacket(b,b.length);

				//将接收到的数据存入数据包中
				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 (Exception e)
		{
			throw new RuntimeException("接收端发生错误");
		}
	}
}

class  UdpTest
{
	public static void main(String[] args) throws Exception
	{
		//创建发送端和接收端对象,构造函数接受DatagramSocket对象
		UdpSend ud = new UdpSend(new DatagramSocket());
		UdpRece ur = new UdpRece(new DatagramSocket(10001));

		//将发送端和接收端对象传入Thread的构造函数中,创建线程,并开启线程
		new Thread(ur).start();
		new Thread(ud).start();
	}
}


            TCP传输

                        

                        Tcp分为客户端和服务端。

                           

                              Socket是客户端对象。ServerSocket是服务端对象。

 

                        步骤


                              1.   建立客户端和服务端


                              2.   建立连接


                              3.   通过Socket中的IO流进行数据的传输


                              4.   关闭Socket

                         

                        客户端


                              通过查询Socket对象,发现在该对象建立时,就可以去连接指定的主机。


                                    因为Tcp是面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功。


                                    形成通路后,在该通道进行数据的传输。

 

                              步骤


                                    1.   创建Socket服务,并指定要连接的主机和端口


                                          构造函数 Socket(String  host  ,  int  port )


                                          host为主机名,也可写IP地址的字符串形式。


                                          例:Socket s = new Socket(“192.168.1.3” , 10018);


                                    2.   为了发送数据,应获取Socket中的输出流,如果要接收服务端的反馈信息,还需要获取Socket的输入流


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


                                          OutputStream getOutputStream() 返回此套接字的输出流。


                                    3.   通过输出流的write()方法,将要发送的数据写入流中


                                    4.   关闭Socket资源

 

                        服务端


                              服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。需监听一个端口。

 

                              步骤


                                    1.   建立服务端的Socket服务,通过ServerSocket对象建立,并监听一个端口


                                          构造函数 ServerSocket(int port)


                                          例:ServerSocket  ss = new  ServerSocket(10018);


                                    2.   获取链接过来的客户端对象,通过ServerSocket的accept()方法。


                                          没有连接就会等待,是阻塞式方法


                                          Socket accept()


                                          例:Socket  s  =  ss.accept();


                                    3.   客户端如果发来数据,那么服务端要使用对应的客户端对象


                                          并获取到该客户端对象的读取流,读取发过来的数据。


                                          使用Socket中的getInputStream()方法获取客户端的读取流


                                          理解:客户端和服务端使用的都是客户端的网络流,服务端通过步骤2获取了客户端对象,所以在使用客户端方法


                                    4.   关闭客户端


                                          一般服务端是常开的,因为在实际应用中,随时有客户端在请求连接和服务。但这里需要定时关闭客户端对象流,


                                          避免某一个客户端长时间占用服务器端。

 

                        获取IP地址


                              InetAddress  getInetAddress();

 

                        Tcp传输客户端和服务端相互联系代码演示

/*
Tcp传输客户端服务端互访演示。

需求
客户端给服务端发送数据,服务端收到后给客户端发回反馈信息。

为了使代码看起来方便,我们就把客户端和服务端写在一起了,一起编译分别运行
*/
import java.net.*;
import java.io.*;

//客户端
class TcpClient
{
	public static void main(String[] args)throws Exception
	{
		//1.创建Socket对象,制定要连接的主机和接口
		Socket s = new Socket("192.168.1.3",10018);

		//2.获取Socket流中的输出流,将要发送的信息写入流中
		OutputStream os = s.getOutputStream();
		os.write("你好啊.你看得到我发的信息吗?".getBytes());

		//3.获取Socket流中的输入流,读取服务端返回的信息并打印
		InputStream is = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = is.read(buf);
		//将字节数组转成字符串形式,并输出
		System.out.println(new String(buf,0,len));

		//4.关闭Socket流
		s.close();
	}
}
//服务端
class  TcpServer
{
	public static void main(String[] args) throws Exception
	{
		//1.建立ServerSocket对象并监听一个端口
		ServerSocket ss = new ServerSocket(10018);

		//2.获取连接过来的客户端对象
		Socket s = ss.accept();

		//打印一下是哪个IP连接进来了
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"--------连接");

		//3.获取连接过来的客户端对象的输入流,读取发送过来的信息
		InputStream in = s.getInputStream();
		byte[] buf = new byte[1024];
		int len = in.read(buf);
		//打印客户端发来的信息
		System.out.println(new String(buf,0,len));
		
		//4.获取连接过来的客户端的输出流,给客户端返回信息
		OutputStream os = s.getOutputStream();
		os.write("我看得到你发来的信息".getBytes());
		//关闭客户端资源
		s.close();
		//关闭服务端,可选操作
		ss.close();
	}
}


                              运行结果


 

 

                        Tcp传输练习


                              练习一

/*
Tcp传输练习一

需求
建立一个文本转换服务器 
客户端给服务端发送文本,服务端会将文本转成大写再返回给客户端。 
而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束。 
 
分析
客户端:
操作设备上的数据,那么就使用io技术,并按照io的操作规律来思考。
源:键盘录入。
目的:网络设备,网络输出流。
而且操作的是文本数据。可以选择字符流。

步骤
1.建立客户端。
2.获取键盘录入。
3.将键盘录入的数据发给服务端。
4.服务端将收到的数据转为大写,在返回给客户端。
5.完事儿,关闭资源

这个程序可能会产生的问题
现象
	客户端和服务端都在莫名的等待。
为什么呢?
	因为客户端和服务端都有阻塞式方法,这些方法没有读到结束标记,就会一直等待。
	从而导致客户端和服务端都在等待。read和accept都是阻塞式方法。
解决方案
	1.可用高效缓冲区类的newLine()换行作为结束标记,并用flush()进行刷新。 
	2.可用PrintWriter(s.getOutputStrean(),true)创建打印流对象,
	  在调用println方法时因为传入了true参数所以自动刷新。"ln"表示打印并换行。
	在这里我们使用第二种方法
*/
import java.io.*;
import java.net.*;

//客户端
class	TcpUpClient
{
	public static void main(String[] args) throws Exception
	{
		//创建客户端对象
		Socket s = new Socket("192.168.1.3",10018);

		//读取键盘录入
		BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
		
		//BufferedWriter bufOut = 
			//new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		//定义PrintWriter对象,将客户端的输出流作为参数传入,并传入true参数,使调用println方法时流可以自动刷新
		PrintWriter out  = new PrintWriter(s.getOutputStream(),true);

		//定义一个Socket读取流,读取服务端返回的信息
		BufferedReader in = 
			new BufferedReader(new InputStreamReader(s.getInputStream()));

		//读取键盘录入的数据
		String line = null;
		while((line = buf.readLine())!=null)
		{
			//如果输入的时over则输入停止输入
			if("over".equals(line))
				break;

			//将键盘录入数据写入输出流中,给服务端送去
			out.println(line);
			//bufOut.write(line);
			//bufOut.newLine();
			//bufOut.flush();

			//读取服务端的返回信息
			String str = in.readLine();
			//输出
			System.out.println("变为大写为:"+str);
		}
		//关闭流和客户端资源。
		buf.close();
		s.close();
	}
}

//服务端
class TcpUpServer
{
	public static void main(String[] args)throws Exception
	{
		//创建ServerSocket对象,监听端口
		ServerSocket ss = new ServerSocket(10018);

		//获取客户端对象
		Socket s = ss.accept();

		//获取IP地址并打印
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"--------------连接");

		//获取Socket流中的数据
		BufferedReader in = 
			new BufferedReader(new InputStreamReader(s.getInputStream()));

		//BufferedWriter bufOut = 
			//new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
		//定义PrintWriter类,用于给客户端返回信息。
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);

		//读取输入流中的信息
		String line = null;
		while((line = in.readLine())!=null)
		{
			//输出获取的字符串
			System.out.println(line);
			//将字母变为大写后返回给客户端
			out.println(line.toUpperCase());
			//bufOut.write(line.toUpperCase());
			//bufOut.newLine();
			//bufOut.flush();
		}
		//关闭客户端资源
		s.close();
		//关闭服务端资源,可选操作
		ss.close();	
	}
}

      

                  运行结果


 

 

                              练习二

/*
需求:向服务器上传一个文件,服务端返回一条信息 

客户端: 
源:文件	目的:网络输出流。 
若操作的是文本数据,可选字符流,并加入高效缓冲区。若是媒体文件,用字节流。 
 
服务端: 
源:socket读取流	目的:socket输出流。 
 
出现的问题:  
现象: 
文件已经上传成功了,但是没有得到服务端的反馈信息。 
即使得到反馈信息,但得到的是null,而不是“上传成功”的信息 
 
原因: 
因为客户端将数据发送完毕后,服务端仍然在等待这读取数据,并没有收到结束标记,就会一直等待读取。 
上个问题解决后,收到的不是指定信息而是null,是因为服务端写入数据后,
需要刷新,才能将信息反馈给客服端。 
 
解决: 
定义时间戳,由于时间是唯一的,在发送数据前,先获取时间,发送完后在
	结尾处写上相同的时间戳,在服务端接收数据前先接收一个时间戳然后在循环中判断时间戳以结束标记。 
通过socket方法中的shutdownOutput(),关闭输入流资源,
	从而结束传输流,以给定结束标记。通常用这个方法。 
*/  
  
import java.io.*;  
import java.net.*;  
  
  
//客户端  
class  TcpTestClient  
{  
    public static void main(String[] args) throws Exception  
    {  
        //创建Socket对象 
        Socket s=new Socket("192.168.1.3",10018);  
          
        //定义读取流读取文件 
        BufferedReader buf = new BufferedReader(new FileReader("c:\\我爱写博客.txt"));  
  
        //定义目的,将数据写入到Socket输出流。发给服务端  
        //BufferedWriter bwout=
			//new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));  
        PrintWriter out = new PrintWriter(s.getOutputStream(),true);  
  
        //定义一个读取流,读取服务端返回信息。  
        BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));  
  
		//读取文件中的数据
        String line = null;  
        while ((line = buf.readLine())!=null)  
        {  
			//将文件中的数据发给服务端
            out.println(line);  
        }  
        
		//关闭客户端的输出流。相当于给流中加入一个结束标记-1. 
        s.shutdownOutput(); 
  
        //读取服务端返回的信息
		String str = in.readLine();
		//输出服务端返回的信息
		System.out.println(str);
		//关闭读取文件的流,和客户端资源
        buf.close();  
        s.close();  
    }  
}  
  
//服务端  
class TcpTestServer  
{  
    public static void main(String[] args)throws Exception  
    {  
        //建立ServerSocket对象,并指定监听端口  
        ServerSocket ss = new ServerSocket(10018);  
          
        //获取客户端  
        Socket s = ss.accept();  
  
        //获取IP地址并打印
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"———————————连接");
  
        //读取Socket读取流中的数据  
        BufferedReader in = 
			new BufferedReader(new InputStreamReader(s.getInputStream()));  
  
        //建立打印流,将客户端发来的数据存入指定目的文件中。  
        PrintWriter pw = 
			new PrintWriter(new FileWriter("123.txt"),true);  
          
        
  
		//读取客户端发来的信息
        String line=null;  
        while ((line=in.readLine())!=null)  
        {  
			//将客户端发来的数据写入打印流中,打印流会自动刷新
            pw.println(line);  
        }  

		//定义打印流,将Socket输出流作为参数传图,将返回信息写入打印流中。  
		PrintWriter out = new PrintWriter(s.getOutputStream(),true); 

        //给客户端返回信息
        out.println("上传成功!");  
     
		//关闭打印流资源,客户端资源
        pw.close();
        s.close();  
		//关闭服务端资源,可选操作
        ss.close();  
    } 
}


                                    运行结果



      

                        从以上两个练习我们可以得出这样的结论


                              1.   使用Tcp传输纯文本文件要用到字符流的缓冲区和转换流,因为Socket对象的方法得到的是字节流,


                                    而操作纯文本文件当然是字符流最为合适。并且换缓冲区有特殊的方法,使程序更加高效。读取时有readLine()方法,


                                    输出时可以使用PrintWriter对象。PrintWriter对象可以直接接收字节流,不用转换流转换,并且传入true参数后可以自动刷新流,


                                    再使用println(String)方法把字符串写入到输出流中会换行,很方便。


                              2.   如果要传输图片,因为都是图片是字节,所以直接使用FileInputStream获取文件即可。


                                    并且直接使用OutputStream和InputStream获取客户端的输入输出流集合。


                                    在服务端返回给客户端文字时,可以使用PrintWriter对象实现,可以简化代码。

                        

                        客户端上传图片


                              单线程(一对一)上传思路


                                    客户端


                                          1.   创建Socket服务,制定服务端地址,和设置端口


                                          2.   定义输入流,读取图片数据


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


                                          4.   通过Socket输入流读取服务端返回的数据。


                                          5.   关闭资源

 

                                    服务端


                                          1.   创建服务端服务,并监听端口


                                          2.   获取客户端对象,并获取客户端的IP地址,用于区分是哪个客户端发来的


                                          3.   建立Socket输入流,读取客户端发来的数据


                                          4.   建立输出流,将客户端发来的数据存入目的文件中。


                                          5.  建立Socket输出流,给客户端返回信息


                                          6.   关闭资源

 

                                    单线程的服务端有个局限性,当A客户端连接上后,被服务端获取到,服务端就执行流程。


                                          这时B客户端连接只能等待,因为还没有处理完A客户端的请求。


                                          还没循环回来执行下一个accept()方法,所以暂时获取不到B客户端对象。

 

                                    那么为了可以让多个客户端同时并发访问服务端,服务端最好就是将每个客户端封装到单独的线程中。


                                          这样就可以同时处理多个客户端请求。

 

                                    如何定义线程呢?


                                          只要明确每一个客户端要在服务端执行的代码即可。将该代码存入run方法中。

                           

                              多个客户端并发上传图片代码演示

/*
Tcp并发上传图片练习
*/
import java.io.*;
import java.net.*;
//客户端
class  PicClient
{
	public static void main(String[] args)throws Exception 
	{
		//判断是否有文件路径,如果没有提醒用户传入一个文件路径
		if(args.length!=1)
		{
			System.out.println("请选择一个jpg格式的图片");
			return ;
		}
		
		//获取args数组中的第一个元素,就是在DOS命令行中在java TcpPicTest 
		//后传入的文件的路径和文件名,通过这种方式指定要被发送的文件。
		File file = new File(args[0]);
		//判断文件是否存在,是否是文件
		if(!(file.exists() && file.isFile()))
		{
			System.out.println("这个文件有问题,不是不存在就是不是文件,查查再输入");
			return ;

		}
		//判断文件后缀名是不是.jpg 看看是不是图片
		if(!file.getName().endsWith(".jpg"))
		{
			System.out.println("你传的不是图片啊,要不就不是jpg文件图片");
			return ;
		}
		//判断文件大小,不能大于5M
		if(file.length()>1024*1024*5)
		{
			System.out.println("传那么大文件干嘛,传个小点儿的");
			return ;
		}
		//经过上面的重重选拔,可以运行到这里的都是符合要求的文件
		//创建Socket对象,指定IP地址和端口
		Socket s = new Socket("192.168.1.3",10018);

		//建立读取流,读取图片数据
		FileInputStream fis = new FileInputStream(file);
		//建立Socket的输出流,用于给服务端发送数据
		OutputStream out = s.getOutputStream();
		//定义容器
		byte[] buf = new byte[1024];
		//int类型变量,用于接收read()方法返回值
		int len = 0;
		//从读取文件的流中读取数据,存入字节数组中
		while((len=fis.read(buf))!=-1)
		{
			//将字节数组中的数据写入给客户端的输出流中
			out.write(buf,0,len);
		}

		//建立结束标记,告诉服务端数据已写完
		s.shutdownOutput();
		//建立Socket的读取流,用于读取服务端返回的信息
		InputStream in = s.getInputStream();
		//定义缓冲区
		byte[] bufIn = new byte[1024];
		//读取服务端返回的信息
		int num = in.read(bufIn);
		//打印客户端返回的信息
		System.out.println(new String(bufIn,0,num));
		//关闭资源
		fis.close();
		s.close();
	}
}

//利用多线程实现并发上传
class PicThread implements Runnable
{
	//定义Socket变量
	private Socket s;
	//在构造函数接收Socket类型参数
	PicThread(Socket s)
	{
		this.s = s;
	}
	//复写run方法
	public void run()
	{
		//定义一个计数器,用于给接收到的照片起名字
		int count = 1;
		//获取客户端的IP地址
		String ip  = s.getInetAddress().getHostAddress();
		//异常处理
		try
		{
			//打印IP地址,显示是哪个客户端连接上了
			System.out.println(ip+"-------连接");
			//创建Socket读取流,读取客户端发送过来的数据
			InputStream in = s.getInputStream();
			//建立保存文件的目录
			File dir =  new File("e:\\Tcp传输图片");
			//根据IP地址和计数器定义接收数据的名称,并封装成对象。
			File file = new File(dir,ip+"("+(count)+")"+".jpg");
			//如果要存入的名称已经存在了
			while(file.exists())
				//那么就把count自增,再设置新的名字。
				file = new File(dir,ip+"("+(count++)+")"+".jpg");

			//建立输出流,与目的文件相关联
			FileOutputStream fos = new FileOutputStream(file);
			//建立容器
			byte[] buf = new byte[1024];
			//定义int类型变量
			int len = 0;
			//读取Socket读取流中的内容,将客户端发来的数据存入数组中
			while((len=in.read(buf))!=-1)
			{
				//将数组中的数据写入目的文件中。
				fos.write(buf,0,len);
			}
			//建立Socket输出流,给客户端返回数据
			OutputStream out = s.getOutputStream();
			
			//告诉客户端,你上传成功啦
			out.write("上传成功OK!".getBytes());
			//关闭资源
			fos.close();
			s.close();
		}
		catch (Exception e)
		{
			throw new RuntimeException(ip+"上传失败");
		}
	}
}

//服务端
class  PicServer
{
	public static void main(String[] args) throws Exception
	{
		//建立ServerSocket服务,监听端口
		ServerSocket ss = new ServerSocket(10018);

		//定义循环,因为要一直接受,所以是无限循环
		while(true)
		{
			//获取连接到服务端的客户端对象
			Socket s = ss.accept();
			//将其作为参数传入PicThread构造函数中,再把PicThread传入Thread构造函数中。
			//创建线程,调用start方法开启线程。
			new Thread(new PicThread(s)).start();	
		}
		//因为要一直接受图片,就不关闭服务端资源了
		//ss.close();
	}
}


                                    运行结果




      

                        客户端并发登录


                              客户端通过键盘录入用户名,服务端从用户目录中寻找这个用户名。


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


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


                                    最多登录三次。

 

                              代码演示

/*
客户端并发登录

得有一个用户列表,显示用户名,然后用户名正确才能登陆
*/
import java.io.*;
import java.net.*;

//客户端
class  LoginClient
{
	public static void main(String[] args) throws Exception
	{
		//创建Sokcet服务,指定IP地址和端口
		Socket s = new Socket("192.168.1.3",10018);
		//读取键盘录入
		BufferedReader bufr = 
			new BufferedReader(new InputStreamReader(System.in));
		//建立Socket输出流,用于给服务端发送数据
		PrintWriter out = new PrintWriter(s.getOutputStream(),true);
		//建立Socket输入流,用于读取服务端返回的信息
		BufferedReader in =
			new BufferedReader(new InputStreamReader(s.getInputStream()));
		//定义循环,一个客户端只能登录三次
		for(int x=0; x<3; x++)
		{
			//读取键盘录入的信息
			String line = bufr.readLine();
			//如果没有录入,则结束
			if(line==null)
				break;
			//将键盘录入读取的信息写入输出流中发送给服务端
			out.println(line);
			//读取服务端返回的信息
			String info = in.readLine();
			//打印服务端返回的信息
			System.out.println("info:"+info);
			//判断返回的信息中是否带有欢迎两个字,如果带,就表示输入的用户名正确,结束循环
			//如果没有则继续循环,等待键盘录入
			if(info.contains("欢迎"))
				break;
		}
		//关闭资源
		bufr.close();
		s.close();
	}
}

//客户端并发登录线程
class UserThread implements Runnable
{
	//定义Socket变量
	private Socket s;
	//构造函数接收Socket对象
	UserThread(Socket s)
	{
		this.s = s;
	}
	//复写run方法
	public void run()
	{
		//获取IP地址并打印
		String ip = s.getInetAddress().getHostAddress();
		System.out.println(ip+"---------连接");
		//处理异常
		try
		{
			//最多三次
			for(int x=0; x<3; x++)
			{
				//创建Socket读取流,读取客户端发来的信息
				BufferedReader in = 
					new BufferedReader(new InputStreamReader(s.getInputStream()));
				//使用readLine方法读取客户端发来的信息
				String name = in.readLine();
				//如果是null结束循环
				if(name==null)
					break;
				//创建读取流读取用户列表中的信息
				BufferedReader bufr = new BufferedReader(new FileReader("e:\\user.txt"));
				//创建Socket输出流,往客户端返回信息
				PrintWriter out = new PrintWriter(s.getOutputStream(),true);
				
				//定义一个String类型变量,用于接收用于列表中的信息
				String line = null;
				//定义标记
				boolean flag = false;
				//读取用户列表中的信息
				while((line=bufr.readLine())!=null)
				{
					//判断,如果用户列表中信息和客户端发来的信息相同
					if(line.equals(name))
					{
						//那么把结束标记设为true,结束循环
						flag = true;
						break;
					}				
				}
				
				//如果结束标记为true,也就是客户端发来的数据在用户列表中可以找到
				if(flag)
				{
					//那么就在服务端输出用户已登录字样
					System.out.println(name+",已登录");
					//并往客户端发送信息,欢迎登录
					out.println(name+",欢迎登录");
					//结束循环
					break;
				}
				//如果标记为false
				else
				{
					//那么就在服务端打印用户尝试登陆
					System.out.println(name+",尝试登录");
					//往客户端返回,用户不存在
					out.println(name+",用户名不存在");
				}
			}
			//关闭资源
			s.close();
		}
		catch (Exception e)
		{
			throw new RuntimeException(ip+"校验失败");
		}
	}
}

//服务端
class  LoginServer
{
	public static void main(String[] args) throws Exception
	{
		//创建ServerSocket对象,并监听端口
		ServerSocket ss = new ServerSocket(10018);
		//建立循环
		while(true)
		{
			//获取客户端对象。
			Socket s = ss.accept();
			//创建并发登录线程,并开启
			new Thread(new UserThread(s)).start();
		}
	}
}


                                    运行结果


 

      

      URL和URLConnection

            

            URL


                  URL的语法由RFC2396文档定义


                  URL和URI的区别:


                        URI的范围比URL大,虽然代表的都是唯一的标识。


                  用URL获取导航栏中的信息,并通过URL对象中的方法,分别获取内容。

 

                  URL中方法


                        1.    构造函数


                               URL(String protocol , String host , int  port , String  file)


                               参数传入 要使用的协议名称,主机名,端口号,主机上的文件。


                        2.   String getProtocol()            获取协议名称


                        3.   String getHost()            获取主机名


                        4.   int getPort()            获取端口号


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


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


 

                  注:当导航栏中没有输入端口号的话,通过getPort()方法返回值若为-1,这是我们就要把它的值改为80


                          如


                              int  port  =  u.getPort();


                              if(port  ==  -1)


                                    port = 80;

 

            URLConnection


                  URLConnection是URL对象调用openConnection()方法得到的远程对象的连接,也就是URLConnection对象。


                        有了URLConnection对象我们就可以在应用层之间传输数据。URLConnection中封装了一部分Socket中的方法。


                        如:getInputSteam()方法,我们可以通过这个方法获取网站中的信息,而Socket是传输层协议,通过URL连接得到的数据


                        只有正文主体,没有信息头,URLConnection还可以用来解析头,获取部分头。

                 

                  URLConnection中方法


                        1.   URLConnection  openConnection();


                              URL对象调用此方法,返回一个URLConnection 对象


                        2.   InputStream getInputStream()            获取输入流,URLConnection中封装了一部分Socket中的方法


                        3.   OutputStream getOutputStream()            获取输出流

 

      小知识点


            1.   Socket对象在构造时可以不传入参数,可以通过 void  connect(SockAddress endpoint)方法来连接指定服务端的IP地址和端口。


                  SocketAddress的子类对象InetSocketAddress的构造函数可以将IP地址和端口号封装。所以也可以通过这个方法连接到服务器。


            2.   ServerSocket类的构造函数             ServerSocket(int port , intbacklog)          


                  backlog表示队列的最大长度,即能连接到服务器的最大同时个数。


            3.   域名解析


                  我们想要在浏览器中打开网址,就要在导航连输入网址或者IP地址。那么如何通过主机名获取IP地址,从而连接上这台主机呢?


                        想要将主机名翻译成IP地址,需要域名解析,DNS(域名解析服务器)


                  在输入解析名进行访问的时候,会现在本地的hosts文件(c:\windows\system32\drivers\ext\host)中找到对应的映射。


                  如果在这个文件中有我们要用的主机名和IP地址,那么会被优先使用,也会对这个文件中的内容进行优先查找。


                        (所以如果想恶作剧,可以把朋友常用的网址,存入到hosts文件中,再对应一个根本不是这个网址的主机名,


                             因为会优先查找hosts中已经存在的映射关系,如果他不懂电脑肯定会觉得电脑坏了,就在旁边捡乐吧,哈哈哈哈)


                  如果直接输入IP地址访问主机,就不通过DNS直接连接到对应的主机

 

 




谢谢大家观看~        

-----------android 培训 java培训 、java学习型技术博客、期待与您交流!---------
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值