一、TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个具有四层体系结构的协议族。
四层体系结构自上而下分为:应用层、传输层、网络层、物理链路层。 如下图:
图1.1
应用层协议:HTTP、FTP、TELNET、SMTP、DNS等协议
传输层协议:TCP、UDP协议
网络层协议:ICMP、IP、IGMP等协议
链路层协议:ARP、RARP等协议
二、TCP、UDP、HTTP
从图1.1可以看出TCP、UDP是传输层协议,HTTP是应用层协议,HTTP基于TCP协议做网络数据传输。
HTTP:超文本传送协议(Hypertext Transfer Protocol),是web互联网的基础,HTTP协议是建立在TCP协议之上的一种应用应用层协议。
HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。
1)在HTTP 1.0中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。
2)在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。
由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。通常的做法是即时不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道客 户端“在线”。若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。
HTTP请求由三部分组成,分别是:请求行、消息报头、请求正文
HTTP响应也是由三部分组成,分别是状态行、消息报头、响应正文
我们以一个登陆页面为例,通过Burp Suite工具看一下http请求和响应
HTTP请求:
GET /HelloWorld/login.jsp HTTP/1.1
Host: 192.168.56.4:8080
Proxy-Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.94 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
HTTP响应:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=B1E7C7183962CAD3A13CD4D7CBD94569; Path=/HelloWorld
Content-Type: text/html;charset=GBK
Content-Length: 346
Date: Thu, 23 Apr 2015 13:54:07 GMT
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GBK">
<title>login</title>
</head>
<body>
<form action="WelcomeServlet" method="post">
UserName:<input type="text" name="username"/><br/>
Password:<input type="password" name="password"/><br/>
<input type="submit" value="Submit"/>
</form>
</body>
</html>
TCP:传输控制协议(Transmission Control Protocol),它是面向连接的运输层协议,提供可靠交付的服务。
建立起一个TCP连接需要经过“三次握手”:
图2.1
第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
UDP:用户数据报协议(User Datagram Protocol),它是面向报文的运输层协议。UDP是无连接的,即发送数据之前不需要建立连接,也就保证不了可靠交付,只是尽最大努力交付。
三、Socket和TCP、HTTP的区别
从上面可以看出TCP、HTTP分别是传输层和应用层的一个具体的协议,而Socket本身并不是一个协议,
它只是对TCP/IP协议的抽象和封装,它提供了一组API来进行网络通信开发,是支持TCP/IP协议的网络通信的基本操作单元。
通过一张图看一下Socket的作用:
图3.1
Java中基于TCP协议实现的网络编程接口有:ServerSocket、Socket
eg. 服务端代码
public class Server {
public static void main( String[] args ) {
try {
ServerSocket serverSocket = new ServerSocket( 8090 );
Socket socket = serverSocket.accept();
/*跟客户端建立好连接之后,就可以获取socket的InputStream,并从中读取客户端发过来的信息*/
Reader reader = new InputStreamReader( socket.getInputStream() );
char chars[] = new char[64];
int len;
StringBuilder sb = new StringBuilder();
while ( ( len = reader.read( chars ) ) != -1 ) {
sb.append( new String( chars, 0, len ) );
}
System.out.println( "client say: " + sb );
reader.close();
socket.close();
serverSocket.close();
} catch ( IOException e ) {
e.printStackTrace();
}
}
}
客户端代码:
public class Client {
public static void main( String[] args ) {
try {
/*要连接的服务端IP地址*/
String host = "127.0.0.1";
/*要连接的服务端对应的监听端口*/
int port = 8090;
/*与服务端建立连接*/
Socket socket = new Socket( host, port );
/*建立连接后往服务端写数据*/
Writer writer = new OutputStreamWriter( socket.getOutputStream() );
writer.write( "hello server." );
writer.flush();
writer.close();
socket.close();
} catch ( Exception e ) {
e.printStackTrace();
}
}
}
先启动服务端程序监听8090端口,然后运行客户端程序,会看到控制台打出“client say: hello server.”
需要注意的是socket通信需要的基本信息有:源主机IP、源主机端口号、目的主机ip、目的主机端口号和使用的协议。
在上面的例子中,源主机IP和目的主机IP都是127.0.0.1,目的主机端口号是8090,即服务器监听8090端口的请求。那源主机端口号是多少呢?
其实每建立一次通信连接操作系统都会分配一个可用的端口(0~65535)作为源主机端口号,我们可以通过在服务端打印出socket的信息来验证。
服务器端要想写一串字节流到客户端就要通过源主机端口号与客户端通信了。
基于UDP协议实现的网络编程接口有:DatagramSocket、DatagramPacket
四、Tomcat基于Socket处理HTTP请求
Tomcat作为一个servlet容器,在经过初始化、启动之后,默认监听8080端口的HTTP请求。以Tomcat6为例,所有的HTTP请求都是由JIoEndpoint这个类负责监听。
监听HTTP的请求是通过JIOEndpoint的一个内部类Acceptor来实现的:
/**
* Server socket acceptor thread.
*/
protected class Acceptor implements Runnable {
/**
* The background thread that listens for incoming TCP/IP connections and
* hands them off to an appropriate processor.
*/
public void run() {
// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused
while (paused) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Ignore
}
}
try {
Socket socket = serverSocketFactory.acceptSocket(serverSocket);
serverSocketFactory.initSocket(socket);
// Hand this socket off to an appropriate processor
if (!processSocket(socket)) {
// Close socket right away
try {
socket.close();
} catch (IOException e) {
// Ignore
}
}
}catch ( IOException x ) {
if ( running ) log.error(sm.getString("endpoint.accept.fail"), x);
} catch (Throwable t) {
log.error(sm.getString("endpoint.accept.fail"), t);
}
// The processor will recycle itself when it finishes
}
}
}
我们再看下processSocket(Socket socket)方法:
/**
* Process given socket.
*/
protected boolean processSocket(Socket socket) {
try {
if (executor == null) {
getWorkerThread().assign(socket);
} else {
executor.execute(new SocketProcessor(socket));
}
} catch (Throwable t) {
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
log.error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
即一个线程用来监听8080端口的客户端请求,每一个请求再通过线程池中的一个线程来处理,以达到并发处理客户端的请求。
以后的逻辑就是解析socket中的HTTP协议的请求,Connector通过管道(pipeline)一层一层传给tomcat中的各级容器(Engine-->Host-->Context-->Wrapper),最终由我们自己写的servlet包装器ServletWrapper来处理客户端请求。
先写这一点吧,再慢慢补充。