------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
IP地址:IP地址为互联网上各主机的标识符,有IPV4与IPV6,后者是对前者的扩充,前者由32位二进制位组成,后者由128位二进制位组成。IPV4常用第8位表示成一个整数的形式表示,如192.168.1.100。分为A类、B类、C类、D类、E类地址。IP地址为供源主机查找目的主机的依据,是网络层的概念。
端口:端口号为主机内部各应用程序的标识,端口为提供数据发送到目的主机时,数据寻找具体程序的依据,每个程序具有至少一个端口号,端口号为1-65535之间的整数,其中1-1024为系统程序所特有。端口有别于物理端口,它是传输层的概念。
协议:网络协议为主机间通讯的规则,如网际层的IP协议、传输层的TCP协议、应用层的HTTP、FTP等协议。只有两台主机安装有同一协议才能正常通讯。
Java中IP地址用InetAddress及其子类所表示。常用方法有getLocalHost获取本地主机IP,该方法为InetAddress中的静态方法,InetAddress对象中封装有IP地址信息,端口号信息等,getHostAddress获取主机IP,getAllByAddress获取目的IP的多个InetAddress,getName获取主机名。
面向连接与无连接的数据传输:TCP是面向连接的传输方式,UDP是面向无连接的传输方式。其中TCP保证数据传输的可靠性,UDP为一种实时应用,不保证可靠性。如常见的下载,网页浏览等都是采用TCP传输的,网络视频会议,数字电视节目等都是UDP传输的。
网络通信实际上是套接字与套接字间的通信,套接字用于程序发送和接收数据,每个套接字封装有一个端口号。Java中用Socket对象来表示套接字,常见的用DatagramSocket,Socket客户端,ServerSocket服务端。
客户端流套接字的初始化代码将IP地址和端口号传递给客户端主机的网络管理软件,管理软件将IP地址和端口号通过NIC传递给服务器端主机;服务器端主机读到经过NIC传递来的数据,然后查看服务器程序是否处于监听状态,这种监听依然是通过套接字和端口来进行的;如果服务器程序处于监听状态,那么服务器端网络管理软件就向客户机网络管理软件发出一个积极的响应信号,接收到响应信号后,客户端流套接字初始化代码就给客户程序建立一个端口号,并将这个端口号传递给服务器程序的套接字(服务器程序将使用这个端口号识别传来的信息是否是属于客户程序)同时完成流套接字的初始化。
如果服务器程序没有处于监听状态,那么服务器端网络管理软件将给客户端传递一个消极信号,收到这个消极信号后,客户程序的流套接字初始化代码将抛出一个异常对象并且不建立通讯连接,也不创建流套接字对象。这种情形就像打电话一样,当有人的时候通讯建立,否则电话将被挂起。
这部分的工作包括了相关联的三个类:InetAddress, Socket, 和 ServerSocket。 InetAddress对象描绘了32位或128位IP地址,Socket对象代表了客户程序流套接字,ServerSocket代表了服务程序流套接字,所有这三个类均位于包java.net中。
InetAddress类在网络API套接字编程中扮演了一个重要角色。参数传递给流套接字类和自寻址套接字类构造器或非构造器方法。InetAddress描述了32位或64位IP地址,要完成这个功能,InetAddress类主要依靠两个支持类Inet4Address 和 Inet6Address,这三个类是继承关系,InetAddrress是父类,Inet4Address 和 Inet6Address是子类。
由于Client使用了流套接字,所以服务程序也要使用流套接字。这就要创建一个ServerSocket对象,ServerSocket有几个构造函数,最简单的是ServerSocket(int port),当使用ServerSocket(int port)创建一个ServerSocket对象,port参数传递端口号,这个端口就是服务器监听连接请求的端口,如果在这时出现错误将抛出IOException异常对象,否则将创建ServerSocket对象并开始准备接收连接请求。
接下来服务程序进入无限循环之中,无限循环从调用ServerSocket的accept()方法开始,在调用开始后accept()方法将导致调用线程阻塞直到连接建立。在建立连接后accept()返回一个最近创建的Socket对象,该Socket对象绑定了客户程序的IP地址或端口号。
由于存在单个服务程序与多个客户程序通讯的可能,所以服务程序响应客户程序不应该花很多时间,否则客户程序在得到服务前有可能花很多时间来等待通讯的建立,然而服务程序和客户程序的会话有可能是很长的(这与电话类似),因此为加快对客户程序连接请求的响应,典型的方法是服务器主机运行一个后台线程,这个后台线程处理服务程序和客户程序的通讯。
UDP是一种面向无连接的网络数据通信技术,以发送数据报的方式进行信息的传输,有效数据最大不超过64K,
该聊天程序包括发送端与接收端,由于发送与接收是两个不同,但可以同时工作的模块,故这里需用到多线程技术。
以下代码可作为聊天程序的一方,可以复制一份存放于另一个文件夹中,配置好彼此接收端的套接字端口号,以及发送端发送的数据报的端口,开两个控制台窗口分别运行两份代码即可做到在一台机器上模拟聊天程序的两端。import java.io.*;
import java.net.*;
class UDPSend implements Runnable
{
private DatagramSocket ds=null;
public UDPSend(DatagramSocket ds)
{
this.ds=ds;
}
public void run()
{
try
{
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));//获取键盘录入
String line=null;
while((line=bufr.readLine())!=null)
{
if("886".equals(line))
break;
byte[] buf=line.getBytes();
DatagramPacket dp=new DatagramPacket(buf,buf.length,InetAddress.getLocalHost(),10010);//包中封装接收端IP,端口号
ds.send(dp);
}
}
catch (Exception e)
{
throw new RuntimeException("发送失败!");
}
}
}
class UDPRec implements Runnable
{
private DatagramSocket ds=null;
public UDPRec(DatagramSocket ds)
{
this.ds=ds;
}
public void run()
{
try
{
while(true)
{
byte[] buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf,buf.length);//用来接收发送来的包
ds.receive(dp);
String ip=dp.getAddress().getHostAddress();//获取包中封装的信息
String data=new String(dp.getData(),0,dp.getLength());//获取包中的数据
System.out.println(ip+"***"+data);
}
}
catch (Exception e)
{
throw new RuntimeException("接收数据失败!");
}
}
}
class UDPChat
{
public static void main(String[] args)throws Exception
{
new Thread(new UDPSend(new DatagramSocket())).start();
new Thread(new UDPRec(new DatagramSocket(10020))).start();
}
}
本程序用于客户端向服务端发送登录请求,服务端通过读取用户信息配置文件来查看是否存在这样的注册用户,若存在,直接登录,不存在,则最多只能登录3次。
下面强调本程序值得注意的几点:
画线处的行写出与缓冲区的flush在本程序中是必须的,因为本程序中套接字的网络输入输出流都分别用BufferedReader与BufferedWriter进行了包装,flush是必须的,这不必多解释。而由于客户端与服务端都使用了readLine方法,由于该方法判定一行的结束为行结束符,故网络输出流中的newLine方法是必须的,否则对方的readLine方法会等待。
当然在进行网络输出流的封装上,也可以采用更为便捷的PrintWriter方法来代替。
还有一些结节也是需要注意的,请参看代码:import java.io.*;
import java.net.*;
class TCPClient
{
public static void main(String[] args) throws Exception
{
Socket s=new Socket(InetAddress.getLocalHost(),8574);
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));//获取键盘录入
BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));//网络输入流
BufferedWriter bufOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));//网络输出流
for (int i=0;i<3 ;i++ )
{
String info=bufr.readLine();
if(info==null)
break;
bufOut.write(info);
bufOut.newLine();
bufOut.flush();
String feedback=bufIn.readLine();//等待反馈信息,阻塞式方法
System.out.println(feedback);
if(feedback.contains("欢迎"))
break;
}
bufr.close();
s.close();
}
}
class ServerRunnable implements Runnable
{
private Socket s=null;
public ServerRunnable(Socket s)
{
this.s=s;
}
public void run()
{
String ip=null;
try
{
ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"...connected");
BufferedReader bufr=new BufferedReader(new FileReader("user.txt"));//用户信息表
BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
BufferedWriter bufOut=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
for (int i=0;i<3 ;i++ )
{
String info=bufIn.readLine();
if(info==null)
break;
String line=null;
boolean flag=false;//用于表明是否存在该用户
while((line=bufr.readLine())!=null)
{
if(line.equals(info))
{
flag=true;
break;
}
}
if(flag==true)
{
System.out.println(ip+"已登录!");
bufOut.write(info+",欢迎登录!");
bufOut.newLine();
bufOut.flush();
break;
}
else
{
System.out.println(ip+"尝试登录!");
bufOut.write(info+"用户不存在!");
bufOut.newLine();
bufOut.flush();
}
}
bufr.close();
s.close();
}
catch (Exception e)
{
throw new RuntimeException(ip+"登录失败!");
}
}
}
class TCPServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss=new ServerSocket(8574);
while(true)
{
Socket s=ss.accept();
new Thread(new ServerRunnable(s)).start();
}
}
}
对文本文件的上传有其特有的readLine行读取方法,以及println写出方法。这些方法是操作文本文件所特有的方法,值得掌握。
此外特别强调的是画线部分的内容:特别要注意的是网络读取流在循环读取时,如何才能读取结束的问题,除了传统的时间戳方法外,java特有的shutdownOutput方法特别便捷。如果客户端在进行文件上传时,在数据写出到网络输出流结束时,若不关闭套接字输出,则服务端的网络读取流会一直等待读取,故服务端也不会发出“上传成功”的字样。
这样的问题主要是出现在对网络读取流的循环读取上。当然,若像上一个程序中只读取网络流中的一行数据,就不会出现这样的问题了,所以注意网络流的循环读取情况就行了。import java.net.*;
import java.io.*;
class TCPClient
{
public static void main(String[] args) throws Exception
{
Socket s=new Socket(InetAddress.getLocalHost(),8526);
BufferedReader bufr=new BufferedReader(new FileReader("C:\\1.txt"));//关联文件
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);//true表示自动推送数据到网络流
BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=bufr.readLine())!=null)
{
pw.println(line);
}
s.shutdownOutput();//数据输出完毕
String info=bufIn.readLine();//等待反馈信息,阻塞式方法
System.out.println(info);
bufr.close();
s.close();
}
}
class TCPServer
{
public static void main(String[] args) throws Exception
{
ServerSocket ss=new ServerSocket(8526);
Socket s=ss.accept();//等待连接,阻塞式方法
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"...connected");
BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter pwOut=new PrintWriter(s.getOutputStream(),true);
PrintWriter pw=new PrintWriter(new FileWriter("2.txt"),true);
String line=null;
while((line=bufIn.readLine())!=null)//网络流的循环读取
{
pw.println(line);
}
pwOut.println("上传成功!");
pw.close();
s.close();
}
}
自定义浏览器主要要了解浏览器发送的消息头数据,可通过自定义服务器来接收IE浏览器的发送内容,画线部分所示,要注意消息头最后要加一行空格。
除了基本的GUI界面的编写外,重点就是对文本框中 的URL地址进行解析。
服务器一端为tomcat服务器,默认8080端口。此自定义浏览器会将HTML文件内容显示在文本域中,为HTML语言,不具备解析功能。
且打印内容带有服务端发送的消息头部分。
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
class CustomizedExplorer
{//GUI界面组件
private Frame f=null;
private TextField tf=null;
private Button btn=null;
private TextArea ta=null;
public CustomizedExplorer()
{
init();
}
public void init()//GUI界面初始化
{
f=new Frame();
f.setBounds(100,100,600,500);
tf=new TextField(50);
btn=new Button("转到");
ta=new TextArea(30,50);
Panel p=new Panel();
f.add(p,BorderLayout.NORTH);
p.add(tf);
p.add(btn);
f.add(ta);
myEvent();
f.setVisible(true);
}
private void myEvent()//组件监听器注册
{
f.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
btn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
showFile();
}
});
tf.addKeyListener(new KeyAdapter(){
public void keyPressed(KeyEvent e)
{
if(e.getKeyCode()==KeyEvent.VK_ENTER)
showFile();
}
});
}
private void showFile()
{
try
{
String url=tf.getText();//获取文本框中URL
String ip=URLParsing.getIP(url);
int port=URLParsing.getPort(url);
String path=URLParsing.getPath(url);
Socket s=new Socket(ip,port);
ta.setText("");//清空之前的文件数据
PrintWriter out=new PrintWriter(s.getOutputStream(),true);
out.println("GET "+path+" HTTP/1.1");//发送浏览器消息头
out.println("Accept: */*");
out.println("Accept-Language: zh-CN");
out.println("Host: "+URLParsing.getIPAndPort(url));
out.println("Connection: Keep-Alive");
out.println("");//用换行分隔,这是必须的
BufferedReader bufIn=new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=bufIn.readLine())!=null)
{//获取网编流中的数据信息
ta.append(line+"\r\n");//自动加上换行符
}
s.close();
}
catch (Exception e)
{
throw new RuntimeException("文件解析失败!");
}
}
public static void main(String[] args)
{
new CustomizedExplorer();
}
}
// http://192.168.1.100:8080/myweb/08.html
class URLParsing//自定义的URL解析工具
{
public static String getIP(String url)//解析IP
{
String substring=getIPAndPort(url);
String[] s=substring.split(":");
String ip=s[0];
return ip;
}
public static int getPort(String url)//解析端口
{
String substring=getIPAndPort(url);
String[] s=substring.split(":");
int port=Integer.parseInt(s[1]);
return port;
}
public static String getPath(String url)//解析路径
{
int start=url.indexOf("//")+2;
int end=url.indexOf("/",start);
String path=url.substring(end);
return path;
}
public static String getIPAndPort(String url)//解析IP与端口
{
int start=url.indexOf("//")+2;
int end=url.indexOf("/",start);
String substring=url.substring(start,end);
return substring;
}
}