day24_网络编程(下)(TCP并发,模拟IE,DNS解析)
1.并发上传(也就是多个用户连接上服务端并发上传->服务端多线程)
如果不采取多线程,简单的加个while(true)行不行?:
/* FileOutputStream fos=new FileOutputStream("C:\\Users\\ZhangHongQ\\Desktop\\Heart.txt"); while(true){ Socket s=ss.accept(); System.out.println("IP: "+s.getInetAddress().getHostAddress() +" Connect Success"); InputStream is=s.getInputStream(); OutputStream os=s.getOutputStream(); byte[] buf=new byte[1024]; int bytes=0; while((bytes=is.read(buf))!=-1) fos.write(buf,0,bytes); os.write("上传成功".getBytes()); fos.close(); s.close(); } //如果有多个用户想上传,采用while(true),这时候产生了一个局限性 //那就是B用户必须等A上传完才能上传,这是因为只有在下次循环才能 //执行到ss.accept(),获取下一个用户的Socket流对象 //那么为了可以让多个客户端同时并发访问服务端 //服务端最好把每个客户端封装到一个单独的线程中,这样就可以同时处理多个客户端请求 */在服务端开启多个线程(连上一个,开一个):
客户端:
package tcp; import java.net.Socket; import java.net.ServerSocket; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; class TCPCopyClient{ public static void main(String[] args)throws Exception{ if(args.length!=1){ System.out.println("请上传一张图片"); return; } File file=new File(args[0]); if(!(file.exists()&&file.isFile())){ System.out.println("文件不存在或不是文件"); return; } if(!args[0].endsWith(".jpg")){ System.out.println("不能上传非.jpg文件"); return; } if(file.length()>5*1024*1024){ System.out.println("文件过大"); return; } Socket s=new Socket("192.168.1.101",10004);//只有不满足以上条件时才建立连接 FileInputStream fis=new FileInputStream(file); OutputStream os=s.getOutputStream(); byte[] buf=new byte[1024]; int bytes=0; while((bytes=fis.read(buf))!=-1) os.write(buf,0,bytes); s.shutdownOutput();//在套接字的输出流末尾加了一个结束标志-1 InputStream is=s.getInputStream(); byte[] bufIn=new byte[1024]; bytes=is.read(bufIn); System.out.println(new String(bufIn,0,bytes)); fis.close(); s.close(); } }服务端:
lass CurrentUpLoad implements Runnable{private Socket s;//s用连上服务端的客户端Socket对象初始化public CurrentUpLoad(Socket s){ this.s=s; } public void run(){ int count=0; String ip=s.getInetAddress().getHostAddress(); try{ File file=new File(ip+".jpg");//已IP地址做为文件名 while(file.exists())//循环判断该文件是否存在,如果已存在,防止覆盖,采取计数处理 file=new File(ip+"("+(++count)+")"+".jpg"); FileOutputStream fos=new FileOutputStream(file); System.out.println("IP: "+ip +" Connect Success"); InputStream is=s.getInputStream(); OutputStream os=s.getOutputStream(); byte[] buf=new byte[1024]; int bytes=0; while((bytes=is.read(buf))!=-1) fos.write(buf,0,bytes); os.write("上传成功".getBytes()); fos.close(); s.close(); } catch(Exception e){ //throw new RuntimeException("上传失败"); e.printStackTrace(); } } } class Server{ public static void main(String[] args)throws Exception{ ServerSocket ss=new ServerSocket(10004);while(true){ Socket s
=ss.accept();
new Thread(newCurrentUpLoad(s)).start(); } /* 服务端启动:主线程,一旦一个客户端连上,创建一个线程并启动 如果依然执行主线程,Socket s=ss.accept();阻塞,直到 另一个客户端连接连接服务端 客户端的线程相互之间不影响 */ } }
2.并发登录:
客户端:
/* 客户端通过键盘录入用户民 服务端对这个用户名进行校验 如果该用户存在,在服务端显示xxx,已登录 并在客户端显示xxx,欢迎光临 如果该用户不存在,在服务端显示xxx,尝试登陆. 并在客户端显示xxx,该用户不存在. 最多登录三次(登录成功不在登录,不成功依然可以登录) */ /* 思想: 对于每个连接成功,进行登录的用户 服务端对每个用户单独用一个线程执行相同的动作(即三次验证) */ package tcp; import java.net.*; import java.io.*; class LoginClient{ public static void main(String[] args)throws Exception{ Socket s=new Socket("192.168.1.100",10004); BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in)); PrintWriter pw=new PrintWriter(s.getOutputStream(),true); String user=null; BufferedReader bfrIn=new BufferedReader(new InputStreamReader(s.getInputStream())); for(int i=0;i<3;++i){//一个用户最多登录三次,成功服务端不在校验 user=bfr.readLine(); if(user==null)//不录入,直接结束 break; pw.println(user); String line=bfrIn.readLine(); System.out.println(line); if(line.contains("欢迎"))//已登录成功 break; } s.close(); } }服务端:
class UserLogin implements Runnable{ private Socket s; public UserLogin(Socket s){ this.s=s; } public void run(){ try{ BufferedReader bfrIn=new BufferedReader(new InputStreamReader(s.getInputStream())); PrintWriter pw=new PrintWriter(s.getOutputStream(),true); String ip=s.getInetAddress().getHostAddress(); System.out.println(ip+" Connect Success"); for(int i=0;i<3;i++){ String user=bfrIn.readLine(); if(user==null) break;//如果在客户端输入过程中结束输入(ctrl+c) //客户端for循环结束,s.close()加入-1标记,bfrIn.readLine()在循环过程中读到两次-1返回null BufferedReader bfr=new BufferedReader(new FileReader("user.txt"));//假设user.txt存入了允许验证通过的用户名 String line=null; boolean flag=false; while((line=bfr.readLine())!=null){ System.out.print(line+" "); if(line.equals(user)){//如果该用户存在,flag=true,结束循环 flag=true; //否则遍历完也没有该用户,flag一直为false break; } } if(flag){ System.out.println(user+"已登录"); pw.println(user+"欢迎光临"); break;//成功登录不在进行校验 } else{ System.out.println(user+"尝试登录"); pw.println(user+"用户不存在"); } } s.close(); } catch(Exception e){ e.printStackTrace(); } } } class LoginServer{ public static void main(String[] args)throws Exception{ ServerSocket ss=new ServerSocket(10004); while(true){//n多客户端向服务端发出校验,因此循环连接 Socket s=ss.accept();//再次循环,将在此堵塞,等待连接建立 new Thread(new UserLogin(s)).start(); } } } /* 测试: 服务端: s.shutdownOutput(); System.out.println(s.isOutputShutdown());//true OutputStream out=s.getOutputStream(); out.write("GG".getBytes()); 客户端: byte[] buf=new byte[1024]; int bytes=is.read(buf); System.out.println(bytes);//-1 System.out.println(new String(buf,0,bytes)); s.close(); 服务端调用 s.shutdownOutput()/s.close();会关闭服务端的s.getOutputStream,会在流的末尾写入-1 客户端的s.getIuputStream()会读到写入的-1; 客户端调用 s.shutdownOutput();会关闭客户端的s.getOutputStream,会在流的末尾写入-1服务端的s.getIuputStream()会读到写入的-1; */
3.客户端:浏览器,服务端:自定义
/* 1.客户端:浏览器 服务端:java应用程序 2.客户端:浏览器 服务端:Tomcat服务器(java编写,封装了ServerSocket) */ package tcp; import java.net.*; import java.io.*; class Server{ public static void main(String[] args)throws Exception{ ServerSocket ss=new ServerSocket(10003); Socket s=ss.accept(); System.out.println(s.getInetAddress().getHostAddress()+"Connect Success"); PrintWriter pw=new PrintWriter(s.getOutputStream(),true); byte[] buf=new byte[1024]; int bytes=s.getInputStream().read(buf); System.out.println(new String(buf,0,bytes)); pw.println("客户端你好"); s.close(); ss.close(); //System.out.println(InetAddress.getLocalHost().getHostAddress()); } } /* 在IE浏览器的地址栏输入http://自己主机的IP地址:端口 看到服务端的反馈信息:客户端你好 或者在cmd中使用telnet: Telnet协议是TCP/IP协议族中的一员, 是Internet远程登陆服务的标准协议和主要方式。 它为用户提供了在本地计算机上完成远程主机工作的能力。 在终端使用者的电脑上使用telnet程序,用它连接到服务器。 终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行, 就像直接在服务器的控制台上输入一样。 前提必须安装,在控制面板中的启用或关闭windows功能中选择开启telnet客户端/服务器 命令: telnet 主机IP地址 端口 同样会收到服务端反馈信息。 */以上代码打印出浏览器向Server发送了什么请求信息:
4.窗体模拟IE的输入链接->转到
/*图形化界面的简单模拟*/ /* 客户端:图形化界面应用程序 图形化界面:Frame,TextField,Button,TextArea 事件源:button 事件:ActionEvent */ package myie; import java.net.*; import java.io.*; import java.awt.*; import java.awt.event.*; class MyIEForm{ private Frame frame; private TextField textField; private Button button; private TextArea textArea; public MyIEForm(){ initi(); } public void initi(){ frame=new Frame("MyIE"); frame.setBounds(200,300,600,500); frame.setLayout(new FlowLayout()); textField=new TextField(50); button=new Button("转到"); textArea=new TextArea(30,60); frame.add(textField); frame.add(button); frame.add(textArea); ListenerReg(); frame.setVisible(true); } public void ListenerReg(){ frame.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent we){ System.exit(0); } }); button.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent ae){ textArea.setText(""); String url=textField.getText(); /*url:http://192.168.1.101:8080/myweb/Demo.html*/ int index_1=url.indexOf("/"); int index_2=url.indexOf("/",index_1+2); String hostPort=url.substring(index_1+2,index_2);//192.168.1.101:8080 String[] address=hostPort.split(":"); String serverSource=url.substring(index_2);//192.168.1.101:8080 try{ openSource(address[0],Integer.parseInt(address[1]),serverSource); } catch(Exception e){ throw new RuntimeException("Open Fail"); } } }); } public void openSource(String ip,int port,String serverSource)throws Exception{ Socket s=new Socket(ip,port); PrintWriter pw=new PrintWriter(s.getOutputStream(),true); pw.println("GET "+serverSource+" HTTP/1.1"); pw.println("Accept: */*"); pw.println("Accept-Language: zh-Hans-CN,zh-Hans"); pw.println("Host: "+ip+":"+port); pw.println("Connection: Keep-Alive"); pw.println();//结尾必须要有换行 InputStream is=s.getInputStream(); byte[] buf=new byte[1024]; int bytes=is.read(buf); textArea.append(new String(buf,0,bytes)); } } class MyIE{ public static void main(String[] args){ new MyIEForm(); } }这里用到的服务端是Tomcat(Web 应用服务器)服务器,下载地址:http://tomcat.apache.org/ 左边的download可以选择6.0/7.0,Tomcat服务端默认端口为8080
下载后直接解压,找到bin目录下的startup.bat启动服务端,在其webapps目录下新建myweb目录,在myweb下放入一个demo.html
之后运行服务端:
可以看到上面的一堆信息为客户端收到的Tomcat服务端发送的响应消息头:服务器,最后一次修改时间,内容长度byte等等,下面获取到的demo.html,由于不能像IE一样解析html代码,因此也会显示出来,可以看到传输层的TCP协议获取服务端全部信息(反馈信息+请求资源).
5.类URL与URLConnection
/* URL: 类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。 资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用, 例如对数据库或搜索引擎的查询。有关 URL 的类型和格式的更多信息, 可从以下位置找到: http://www.socs.uts.edu.au/MosaicDocs-old/url-primer.html 通常,URL 可分成几个部分。上面的 URL 示例指示使用的协议为 http (超文本传输协议)并且该信息驻留在一台名为 www.socs.uts.edu.au 的主机上。 主机上的信息名称为 /MosaicDocs-old/url-primer.html。 主机上此名称的准确含义取决于协议和主机。该信息一般存储在文件中,但可以随时生成。 该 URL 的这一部分称为路径 部分。 URL 可选择指定一个“端口”,它是用于建立到远程主机 TCP 连接的端口号。 如果未指定该端口号,则使用协议默认的端口。 例如,http 协议的默认端口为 80。还可以指定一个备用端口,如下所示: http://www.socs.uts.edu.au:80/MosaicDocs-old/url-primer.html */ package url; import java.net.URL; import java.net.URLConnection; import java.io.InputStream; class URLDemo{ public static void method(URL url){ System.out.println("getFile: "+url.getFile()+"\ngetHost: " +url.getHost()+"\ngetPath: "+url.getPath() +"\ngetPort: "+url.getPort()+"\ngetProtocol: "+ url.getProtocol()+"\ngetQuery: "+url.getQuery()); } public static void main(String[] args)throws Exception{ /*重要方法*/ URL url=new URL("http://192.168.1.101:80/myweb/demo.html?name=zhang&age=10");//可能解析不了该字符串->MalformedURLException method(url); System.out.println(); url=new URL("http://192.168.1.101/myweb/demo.html"); method(url); /* 如果getPort返回-1(未指定端口),则使用协议默认端口 */ } }
/* public URLConnection openConnection() throws IOException 返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。 每次调用此 URL 的协议处理程序的 openConnection 方法都打开一个新的连接。 如果 URL 的协议(例如,HTTP 或 JAR)存在属于以下包或其子包之一的公共、专用 URLConnection 子类:java.lang、java.io、java.util、java.net,返回的连接将为该子类的类型。例如,对于 HTTP,将返回 HttpURLConnection,对于 JAR,将返回 JarURLConnection。 */ class URLConnDemo{ public static void main(String[] args)throws Exception{ URL url=new URL("http://192.168.1.101:8080/myweb/Demo.html");//连接到Tomcat服务器 URLConnection urlCon=url.openConnection();//连接到远程主机 //System.out.println(urlCon); /* sun.net.www.protocol.http.HttpURLConnection: http://192.168.101.1:8080/myweb/Demo .html */ InputStream is=urlCon.getInputStream();//获取到输入流 byte[] buf=new byte[1024]; int bytes=is.read(buf); System.out.println(new String(buf,0,bytes)); /* 读取到的反馈信息中没有响应头 原因参照:(示意图) */ } }
6.域名解析:
/* 浏览器对 http://192.168.1.101:8080/myweb/Demo.html 解析: 首先查看是什么协议,然后启动相应的协议软件解析, 把192.168.1.101:8080封装成Socket 但是一般写法(便于记忆):http://www.sina.com.cn/myweb/demo.html www.sina.com.cn->将主机名翻译成IP地址->域名解析->DNSDNS:
DNS 是域名系统 (Domain Name System) 的缩写,
是因特网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据库,
能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。*/
示意图: