网络编程
端口的概念
数据的发送需要逻辑端口,端口是用于标识进程的逻辑地址。
端口数字范围:0-65535(short的范围)。
其中 0-1024 是系统使用或保留的端口。网络模型:
OSI参考模型 :分成7层
应用层 –将数据先封装。有应用层的特征。依次往下加每一层的特征。
表示层
会话层
传输层 –特征加到这里,将数据信息打包完。开始往下发
网络层 –给数据信息加IP地址。
数据链路层 –确定通过什么传输方式传输
物理层 –通过网线等设备发送出去。到达另一个机器的主机时,反向拆包得到数据。TCP/IP参考模型 :分为四层
应用层 –合并OSI中的前三个层
传输层
网际层
主机至网络层 –合并OSI中的后两个层IP地址:是网络中的设备标识。
本地地址 127.0.0.1 主机名:localhost。
IP地址不易记忆,所以一般会给IP取名。InetAddress : 互联网协议地址。
此类不可以 new 对象,可通过类中方法返回一个对象。
常用方法:static InetAddress getLocalHost() //获取主机名加主机地址,可以返回本类对象 static InetAddress getByName() //通过主机名获取IP地址 String getHostAddress() //获取主机IP地址 String getHostName() //获取主机名称
举例:
import java.net.*; class Demo { public static void main(String [] args) { try { InetAddress iad=InetAddress.getLocalHost(); System.out.println(iad.toString()); System.out.println(iad.getHostName()); //获取主机名 System.out.println(iad.getHostAddress()); //获取主机地址 InetAddress iad2=InetAddress.getByName("www.nowup.net"); System.out.println(iad2.getHostName()); System.out.println(iad2.getHostAddress()); } catch (UnknownHostException e) { throw new RuntimeException("找不到主机地址!"); } } }
注意:
InetAddress 要抛未知主机异常, InetAddress.toString()方法就是getHostName()和getHostAddress().
以IP地址为目的比较方便。如果以域名为目的,需要解析过程。而且返回的IP对象可能不唯一。
如果IP地址和对应主机名没有在网络上,则只能找到地址,但解析不了主机名。通讯协议
通讯规则一般有两种,UDP协议、TCP协议。(1)UDP:
面向无连接,不需要建立连接,所以是不可靠协议。只管将数据打包发出,接收不接受无所谓。
速度比较快,省略了接受端确认的过程。
每次发送数据包的大小不得超过64K。
当数据只是瞬间存在的时候,选择使用UDP速度快。如远程视频,网络会议,步话机,广播,丢包不影响。实现UDP传输需要两个类, DatagramPacket , DatagramSocket 。
DatagramPacket :
用于将数据封包;构造方法:
DatagramPacket(byte[] b,len) DatagramPacket(byte[] b,len,InetAddress,port)
常用方法:
InetAddress getAddress() //返回机器的IP地址 byte[] getData() //返回数据缓冲区 int getLength() //获取数据长度 int getPort() //获取数据端口 void setAddress(address) //设置IP地址 void setLength(len) //设置长度 void setPort(port) //设置端口
DatagramSocket :
用于发送和接收封装好的数据包。构造方法:
DatagramSocket() //构造空端口的套接字 DatagramSocket(port) //构造指定端口的套接字
常用方法:
void close() //关闭套接字 int getPort() //获取端口 void disconnect() //断开连接 void connect(InetAddress,port) //与指定地址和端口连接 void receive(DatagramPacket dp) //接收数据包 void send(DatagramPacket dp) //发送数据包 InetAddress getInetAddress() //返回套接字连接的地址
举例:
通过UDP传输方式,将一段文字数据发送出去。然后定义一个程序用于接收UDP协议传输的数据并处理。import java.net.*; class SendDemo { //发送端 public static void main(String [] args) throws Exception { //通过DatagramSocket对象创建UDP发送服务,指定发送端口。 DatagramSocket ds=new DatagramSocket(1234); byte [] b="aaaa".getBytes(); //将字符串变成字节数组,等待封装发送。 //用DatagramPacket将数据封包。指定要发送的内容,发送地址,端口。 DatagramPacket dp=new DatagramPacket(b,b.length,InetAddress.getByName("10.2.56.38"),5678); ds.send(dp); //通过Socket服务的send方法,将数据包发送出去。 ds.close(); //关闭资源。 } } class ReceiveDemo { //接收端 public static void main(String [] args) throws Exception { //创建UDP Socket,建立接收端点。括号内指定端点 DatagramSocket ds=new DatagramSocket(5678); byte [] b=new byte [1024]; //存储数据的数据包。 DatagramPacket dp=new DatagramPacket(b,b.length); ds.receive(dp); //通过UDP的receive方法将收到的数据存到数据包中。 String ip=dp.getAddress().getHostAddress(); String s=new String(dp.getData(),0,dp.getLength()); int port=dp.getPort(); System.out.println(ip+"::::"+s+"::::"+port); ds.close(); //关闭资源 } }
(2)TCP :
必须要建立连接,形成传输数据的通道。在连接中进行大数据量传输。
通过三次握手完成连接,是可靠协议。但效率偏低。
(三次握手:1.发送确认请求【你在吗】2.接收请求后回复【我在】3.接收回复后确认【好,知道了】)
下载的数据不可以丢包。要使用TCP。打电话也是TCP。建立通道才可以对话。实现TCP传输需要两个类, Socket , ServerSocket 。
Socket:
为网络服务提供的一种机制。套接字,可理解为入口。
通信的两端都要有Socket。数据在两个Socket间通过IO传输。网络通信其实就是Socket间的通信。构造方法:
Socket() //创建未连接套接字 Socket(InetAddress,port) //创建指定IP和端口的套接字
常用方法:
void close() //关闭套接字 void connect(SocketAddress sa) //将此套接字连接到服务器 InetAddress getInetAddress() //获取套接字连接的地址 InputStream getInputStream() //获取套接字的输入流 OutputStream getOutputStream() //获取套接字的输出流
ServerSocket :
服务器套接字。构造方法:
ServerSocket() //创建未绑定的服务器套接字 ServerSocket(port) //创建指定端口的服务器套接字 ServerSocket(port,backlog) //创建指定端口和连接容量的服务器套接字 ServerSocket(port,backlog,InetAddress) //创建指定端口和要绑定的IP地址和容量的服务器套接字
常用方法:
Socket accept() //接收此套接字的连接 void close() //关闭套接字 InetAddress getInetAddress() //返回服务器套接字的地址
举例一:客户端发送数据,被服务端接收
import java.io.*; import java.net.*; class ClientDemo { //客户端 public static void main(String [] args) { try { //创建Socket服务,指定要连接的目的主机和端口 Socket s=new Socket(InetAddress.getLocalHost().getHostAddress(),9090); //将数据写入到Socket流中的输出流 OutputStream os=s.getOutputStream(); os.write("tcp gemen laile".getBytes()); s.close(); } catch (UnknownHostException e) { throw new RuntimeException("未知主机!"); } catch (IOException e) { throw new RuntimeException("连接客户端错误!"); } } } class ServerDemo { //服务端 public static void main(String [] args) { try { //建立服务端Socket服务,并监听一个端口。 ServerSocket ss=new ServerSocket(9090); Socket s=ss.accept(); //通过accept方法获取链接过来的客户端对象。 System.out.println(s.getInetAddress().getHostAddress()); //获取客户端发过来的数据 InputStream is=s.getInputStream(); byte [] buf=new byte [1024]; int len=is.read(buf); System.out.println(new String (buf,0,len)); s.close(); //关闭客户端连接。 } catch (IOException e) { throw new RuntimeException("连接错误!"); } } }
举例二:使用TCP服务实现文本大写转换。
import java.io.*; import java.net.*; class Demo { //客户端 public static void main(String [] args) { BufferedReader in=null; try { Socket s=new Socket(InetAddress.getLocalHost().getHostAddress(),9876); in=new BufferedReader(new InputStreamReader(System.in)); //将数据写到Socket输出流 PrintWriter pw=new PrintWriter(s.getOutputStream(),true); //接收服务端发回的Socket输入流 BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream())); String line=null; while ((line=in.readLine())!=null) { if ("over".equals(line)) break; pw.println(line); System.out.println("Upper:"+br.readLine()); } s.close(); } catch (IOException e) { throw new RuntimeException("连接失败!"); } finally { if (in!=null) try { in.close(); } catch (IOException e) { throw new RuntimeException("读取失败!"); } } } } class Demo { //服务端 public static void main(String [] args) { try { ServerSocket ss=new ServerSocket(9876); Socket s=ss.accept(); System.out.println(s.getInetAddress().getHostAddress()); //读取客户端发来的数据 BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream())); //输出数据给客户端,TRUE代表可以自动刷新 PrintWriter pw=new PrintWriter(s.getOutputStream(),true); String line=null; while ((line=br.readLine())!=null) { System.out.println(line); pw.println(line.toUpperCase()); } s.close(); ss.close(); } catch (IOException e) { throw new RuntimeException("等待连接错误!"); } } }
举例三:通过TCP服务实现多线程上传图片文件到服务器。
import java.io.*; import java.net.*; class Demo { public static void main(String [] args) { try { ServerSocket ss=new ServerSocket(11111); while (true) { Socket s=ss.accept(); new Thread(new PicThread(s)).start(); } } catch (IOException e) { throw new RuntimeException("等待连接失败了"); } } } class PicThread implements Runnable { private Socket s; PicThread(Socket s) { this.s=s; } public void run() { String ip=s.getInetAddress().getHostAddress(); try { System.out.println(ip+"connected"); InputStream in=s.getInputStream(); FileOutputStream fos=new FileOutputStream("ss.jpg"); byte [] buf=new byte [1024]; int len=0; while ((len=in.read(buf))!=-1) { fos.write(buf,0,len); } OutputStream out=s.getOutputStream(); out.write("上传成功!".getBytes()); fos.close(); s.close(); } catch (IOException e) { throw new RuntimeException(ip+"上传失败!"); } } } class Client { public static void main(String [] args) { try { if (args.length==0) { System.out.println("请选择一张照片"); //主函数的数组中没有传值的话,长度就是0.报出提示。 return; } File file=new File(args[0]); if (!(file.exists() && file.isFile())) { System.out.println("文件不存在或不是文件格式"); //判断文件存在和是不是文件 return; } if (!(file.getName().endsWith(".jpg") || file.getName().endsWith(".bmp"))) { System.out.println("图片格式不对,重新选择"); //判断文件格式 return; } if (file.length()>1024*1024*5) { System.out.println("文件过大,请重选!"); //判断文件大小是否合适 return; } Socket s=new Socket(InetAddress.getLocalHost().getHostAddress(),11111); File file=new File("1.jpg"); if (!file.exists()) throw new RuntimeException("文件读取失败了"); FileInputStream fis=new FileInputStream(file); OutputStream out=s.getOutputStream(); byte [] buf=new byte [1024]; int len=0; while ((len=fis.read(buf))!=-1) { out.write(buf,0,len); } s.shutdownOutput(); InputStream in=s.getInputStream(); byte [] b=new byte [1024]; int num=in.read(b); System.out.println(new String (b,0,num)); fis.close(); s.close(); } catch (IOException e) { throw new RuntimeException("服务器连接失败了"); } } }
注意:同一用户上传的同名文件可以判断报错。不同用户上传同名文件因为IP不同所以不同。
浏览器与客户端
(1) 并发登录举例:
客户端录入用户名,服务端对其校验。若名存在,则显示已登录。并在客户端显示 “welcome” 登录。
若服务端不存在此名,在服务端显示名+loading,并在客户端显示**正在登录。
最多登录三次,就结束。import java.io.*; import java.net.*; class Client { public static void main(String [] args) { try { Socket s=new Socket(InetAddress.getLocalHost().getHostAddress(),11222); BufferedReader sin=new BufferedReader(new InputStreamReader(System.in)); PrintWriter out=new PrintWriter(s.getOutputStream(),true); BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream())); for (int x=0;x<3;x++) { String line=sin.readLine(); if (line==null) //不录入了,就结束 break; out.println(line); String info=br.readLine(); System.out.println("tip::"+info); if (info.contains("welcome")) //服务端返回welcome了,就是已经登录成功,可以结束 break; } sin.close(); s.close(); } catch (IOException e) { throw new RuntimeException("与服务器连接失败"); } } } class Server { public static void main(String [] args) { try { ServerSocket ss=new ServerSocket(11222); while (true) { Socket s=ss.accept(); new Thread(new UserThread(s)).start(); } } catch (IOException e) { throw new RuntimeException("等待连接失败"); } } } class UserThread implements Runnable { private Socket s; UserThread(Socket s) { this.s=s; } public void run() { String ip=s.getInetAddress().getHostAddress(); System.out.println(ip); try { for (int x=0;x<3;x++) { BufferedReader br=new BufferedReader(new InputStreamReader(s.getInputStream())); String name=br.readLine(); if (name==null) break; BufferedReader bufr=new BufferedReader(new FileReader("user.txt")); PrintWriter out=new PrintWriter(s.getOutputStream(),true); String line=null; boolean bool=false; while ((line=bufr.readLine())!=null) { if (line.equals(name)) { bool=true; break; } } if (bool) { System.out.println(name+"已登录"); out.println(name+", welcome"); } else { System.out.println(name+" loading"); out.println(name+" 不存在"); } } s.close(); } catch (IOException e) { throw new RuntimeException(ip+"校验失败"); } } }
(2)浏览器客户端 - - 自定义服务端。
给访问它的客户端返回一个消息即可。import java.io.*; import java.net.*; class Demo { public static void main(String [] args) { try { ServerSocket ss=new ServerSocket(11223); Socket s=ss.accept(); System.out.println(s.getInetAddress().getHostAddress()); PrintWriter out=new PrintWriter(s.getOutputStream(),true); out.println("<font color='red' size='8'>客户端你好啊--我是本机服务端</font>"); s.close(); ss.close(); } catch (IOException e) { throw new RuntimeException("dengdailianjieshibai"); } } }
(3)演示浏览器和tomcat服务器。
import java.io.*; import java.net.*; class Demo { public static void main(String []args) { try { ServerSocket ss=new ServerSocket(9988); Socket s=ss.accept(); System.out.println(s.getInetAddress().getHostAddress()); InputStream in=s.getInputStream(); byte [] buf=new byte [1024]; int len=in.read(buf); System.out.println(new String (buf,0,len)); PrintWriter out=new PrintWriter(s.getOutputStream(),true); out.println("<font color='red' size='9'>客户端你好</font>"); s.close(); ss.close(); } catch (IOException e) { throw new RuntimeException("等待连接失败!"); } } } class MyClient { //自定义的客户端浏览器,需要启动Apache服务器 public static void main(String []args) { try { String ip=InetAddress.getLocalHost().getHostAddress(); Socket s=new Socket(ip,9876); PrintWriter out=new PrintWriter(s.getOutputStream(),true); //定义客户端浏览器的消息头 out.println("GET / HTTP/1.1"); out.println("Accept: +++"); out.println("Accept-Language: zh-cn"); out.println("Host: \""+ip+"\":9988"); out.println("Connection: closed"); 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(); } catch (IOException e) { throw new RuntimeException("连接失败!"); } } }
(4)自定义图形界面浏览器的客户端。
import java.io.*; import java.awt.*; import java.awt.event.*; import java.net.*; class Demo { private Frame f; private TextField tf; private Button b; private TextArea ta; Demo() { init(); } public void init() { f=new Frame("我的浏览器"); f.setBounds(150,50,999,666); f.setLayout(new FlowLayout()); tf=new TextField(90); b=new Button("转到"); ta=new TextArea(25,100); f.add(tf); f.add(b); f.add(ta); myEvent(); f.setVisible(true); } private void myEvent() { tf.addKeyListener(new KeyAdapter(){ public void keyPressed(KeyEvent e) { if (e.getKeyCode()==KeyEvent.VK_ENTER) show(); } }); b.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { show(); } }); f.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e) { System.exit(0); } }); } private void show() { ta.setText(""); String link=tf.getText(); int indexHost=link.indexOf("//"+2); int indexDir=link.indexOf("/",indexHost); String host=link.substring(indexHost,indexDir); String path=link.substring(indexDir); String [] arr=host.split(":"); String ip=arr[0]; int port=Integer.parseInt(arr[1]); //arr[1]是字符串,后面不加基数指的是转成十进制。 try { String ipadd=InetAddress.getLocalHost().getHostAddress(); Socket s=new Socket(ipadd,9876); PrintWriter out=new PrintWriter(s.getOutputStream(),true); //定义客户端浏览器的消息头 out.println("GET / HTTP/1.1"); out.println("Accept: +++"); out.println("Accept-Language: zh-cn"); out.println("Host: \""+ipadd+"\":9988"); 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 (IOException e) { throw new RuntimeException("连接失败!"); } } public static void main(String [] args) { new Demo(); } }
URL 和 URLConnection
(1)URL:
统一资源定位符构造方法:
URL(String url) //根据指定URL形式字符串创建URL对象 URL(String protocol, host, port, file) //根据指定协议,主机,端口,文件创建URL对象
常用方法:
int getPort() //获取端口,如URL没有指定端口,就返回-1. String getProtocol() //获取协议 String getHost() //获取主机名 String getPath() //获取路径 String getQuery() //获取查询部分 String getFile() //获取路径和查询部分 URLConnection operConnection() //返回一个URLConnection 对象,表示到 URL 所引用的远程对象的连接
(2)URLConnection:
抽象的类,可以直接连接此子类对象指定的 URL ,而不用使用 Socket 连接了,因为该类内部封装了Socket协议。常用方法:
InputStream getInputStream() OutputStream getOutputStream()
举例:
import java.io.*; import java.net.*; class Demo { public static void main(String [] args) { try { URL url=new URL("http://192.168.1.191/myweb/demo.html"); URLConnection uc=url.openConnection(); System.out.println(uc); InputStream in=uc.getInputStream(); byte [] b=new byte [1024]; int len=in.read(b); System.out.println(new String (b,0,len)); //如果读取成功,内容是没有响应头的 } catch (IOException e) { throw new RuntimeException("连接错误!"); } } }
举例:
使用URL实现自定义浏览器的简化。import java.io.*; import java.awt.*; import java.awt.event.*; import java.net.*; class Demo { private Frame f; private TextField tf; private Button b; private TextArea ta; Demo() { init(); } public void init() { f=new Frame("我的浏览器"); f.setBounds(150,50,999,666); f.setLayout(new FlowLayout()); tf=new TextField(90); b=new Button("转到"); ta=new TextArea(25,100); f.add(tf); f.add(b); f.add(ta); myEvent(); f.setVisible(true); } private void myEvent() { tf.addKeyListener(new KeyAdapter(){ public void keyPressed(KeyEvent e) { if (e.getKeyCode()==KeyEvent.VK_ENTER) show(); } }); b.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { show(); } }); f.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e) { System.exit(0); } }); } private void show() { ta.setText(""); String link=tf.getText(); try { URL url=new URL(link); URLConnection uc=url.openConnection(); InputStream in=uc.getInputStream(); byte [] b=new byte [1024]; int len=in.read(b); ta.setText(new String(b,0,len)); } catch (IOException e) { throw new RuntimeException("连接错误!"); } } public static void main(String [] args) { new Demo(); } }
域名解析
将域名翻译成IP地址。DNS服务器
浏览器先拿域名去找DNS服务器,DNS返回一个域名对应的IP地址。浏览器再拿这个IP地址去找对应主机如果是找本机,直接localhost或者127.0.0.1.
它俩的映射关系就在本机。当访问主机时,浏览器先找的是本地host,再去DNS上找。
如果不详访问某个网站或者达到一定目的时,可以在本地host文件上添加指定域名指向本地或具体IP,可以按照host中的内容规则访问。