Java 网络编程 第二部分
一. TCP传输协议网络编程
回顾:TCP传输协议是面向网络连接的,是可靠安全的,相对效率低些
它通信传输方式是客户端(Socket)和服务端(ServerSocket),相对与UDP来说不同之处在于。UDP是接收方和发送方的说法。
1. 如何创建客户端Soket服务?
不多说,这个首先也是创建一个客户端的Socket服务,在该对象建立时就可去连接指定主机,因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功,形成通路后,再通过该通道进行数据的传输。
针对创建客户端Socket Java提供了Socket类。
步骤1:
Socket():无参数的构造方法,创建一个无连接的Socket服务对象,要通过connect
(SocketAddress endpoint)
或connect
(SocketAddress endpoint, int timeout)
将此套接字连接到具有指定超时值的服务器。方法将此客户端服务连接到服务器。
Socket
(InetAddress address, int port)
创建一个流套接字即Socket并将其连接到指定 IP 地址的指定端口号,目的IP用InetAddress对象的方式传递。
Socket
(String host, int port)
创建一个流套接字即Socket并将其连接到指定 IP 地址的指定端口号,服务器IP使用String的方式传递。
以上两种指定
IP
和端口的构造方法比较常用。
步骤
2:
创建Socket之后,就可以通过这个Socket对象获得对应的输入流和输出,输出流(Write方法)是用来向服务器端发送信息或请求的,而输入流使用来接收(read()方法)服务器端的数据活回应,这两个流都是用来操作字节数据的。
获得输入流:
Socket sc = new Socket("172.26.46.255",10012);
InputStreamsc.getInputStream();
以上就获取到了这个Socket对象的输入流,可以根据读取对象是什么去使用缓冲流加以装饰,这是一个阻塞式方法,当服务器没有数据回应过来时,会一直等待。
获得输出流:
Socket sc = new Socket("172.26.46.255",10012);
InputStreamsc.getOutputStream();
以上就获取到了这个Socket对象的输出流,可以格局写入对象是什么去使用缓冲流加以装饰,如果操作一些字符串、文本信息等数据时,可以使用PrintWriter,它可以接收一个OutputStream和Writer类型的参数,而且第二个参数设置为true的话,具有自动缓冲功能,Println()还具有自动换行功能。
步骤3
使用完毕之后,就要关闭Socket套接字流。
sc.close()
如何创建一个服务端Socket服务呢?
针对服务器端Socket,Java提供了ServerSocket服务,此类实现服务器套接字。服务器套接字等待客户端网络接入,具有阻塞性。它基于该请求执行某些操作,然后可能向请求者返回结果,它在构建的时候,必须指定一个端口,服务可以没有客户端联入的情况下存在,以为的accept()方法具有阻塞性,会一直等客户端的连接。
步骤1:
ServerSocket();创建一个非绑定端口的套接字,一般不使用。
ServerSocket(intport)
创建绑定到特定端口的服务器套接字,一般都使用这个。
步骤2:
accept
()
侦听并接受到此套接字(客户端Socket)的连接,具有阻塞性的一个方法,等待客户端的连接,该方法返回一个Socket对象,即客户端的Socket对象。
//创建一个服务器端的Socket,并为其指定一个10010。
ServerSocketss =new ServerSocket(10010);
//等待接受客户端的连接请求。
Socketsc = ss.accept();
//拿到连接到这个服务器客户端socket对象的输入输出字符流。
InputStreamis = sc.getInputStream();
OutputStream os =sc.getOutputStream();
步骤3:
通过accept()方法返回一个客户端Soket对象,拿到这个对象之后,可以通过这个对象拿到这个对象对应IO流即输入和输出流,就这两个流来接收客户端的请求和信息,并向客户端发出回响。
//创建一个服务器端的Socket,并为其指定一个10010。
ServerSocketss =new ServerSocket(10010);
//等待接受客户端的连接请求。
Socketsc = ss.accept();
//拿到连接到这个服务器客户端socket对象的输入输出字符流。
InputStreamis = sc.getInputStream();
OutputStream os =sc.getOutputStream();
PS:shutdownInput()和shutdownOutput()方法禁用输入和输出流的时候,对方将收到一个通知,这个通知就是对方的读取返回一个-1。
步骤4
使用完毕之后,就要关闭ServerSocket套接字流。
ss.close()
一般服务端是常开的,因为在实际应用中,随时有客户端在请求连接和服务,所有这里需要定时关闭客户端对象流,避免某一个客户端长时间占用服务器端。
/* 需求:客户端发信,服务端收信,再反馈信息 */ import java.io.*; import java.net.*; //客户端 class TcpSocketDemo { public static void main(String[] args) throws Exception { //创建socket服务,指定连接的主机端口 Socket s = new Socket("192.168.1.101",10003); //获取socket的输出流,并将数据写入流中 OutputStream out = s.getOutputStream(); out.write("socket is coming...".getBytes()); //获取socket的输入流,读取服务端的反馈信息 InputStream in = s.getInputStream(); byte[] b = new byte[1024]; int len = in.read(b); System.out.println(new String(b,0,len)); //关闭流资源 s.close(); } }
import java.io.*; import java.net.*; //服务端 class TcpServerDemo { public static void main(String[] args) throws Exception { //创建ServerSocket服务,并指定接收的端口 ServerSocket ss = new ServerSocket(10003); //通过accept方法获取链接过来的客户端对象 Socket s = ss.accept(); //获取连接的客户端的IP地址 String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+".....connected"); //通过客户端对象,获取输入流,读取接收的信息 InputStream in = s.getInputStream(); byte[] b = new byte[1024]; int len = in.read(b); System.out.println(new String(b,0,len)); //通过客户端对象,获取输出流,将信息反馈给客户端 OutputStream out = s.getOutputStream(); out.write("嘿嘿,收到了".getBytes()); //关闭流资源 s.close(); ss.close();//服务端流资源可不用关 } }
示例一:文本转换器,客户端通过键盘发送信息,服务端将信息以大写形式返回到控制台上,形成客户端和服务端的互访。
分析:既然是设备上的数据,则可用IO技术,并依IO操作规律操作。
1、客户端:
源:键盘录入;目的:网络设备,即网络输出流。-->操作的是文本数据,可选字符流,并加入高效缓冲区。
步骤:
1)建立服务。
2)获取键盘录入
3)将数据发给服务端
4)之后去服务端返回数据
5)结束,关闭资源
2、服务端:
源:socket读取流;目的:socket输出流。
注:socket的close()方法中加入了结束标记-1,所以客户端结束了,服务端也结束了。
3、该例可能出现的问题:
现象:客户端和服务端都在莫名的等待,似乎数据都没有传输过去。
原因:因为客户端和服务端都有阻塞方法,这些方法没有读到结束标记,就会一直等待,而导致两端都在等待。
解决:需要用到刷新和换行的方式将写入和读取的数据从流中刷新到内存中
方式一:可用高效缓冲区类的newLine()换行作为结束标记,并用flush()进行刷新。
方式二:可用PrintWriter(s.getOutputStrean(),true)创建输出流对象,true作用是刷新,通过打印方法println()换行,“ln”表示换行。
import java.io.*; import java.net.*; //客户端 class TransClient { public static void main(String[] args) throws Exception { //创建Socket服务 Socket s = new Socket("192.168.1.101",10003); //定义读取键盘数据的流对象 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //定义目的,将数据写入到socket输出流,发送服务端 //BufferedWriter bufOut = // new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); PrintWriter out = new PrintWriter(s.getOutputStream(),true); //定义一个socket读取流,读取服务端返回的信息,数据 BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream())); String line = null; while((line=bufr.readLine())!=null) { if("over".equals(line)) break; out.println(line); //bufOut.write(line); //bufOut.newLine(); //bufOut.flush(); String str = bufIn.readLine(); System.out.println("Server:" + str); } bufr.close(); s.close(); } }
import java.io.*; import java.net.*; //服务端 class TransSever { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(10003); Socket s = ss.accept(); String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+"....connected"); BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream())); //BufferedWriter bufOut = // new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); PrintWriter out = new PrintWriter(s.getOutputStream(),true); String line = null; while((line=bufIn.readLine())!=null) { System.out.println(line); out.println(line.toUpperCase()); //bufOut.write(line.toUpperCase()); //bufOut.newLine(); //bufOut.flush(); } s.close(); ss.close(); } }
示例二:TCP复制文件
1、客户端:
源:硬盘上的文件;目的:网络设备,即网络输出流。-->若操作的是文本数据,可选字符流,并加入高效缓冲区。若是媒体文件,用字节流。
2、服务端:
源:socket读取流;目的:socket输出流。
3、出现的问题:
现象:
a.文件已经上传成功了,但是没有得到服务端的反馈信息。
b.即使得到反馈信息,但得到的是null,而不是“上传成功”的信息
原因:
a.因为客户端将数据发送完毕后,服务端仍然在等待这读取数据,并没有收到结束标记,就会一直等待读取。
b.上个问题解决后,收到的不是指定信息而是null,是因为服务端写入数据后,也需要刷新,才能将信息反馈给客服端。
解决:
a.方法一:定义结束标记,先将结束标记发送给服务端,让服务端接收到结束标记,然后再发送上传的数据。但是这样定义可能会发生定义的标记和文件中的数据重复而导致提前结束。
b. 方法二:定义时间戳,由于时间是唯一的,在发送数据前,先获取时间,发送完后在结尾处写上相同的时间戳,在服务端,接收数据前先接收一个时间戳,然后在循环中判断时间戳以结束标记。
C. 方法三:通过socket方法中的shutdownOutput(),关闭输入流资源,从而结束传输流,以给定结束标记。这里主要用这个方法。
import java.io.*; import java.net.*; //客户端 class CopyClient { public static void main(String[] args) { //定义全局变量 Socket s = null; try { s = new Socket("192.168.1.101",10003); } catch (IOException e) { throw new RuntimeException("创建连接失败"); } BufferedReader bufr = null; try { //创建读取流,读取指定文件 bufr = new BufferedReader(new FileReader("D:\\File\\udp.java")); //创建写入缓冲区,写入网络输出流中 BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //读取文本,并写入流中 String line = null; while((line=bufr.readLine())!=null) { bufOut.write(line); bufOut.newLine(); bufOut.flush(); } //关闭客户端的输出流。 s.shutdownOutput(); //创建读取流,读取网络输入流 BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream())); //读取反馈信息 String str = bufIn.readLine(); System.out.println(str); } catch (IOException e) { throw new RuntimeException("发送失败"); } //关闭流资源 finally { try { if(bufr!=null) bufr.close(); s.close(); } catch (IOException e) { throw new RuntimeException("资源关闭失败"); } } } }
import java.io.*; import java.net.*; //服务端 class CopyServer { public static void main(String[] args) { //定义全局变量 ServerSocket ss = null; Socket s= null; try { //创建服务,接收客户端数据 ss = new ServerSocket(10003); s= ss.accept(); String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+"....connected"); } catch (IOException e) { throw new RuntimeException("连接失败"); } BufferedWriter bufw = null; try { //创建写入流 bufw = new BufferedWriter(new FileWriter("server.txt")); //创建读取流 BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream())); String line = null; //读取网络输入流数据 while((line=bufIn.readLine())!=null) { bufw.write(line); bufw.newLine(); bufw.flush(); } //创建写入流,反馈给客户端信息 BufferedWriter bufOut = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); bufOut.write("上传成功"); bufOut.flush(); } catch (IOException e) { throw new RuntimeException("发送失败"); } //关闭流资源 finally { try { if(bufw!=null) bufw.close(); s.close(); ss.close(); } catch (IOException e) { throw new RuntimeException("资源关闭失败"); } } } }
实例三.TCP并发执行请求(图片上传)
客户端:
1、创建服务端点
2、读取客户端以后图片数据
3、通过Socket输出流将数据发给服务端
4、读取服务端反馈信息
5、关闭客户端
服务端
对于客户端并发上传图片,服务端如果单纯的使用while(true)循环式有局限性的,当A客户端连接上以后,被服务端获取到,服务端执行具体的流程,这时B客户端连接就只有等待了,因为服务端还未处理完A客户端的请求,还有循环来回执行下须accept方法,所以暂时获取不到B客户端对象,那么为了可让多个客户端同时并发访问服务端,那么服务端最好就是将每个客户端封装到一个单独的线程,这样就可以同时处理多个客户端的请求。如何定义线程呢?只要明确每个客户端要在服务端执行的代码即可,将改代码存入到run方法中。
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; class ThreadTcpUpload { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(21314); UploadServer us = null; while(true) { Socket sc = ss.accept(); us = new UploadServer(sc); new Thread(us).start(); } } } class ClientUpload { public static void main(String[] args) throws Exception, IOException { if(args.length != 1) { System.out.println("请指定一个jpg格式的图片作为参数,再开始上传!"); return; } File file = new File(args[0]); if( !file.isFile() || !file.exists()) { System.out.println("对不起! 你指定的的图片文件不存在,或不是文件!"); return; } if(! (file.getName().endsWith(".jpg"))) { System.out.println("对不起!你指定的文件不是jpg格式!"); return; } Socket client = new Socket("10.34.110.115",21314); FileInputStream fis = new FileInputStream(file); OutputStream os = client.getOutputStream(); BufferedReader br = new BufferedReader (new InputStreamReader(client.getInputStream())); String result = br.readLine(); System.out.println(result); byte[] data = new byte[1024]; int length = 0; while( (length = fis.read(data)) != -1) { os.write(data,0,length); } client.shutdownOutput(); result = br.readLine(); System.out.println(result); } } class UploadServer implements Runnable { private Socket sc ; public UploadServer( Socket sc) { this.sc = sc; } @Override public void run() { int count = 1; File file = null; byte[] data = new byte[1024]; FileOutputStream fos = null; long stime = System.currentTimeMillis(); try { String ip = sc.getInetAddress().getHostAddress(); System.out.println("客户端 "+ip+" 已经连接到服务器!" ); file = new File( "d:\\javaTest\\Upload\\"+ip+".jpg"); while(file.exists()) { file = new File("d:\\javaTest\\Upload\\"+ip+"("+(count++)+")"+".jpg"); } PrintWriter pw = new PrintWriter(sc.getOutputStream(),true); pw.println("已经成功和服务器建立了连接!"); fos = new FileOutputStream(file); InputStream is = sc.getInputStream(); int length = 0; while( (length = is.read(data)) != -1) { fos.write(data , 0 , length); } long etime = System.currentTimeMillis(); pw.println("上传完毕!"+" 共耗时"+(etime-stime)+"毫秒!"); } catch(Exception e) { throw new RuntimeException("上传文件失败!"); } finally { try { if(fos != null) fos.close(); sc.close(); } catch(Exception e) { throw new RuntimeException("写入流关闭失败"); } } } }
实例四. 客户端并发登陆:
客户端通过键盘录入用户名。服务端对这个用户名进行校验。如果该用户存在,在服务端显示xxx,已登陆。并在客户端显示xxx,欢迎光临。如果该用户存在,在服务端显示xxx,尝试登陆。并在客户端显示xxx,该用户不存在。最多就登录三次。
package com.net; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.text.SimpleDateFormat; import java.util.Date; class ThreadTcpLogin { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(21314); while(true) { Socket sc = ss.accept(); new Thread(new ServerLogin(sc)).start(); } } } class UserLogin { public static void main(String[] args) { Socket sc = null; BufferedReader inbr = null ; try { sc = new Socket("172.26.46.106",21314); inbr = new BufferedReader(new InputStreamReader(System.in)); PrintWriter scpw = new PrintWriter(sc.getOutputStream(),true); BufferedReader scbr = new BufferedReader(new InputStreamReader(sc.getInputStream())); String userName = null; String result = null; boolean flag = false; int count =0; for(int i = 0 ; i < 3 ; i++) { ++count; System.out.println("请输入用户名:"); userName = inbr.readLine(); if(userName == null) { System.out.println("对不起,输入的用户名不能为空!"); break; } scpw.println(userName); result = scbr.readLine(); System.out.println( result); if(result.contains("恭喜你!登入成功!")) { //System.out.println( result); flag = true; return; } } if(!flag && count==3 ) System.out.println( "对不起!你今日登入错误超过三次,请次日再登入!"); } catch(Exception e) { e.printStackTrace(); } finally { try { if(sc != null) { sc.close(); } } catch(Exception e) { e.printStackTrace(); } try { if(inbr != null) { inbr.close(); } } catch(Exception e) { e.printStackTrace(); } } } } class ServerLogin implements Runnable { private Socket sc ; public ServerLogin( Socket sc) { this.sc = sc; } @Override public void run() { BufferedReader fbr = null; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss a"); try { String ip = sc.getInetAddress().getHostAddress(); System.out.println(" 客户端 "+ip+ "已经连接到本服务器,正在尝试......"); BufferedReader scbr = new BufferedReader(new InputStreamReader(sc.getInputStream())); PrintWriter pw = new PrintWriter (sc.getOutputStream(),true); String getName = null; String userName = null; boolean flag = false; for(int i = 0 ; i < 3 ; i++) { getName = scbr.readLine(); if(getName == null) { return ; } fbr = new BufferedReader(new FileReader("d:\\database.txt")); while( (userName = fbr.readLine() )!= null) { if(userName.equals(getName)) { System.out.println("用户名 : "+ getName); System.out.println("登入时间 : "+ sdf.format(new Date())); pw.println("恭喜你!登入成功!"); flag = true; return; } } pw.println("对不起!登入校验失败!"); } } catch(Exception e) { e.printStackTrace(); } finally { try { if(fbr != null) { fbr.close(); } } catch(Exception e) { e.printStackTrace(); } try { if(sc != null) { sc.close(); } } catch(Exception e) { e.printStackTrace(); } } } }
二. URL 和URLConnection
浏览器是一个标准的客户端,属于应用层,它可以对服务端传送过来的数据消息进行解析,把符合应用层协议的消息部分解析后,将头信息拆包掉,传送到应用层,只保留了正确的正文主题部分显示在主题部分上。
而由于使用java编译是在传输层和网际层处理的,所以,会接受到全部的消息,包含了头消息。而浏览器处于应用层,已将发送来的头消息去除,只留下了主体信息。
1. URL:该类继承自Object类,是一个final修饰的类,实现了Serializable接口标记,是一个统一资源局定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询
2. URI: 范围比URL大,条形码也包含再次范围。
3. 创建URL对象:
构造函数:URL(Stringprotocol,String host,int port,String file)依protocol,host,port,file创建URL对象
URL
(String spec)
根据String
表示形式创建 URL
对象。
4. 对象方法:
1)StringgetProtocol()获取协议
2)StringgetHost() 获取主机名
3)intgetPort()获取端口号
4)StringgetFile() 获取URL文件名
5)StringgetPath() 获取此URL的路径部分
6)StringgetQuery() 获取此URL的查询部,客户端传输的特定信息
7)openConnection
()
返回一个URLConnection
对象,它表示到 URL
所引用的远程对象的连接,也就是连接到了URL所表示的那台主机。
5. getPort()方法的应用
一般输入网址,是不带端口号的,此时可进行设置,在URL中写port,若port为-1,则分配一个默认的80端口,否则用自己定义的,如
int port = getPort();
if(port == -1)
port = 80;
二. 与URL紧密联系的URLConnection
抽象类,可以通过URL对象的openConnection获得此类对象,通过该类对象可以的getInputStream()和getOutputStream()获得一个套接字的获取输入流和输出流对象。
private void showInfo()throws Exception { ta.setText(""); //获取路径 String urlPath = tf.getText();//http://192.168.1.101:8080/myweb/myIE.html //创建URL对象,解析路径 URL url = new URL(urlPath); //获取连接 URLConnection conn = url.openConnection(); //获取输入流,读取获得的数据,并显示在文本框中 InputStream in = conn.getInputStream(); byte[] buf = new byte[1024]; int len=in.read(); ta.setText(new String(buf,0,len)); } public static void main(String[] args) { new MyIEGUI(); } }
小知识点
1、InetSocketAddress对象(IP+端口)
2、ServerSocket对象中的构造函数:
ServerSocket(int port,int backlog),其中的backlog表示队列的最大长度,即最多连入客户端的个数,即最大连接数。
3、在进行浏览器输入网址访问一台主机所做的操作:
如http://192.168.1.100:8080/myweb/myIE.html,一般直接输入主机名:http://baidu.com.cn等,那么如何通过主机名获取IP地址,从而连接到这台主机的呢?这就需要将主机名翻译成IP地址,即域名解析:DNS
在进行访问的时候,会现在本地的hosts文件(C:\WINDOWS\system32\drivers\etc\hosts)中找对应的映射,若有,则直接返回请求,若无,则到公网的映射列表即DNS中找对应的映射,找到后,将主机名对应的IP地址返回给本机,本机通过这个IP地址找到对应的服务器。