------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
网络编程概述
我们以前学习的程序,比如文件的复制,还有其它的程序的运行都是在本机上,但是在生活中我们往往需要不同的电脑之间进行通信和交互。这时就需要进行网络通信
名词的解释:
IP:每台计算机的独立的标识,想要和一台计算机进行通讯,那么首先找到对方计算机的IP,IPV4以一般都是使用4个字节来表示,每一个字节最大的数是255,但是时间上不可能为每台计算机都分配一个公网地址,所以就使用子网掩码来为一块区域分配一个公网地址。同样也使用IPV6来用6个字节进行表示。并且IPV6中不仅包含数字,而且也包含字母。另外127.0.0.1这个地址是本机在没有分配IP地址时,默认的一个本机回环地址,可以用它来检测网卡是否安装好。
端口:数据要发送到指定的程序上,为了标识这些应用程序,所以给这些网络应用程序进行数字标识,而这个数字标识就有一个清晰的称呼:端口;端口的范围是0-65535,而0-1024一般都被系统占用,所以就需要设置其他的端口。
3定义通信规则,而这个通信规则就是网络协议,国际组织定义了通用协议TCP/IP.
常见的网络协议有TCP/IP协议和UDP协议。
接下来我们来了解两种网络模型:
OSI参考模型和TCP/IP参考:
OSI参考模型是使用7层
通过上面的参考模型可以发现网络都是使用七层,而每一层之间都有特定的协议,数据在传输时,一层一层的向下封包,然后在对方一层一层的向上拆包获取数据。而我们平时则是在应用层将特有的信息发送到指定IP的电脑上,然后我们可以进行数据通信。
通过上面的传输协议,我们发现,OSI传输协议比较繁琐,因此接下来我们介绍一种比较简单的网络模型就是TCP/IP协议,采用的四层模型,应用层,传输层,网际层,主机至网络层。
参考模型如下:
IP地址:
IP地址主要是用于标识每一台网络设备。但是IP地址不容易记忆,因此每一台主机IP地址都对应主机名。这样容易比较记忆。因此主机名和主机的IP地址是一一对应的。
而一台网络设备往往有其对应的主机名和主机地址。
接下来我们来演示Java中的IP地址的使用方法。
实例:获取本机的IP地址和主机名。和指定任意一台主机的地址和主机名。
通过了解了IP地址,它在Java中对应的对象时InetAddress,接下来我们会讲端口,端口号是用于标识进程的数字标识,不同的进程的标识不一样,有效的范围是0~65535,
就是一个数字而已,没有什么可以讲的。
接下来我们介绍一个重要的传输协议TCP协议和UDP协议。
UDP的特点:
1将数据,源,目的封装在数据包中,不需要进行连接。
2 每一个数据包的大小限制在64K
3因为是不需要进行连接的,因此是不可靠协议
4不需要进行连接,因此速度比较快。
总结:面向无连接。
生活中的实例:QQ,飞秋,等软件,数据的丢失不是特重要,但是速度比较快,
TCP的特点:
1需要连接,并形成传输数据的通道。
2在连接中可以传送大量的数据。
3通过三次握手完成连接,是一个可靠协议。
4必须建立连接,因此效率会较低。
生活中的实例:
打电话
接下来我们介绍Socket,本意上指的是插座,但是在Java中Socket中就是指的是为网络服务提供的一种机制。
通信的两端都需要使用Socket,而网络通信指的就是Socket间的通信。
数据在两个Socket之间主要是通过IO来进行传输。
我们可以将其形象的网络通信就相当于两个码头之间进行货物的通过船只来进行运输货物,而想要运输货物的前提就是必须先要有码头,而这个码头就是在网络中指的就是Socket,
有了Socket这个通信端点以后我们发现,不同的传输协议所对应的通信端点的建立方式也不一样。那么接下来我们为大家介绍UDP传输协议下的通信端点Socket的建立方式。
在UDP的传输协议下是使用DatagramSocket来创建通信端点的。而DatagramPacket表示数据包,主要是用于封装我们的数据,里面包括要发送的数据,和发送方的ip和接受方的IP
实例演示二:
//使用Socket进行通信的步骤如下:
/*
1在UDP传输协议下,使用DatagramSocket建立socket服务
2创建数据包,封装我们需要的数据,包含我们需要发送的数据
3通过Socket服务发送我们需要发送的数据
4关闭Socket服务
*/
import java.net.*;
class NetDemo1_2
{
public static void main(String[] args) throws Exception
{
//1创建Socket服务
DatagramSocket ds=new DatagramSocket();
//2创建数据报包
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
byte[] msg="hellow Socket".getBytes();
DatagramPacket dp=new DatagramPacket(msg,msg.length,InetAddress.getByName("192.0.0.1"),1000);
//3通过Socket服务发送数据包
ds.send(dp);
//4关闭服务
ds.close();
}
}
上面我们使用UDP传输协议进行创建了通信端点,并发送了数据包,但是我们可以发现由于UDP传输协议是无连接的,所以上面没有接受方,因此数据包便会丢失。
上面我们创建了UDP的发送端,那么我们必须同时创建一个UDP接受端。
实例代码如下:
/*
*1创建一个Socket服务
*2创建一个数据包,并定义存放数据的缓冲区
*3使用Socket服务的receive方法来接收发送端的数据,并存储到数据包中
*4关闭服务。
*
*/
import java.net.*;
class NetDemo1_3
{
public static void main(String[]args)throws Exception
{
//1一般在创建UDP接收端服务时,通常会监听一个端口,也就是给接收端程序定义一个数字标识,方便与明确,哪些数据过来,该应用程序可以处理
DatagramSocket ds=new DatagramSocket(10000);
//2定义数据包,用于接收数据
byte [] msg=new byte[1024];
//使用DatagramPacket用来接收长度为 length 的数据包。
DatagramPacket dp=new DatagramPacket(msg,msg.length);
//使用Socket服务的receive这个方法来将数据封装到数据包中。
ds.receive(dp);
int port =dp.getPort();//获取发送端的端口
String ip=dp.getAddress().getHostAddress();//获取发送端的主机IP地址
String data=new String(dp.getData(),0,dp.getLength());//获取发送端的数据内容。
System.out.println(ip+"::"+data+"::"+port);
}
}
从发送端发送数据,然后在接受端接受数据,运行结果如下:
接下来我完成一个小实例:
需求:从键盘上输入一个字符,并发送到指定的ip号的主机上,在指定的IP的主机上接受发送的消息
代码如下
//发送方
import java.net.*;
import java.io.*;
class NetDemo1_4
{
public static void main(String[] args)throws SocketException
{
DatagramSocket ds=null;
BufferedReader br=null;
try
{
ds=new DatagramSocket();
//从键盘上获取用户输入的信息,并发送到IP地址为127.0.0.1的主机上去。
br=new BufferedReader(new InputStreamReader(System.in));
String line=null;
while((line=br.readLine())!=null)
{
if("886".equals(line))
break;
byte[]msg=line.getBytes();
DatagramPacket dp=new DatagramPacket(msg,msg.length,InetAddress.getByName("127.0.0.1"),10000);
ds.send(dp);
}
}
catch (IOException e)
{
throw new RuntimeException("读取数据失败");
}
finally
{
try
{
if(br!=null)
br.close();
}
catch (IOException e)
{
throw new RuntimeException("字符读取流关闭失败");
}
ds.close();
}
}
}
//接收端
class NetDemo1_5
{
public static void main(String[] args)throws IOException ,SocketException
{
//注意DatagramSocket这个服务不能放入到while循环体中,因为这个服务已经绑定了一个端口号进行监听,所以放到循环体中就会使其重复绑定端口号,就会报出异常。
DatagramSocket ds=new DatagramSocket(10000);
while(true)
{
byte[]msg=new byte[1024];
DatagramPacket dp=new DatagramPacket(msg,msg.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);
}
}
}
运行结果如下:
注意事项:
IP地址的4段的最后一段的有两个数字是不能用的,一个是0,比如192.168.1.0,这个0表示这个网段的IP地址,另外一个IP地址就是192.168.1.255这个ip地址是广播地址,如果发送数据到这个IP地址表示就是将数据发送到整个网段中的所有的主机。
通过上面的实例我们发现,要实现接受方和发送方的同时运行,那我们需要开启两个DOS窗口,但是在现实中,我们在发送消息的同时,也还可以接受信息,也就是在同一个时刻必须运行两个服务,那么这时就必须使用多线程技术了,一个线程控制发送消息,一个线程控制接受消息。
代码如下:
import java.io.*;
import java.net.*;
class NetDemo1_6
{
public static void main(String[] args) throws Exception
{
sendCls s=new sendCls(new DatagramSocket());
recCls r=new recCls(new DatagramSocket(10002));
new Thread(s).start();
new Thread(r).start();
}
}
class recCls implements Runnable
{
private DatagramSocket ds;
recCls(DatagramSocket ds)
{
this.ds=ds;
}
public void run()
{
try
{
byte[]msg=new byte[1024];
DatagramPacket dp=new DatagramPacket(msg,msg.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("接收端接受数据失败");
}
finally
{
ds.close();
}
}
}
class sendCls implements Runnable
{
private DatagramSocket ds;
private BufferedReader br=null;
sendCls(DatagramSocket ds)
{
this.ds=ds;
}
public void run()
{
try
{
br=new BufferedReader(new InputStreamReader(System.in));
String line=null;
while((line=br.readLine())!=null)
{
if("886".equals(line))
break;
byte[]msg=line.getBytes();
DatagramPacket dp=new DatagramPacket(msg,msg.length,InetAddress.getByName("127.0.0.1"),10002);
ds.send(dp);
}
}
catch (Exception e)
{
throw new RuntimeException("发送端发送数据失败");
}
finally
{
ds.close();
}
}
}
使用多线程技术,可以实现一个简单的聊天工具的特点。
讨论完了UDP的传输协议,接下来我们来讨论TCP的传输特点
实例如下:
import java.net.*;
import java.io.*;
//客服端
class NetDemo1_11
{
public static void main(String[] args) throws Exception
{
Socket s=new Socket("127.0.0.1",10012);
OutputStream os=s.getOutputStream();
os.write("hellow Socket".getBytes());
s.close();
}
}
//服务端
class NetDemo1_12
{
public static void main(String[]args) throws Exception
{
ServerSocket ss=new ServerSocket(10012);
while(true)
{
Socket s=ss.accept();
InputStream is=s.getInputStream();
byte[]msg=new byte[1024];
int len;
while((len=is.read(msg))!=-1)
{
System.out.println(new String(msg,0,len));
}
}
}
}
接下来我们来实现一个TCP传输的实例
需求:客户端输入一个字符串,服务端将其转换成大写后然后返回给客户端,客服端可以不断的向服务端发送请求
代码如下:
import java.net.*;
import java.io.*;
class NetDemo1_13
{
public static void main(String[] args) throws Exception
{
//建立服务
Socket s=new Socket("127.0.0.1",10015);
//向服务端发送数据
//这里客户端发送数据的目的
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//客服端发送数据的源
//从键盘录入
BufferedReader br=
new BufferedReader(new InputStreamReader(System.in));
//定义一个客服端读取流,用于读取从服务端返回的大写字符
BufferedReader br_server=
new BufferedReader(new InputStreamReader(s.getInputStream()));
byte[]msg=new byte[1024];
String line=null;
while((line=br.readLine())!=null)
{
if("over".equals(line))
break;
else
{
bw.write(line);//注意在服务端的方法中是用readLine()的方法来读取客服端的输入的信息,而这个方法判断接受的标志是换行符,因此在客服端需要加一个换行表示符,这样服务端才能收到客服端的输入的信息。
bw.newLine();
bw.flush();
String retLine=br_server.readLine();
System.out.println(retLine);
}
}
s.close();
bw.close();
br.close();
}
}
class NetDemo1_14
{
public static void main(String[] args) throws Exception
{
ServerSocket ss=new ServerSocket(10015);
while(true)
{
Socket s=ss.accept();
InetAddress i=s.getInetAddress();
System.out.println(i.getHostAddress()+"succeeded connected");
//建立一个Socket输入流,获取客服端的数据
BufferedReader br=
new BufferedReader(new InputStreamReader(s.getInputStream()));
//建立一个Socket写入流,将大写数据返回给客服端。
BufferedWriter bw=
new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line=null;
while((line=br.readLine())!=null)
{
System.out.println(line);
bw.write("Server return:"+line.toUpperCase());
//服务端处理结果后也需要添加一个换行标识符,那样客服端才能收到服务端处理的结果。
bw.newLine();
bw.flush();
}
}
}
}
运行结果:
接下来我们将演示使用TCP来实现文件的复制。
需求:客服端上传图片给服务端。
代码如下:
import java.net.*;
import java.io.*;
class NetDemo1_15
{
public static void main(String[] args) throws Exception
{
File f=new File("D:\\sunny\\psbe (3).jpg");
Socket s=new Socket("127.0.0.1",10010);
if(f.exists())
{
System.out.println("xxxxx");
//客户端源
BufferedInputStream bis=
new BufferedInputStream(new FileInputStream(f));
//客服端目的
BufferedOutputStream bos=
new BufferedOutputStream(s.getOutputStream());
int len;
byte[]msg=new byte[1024];
while((len=bis.read(msg))!=-1)
{
bos.write(msg,0,len);
bos.flush();
}
s.shutdownOutput();//在客户端上传文件以后要加一个结束标识,那样服务端才不会一直等在那里。
//获取上传的结果
InputStream is=s.getInputStream();
byte[]retMsg=new byte[1024];
int leg;
leg=is.read(retMsg);
System.out.println(new String(retMsg,0,leg));
}
}
}
class NetDemo1_16
{
public static void main(String[]args) throws Exception
{
ServerSocket ss=new ServerSocket(10010);
Socket s=ss.accept();
String clientip=s.getInetAddress().getHostAddress();
System.out.println(clientip+"succeeded connected");
//获取客服端传来的数据
InputStream is=s.getInputStream();
//将客服端传来的数据保存在当前的文件下.
BufferedOutputStream bos=
new BufferedOutputStream(new FileOutputStream("tcp_upload.jpg"));
byte[]msg=new byte[1024];
int len;
while((len=is.read(msg))!=-1)
{
bos.write(msg,0,len);
bos.flush();
}
//向客服端发送上传的结果
OutputStream os=s.getOutputStream();
os.write("文件上传成功".getBytes());
}
}
演示过利用TCP上传图片完了以后,我们利用TCP来上传文件。
代码如下:
import java.io.*;
import java.net.*;
//客户端
class NetDemo1_17
{
public static void main(String[] args) throws Exception
{
//源
File f=new File("D:\\text1.txt");
Socket s=new Socket("127.0.0.1",10023);
if(f.exists()&&f.isFile())
{
BufferedReader br=
new BufferedReader(new FileReader(f));
//将客服端的文件上传给服务端
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);//使用PrintWriter这个对象必须要设置为自动刷新,不然必要手动刷新。不然数据无法从缓冲区刷出去。
String line=null;
while((line=br.readLine())!=null)
{
System.out.println(line);
pw.println(line);
}
s.shutdownOutput();//给服务端发送一个文件结束的标志
//获取上传的结果
InputStream is=s.getInputStream();
int num=0;
byte[] msg=new byte[1024];
num=is.read(msg);
System.out.println(new String(msg,0,num));
}
s.close();
}
}
//服务端
class NetDemo1_18
{
public static void main(String[] args)throws Exception
{
ServerSocket ss=new ServerSocket(10023);
//源
Socket s=ss.accept();
String clientip=s.getInetAddress().getHostAddress();
System.out.println(clientip+"succeed connected");
InputStream is=s.getInputStream();
//目的
PrintWriter pw=new PrintWriter(new FileWriter("file_copy.txt"),true);
byte[]msg=new byte[1024];
int len=0;
while((len=is.read(msg))!=-1)
{
String line=new String(msg,0,len);
System.out.println(line);
pw.println(line);
}
//给客服端返回上传的结果
PrintWriter ser_pw=new PrintWriter(s.getOutputStream(),true);
ser_pw.println("上传成功");
ss.close();
s.close();
}
}
通过上面的实例我们可以实现客户端和服务端的文件的上传,但是我们发现当如果有多个客户端同时上传时,因为服务器中只有一个线程,那么便会等待一个客户端上传完了后,另一个客户端才能上传,那么这样就会导致客户端会一直等待,那么我们这时必须要使用多线程技术,给每个客户端分配一个处理上传文件的单独线程,这样才能让多个客户端并发的访问服务端。这样服务端就可以同时处理多个客户端请求。
代码如下:
//客户端
import java.net.*;
import java.io.*;
class NetDemo1_19
{
public static void main(String[] args) throws Exception
{
if(args.length!=1)
{
System.out.println("请上传一个文件");
return;
}
File f=new File(args[0]);
if(!(f.exists()&&f.isFile()))
{
System.out.println("文件不存在,或者上传的不是文件");
return;
}
if(!f.getName().endsWith(".jpg"))
{
System.out.println("请上传一个JPG格式的文件");
return;
}
if(f.length()>1024*1024*5)
{
System.out.println("上传的不是文件,不安好心");
return;
}
Socket s=new Socket("127.0.0.1",10014);
//源
BufferedInputStream bis=new BufferedInputStream(new FileInputStream(f));
//目的
BufferedOutputStream bos=new BufferedOutputStream(s.getOutputStream());
byte[]msg=new byte[1024];
int len;
while((len=bis.read(msg))!=-1)
{
bos.write(msg,0,len);
}
s.shutdownOutput();
//获取上传结果
InputStream is=s.getInputStream();
byte[]retMsg=new byte[1024];
len=is.read(retMsg);
System.out.println(new String(retMsg,0,len));
s.close();
}
}
class MyThread implements Runnable
{
Socket s;
MyThread(Socket s)
{
this.s=s;
}
public void run()
{
String ip=s.getInetAddress().getHostAddress();
int count=1;
BufferedOutputStream bos=null;
try
{
//判断文件是否重复。
File f=new File(ip+"("+count+").jgp");
//如果文件重复就重新命名
while(f.exists())
f=new File(ip+"("+(count++)+").jpg");
InputStream is=s.getInputStream();
bos=new BufferedOutputStream(new FileOutputStream(f));
int num;
byte[]msg=new byte[1024];
while((num=is.read(msg))!=-1)
{
bos.write(msg,0,num);
bos.flush();
}
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
pw.println("文件上传成功");
}
catch (IOException e)
{
throw new RuntimeException("文件上传失败");
}
finally
{
try
{
if(bos!=null)
{
bos.close();
s.close();
}
}
catch (IOException e)
{
throw new RuntimeException("字符写入流关闭失败");
}
}
}
}
class NetDemo1_20
{
public static void main(String[] args)throws Exception
{
ServerSocket ss=new ServerSocket(10014);
while(true)
{
Socket s=ss.accept();
new Thread(new MyThread(s)).start();
}
}
}
使用了多线程技术来解决多用户并发访问服务端,接下来我们来看下一段代码
实例代码
/*
*需求:
*客户端从键盘录入姓名,然后发送服务端进行校验,只校验三次,如果成功就显示 xxx,欢迎登陆,如果校验失败,就就在服务端显示xxx尝试登陆,并在客户端显示提示用户名不存在
*/
import java.io.*;
import java.net.*;
//客户端
class NetDemo1_21
{
public static void main(String[] args) throws Exception
{
//源:键盘录入
Socket s=new Socket("127.0.0.1",10013);
BufferedReader br=
new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
BufferedReader br1=
new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
for(int i=0;i<3;i++)
{
line=br.readLine();
if(line==null)//如果用户没有输入任何登录名,就直接停止服务
break;
pw.println(line);
String ret=br1.readLine();
System.out.println(ret);
if(ret.contains("欢迎"))
break;
}
br.close();
s.close();
}
}
class NetDemo1_22
{
public static void main(String [] args)throws Exception
{
ServerSocket ss=new ServerSocket(10013);
while(true)
{
Socket s=ss.accept();
new Thread(new MyThread(s)).start();
}
}
}
class MyThread implements Runnable
{
private Socket s;
MyThread(Socket s)
{
this.s=s;
}
public void run()
{
//获取客户端输入
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"succeeded connected");
BufferedReader br=null;
BufferedReader br1=null;
try
{
for(int i=0;i<3;i++)
{
br=new BufferedReader(new InputStreamReader(s.getInputStream()));
String name=br.readLine();
//System.out.println(name);
if(name==null)
break;
br1=new BufferedReader(new FileReader("d:\\text.txt"));
String line=null;
boolean flag=false;
while((line=br1.readLine())!=null)
{
if(name.equals(line))
{
flag=true;
break;
}
}
if(flag)
{
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
System.out.println(name+"已经登陆");
pw.println(name+"欢迎登陆");
break;
}
else
{
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
pw.println("用户名"+name+"不存在");
System.out.println(name+"尝试登陆失败");
}
}
}
catch (Exception e)
{
throw new RuntimeException(ip+" 校验失败");
}
finally
{
try
{
if(br!=null&&br1!=null)
{
br.close();
br1.close();
s.close();
}
}
catch (Exception e)
{
throw new RuntimeException("字符读取流关闭失败");
}
}
}
}
通过上面的实例我们可以在有效的控制客户端输入登录名的次数,并且还可以对输入的用户名进行有效的校验。
接下来,我们使用浏览器作为客户端,自己定义服务端,然后进行客服端方位服务端,并在浏览器上返回服务端的处理结果
同时我们也可以在Dos窗口下使用telnet 主机IP地址,端口号,进行远程访问服务器。并对服务器进行设置。
演示代码如下:
import java.io.*;
import java.net.*;
class NetDemo1_23
{
public static void main(String[] args)throws Exception
{
ServerSocket ss=new ServerSocket(10024);
while(true)
{
Socket s=ss.accept();
new Thread(new MyThread(s)).start();
}
}
}
class MyThread implements Runnable
{
private Socket s;
MyThread(Socket s)
{
this.s=s;
}
public void run()
{
try
{
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"succeeded connected");
BufferedReader br=
new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=br.readLine())!=null)
{
System.out.println(line);
}
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
pw.println("<font size='9' color='green'>Java欢迎您</font>");
}
catch (Exception e)
{
throw new RuntimeException("处理失败");
}
finally
{
try
{
s.close();
}
catch (Exception e)
{
throw new RuntimeException("Socket 服务停止失败");
}
}
}
}
接下来我们向服务器端上传文件,并保存在服端。并且返回给客户端上传的信息
演示代码如下:
import java.io.*;
import java.net.*;
//客户端
class NetDemo1_25
{
public static void main(String[] args)throws Exception
{
Socket s=new Socket("127.0.0.1",10030);
//发送数据
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
pw.println("Host: 127.0.0.1:10025");
pw.println("Accept-Language: zh-CN,zh;q=0.8");
pw.println("Connection: keep-alive");
pw.println();
pw.println();
s.shutdownOutput();//这里必须要标识结束标记,不然服务端就无法识别,这样服务端就会阻塞。
//获取服务端发送的信息
InputStream is=s.getInputStream();
byte[] retMsg=new byte[1024];
int num;
while((num=is.read(retMsg))!=-1)
{
System.out.println(new String(retMsg,0,num));
}
s.close();
is.close();
}
}
//服务端
class NetDemo1_26
{
public static void main(String[] args)throws Exception
{
ServerSocket ss=new ServerSocket(10025);
while(true)
{
Socket s=ss.accept();
new Thread(new MyThread(s)).start();
}
}
}
class MyThread implements Runnable
{
private Socket s;
MyThread(Socket s)
{
this.s=s;
}
public void run()
{
try
{
//获取客户端的数据
/*String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"succeeded connected");
BufferedReader br=
new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=br.readLine())!=null)
{
System.out.println(line);
}
*/
//向客服端发送请求的数据
File f=new File("d:\\basetest.html");
if(f.exists()&&f.isFile())
{
//源:硬盘上文件
//目的:Socket写入流
BufferedInputStream bis=
new BufferedInputStream(new FileInputStream(f));
OutputStream out=s.getOutputStream();
byte[]msg=new byte[1024];
int len;
while((len=bis.read(msg))!=-1)
{
out.write(msg,0,len);
out.flush();
}
}
}
catch (Exception e)
{
throw new RuntimeException("向客服端发送数据失败");
}
finally
{
try
{
s.close();
}
catch (Exception e)
{
throw new RuntimeException("向客服端发送数据失败");
}
}
}
}
//下面的信息是我们用浏览器想自定义服务端发送请求时,浏览器发送的信息,通过下面的信息我们可以自定义个一个浏览器然后和服务器进行交互
/*
Host: 127.0.0.1:10024
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,;q=0
.8
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/39.0.2171.95 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
*/
了解到客户端和服务端的通信原理后,我们接下来自定义一个浏览器来模拟这个过程。
/*
*接下来我们来写一个客户端程序来模仿IE浏览器来访问IIS服务器上的数据
*/
import java.io.*;
import java.net.*;
class NetDemo1_27
{
public static void main(String[] args) throws Exception
{
//
Socket s=new Socket("127.0.0.1",8090);
PrintWriter out=new PrintWriter(s.getOutputStream(),true);
out.println("GET /WebDemo/2/index.html HTTP/1.1");
out.println("Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
out.println("Accept-Language: zh-CN,zh;q=0.8");
out.println("Accept-Encoding: gzip, deflate,sdch");
out.println("Host: 127.0.0.1:8090");
out.println("Connection: closed");
out.println("User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36");
out.println();
out.println();
BufferedReader br=
new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=br.readLine())!=null)
{
System.out.println(line);
}
s.close();
}
}
class NetDemo1_28
{
public static void main(String[] args)throws Exception
{
ServerSocket ss=new ServerSocket(8090);
Socket s=ss.accept();
BufferedReader br=
new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=br.readLine())!=null)
{
System.out.println(line);
}
}
}
上几个实例我们都是使用Dos命令行来进行客户端和服务端的通信,接下来我们将使用GUI界面来模拟浏览器的功能,从而实现客户端和服务端之间的通信
演示代码如下:
/*
*
*/
import java.awt.*;
import java.io.*;
import java.net.*;
import java.awt.event.*;
class NetDemo1_32
{
private Frame f;
private TextField tf;
private Button btn;
private TextArea ta;
NetDemo1_32()
{
init();
}
public void init()
{
f=new Frame("我的浏览器");
tf=new TextField(60);
btn=new Button("转到");
ta=new TextArea(40,60);
f.setBounds(100,200,600,600);
f.setLayout(new FlowLayout());
f.add(tf);
f.add(btn);
f.add(ta);
myEvent();
f.setVisible(true);
}
public void myEvent()
{
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
} );
btn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
getData();
}
});
tf.addKeyListener(new KeyAdapter(){
public void keyPressed(KeyEvent e)
{
if(e.getKeyCode()==KeyEvent.VK_ENTER)
{
getData();
}
}
});
}
private void getData()
{
//清空TextArea中的数据
ta.setText("");
//解析url
String url=tf.getText();
//System.out.println(url);
int index1=url.indexOf("//")+2;
int index2=url.indexOf("/",index1);
String hostAndPort=url.substring(index1,index2);
String webSource=url.substring(index2);
String []str=hostAndPort.split(":");
String host=str[0];
int port =Integer.parseInt(str[1]);
try
{
//建立服务
Socket s=new Socket(host,port);
PrintWriter out=new PrintWriter(s.getOutputStream(),true);
out.println("GET /2/index.html HTTP/1.1");
out.println("Accept: */*");
out.println("Accept-Language: zh-cn");
out.println("Host: 127.0.0.1:8090");
out.println("Connection: closed");
out.println();
out.println();
BufferedReader br=
new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=br.readLine())!=null)
{
ta.append(line+"\r\n");
}
s.close();
}
catch (Exception ex)
{
throw new RuntimeException("浏览器跳转失败");
}
}
public static void main(String[] args)
{
new NetDemo1_32();
}
}
//http://127.0.0.1:8090/2/index.html
接下来我们按照IIS服务器,然后开启服务,然后我们模拟客服端来向IIS发送请求,获取我们需要的资源。
演示代码如下:
import java.io.*;
import java.net.*;
class NetDemo1_33
{
public static void main(String[] args) throws Exception
{
Socket s=new Socket("127.0.0.1",8090);
PrintWriter out=new PrintWriter(s.getOutputStream(),true);
out.println("GET /2/index.html HTTP/1.1");
out.println("Accept: */*");
out.println("Accept-Language: zh-cn");
out.println("Host: 127.0.0.1:11000");
out.println("Connection: closed");
out.println();
out.println();
int len;
BufferedInputStream bis=
new BufferedInputStream(s.getInputStream());
byte[] msg=new byte[1024];
while((len=bis.read(msg))!=-1)
{
System.out.println(new String(msg,0,len));
}
s.close();
}
}
/*通过URL地址,我们可以向服务器端发送我们请求的数据,服务器就会对我们的请求做出响应,返回信息,但是刚才我们做的协议是传输层的TCP协议,因此返回的时候也会返回报文头部和报文的尾部,但是这些报文头部和尾部用户是不需要知道的,因此就需要对这些数据向上拆包,去掉这些报文头部和尾部,这时Java应用层就给我提供了一个URL对象,使用这个对象可以对服务器端返回的数据进行拆包过滤,从而不需要们单独的写Socket通信端点来进行数据的传输*/
演示代码如下:
import java.io.*;
import java.net.*;
class NetDemo1_36
{
public static void main(String[] args) throws Exception
{
//通过URL这个对象可以对URL(统一资源定位符)(唯一的定位符)地址进行封装,从而用里面的方法对URL地址
URL url=new URL("http://127.0.0.1:8090/2/index.html");//对URL地址进行封装
System.out.println("host:"+url.getHost());
System.out.println("Protocol:"+url.getProtocol());
System.out.println("Port:"+url.getPort());
System.out.println("Path:"+url.getPath());
System.out.println("Query:"+url.getQuery());
URLConnection con=url.openConnection();//在应用层创建一个和服务器连接的对象,然后在应用层和服务器之间进行通信,只要调用这个方法就会连接主机,返回一个连接对象。
BufferedReader br=
new BufferedReader(new InputStreamReader(con.getInputStream()));
String line=null;
while((line=br.readLine())!=null)
{
System.out.println(line);
}
}
}
/*
String getFile()
获取此 URL 的文件名。
String getHost()
获取此 URL 的主机名(如果适用)。
String getPath()
获取此 URL 的路径部分。
int getPort()
获取此 URL 的端口号。
String getProtocol()
获取此 URL 的协议名称。
String getQuery()
获取此 URL 的查询部分。
*/
使用Socket连接服务器,然后返回的给自定义的浏览器,由于我们无法对传输层的报文头进行拆包,因此返回了我们不需要的数据。现在我们使用应用层的连接对象,然后再向服务器发送请求,这时返回的数据就不需要进行拆包
接下来我们来练习自定义一个客服端浏览器,向IIS发送请求,获取我们需要的信息。
演示代码如下:
import java.io.*;
import java.awt.*;
import java.net.*;
import java.awt.event.*;
class NetDemo1_37
{
private Frame f;
private Button btn;
private TextArea ta;
private TextField tf;
NetDemo1_37()
{
init();
}
public void init()
{
f=new Frame();
btn=new Button();
ta=new TextArea();
tf=new TextField();
f.setLayout(new FlowLayout());
f.setBounds(100,200,500,500);
btn=new Button("转到");
ta=new TextArea(20,40);
tf=new TextField(30);
f.add(tf);
f.add(btn);
f.add(ta);
myEvent();
f.setVisible(true);
}
public void myEvent()
{
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
btn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
showInfo();
}
});
tf.addKeyListener(new KeyAdapter(){
public void keyPressed(KeyEvent e)
{
if(e.getKeyCode()==KeyEvent.VK_ENTER)
{
showInfo();
}
}
});
}
private void showInfo()
{
ta.setText("");
BufferedReader br=null;
try
{
String urlStr=tf.getText();
URL url=new URL(urlStr);
URLConnection con=url.openConnection();
br=new BufferedReader(new InputStreamReader(con.getInputStream()));
String line=null;
while((line=br.readLine())!=null)
{
ta.append(line+"\r\n");
}
}
catch (Exception ex)
{
throw new RuntimeException("服务请求失败");
}
finally
{
try
{
if(br!=null)
br.close();
}
catch (Exception ex)
{
throw new RuntimeException("字符读取流关闭失败");
}
}
}
public static void main(String[] args)
{
new NetDemo1_37();
}
}
/*通过代码我们可以发现使用应用层的连接对象可以减少代码的书写。
另外ServerSocket这个类的有一个构造函数形式是
ServerSocket(int port, int backlog)
利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号,这个Backlog的个数表示可以同时连接服务器的最大连接数,如果超过这个数,服务器性能会降低,而且客户端不一定能连接成功。
*/
在生活中我们访问网站一般会直接在浏览器地址栏中输入主机名,不会直接输入主机IP地址,因为IP地址不容易记住,但是我们直接输入主机名同样也会访问主机,这是为什么呢??
通过查阅资料我们发现这需要DNS域名解析技术来获得主机的IP地址,我们访问一个网站,一般会首先访问DNS服务器,DNS服务器主要是存储一些大型网站的主机名和IP的映射关系,通过客户端发送的主机名从而将主机名对应的IP地址返回给客户端,然后客户端通过IP地址,然后再和服务器进行交互,
但是为什么我们访问本机的IP地址127.0.0.1可以,同时还可以输入localhost也可以呢??
通过查阅文档我们发现原来在本地C:\Windows\System32\drivers\etc\host文件中存储有127.0.0.1和localhost的映射关系,然后我们将localhost改成www.sina.com然后再输入www.sina.com我们发现依然是访问的本地,而不是新浪网站,这样我们发现DNS首先是在本地的文件中找主机名对应的IP地址,如果没有找到,才会继续到公网上的DNS服务器上进行查找,如果在本地找到了就不会去公网上找,通过这个发现,我们可以将一些常用的网站和对应的IP地址存在本地,这样就会提高网络解析速度。同时还可以将一些危险网站和本机对应的IP地址这种映射关系存在本地从而防止木马病毒。