黑马程序员——Java 网络编程

时间:2023-02-18 11:12:09

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

一、概述

 1.网络模型

  网络模型常见的有ISO参考模型和TCP/IP参考模型,两者的对应关系如下图:

黑马程序员——Java 网络编程

  ISO参考模型分为七个层次:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。

  TCP/IP参考模型分为四个层次:应用层、传输层、网际层、主机至网络层。

  1)对于TCP/IP参考模型,开发人员一般处于传输层和网际层,web开发属于应用层,用户操作也属于应用层。各层所对应的常见协议如下:

   应用层:HTTP协议、FTP协议等。

   传输层:UDP协议、TCP协议等。

   网际层:IP协议等。

  2)数据封包与数据拆包:

   数据封包:数据在传输前,按照某一参考模型逐层进行封装该层的特有标识的过程。

   数据拆包:数据传输完后,按照某一参考模型逐层进行拆解该层的特有标识的过程。

   示例:ISO参考模型的封包与拆包图解。

黑马程序员——Java 网络编程

 2.网络通讯要素

  1)IP地址:

   网络设备的标识,是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

   a)IP 地址是 IP 使用的 32 位或 128 位无符号数字,它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的。

   b)IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。

   c)每个IP地址至少对应一个或多个主机名,主机名比IP地址更容易记忆。

   d)IP地址127.0.0.1为本地回环地址,可以用于测试网卡,对应的主机名为localhost,主机名可以在host文件进行修改。

   IP地址在java中用InetAddress类进行描述,InetAddress有两个子类,分别为Inet4Address和Inet6Address。

   获取InetAddress对象的方法:

    a)通过给定原始IP地址获取:

     static InetAddress getByAddress(byte[] addr):该方法将IP地址的四段分别存入字节数组,由于字节数组的范围在-128到127,如果超出127的段,则返回的为负数,使用原始IP地址时应注意修正。

    b)通过主机名和IP地址获取:

     static InetAddress getByAddress(String host, byte[] addr):

    c)通过主机名获取:

     static InetAddress getByName(String host):

    d)直接调用返回本地主机: 

     static InetAddress getLocalHost():

    e)通过主机名,返回其 IP 地址所组成的数组:

     static InetAddress[]getAllByName(String host):

    注:所有获取InetAddress对象的方法,都将抛出UnknownHostException异常,该异常为IOException异常的子类。

   InetAddress类的常用方法:

    a)获取IP地址的字符串表示:

     String getHostAddress():

    b)获取主机名:

     String getHostName():

    c)获取InetAddress对象的原始IP地址:

     byte[] getAddress():

  2)端口号:

   a)用于表示进程的逻辑地址。

   b)端口号的范围为0到65535,其中0到1024为系统使用或保留端口,但也可以使用,只要与系统不冲突即可。

  3)传输协议:

   传输协议即通信规则,常见的协议有UDP和TCP。


二、UDP通信

 1.特点:

  UDP是面向无连接的,即UDP没有建立连接的过程,没有发送连接请求数据包,而是直接发送要传输的数据包,因此具有连接速度快,但一次传输数据量较少且传输可靠性较低的特点,传输过程容易丢失数据。因此UDP特点总结如下:
  1)将数据及源和目的封装成数据包中,不需要建立连接,速度快。
  2)每个数据报包的大小在限制在64k内,一次传输少量数据。
  3)因无连接,是不可靠的协议,有可能丢失数据。

 2.应用场合:

  UDP适用于对传输速度要求较高,一次传输数据量不大,且可靠性要求不高的应用环境,如视频会议、视频
直播、桌面共享等。

 3.DatagramSocket类:

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

  1)创建DatagramSocket对象:只能通过构造函数创建。

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

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

   DatagramSocket(int port, InetAddress laddr):创建数据报套接字,将其绑定到指定的本地地址。

   DatagramSocket(SocketAddress bindaddr): 创建数据报套接字,将其绑定到指定的本地套接字地址。

  2)常用方法:

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

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

    InetAddress getInetAddress():返回此套接字连接的地址。

    int getPort():返回此套接字的端口。

    void connect(InetAddress address, int port):将套接字连接到此套接字的远程地址。

    void connect(SocketAddress addr):将此套接字连接到远程套接字地址(IP 地址 + 端口号)。

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

  注:更多方法参见API文档。 

 4.DatagramPacket类:

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

  1)创建DatagramPacket对象:

   DatagramPacket(byte[] buf, int length):

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

   DatagramPacket(byte[] buf, int length, SocketAddress address):

  2)常用的方法:

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

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

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

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

   SocketAddress getSocketAddress():获取要将此包发送到的或发出此数据报的远程主机的 SocketAddress(通常为 IP 地址 + 端口号)。

  注:更多方法参见API文档。

 5.UDP传输:

  1)UDP发送端的创建步骤:

   a)建立DatagramSocket服务,可指定发送端的端口。
   b)将要发送的数据封包,封包的类为DatagramPacket,且需要指定目标IP对象和目标端口以及封装的数据和长度。
   c)利用DatagramSocket服务的send方法将数据包发送出去。
   d)关闭资源。

  2)UDP接收端的创建步骤:

   a)建立DatagramSocket服务,并指定端口,以接收发送端发来的数据。
   b)定义一个数据包,用来存储接收来的数据。
   c)通过DatagramSocket服务的receive方法将数据存储到所定义的数据包中。
   d)利用数据包对象的更多功能实现对接收数据的不同操作。
   e)关闭资源。

  3)代码示例:

//定义UDP发送端
import java.net.*;
class UdpSend
{
public static void main(String[] args) throws Exception
{
//建立发送端Socket服务,并指定发送端程序的端口为8888
//如果不指定端口,则系统会自行分配
DatagramSocket ds=new DatagramSocket(8888);

//定义要一个字节数组,用来存储要发送的数据
byte[] buf="hello UdpRece".getBytes();

//将要发送的数据封包,并指定目标主机IP对象和目标端口
DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),10000);

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

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

}
}

//定义UDP接收端
class UdpRece
{
public static void main(String[] args) throws Exception
{
//建立发送端Socket服务,并指定接收端程序的端口为10000
DatagramSocket ds=new DatagramSocket(10000);

//定义要一个字节数组
byte[] buf=new byte[1024];

//定义一个数据包,用来存储要接受的数据
DatagramPacket dp=new DatagramPacket(buf,buf.length);

//通过接收端Socket服务中的receive方法将数据存储到所定义的数据包中
ds.receive(dp);

//获取发送端主机IP对象,并通过该对象获取发送端主机地址
String ip=dp.getAddress().getHostAddress();

//获取接收的数据内容
byte[] data=dp.getData();

//获取发送端主机程序端口
int port=dp.getPort();

//打印获取的数据到控制台
//获取打印数据的实际长度为getLength方法
System.out.println(ip+"--"+(new String(data,0,dp.getLength()))+"--"+port);

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

}
}
  6.代码练习:

  练习1:UDP发送端通过键盘录入的方式向UDP接收端发送数据。

<pre name="code" class="java">import java.net.*;
import java.io.*;
class UdpSend
{
public static void main(String[] args)
{
DatagramSocket ds=null;
BufferedReader bufr=null;
try
{
ds=new DatagramSocket();
bufr=new BufferedReader(new InputStreamReader(System.in));
String line=null;
while((line=bufr.readLine())!=null)
{
if (line.equals("over"))
{
break;
}
byte[] buf=line.getBytes();
DatagramPacket dp=
new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.90.31"),10002);
ds.send(dp);

}
}

//SocketException异常为IOException异常的子类
catch(SocketException ex)
{
System.out.println("Socket异常:"+ex.toString());
}
catch (IOException e)
{
System.out.println("IO异常:"+e.toString());
}
finally
{
if (bufr!=null)
{
try
{
bufr.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
ds.close();
}
}
}

class UdpRece
{
public static void main(String[] args)
{
DatagramSocket ds=null;
try
{
ds=new DatagramSocket(10002);
while (true)
{
byte[] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,buf.length);
ds.receive(dp);//抛出了IOException异常
String ip=dp.getAddress().getHostAddress();
String data=new String(dp.getData(),0,dp.getLength());
System.out.println(ip+"--"+data);
}

}
catch (SocketException e)
{
throw new RuntimeException("Socket服务创建失败");
}
catch (IOException ex)
{
throw new RuntimeException("接受数据异常");
}
finally
{
ds.close();
}
}
}

  练习2:UDP发送端与接收端互相通信。 

<pre name="code" class="java">//定义UDP发送端
import java.net.*;
class UdpSend3
{
public static void main(String[] args) throws Exception
{
//向UDP接收端发送数据包
DatagramSocket ds=new DatagramSocket(8888);
byte[] buf="hello UdpRece".getBytes();
DatagramPacket dp=
new DatagramPacket(buf,buf.length,InetAddress.getByName("192.168.90.36"),10000);
ds.send(dp);

//接收UDP发送端发送的数据包
buf=new byte[1024];
DatagramPacket dps=new DatagramPacket(buf,buf.length);
ds.receive(dps);
String ip=dps.getAddress().getHostAddress();
byte[] data=dps.getData();
int port=dps.getPort();
System.out.println(ip+"--"+(new String(data,0,dps.getLength()))+"--"+port);

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

}
}

//定义UDP接收端
class UdpRece3
{
public static void main(String[] args) throws Exception
{
//接收UDP发送端发送的数据包
DatagramSocket ds=new DatagramSocket(10000);
byte[] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,buf.length);
ds.receive(dp);
String ip=dp.getAddress().getHostAddress();
byte[] data=dp.getData();
int port=dp.getPort();
System.out.println(ip+"--"+(new String(data,0,dp.getLength()))+"--"+port);

//向UDP发送端返回数据包
buf="hello UdpSend".getBytes();
DatagramPacket dpr=new DatagramPacket(buf,buf.length,dp.getAddress(),dp.getPort());
ds.send(dpr);


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

 

三、TCP通信

 1.特点:

  TCP是面向有连接的,即TCP在发送数据之前,会先发送连接请求数据包,通过三次握手建立起连接,然后再进行数据的发送,因此具有传输可靠性强,一次传输数据量大的特点,但由于需要建立连接,传输速度稍慢。因此TCP的特点总结如下:  

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

   2)在连接中进行大数据传输。  

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

   4)因为必须建立连接,所以效率会稍低,速度稍慢。

 2.应用场合:

  TCP通信适用于对传输速度要求不高,但一次传输数据量较大,且可要性要求很高的应用场合,如数据下载、文件传输等。

 3.Socket类:

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

  1)创建Socket对象的常见构造方法:

   Socket(InetAddress address, int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号。

   Socket(String host, int port):创建一个流套接字并将其连接到指定主机上的指定端口号。

  2)常用方法:

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

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

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

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

   InetAddress getInetAddress():返回套接字连接的地址。 

   int getPort():返回此套接字连接到的远程端口。

   void close():关闭此套接字。

  注:更多方法参见API文档。

 4.ServerSocket类:

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

  1)创建ServerSocket对象的常见构造方法:

   ServerSocket(int port): 创建绑定到特定端口的服务器套接字。

   ServerSocket(int port, int backlog):利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。

  2)常见的方法:

   Socket accept():侦听并接受到此套接字的连接。

   InetAddress getInetAddress():返回此服务器套接字的本地地址。

   void close(): 关闭此套接字。

  注:更多方法参见API文档。

 5.TCP传输:

  1)TCP客户端的创建步骤:

   a)建立Socket服务,并指定连接的主机和端口。   

   b)获取键盘录入或读取硬盘文件数据。   

   c)获取Socket输出流,将数据发送给服务端。   

   d)获取Socket读取流,读取服务度返回的数据。   

   e)关闭资源。

  2)TCP服务器的创建步骤:

   a)建立ServerSocket服务,并监听一个端口。   

   b)通过accept方法获取客户端Socket对象。   

   c)获取Socket读取流,读取客户端发来的数据。   

   d)获取Socket输出流,将客户端请求的数据发送给客户端。   

   e)关闭资源。

  3)代码示例:

   示例1:TCP客户端与服务端通信。

//Tcp客户端

import java.net.*;
import java.io.*;
class TcpClient
{
public static void main(String[] args)
{
Socket s=null;
try
{
//建立Socket服务,需指定连接主机IP和端口,抛出UnknownHostException异常
s=new Socket("127.0.0.1",10004);

//获取输出流对象
OutputStream os=s.getOutputStream();

//把数据写入输出流中
os.write("hello TcpServer".getBytes());

//获取读取流对象
InputStream is=s.getInputStream();

byte[] buf=new byte[1024];

//将读取的数据写入字节数组中
int len=is.read(buf);

System.out.println(new String(buf,0,len));

}
catch (UnknownHostException e)
{
throw new RuntimeException("未知主机异常");
}
catch (IOException ex)
{
throw new RuntimeException("IO异常");
}
finally
{
if(s!=null)
try
{
//关闭服务端
s.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}

}
}

//Tcp服务端
class TcpServer
{
public static void main(String[] args)
{
ServerSocket ss=null;
Socket s=null;
try
{
//建立服务端ServerSocket服务,并监听10004端口
ss=new ServerSocket(10004);

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

//获取客户端主机IP地址
String ip=s.getInetAddress().getHostAddress();

System.out.println(ip+".....connected");

//从Socket中获取读取流对象
InputStream is=s.getInputStream();

//定义字节数组
byte[] buf=new byte[1024];

//将数据读取到字节数组中
int len=is.read(buf);

//打印字节数组中的数据
System.out.println(new String(buf,0,len));

//从Socket对象获取输出流对象
OutputStream os=s.getOutputStream();

Thread.sleep(2000);
//给客户端写数据
os.write("hello TcpClient".getBytes());

}
catch (IOException e)
{
throw new RuntimeException("IO异常");
}
catch(InterruptedException ex)
{
throw new RuntimeException(ex.toString());
}
finally
{
try
{
//关闭客户端连接
s.close();

//关闭服务端连接
ss.close();

}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
   示例2:复制文件。

import java.io.*;
import java.net.*;
class TcpClient1
{
public static void main(String[] agrs)
{
Socket s=null;
BufferedReader bufr=null;
try
{
//建立Socket服务,并指定目标主机和端口
s=new Socket("127.0.0.1",10004);

//创建字符流读取缓冲区对象并关联1.txt文件
bufr=new BufferedReader(new FileReader("E:\\heima\\measure\\1.txt"));

//创建字符流读取缓冲区对象,并关联Socket读取流
BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

//创建打印流对象,并关联Socket输出流
PrintWriter Out=new PrintWriter(s.getOutputStream(),true);
String line=null;
while ((line=bufr.readLine())!=null)
{
Out.println(line);//自动换行并刷新
}
s.shutdownOutput();//关闭Socket输出流
String str=bufIn.readLine();
System.out.println(str);
}
catch (IOException e)
{
throw new RuntimeException("客户端异常"+e.toString());
}
finally
{
if (s!=null)
{
try
{
s.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
if (bufr!=null)
{
try
{
bufr.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
}
}
}

class TcpServer1
{
public static void main(String[] agrs)
{
Socket s=null;
BufferedWriter bufw=null;
try
{
//建立ServerSocket服务,并监听10004端口
ServerSocket ss=new ServerSocket(10004);
s=ss.accept();//获取客户端Socket对象

//获取连接服务器的客户端IP地址,并打印到控制台
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+".....connected");

//创建字符流写入缓冲区对象,并关联server.txt文件
bufw=new BufferedWriter(new FileWriter("E:\\heima\\measure\\server.txt"));

BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

PrintWriter out=new PrintWriter(s.getOutputStream(),true);
String line=null;

//如果客户端的Socket输出流未关闭,该循环不会结束
while ((line=bufIn.readLine())!=null)
{
bufw.write(line);
bufw.newLine();
bufw.flush();
}
out.println("上传成功");
}
catch (IOException e)
{
throw new RuntimeException("服务端异常"+e.toString());
}
finally
{
if (s!=null)
{
try
{
s.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
if (bufw!=null)
{
try
{
bufw.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
}
}
}
  6.代码练习:

  练习1:客户端从键盘输入读取一个字母形式的字符串,发送到服务器,并在服务器完成字符串的大写转换,将转换后的字符串返回给客服端。

/*
* 客户端:
* 由于要操作设备上的数据,因此可选择使用IO技术,按照IO的思路就需要明确源和目的:
* 源:键盘录入;
* 目的:网络设备,即网络输出流。
* 且键盘录入数据属于文本数据,可以选择字符流,同时提高效率,加入缓冲区。
* 步骤:
* 1.建立服务;
* 2.获取键盘录入;
* 3.将数据发给服务端;
* 4.获取服务端返回的数据;
* 4.结束,关闭资源。
*
* 服务端:
* 由于服务端是通过获取客户端的socket对象,因此服务端的源和目的为:
* 源:socket读取流;
* 目的:socket输出流。
* 步骤:
* 1.建立ServerSocket的服务;
* 2.获取客户端Socket对象;
* 3.读取客户端发送的数据;
* 4.将处理后的数据返回给客户端;
* 5.结束,关闭客户端Socket服务。
*
*/

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

//定义一个主类
public class TcpDemo3{
//主函数中使用了IO,直接在函数上抛出IOException
public static void main(String[] args)throws IOException{
//创建一个运行客户端的线程,并启动该线程
new Thread(new TcpClient()).start();
//创建一个运行服务端的线程,并启动该线程
new Thread(new TcpServer()).start();
}
}
//定义一个客户端,该类实现了Runnable接口
class TcpClient implements Runnable{
//覆盖Runnable接口的run()方法,创建的客户端线程将在该方法中运行
public void run(){
BufferedReader bufr=null;
Socket s=null;
//由于使用到IO,会产生IO异常,且Runnable接口不能将该异常抛出,因此直接将代码进行异常处理
try{
//初始化目标主机ip地址和端口
s=new Socket("127.0.0.1",10008);

//定义读取键盘数据的读取流
bufr=new BufferedReader(new InputStreamReader(System.in));

//定义目的,将数据写入到socket输出流,发给服务端
BufferedWriter bufOut=
new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));

//定义一个socket读取流,读取服务端返回的数据
BufferedReader bufIn=
new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
//将键盘录入的数据发送给服务端,并将服务端返回的数据进行输出
while((line=bufr.readLine())!=null){
//键盘录入的字符串为"over"则结束
if("over".equals(line)){
break;
}
//将数据写到缓冲区
bufOut.write(line);
//换行
bufOut.newLine();
//将缓冲区的数据发送出去
bufOut.flush();
//获取服务器返回的数据
String str=bufIn.readLine();
//输出服务器返回的数据
System.out.println("Server="+str);
}
}
catch(Exception e){
//如果发生IO异常,则输出“客户端发生异常”
System.out.println("客户端发生异常"+e.toString());
}
finally{
if(s!=null)
//进行IO异常处理
try{
//关闭socket流
s.close();
}
catch(IOException ex){
//如果发生异常,则输出“客服端发生异常”
System.out.println("客户端发生异常"+ex.toString());
}
if(bufr!=null)
//进行IO异常处理
try{
//关闭IO流
bufr.close();
}
catch(IOException ex){
//如果发生异常,则输出“客服端发生异常”
System.out.println("客户端发生异常"+ex.toString());
}
}
}
}

//定义一个服务端类,并实行Runnable接口
class TcpServer implements Runnable{
public void run(){

Socket s=null;
//进行IO异常处理
try{
//建立ServerSocket服务,并监控10008端口
ServerSocket ss=new ServerSocket(10008);

//获取客户端Socket对象
s= ss.accept();
//读取客服端数据
BufferedReader bufIn=
new BufferedReader(new InputStreamReader(s.getInputStream()));
//将处理后的数据返回给客户端
BufferedWriter bufOut=
new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line=null;
while((line=bufIn.readLine())!=null){
//将处理好的数据写到缓冲区
bufOut.write(line.toUpperCase());
//换行
bufOut.newLine();
//将缓冲区的数据发送出去
bufOut.flush();
}
}
catch(Exception e){
//如果服务端发生IO异常,则输出“服务端发生异常”
System.out.println("服务端发生异常"+e.toString());
}
finally{
if(s!=null)
try{
//关闭客户端socket服务
s.close();
}
catch(IOException ex){
//如果服务端发生发生异常,则输出“服务端发生异常”
System.out.println("服务端发生异常"+ex.toString());
}
}
}
}
  练习2:客户端并发上传图片。

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

//定义一个线程,使每一个客户端都能对应一个独立的线程
class PngThread implements Runnable
{
private Socket s;
PngThread(Socket s)
{
this.s=s;
}

public void run()
{
int count=1;
BufferedOutputStream bufos=null;
try
{
//获取客户端Socket对象,并打印到服务器控制台
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+".....connected");

//创建file对象,并关联server.png,
File file=new File("E:\\heima\\measure\\server.png");

//循环判断file抽象路径名是否存在
while (file.exists())
{
//通过让count自增,保证file文件的抽象路径名不重复
file=new File("E:\\heima\\measure\\server("+(count++)+").png");
}

bufos=new BufferedOutputStream(new FileOutputStream(file));

//分别关联Socket读取流和输出流
InputStream in=s.getInputStream();
PrintWriter out=new PrintWriter(s.getOutputStream(),true);

byte[] buf=new byte[1024];
int len=0;

//将客户端图片文件上传到服务器中
while ((len=in.read(buf))!=-1)
{
bufos.write(buf,0,len);
}

//给客户端返回图片上传成功提示
out.println(file.getName()+"上传成功");
}
catch (IOException e)
{
throw new RuntimeException("服务端异常"+e.toString());
}
finally
{
if (s!=null)
{
try
{
s.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
if (bufos!=null)
{
try
{
bufos.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
}
}
}

//TCP客户端
class TcpClient3
{
public static void main(String[] agrs)
{
Socket s=null;
BufferedInputStream bufis=null;
try
{
s=new Socket("127.0.0.1",10006);

//关联要上传的图片文件1.png
bufis=new BufferedInputStream(new FileInputStream("E:\\heima\\measure\\1.png"));

//分别关联Socket读取流和输出流
BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
OutputStream out=s.getOutputStream();

byte[] buf=new byte[1024];
int len=0;

//将图片上传到服务器
while ((len=bufis.read(buf))!=-1)
{
out.write(buf,0,len);
}
s.shutdownOutput();//关闭Socket输出流

//读取服务器返回的数据,并打印到客户端控制台
String str=bufIn.readLine();
System.out.println(str);
}
catch (IOException e)
{
throw new RuntimeException("客户端异常"+e.toString());
}
finally
{
if (s!=null)
{
try
{
s.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
if (bufis!=null)
{
try
{
bufis.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
}
}
}

//TCP服务端
class TcpServer3
{
public static void main(String[] agrs)
{
Socket s=null;
try
{
ServerSocket ss=new ServerSocket(10006);

//对客户端的连接数没有限制
while (true)
{
s=ss.accept();//获取客户端Socket对象

//给每一个客户端创建一个独立的线程
new Thread(new PngThread(s)).start();
}

}
catch (IOException e)
{
throw new RuntimeException("服务端异常"+e.toString());
}
finally
{
if (s!=null)
{
try
{
s.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}

}
}
}
  练习3:客户端并发登陆。

  要求:客户端通过键盘录入用户名,服务端对这个用户名进行校验。如果该用户存在,在服务端显示xxx,已登录。如果该用户不存在,在服务端系显示xxx,尝试登陆。并在客户端显示xxx,该用户不存在。最多登陆3次。

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

//定义用户线程
class UseThread implements Runnable
{
private Socket s;
UseThread(Socket s)
{
this.s=s;
}

public void run()
{
boolean flag=false;
BufferedReader bufr=null;
try
{
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+".....connected");

//关联用户列表文件
File file=new File("E:\\heima\\measure\\useList.txt");
bufr=new BufferedReader(new InputStreamReader(new FileInputStream(file)));

//关联Socket读取流
BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

//关联Socket输出流
PrintWriter out=new PrintWriter(s.getOutputStream(),true);

//每个用户登陆次数限制为3次
for (int x=0;x<3;x++ )
{
//读取用户名数据,通常情况当客户端Socket输出流被关闭时,该方法返回null
String line=bufIn.readLine();
if(line==null)
break; //如果用户名为空,则结束登陆。
String name=null;
while ((name=bufr.readLine())!=null)
{
//服务端校验用户名
if(line.equals(name))
{
flag=true;
break;
}
}

//如果flag为true,则用户登陆成功
if (flag)
{
System.out.println(line+",已登录");
out.println("欢迎:"+line);
break; //如成功登陆,则跳出循环
}
//否则用户登陆失败
else
{
System.out.println(line+",尝试登陆");
out.println(line+",该用户名不存在");
}
}
}
catch (IOException e)
{
throw new RuntimeException("服务端异常"+e.toString());
}
finally
{
if (s!=null)
{
try
{
s.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
if (bufr!=null)
{
try
{
bufr.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
}
}
}
class TcpClient4
{
public static void main(String[] agrs)
{
Socket s=null;
BufferedReader bufr=null;
try
{
s=new Socket("127.0.0.1",10007);

//键盘录入用户名登陆
bufr=new BufferedReader(new InputStreamReader(System.in));

//关联Socket读取流
BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));

//关联Socket输出流
PrintWriter out=new PrintWriter(s.getOutputStream(),true);

//客户端限制用户登陆次数为3次
for (int x=0;x<3 ;x++ )
{
//读取用户名数据,当录入时,按下ctrl+c,该方法返回null
String line=bufr.readLine();
if (line==null)
break;
//将读取的用户名数据发给服务端
out.println(line);

//读取服务端返回的数据
String info=bufIn.readLine();
System.out.println(info);
if (info.contains("欢迎"))
break;
}
}
catch (IOException e)
{
throw new RuntimeException("客户端异常"+e.toString());
}
finally
{
if (s!=null)
{
try
{
s.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
if (bufr!=null)
{
try
{
bufr.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}
}
}
}

class TcpServer4
{
public static void main(String[] agrs)
{
Socket s=null;
try
{
ServerSocket ss=new ServerSocket(10007);
while (true)
{
s=ss.accept();
new Thread(new UseThread(s)).start();
}

}
catch (IOException e)
{
throw new RuntimeException("服务端异常"+e.toString());
}
finally
{
if (s!=null)
{
try
{
s.close();
}
catch (IOException ex)
{
throw new RuntimeException(ex.toString());
}
}

}
}
}
  练习4:自定义图形界面浏览器与web服务器。

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
class MyIEByGUI
{
public static void main(String[] args)
{
new MyWindow();
}
}

class MyWindow
{
private Frame f;
private TextField tf;
private TextArea ta;
private Button but;

MyWindow()
{
init();
}


public void init()
{
//创建框架,并进行设置
f=new Frame();
f.setBounds(300,200,600,400);
f.setLayout(new FlowLayout());

tf=new TextField(50);//创建文本框
ta=new TextArea(20,70);//创建文本区域
but=new Button("转到");//创建按钮

//向框架添加组件
f.add(tf);
f.add(but);
f.add(ta);

myEvent();

f.setVisible(true);//显示框架
}

//事件处理
public void myEvent()
{
//对窗体注册窗口监听器
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});

//对按钮注册活动监听器
but.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
showDir();
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
});

//对文本框注册键盘监听器
tf.addKeyListener(new KeyAdapter()
{
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode()==e.VK_ENTER)
{
try
{
showDir();

}
catch (Exception ex)
{

}
}
}
});

}

//列出文本框中指定目录的内容
public void showDir()throws Exception
{
//清空文本区域
ta.setText("");

String url=tf.getText();//获取文本框的内容

//获取url的两个分割点
int index1=url.indexOf("//")+2;
int index2=url.indexOf("/",index1);

//截取IP地址
String str=url.substring(index1,index2);

//获取主机名并打印到控制台
String host=InetAddress.getByName(str).getHostAddress();
System.out.println(host);
//获取路径部分
String path=url.substring(index2);

Socket s=new Socket(host,80);//建立Socket服务,并连接服务器主机和端口

//关联Socket输出流
PrintWriter out=new PrintWriter(s.getOutputStream(),true);

//向web服务器发送请求消息头数据
out.println("GET "+path+" HTTP/1.1");
out.println("Accept: */*");
out.println("Accept-Language: zh-CN");
out.println("host: "+host+":80");
out.println("Connection: closed");
out.println();
out.println();

//关联Socket读取流
BufferedReader bufr=new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;

//读取web服务器返回的数据,并写入到文本区域中
while ((line=bufr.readLine())!=null)
{
ta.append(line+"\r\n");
}
s.close();//关闭Socket服务

}

}


四、URL

 1.概述:

  URL是指统一资源定位符,
  结构组成:
   基本URL包含:模式(或称协议)、服务器名称(或IP地址)、路径和文件名,如“协议://授权/路径?查询”。
   1)模式/协议(scheme):它告诉浏览器如何处理将要打开的文件。最常用的模式是超文本传输协议(Hypertext Transfer Protocol,缩写为HTTP),这个协议可以用来访问网络。
   常见协议:
    http——超文本传输协议资源
    https——用安全套接字层传送的超文本传输协议
    ftp——文件传输协议
    file——当地电脑或网上分享的文件
   2)服务器名称或IP地址:服务器的名称或IP地址后面有时还跟一个冒号和一个端口号。它也可以包含接触服务器必须的用户名称和密码。
   3)路径和文件名:路径部分包含等级结构的路径定义,一般来说不同部分之间以斜线(/)分隔。询问部分一般用来传送对服务器上的数据库进行动态询问时所需要的参数。
    有时候,URL以斜杠“/”结尾,而没有给出文件名,在这种情况下,URL引用路径中最后一个目录中的默认文件(通常对应于主页),这个文件常常被称为 index.html 或 default.htm。
   示例:http://www.baidu.com/myweb/demo.html?name=zhangsan&age=10
   说明:
    协议:http协议。
    服务器名称:www.baidu.com。
    路径:/myweb/demo.html。
    查询:name=zhangsan&age=10。
   完整的、带有授权部分的普通统一资源标志符语法看上去如下:协议://用户名:密码@子域名.域名.*域名:端口号/目录/文件名.文件后缀?参数=值#标志。

 2.URL类
  1)概述:
   类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用。
   示例:http://www.socs.uts.edu.au/MosaicDocs-old/url-primer.html
   说明:
    a)上面的 URL 示例指示使用的协议为 http (超文本传输协议)并且该信息驻留在一台名为 www.socs.uts.edu.au 的主机上。主机上的信息名称为 /MosaicDocs-old/url-primer.html。主机上此名称的准确含义取决于协议和主机。该信息一般存储在文件中,但可以随时生成。该 URL 的这一部分称为路径部分。URL 可选择指定一个“端口”,它是用于建立到远程主机 TCP 连接的端口号。如果未指定该端口号,则使用协议默认的端口。例如,http 协议的默认端口为 80。
    URL 后面可能还跟有一个“片段”,也称为“引用”。该片段由井字符 "#" 指示,后面跟有更多的字符。例如:
      http://java.sun.com/index.html#chapter1
    b)绝对URL与相对URL:相对 URL 不需要指定 URL 的所有组成部分。如果缺少协议、主机名称或端口号,这些值将从完整指定的 URL 中继承。但是,必须指定文件部分。可选的片段部分不继承。
    应用程序也可以指定一个“相对 URL”,它只包含到达相对于另一个 URL 的资源的足够信息。HTML 页面中经常使用相对 URL。
    例如,假设 URL 的内容是: 
      http://java.sun.com/index.html。
    其中包含的相对 URL:
      FAQ.html
    其绝对URL形式为:
      http://java.sun.com/FAQ.html

  2)创建URL对象的常见方法:

   URL(String spec):根据 String 表示形式创建 URL 对象。

   URL(String protocol, String host, String file):根据指定的 protocol 名称、host 名称和 file 名称创建 URL。

   URL(String protocol, String host, int port, String file):根据指定 protocol、host、port 号和 file 创建 URL 对象。
  3)常用方法的功能:
   示例:http://192.168.90.36:80/myweb/demo.html?name=zhangsan&age=10
   a)String getFile():获取文件名。如:/myweb/demo.html?name=zhangsan&age=10。
   b)String getHost():获取主机名(如果适用)。如:192.168.90.36。
   c)String getPath():获取路径部分。如:/myweb/demo.html。
   d)int getPort():获取端口号。如:80。
   e)String getProtocol():获取协议名称。如:http。
   f)String getQuery():获取查询部。如:name=zhangsan&age=10。

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

  4)代码示例:自定义图形界面客户端——web服务器。

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
class URLBrowser
{
public static void main(String[] args)
{
new MyWindow();
}
}

class MyWindow
{
private Frame f;
private TextField tf;
private TextArea ta;
private Button but;

MyWindow()
{
init();
}


public void init()
{
f=new Frame();
f.setBounds(300,200,600,400);
f.setLayout(new FlowLayout());

tf=new TextField(50);
ta=new TextArea(20,70);
but=new Button("转到");

f.add(tf);
f.add(but);
f.add(ta);

myEvent();

f.setVisible(true);
}

//事件处理
public void myEvent()
{
//对窗体注册窗口监听器
f.addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});

//对按钮注册活动监听器
but.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
try
{
showDir();
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
});

//对文本框注册键盘监听器
tf.addKeyListener(new KeyAdapter()
{
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode()==e.VK_ENTER)
{
try
{
showDir();

}
catch (Exception ex)
{

}
}
}
});

}

//显示服务器返回的数据信息
public void showDir()throws Exception
{
ta.setText("");

//创建URL对象
URL url=new URL(tf.getText());

//返回URL对象,并与URL所引用的远程地址建立连接
URLConnection conn=url.openConnection();

//获取远程连接的输入流对象
InputStream in=conn.getInputStream();
byte[] buf=new byte[1024];
int len=0;

//读取服务器返回的数据,并写入到文本区域中
while ((len=in.read(buf))!=-1)
{
ta.append(new String(buf,0,len)+"\r\n");
}

}

}


五、小知识点

 1.InetAddress与SocketAddress的区别:
  InetAddress封装的是IP地址,不包括端口。
  SocketAddress封装的是IP地址和端口,该类是抽象类,创建对象使用其子类InetSocketAddress。
 2.ServerSocket可通过构造函数指定队列的最大长度。队列的最大长度是指能够同时连接到服务器的客户端的最大个数,如果服务器的连接数过多,则会导致服务器瘫痪。
  可指定队列最大长度的构造函数为:ServerSocket(int port,int backlog)
  其中,backlog表示队列的最大长度。
 3.域名解析:
  网址示例:“https://www.baidu.com/s?ie=utf-8&f=8”

  “www.baidu.com”:表示主机名,主机名与IP地址具有映射关系,由于IP地址不便记忆,因此上网只需输入对应的主机名即可。但使用主机名时,主机名必须先要进行域名解析,即先访问域名解析服务器(DNS)将主机名转换成IP地址,然后再去访问所属IP地址的目的主机。DNS服务器可自行配置,选择最优的DNS服务器。浏览器进行域名解析时,会先访问本机的host文件,该文件是用来存储域名与IP地址的映射关系,如果该文件存储有需要解析的域名,则不会再访问DNS服务器。
  host文件的作用:
   1)屏蔽指定的网址:如病毒、木马网址或者恶意网址等。
     示例:127.0.0.1 www.baidu.com
     说明:屏蔽百度官网。
   2)提高上网速度:
     方法:先获取某个网址的IP地址,然后将该网址与IP地址存储到host文件中,这样就节省了访问DNS服务器的时间,提高上网速度。
   3)可指定某个IP地址对应多个域名。