当前位置: 首页 > 科技观察

Java的socket编程搭建web服务器,了解Apache、Tomcat

时间:2023-03-12 09:14:09 科技观察

在做web应用之前,你总是在本地安装一个Apache、Tomcat等软件,然后把完成的网页文件放到它们的工作目录下(比如Apachehtdocs),然后打开浏览器输入127.0.0.1或localhost直接访问。太神奇了,但是为什么,它是如何实现的呢?早就知道有Socket(套接字)了,只是之前没放这个。结合这两方面,今天我们就来看看这是为什么。有同学表示,还是不明白什么是Socket。这个东西很抽象。只有在计算机网络原理中讲协议的时候才会看到。今天我们完全忽略那些过于严谨和学术化的定义,让我们来看看什么是Socket。想象一下,你把电脑的电源插到插座上,你的电脑就可以使用了,为什么呢?您的计算机可以与千里之外的发电厂“通信”。将您的计算机视为客户端,将千里之外的发电厂视为服务器。通过socket和很长很长的电缆,你可以把它挂起来,那么这里面的Socket相当于什么?“插座!”没错,就是插座!我的电脑,我要它用电工作,我只需要一个插座,什么?插座是用什么做的,电缆是什么型号,供电厂的经纬度在哪里,电力是怎么传输的,我不管它做什么,都跟我没关系!我只需要知道,我需要的不是整个世界,而是整个世界。..一个插座!读到这里,同学们一定对“套接字”有了很好的认识;再举个例子,你和你朋友的电脑通过有线方式连接到同一个路由器。这时候就可以直接通过内网IP地址访问了。在此过程中,方形接口(RJ45接口)为“Socket”。反正插上“插座”就可以用了。我不关心如何通过Socket实现通信。在计算机编程的网络世界里,作为一个应用程序,我只需要一个“套接字”就可以与任何服务器通信。插上电源,发电厂也需要插上插座才能给你供电——也就是说,发电厂需要一个“插座”!..废话,,,,是的,没错,服务端也需要一个“socket”,不过叫ServerSocket(貌似是继承自Socket的,不知道,待查)。有了“Socket”的概念,我们就可以愉快的让电脑(客户端)和电厂(服务器)进行通信了。不管是客户端还是服务端,都需要Socket。由于我们今天的主题是“搭建网络服务器”,所以我们来看看如何为服务器创建一个ServerSocket。说到这里,有同学会问,客户端不需要Socket吗?确实有必要,因为我们是用浏览器访问本地IP“127.0.0.1”,所以客户端的Socket是浏览器自己维护的,不需要我们手写。“但是我还是不明白为什么在浏览器中输入127.0.0.1后就可以看到我的网页了?求解释。”好了,我们慢慢来,先写一个服务器端的ServerSocket。下面我们来创建一个服务器端Socket的步骤如下:1.创建一个ServerSocket对象ServerSocketserverSocket=newServerSocket("80");//这里,只需要说明当前程序监听80端口即可,至于为什么是80,因为我喜欢!“很霸道……”因为我们要监听web请求,默认是80端口.其实1-1024端口是被操作系统占用的,1025-65535端口只要不和其他应用冲突就可以使用(3389这种常用端口不要用...)2。作为服务端,我需要知道我的任务是等待客户端发送请求,也就是客户端发送一个Socket,我必须先hold住!套接字socket=serverSocket.accept();//这里需要特别说明一下,accept方法比较特殊,它是一个阻塞方法(blockmethod),因为只要等不及客户端发来的请求(Socket),它就会一直等下去继续执行它下面的代码。唉,为什么像我这样的恋人?O(∩_∩)O3。如果client要向我表白,给我发情书,那么作为server,我只需要得到它的输入即可。InputStreaminputStream=socket.getInputStream();//注意客户端发送的告白信息是在socket中,而不是在serverSocket中。如果写错了,看不懂情书的内容,活该单身。(我只是冷笑...)4.收到情书后,好想知道里面写的是什么!按!不!等待!OK,开始解析情书的内容BufferedReaderreader=newBufferedReader(newInputStreamReader(inputStream));//java封装类,只为阅读写给我的情书,yeah~Stringline="";while((line=reader.readLine())!=null){System.out.println(line);}5.组装前4步的代码,会要求你尝试捕获异常,可以正常捕获Socketsocket=serverSocket.accept();System.out.println("收到情书,我要开始解析了!");InputStreaminputStream=socket.getInputStream();BufferedReaderreader=newBufferedReader(newInputStreamReader(inputStream));Stringline="";while((line=reader.readLine())!=null){System.out.println(line);}}catch(Exceptione){e.printStackTrace();}}}好了,我们已经完成了服务端的编写——边码,你接下来要做什么?我不知道。..不过还记得刚才提出的问题——“可是我还是不明白为什么在浏览器中输入127.0.0.1就可以看到我的网页了?”如果输入127.0.0.1或localhost会怎样?首先,你必须运行我们刚才写的服务器端程序,然后打开浏览器。记住,必须先运行服务器端程序,否则情书就会丢失。..运行服务端程序,如图:注意红圈中的两点:由于此时客户端没有发送情书,还记得accept()的block方法吗现在,就是等啊等,没来我就等,所以红圈会显示“等情书……”;那么那个指向右边的箭头是什么意思,一个红色的停止图标,也就是说这个程序现在一直在执行,还没有结束,好像死了和循环一样(当然肯定不是死循环,其实是阻塞的,不过看起来像死循环,死循环后面再说,别着急,早晚会死的)接下来打开浏览器,在地址栏输入127.0.0.1/index.html并按Enter以查看浏览器如何响应。....过了一会儿,浏览器完全没有反应,然后告诉我页面无法显示。我去。..难道我们说了这么多都失败了,我哭了。然后打开eclipse看一下server端有没有动静。打开服务器一看,妈的,瞬间全世界都迎接我了!注意红笔标记,我收到情书了!我要开始解析了!那就别问我情书里写的是什么,继续往下看。“好熟悉的报文,我们好像在哪里见过,记得,那是一个春天,你刚刚发芽……”没错,这就是计算机网络原理中的HTTP请求报文。没学过物联网也没关系,看前两行(其实后面我们只会用到第一行),“Isawindex.html”是的,这就是我们刚刚在浏览器中输入地址;第二行,“Ialsosaw127.0.0.1”,没错,也是我们刚刚在浏览器中输入的。这是什么意思?我太激动了,我不能说这是什么意思,但我想你,读者,已经猜到了一些东西。写到这里,作为服务端的我已经收到了客户端的情书,为什么客户端(浏览器)一点反应都没有,甚至隔了一段时间还是“页面无法显示”。因为,有人给你写了一封情书,你却没有给他回信。等了一会儿,他心痛的是,什么都没有!是的,你要告诉他你喜不喜欢,给他一个答案。就说:“对不起,你是个好人……”你分心了吗?你好像在说你自己?回来,我们现在的任务是如何给人们一个答案。怎么给,怎么给,怎么给。..快想想,既然人家在127.0.0.1中指定要告白“index.html”,那么index.html当然要回答他。怎么回答,怎么回答,怎么回答。..快想想,既然index.html是一个文件,难道我不能读取文件的内容直接发送给客户端吗?但是送什么呢?没错,就是插座!我们使用sockets将文件内容返回给客户端就好了。那么问题来了。..“很好,关键是怎么做!”——如何先读取文件?假设我们的index.html在我电脑的E://courseware/computernetworkprinciple/experiment/experiment1/文件夹下,并且假设不会跨域访问,那么:1.定义一个字符串来存放我们的workingdirectoryStringbase_url="E://courseware/computernetworkprinciples/experiment/experiment1/";//这只是我原来电脑的目录,至于你的电脑,你可以自己改2.我怎么通过消息知道client要向index.html表白呢?看情书第一行GET/index.htmlHTTP/1.1,所以只需要获取情书第一行,解析出index.html,easy,let'sstart//因为只有第一行目前需要,所以我们不要像上面那样循环读取,读取一行就够了Stringline=reader.readLine();//使用字符串截取函数提取字符串"index.html"Stringurl=line.substring(5,line.indexOf("HTTP")-1);3.所以我们的index.html的绝对路径就是base_url+url。终于,我从人海中找到了我爱的人。看她怎么回答我——获取文件内容inputStream=newFileInputStream(base_url+url);OutputStreamoutputStream=socket.getOutputStream();//我想从服务器回复给客户端。对于服务器来说,这是发送的内容,所以Out了!byte[]buffer=newbyte[4*1024];//定义字节缓冲区intlen=0;while((len=inputStream.read(buffer))!=-1){outputStream.write(buffer,0,len);//很重要!通过socket的outputStream,将我们解析出来的文件内容一字不差的发送出去。如果你不写这个,它会导致你所爱的人向你表白并抑郁而死。你活该单}outputStream.flush();//如果上次写的时候没放bu如果ffer已满,则不会自动发送。您需要调用flush方法强制从缓冲区发送内容。文件读取完成后返回给客户端。亲爱的,他能接受吗?还是一样,一定要先运行服务器端程序,然后打开浏览器输入127.0.0.1/index.html回车。我又紧张又兴奋。我能收到回复吗?我会得到什么样的回复?如图所示。..为什么是这样???!!!好吧,让我们看看女神的index.html文件里写的是什么。..欢迎光临

王欢,你是个好人...

/body>看到这里,我是该高兴还是该悲痛。..幸运的是,我的女神回答了我;心烦意乱是的。..那么问题来了,,,哪个更擅长学习表白技巧呢?开玩笑,那么我们这次简单讨论的话题就完成了吗?也可以说是,可是一次告白失败也就算了?我还想告白第二次!(其实我不是这样的,在这里我只能牺牲我的人品让大家更好的理解,呵呵)。好吧,刚才我的工作目录里多了another.html,这次就让我跟她表白吧!好的!继续在浏览器中输入127.0.0.1/another.html回车,希望这次告白成功。可我等啊等啊,浏览器在那儿兜圈子,浏览器是不是知道我太辛苦了,不肯帮我送情书了?嗯,我打开浏览器再试试,输入127.0.0.1/index.html,咦?连第一女神都不理我?!该死!为什么!冲动是魔鬼!冷静下来!打开eclipse控制台,发现服务器端根本就没有“waitingforloveletter...”,于是问浏览器当然,发过去的情书丢了,因为没人收到.(庆幸的是,并不是因为我太花心,所以浏览器没有帮我投递情书)但是为什么呢?冷静下来,分析代码。其实我们可以认为这段代码执行一次之后就结束了,那么我第二次当然会向她发送请求,她是不会接收到的。没错,那么我应该怎么做才能解决这个问题呢?跪求媒人帮忙!媒人说:给服务端程序一个死循环,让她反复等待客户端的请求。”(其实媒人一直在死循环)媒人确实是媒人(不然谁...),那就按照她说的试试吧!更改代码并添加while(true)无限循环:publicclassMultiWebServer{publicstaticvoidmain(String[]args){Stringbase_url="E://courseware/computernetworkprinciples/experiment/experiment1/";while(true){try{ServerSocketserverSocket=newServerSocket(80);System.out.println("等待情书...");Socketsocket=serverSocket.accept();System.out.println("收到情书,我要开始解析了!");InputStreaminputStream=socket.getInputStream();BufferedReaderreader=newBufferedReader(newInputStreamReader(inputStream));Stringline=reader.readLine();System.out.println(line);Stringurl=line.substring(5,line.indexOf("HTTP")-1);System.out.println("情书解析完毕,我想想想怎么回复...");//获取文件内容inputStream=newFileInputStream(base_url+url);OutputStreamoutputStream=socket.getOutputStream();byte[]buffer=newbyte[4*1024];intlen=0;while((len=inputStream.read(buffer))!=-1){outputStream.write(buffer,0,len);}outputStream.flush();系统。out.println("情书请求已发送至客户端");//关闭对应资源serverSocket.close();socket.shutdownInput();社会ket.close();inputStream.close();reader.close();outputStream.close();}catch(Exceptione){}}}}就这样,这个媒人一直在这里等啊等啊,一个For客户,我会处理他的情书请求,处理完之后,继续以同样的方式循环,等待,处理,等待,处理。..嗯,接下来试试,不过还是要先运行服务端程序,然后跟第一位女神表白一下,就是127.0.0.1/index.html是不是还是好朋友,别问我返回结果。..这时候打开eclipse控制台,如果发现右上角的红色暂停标志可以点击,说明我们的红娘还在努力中!好吧,抽空表白二女神,看她怎么说,在浏览器中输入127.0.0.1/another.html,回车!这么快,女神回复我了。..这。..(她怎么知道不到十分钟?是不是突然想到cookies可以记录客户信息,不过我们这里不用cookies)我们看看another.html文件里面写了什么Welcome

我记得你刚刚跟一个人表白了,才十分钟,你怎么会一个好人!

好了,我不说了,亲们,我还要跟第三女神表白吗?..?浏览器主动告诉我:“你表白,这次我可以给你发多少封表白信给服务器,因为她一直在等我发给她!”想一想,算了,人生就是这样,何必多说。..代码都贴出来了。其实看起来挺简单的,但是在实际操作中,会遇到各种各样的问题。还有一些遗留问题需要继续:1.Q:什么是端口?A:这是一个比较抽象的进程间通信的概念。每个进程只能占用一个端口,也就是说,多个进程永远不会同时占用一个端口2.Q:由于多个进程不能同时占用一个端口,所以我们常说的web服务默认使用80端口.我的电脑有三个浏览器,谷歌、♂、IE。您可以同时上网。这不是端口冲突吗?A:人们常说的Web服务使用80端口,指的是服务器监听Web请求的端口。它是服务器,而不是您自己的客户端。一般来说,一个应用打开后,本地操作系统分配的访问网络的端口号是随机的,所以虽然三个浏览器同时收到了web服务器的回复报文,但是由于三个浏览器占用的端口不同,所以不会有冲突。3、Q:由于我的应用使用的端口是随机的,服务器收到请求后如何知道将响应报文发送给谁?-答:插座!通过刚才的实际编程,在我的理解中,Socket肯定会至少包含四部分:IP地址、端口号、输入流和输出流。也就是说,客户端发送给服务器的Socket中必须有客户端的IP地址和对应的应用程序的端口号,这样服务器自然就知道该向谁发送响应报文了。4.Q:一定要用80端口吗?答:不一定。我们刚才编程的时候确实用到了80端口,所以我们在浏览器中输入127.0.0.1/index.html,浏览器默认我们会向127.0.0.1主机的80端口发送请求。不过这个80端口号只是默认的,我们可以自己更改,比如在java代码serverSocket=newServerSocket(3456);中将服务端的ServerSocket改为ServerSocket。这时候我们需要在浏览器中输入127.0。0.1:3456/index.html,效果是一样的,大家可以试试。5、Q:谁是client,谁是server?A:我们只有一台电脑,既是客户端又是服务器。当浏览器请求一个网页时,它就是一个客户端;当80端口收到请求报文并响应时,就是服务器。如果真的不懂,想想什么是自恋,或者,自我放纵还勉强够用。.6、Q:有什么问题请留言~java写的这个webserver的简单说明:这段代码很简单,肯定不是真正的webserver使用的代码。我们的只能回答最基本的web请求,不能检测是否跨域访问等等。但最基本的,使用socket编程是肯定的。另外,对于这个程序,只给出了一种处理输入输出流的方法。对于输入流,除了我们刚才使用的BufferedReader包装类外,还可以直接使用InputStream的read()方法;对于输出流,除了我们刚才使用的OutputStream的write()方法外,还可以使用BufferedWritter、PrintWritter等,这些都是javaIO的基本用法。根据网络环境和要读取的文件大小,可以时时灵活。这是旁观者的智慧。