相信很多人在socket编程学习过程中,都会遇到类似的web服务器的设计案例,可是对于刚接触socket的新手来说,往往难以迈出第一步,于是在此分享一个多线程的web服务器代码(参考计算机网络课程的代码框架),便于各位网友参考学习使用!
一、理解多线程
多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。
线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。
多个线程的执行是并发的,也就是在逻辑上“同时”,而不管是否是物理上的“同时”。如果系统只有一个单核CPU,那么真正的“同时”是不可能的。不过,由于CPU的处理速度非常快,用户感觉好像自己的程序连续运行一样。
多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的
二、案例需求
以JDK为开发工具,利用Socket通信机制实现一个多线程的WEB服务器,该服务器具有以下功能:
- 能够并行服务于多个请求。
- 对于每个请求,显示接收到的HTTP请求报文的内容,并产生适当的响应(若找到用户请求对象,则返回该对象。否则发送一个包含适当提示信息的响应消息,从而可以在浏览器窗口中显示差错信息。
三、代码部分
import java.io.*;
import java.net.*;
import java.util.*;
public final class WebServer {
public static void main(String argv[]) throws Exception {
int port = 6666;//定义端口号
ServerSocket welcomeSocket = new ServerSocket(port);
while (true) {
Socket connectionSocket = welcomeSocket.accept();
HttpRequest request = new HttpRequest(connectionSocket);
Thread thread = new Thread(request);
thread.start();
}
}
}
final class HttpRequest implements Runnable {
final static String CRLF = "\r\n";
Socket socket;
public HttpRequest(Socket socket) throws Exception {
this.socket = socket;
}
public void run() {
try {
processRequest();
} catch (Exception e) {
System.out.println(e);
}
}
private void processRequest() throws Exception {
InputStream is = socket.getInputStream();
DataOutputStream os = new DataOutputStream(socket.getOutputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String requestLine = br.readLine();
System.out.println();
System.out.println(requestLine);
String headerLine = null;
while ((headerLine = br.readLine()).length() != 0) {
System.out.println(headerLine);
}
// 从请求行中提取出文件名
StringTokenizer tokens = new StringTokenizer(requestLine);
tokens.nextToken(); // 跳过method
String fileName = tokens.nextToken();
fileName = "." + fileName;//构造请求文件名
FileInputStream fis = null;
boolean fileExists = true;
// 判断请求对象是否存在
try {
fis = new FileInputStream(fileName);
} catch (FileNotFoundException e) {
fileExists = false;
}
String statusLine = null;
String contentTypeLine = null;
String entityBody = null;
if (fileExists) {// 请求文件存在构造响应的status 和 contentType
statusLine = "HTTP/1.1 200 OK" + CRLF;
contentTypeLine = "Content-type: " + contentType(fileName) + CRLF;
} else {// 请求文件不存在
statusLine = "HTTP/1.1 404" + CRLF;
contentTypeLine = "Content-type: " + contentType(fileName) + CRLF;
entityBody = "<!DOCTYPE html><HTML>" + "<HEAD><TITLE>Not Found</TITLE></HEAD>" + "<BODY>Not Found</BODY></HTML>";
}
os.writeBytes(statusLine);
os.writeBytes(contentTypeLine);
os.writeBytes(CRLF);
if (fileExists) {
sendBytes(fis, os);
fis.close();
} else {
os.writeBytes(entityBody);
}
os.close();
br.close();
socket.close();
}
private static void sendBytes(FileInputStream fis, OutputStream os) throws Exception {
byte[] buffer = new byte[1024];
int bytes = 0;
while ((bytes = fis.read(buffer)) != -1) {
os.write(buffer, 0, bytes);
}
}
private static String contentType(String fileName)
{
//根据文件名返回相应的contentType
if (fileName.endsWith(".htm") || fileName.endsWith(".html")) {
return "text/html";
}
if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
return "image/jpeg";
}
if (fileName.endsWith(".png")) {
return "image/png";
}
if (fileName.endsWith(".css")) {
return "text/css";
}
if (fileName.endsWith(".gif")) {
return "image/gif";
}
if (fileName.endsWith(".png")) {
return "image/png";
}
return "application/octet-stream";
}
}
四、代码测试
测试时使用浏览器访问localhost:port,本例为127.0.0.1:6666,测试用的文件放在项目根目录即可,如使用eclipse编写的项目则需要将文件放在项目名称的目录下(有bin和src的那个目录)
测试截图如下
请求成功时正确返回请求文件
请求失败时给出提示