在前一篇了又重新学习了一下HTTP协议,对它的工作过程和原理又加深了认识。那么当我们通过浏览器访问一个在线资源的时候,浏览器是怎么要将我们的请求发送到资源所在的服务器,又如何获得服务器对请求的响应呢。其实它用的就是我们常见的Socket。
Socket中文通常翻译为‘套接字’,套接字是两台机器之间的通信端点,Socket是工作在TCP/IP协议之上的。两台机器通过Socket通信时有两种方式,一种是无差错的TCP方式,一种是无序有且可以有差错的UDP方式。
TCP方式一般用在文件传输中,因为两个机器在通信前一般要经过三次握手的连接,所以开销也比较大。
UDP是一种可以有差错的协议,它发送的数据是无序的,一般用在视频,通话等场景下。这些场景一般不要求数据的完整性,可以容许部分的错误,只要数据在一定程度上是连续的即可。
1、 UDP方式
DatagramSocket表示用来发送和接收数据报包的套接字。数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。在 DatagramSocket 上总是启用 UDP 广播发送。
2、 TCP方式
在JAVA中,通过Socket以TCP方式发送与接收数据,常用的JAVAAPI是Socket与ServerSocket。其中ServerSocket类实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。
下面通过两段代码,看一个单线程的客户端向服务端的Socket连接
1)服务端代码public static void main(String[] args) { // TODO Auto-generated method stub try { //建立一个服务端Socket,在1355端口等接收数据。默认是本机 ServerSocket server = new ServerSocket(1355); //accept方法会在指定端口一直侦听,在有链接到来之前会一直阻塞 Socket socket = server.accept(); //有链接到来时,获得SOCKET的输入流,用来获取客户端发送过来的数据 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()) ); //获取Socket的输出流,用来向客户端发送数据 PrintWriter out = new PrintWriter(socket.getOutputStream()); while(true){ String line; //readLine是一个阻塞方法,在输入数据可用、检测到流的末尾或者抛出异常前,此方法一直阻塞 line = br.readLine(); if(line !=null){ //向客户端响应数据 out.println("server received "+line); out.flush(); //如果客户端输出的是bye,关闭链接 if(line.equals("bye")) break; } } socket.close(); // br.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
public static void main(String[] args) { // TODO Auto-generated method stub try { //向本机的1355端口请求链接,请求连接时要求服务端已经启动,否则抛出异常 Socket socket = new Socket("127.0.0.1",1355); //有链接到来时,获得SOCKET的输入流,用来获取服务端发送过来的数据 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); //获取Socket的输出流,用来向服务端发送数据 PrintWriter out = new PrintWriter(socket.getOutputStream()); //用来接收用户控制台的输入 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); while(true){ //readLine是一个阻塞方法,在输入数据可用、检测到流的末尾或者抛出异常前,此方法一直阻塞 String msg = reader.readLine(); out.flush(); if(msg.equals("bye")){ break; } //打印服务端返回的数据 System.out.println(in.readLine()); } socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
测试时先启动服务端,再启动客户端,就可以通过客户端的控制台向服务器发送数据,并接收客户端的响应信息。
通过上面的例子,我们可以总结以下几点:
1) 服务器先启动,并等待客户端的连接请求
2) 客户端在需要时(可以是任务时候),向服务端发起连接请求
3) 服务端接收到客户发起的连接请求,接收客户端发送过的数据,可以根据需要对数据进行处理,然后向客户端发送响应数据。
4) 服务端并不主动发起连接通讯。
通过上面几点我们可以发现,这种工作方式正是J2EE中常用的工作方式。我们通过浏览器向一个站点发起请求时,Web服务器就相当于服务端的ServerSocket,浏览器相当于客户端的Socket,Web服务器接收客户浏览器的请求,经过对请求处理后向浏览器发送响应数据,然后浏览器将响应数据显示出来。
下面,通过几小段代码,使用Socket手动建立一个Web服务器,响应客户端浏览器对服务器静态资源的请求。下面的代码需要一点点HTTP协议的知识。
为了测试以下的代码,可以新建一个项目,JAVA项目或Web项目都可以httpServer代码如下
public class HttpServer { public static final String WEB_ROOT = System.getProperty("user.dir")+File.separator+"webroot"; private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; private boolean shutdown = false; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub HttpServer server = new HttpServer(); server.await(); } public void await(){ ServerSocket serverSocket = null; int port = 8080; try{ //在本机8080端口开启一个服务端SOCKET serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1")); }catch (Exception e) { // TODO: handle exception e.printStackTrace(); System.exit(1); } //如果传递的请求不是shutdown,调用accept,等待客户端浏览器的请求 while(!shutdown){ Socket socket = null; InputStream input = null; OutputStream output = null; try{ socket = serverSocket.accept(); //接收到请求,获取输入输出流 input = socket.getInputStream(); output = socket.getOutputStream(); Request request = new Request(input); //格式化请求,解析HTTP请求头信息,解析出请求URI request.parse(); //向客户端发送响应信息,然后关闭SOCKET Response response = new Response(output); response.setRequest(request); response.sendStaticResource(); socket.close(); shutdown = request.getUri().equals(SHUTDOWN_COMMAND); }catch (Exception e) { // TODO: handle exception e.printStackTrace(); continue; } } } }
public class Request { public Request(InputStream input) { // TODO Auto-generated constructor stub this.input = input; } private InputStream input; private String uri; /** * 返回HTTP请求字符串中头两个空格之间的文本。 * HTTP请求头一行是‘请求行’格式如:GET /index.html HTTP/1.1 * 所以此方法返回的是请求的URI */ private String parseUri(String requestString){ int index1,index2; index1 = requestString.indexOf(" "); if(index1 != -1){ index2 = requestString.indexOf(" ",index1+1); if(index2 > index1) return requestString.substring(index1+1,index2); } return null; } /** * 从Socket的输入流中获取请求信息 */ public void parse(){ StringBuffer request = new StringBuffer(); int i; byte[] buffer = new byte[2048]; try{ i = input.read(buffer); }catch (Exception e) { // TODO: handle exception e.printStackTrace(); i = -1; } for(int j=0;j<i;j++){ request.append((char)buffer[j]); } uri = parseUri(request.toString()); } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } }
public class Response { private static final int BUFFER_SIZE = 1024; Request request; OutputStream output; public Response(OutputStream output){ this.output = output; } public void setRequest(Request request){ this.request = request; } /** * 根据客户端浏览器的请求,读取一个静态资源文件响应到客户端 */ public void sendStaticResource()throws IOException{ byte[] bytes = new byte[BUFFER_SIZE]; FileInputStream fis = null; try{ //根据请求URI,读取指定目录下的静态资源文件 File file = new File(HttpServer.WEB_ROOT,request.getUri()); if(file.exists()){ fis = new FileInputStream(file); int ch = fis.read(bytes,0,BUFFER_SIZE); while(ch!=-1){ output.write(bytes,0,ch); ch = fis.read(bytes,0,BUFFER_SIZE); } }else{ //资源文件不存在就返回一个错误页面 StringBuffer sb = new StringBuffer(); sb.append("HTTP/1.1 404 FILE NOT FOUND\r\n"); sb.append("Content-Type: text/html\r\n"); sb.append("Content-Length: 23\r\n"); sb.append("\r\n"); sb.append("<h1>File Not Found</h1>"); output.write(sb.toString().getBytes()); } }catch (Exception e) { // TODO: handle exception System.out.println(e.toString()); }finally{ if(fis!=null) fis.close(); } } }
<html> <body> <h4>大家好,欢迎访问我!!!</h4> </body> </html>
然后再输入http://localhost:8080/hello.html测试,如下:
可以看到成功访问了hello.html这个静态资源文件。
通过以上的内容大家可以看到Socket的基本工作原理。也可以看到Web服务器一般是如何工作的。原文:http://blog.csdn.net/shiziaishuijiao/article/details/18970151