Tomcat 学习进阶历程之Socket

Tomcat 学习进阶历程之Socket

        在前一篇了又重新学习了一下HTTP协议,对它的工作过程和原理又加深了认识。那么当我们通过浏览器访问一个在线资源的时候,浏览器是怎么要将我们的请求发送到资源所在的服务器,又如何获得服务器对请求的响应呢。其实它用的就是我们常见的Socket。

       Socket中文通常翻译为‘套接字’,套接字是两台机器之间的通信端点,Socket是工作在TCP/IP协议之上的。两台机器通过Socket通信时有两种方式,一种是无差错的TCP方式,一种是无序有且可以有差错的UDP方式。

       TCP方式一般用在文件传输中,因为两个机器在通信前一般要经过三次握手的连接,所以开销也比较大。

       UDP是一种可以有差错的协议,它发送的数据是无序的,一般用在视频,通话等场景下。这些场景一般不要求数据的完整性,可以容许部分的错误,只要数据在一定程度上是连续的即可。

1、 UDP方式

       在JAVA中,通过Socket以UDP方式发送数据,常用的javaAPI是DatagramSocket与DatagramPacket类。
       DatagramPacket表示数据报包,数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。

       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();
		}
	}

2)客户端代码
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项目都可以

Java项目

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;
			}
		}
	}
}

Request代码如下:

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;
	}
}

Response代码如下:

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();
		}
	}
}

为了测试,还建立了一个名为 hello.html 的简单 HTML 页面。内容如下:

<html>
<body>
  <h4>大家好,欢迎访问我!!!</h4>
 </body>
</html>

测试时,先启动HttpServer类。然后在浏览器地址栏中输出: http://localhost:8080/aa.html,因为aa.html页面并不存在,所以可在浏览器中看到如下的响应:

没有找到文件时响应页面

然后再输入http://localhost:8080/hello.html测试,如下:

找到文件时响应页面

可以看到成功访问了hello.html这个静态资源文件。

通过以上的内容大家可以看到Socket的基本工作原理。也可以看到Web服务器一般是如何工作的。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值