近日由于老师的要求及学习的需要,要使用安卓搭建HTTP Server,思路上和平常搭建HTTP Server没什么区别,只不过现在的载体是手机。存在一个较大的问题,就是以前没有接触过HTTP Server,虽然之前的编程中对HTTP协议有过一定的了解,但是对自己搭建一个HTTP Server而言还远远不足。
搭建这样一个HTTP Server,首要解决的问题就是如何开始。如何迈出第一步,开始时我是有些纠结的,而google官方是有一个例子的,叫做SimpleWebServer,显而易见,这个例子就搭建了一个简单的服务器,从例子中可以看到,这个例子采用了java中的ServerSocket和Socket来建立连接,而且这个例子只能允许一个客户端连接,返回简单的信息。这就给了我一个初步的思路了,要想建立自己的http server,我的想法就是模仿SimpleWebServer来建立,使用ServerSocket来连接,不过要实现多线程,这样才能允许多客户端连接。
原先的思路有些错误了,原先的思路里面,总觉得客户端与服务器的交互是类似于人这样的,当客户端请求数据时要对客户端进行标识,否则不同的客户端之间容易发生冲突。后来发现其实都是定义好的,客户端可以执行的操作都是由服务器定义好的,服务器对自己定义的操作进行了识别,并且返回相应的处理,对其他不能识别的操作则返回错误。这样的思路处理起来就简单了,客户端执行操作,服务器在接收到客户端的请求时就“直接”进行处理,并不保存客户端的任何信息,在http 1.1中虽然可以保持客户端的长连接,但是不过是保持连接不被关闭,减少建立连接的资源,并不代表服务端应用层及应用要保存客户端的信息,应用层及应用在接收到客户端的请求时对相应的请求进行处理及反馈,反馈结果送入运输层,此时就不再于服务端的应用有关,而由TCP/IP协议向客户端发送反馈。
接下来一个问题就是http server返回信息的格式的处理,返回信息要符合http协议中的响应头格式,我们可以在服务器中每个相应的处理里面写响应头的格式,显然这样会显得冗余,代码上重复的部分太多,一开始对服务器进行测试时还可这样简单的写一写,后面要将响应头这里封装到Response类中,让这个类负责对返回信息进行处理,服务器向这个类传递需要发送的数据,为这个类设定发送的状态及一些必要的格式,这个类则返回给服务器封装的发送的数据,其后将数据发送给客户端。
建立HTTP Server存在的一个难点,也是重点,就是面对的客户端是多种多样的,而不是确定的某一样客户端,为了适配更多的客户端,获得更好的兼容性,就要看对HTTP协议的理解了,也就是上面所说的返回信息的格式的处理。HTTP协议的请求响应等内容反复看了多次,加上对一些网络上其他人的代码的理解,建议是最好能阅读RFC文档,第一手资料,详细准确。
在我的建立过程中,参考了nanohttpd,这里网络上的一个开源的java库,实现了http server的一些功能,使用这个nanohttpd,在android手机上完成了一个http的服务器,实现了下载文件的功能,即android手机作为http服务端,可以从手机中选取文件分享,电脑则可以通过浏览器从android手机中下载手机中分享的文件。
完成了文件分享下载的功能后,接下来的一步就是文件的上传了,如何将文件上传,这又是另外一个问题。这里有个地方要特别提醒下,在安卓手机上完成HTTP Server,要注意权限的问题,权限一定要设置好,否则又无法实现功能,又难以定位错误。文件上传,一开始使用的是post,后来经过多方资料的查找,使用put是更好的方式。简单在代码中添加了H5的表单,暂时没有测试,完成后会在后文说明。
使用nanohttpd完成了文件的下载后,自然而然的想通过nanohttpd来完成文件的上传功能,nanohttpd是一个优秀的开源库,可惜我在测试时忘记在添加写权限,导致功能无法完成,过了好久才发现错误,只能说要特别注意特别小心。因为在此处无法正常上传文件,自己也不知道为什么,怀疑是对nanohttpd的源码理解不够,所以打算重新写,而且在使用nanohttpd开发的过程中,自身也对HTTP协议理解的更加深刻了,如使用java代码完成HTTP Server的开发,会比使用他人的开源库更得心应手。于是打算自己完成整个HTTP Server的内容了。
一开始对java nio中的内容有些一知半解,对ServerSocketChannel及SocketChannel的内容不是很明白,就凭借着一知半解打算使用这两个类来建立httpserver,代码写的很开心,参照了一些网路上的资料,运行却总是不成功。打个Log发现,一直处于死循环的状态,本来在收到来自客户端的请求以后,就应该进入某个for循环中,却不知为什么始终无法进去,抓包也觉得没有问题,打了log也觉得没有问题,没有问题就是最大的问题吧,一直不成功,于是就放弃。改用传统的多线程来实现。
传统的多线程实现,使用了一个线程池,采用这种方式建立,即使用serversocket 和socket来建立。serversocket接收一个连接请求,返回一个socket,这个socket建立一个线程,线程中对socket的请求进行处理,将线程交给线程池,serversocket 继续监听端口,等待后面的连接。下面讲讲整个操作的过程。
监听并获取到socket后,交由单独的客户线程处理,客户线程从socket中获取inputStream和outputStream,从inputStream中读取byte流,将byte流分析得到http的首部及内容,即可进行操作。
这里的HTTP Server做的简单,只支持get和post两个方法,get方法用于向HTTP Server请求资源,即数据下载,这里倒是简单些,有些问题的地方是安卓的文件选择器问题,文件选择器选择的文件路径容易有问题,得到的路径不是绝对路径,需要专门做些处理。同样也要注意权限的问题。
get方法的处理较为简单些,从客户端的socket 中读取到客户端请求的数据,而后使用fileinputstream读取文件,使用outputstream向客户端发送数据。发送完毕后将fileinputstream和outputstream关闭,即完成一次传输。需要注意的是,客户端浏览器可以通过服务器返回的html中包含的文件信息选择下载,而客户端是不确定的,如果不管客户端请求什么都向客户端发送,显然安全性是不够好的,于是添加判断,只有分享的文件才可以被访问,其他文件不可。
post方法的处理较为复杂,因为这是向HTTP Server发送文件,HTTP Server上要接收文件,并且将文件保存到本地,操作起来复杂度较高。由于HTTP协议特点,要先从http协议的首部获取boundary字段值,boundary字段值是文件分隔符,也是文件部分开始的符号,结束符号是在boundary字段值后面添加“--”。第一次使用了Scanner来读取数据,但是Scanner不会读入换行符,在没有读到换行符或者结束之前会阻塞,这就是个比较严重的问题了。文件因为缺少几个字节,导致上传一直不成功。后来使用BufferedReader 或者BufferedInputStream都有相似的问题。最后决定直接使用InputStream来读取文件。因为是流的处理,多一个字节少一个字节都要特别小心,不能出错。否则整个读取的过程就会失败。使用byte数组读取,写入文件时要特别小心,从inputStream中读取了多少byte,就向文件中写入多少byte,否则会出错,当遇到文件结束符时退出,不退出就会阻塞,导致失败。
完成了这些基本功能后,HTTP Server的基础算是完成了,接下来要解决的就是一些其他的问题了,例如解决下载续传的问题,这些地方就见仁见智了,不同的人有不同的想法,能做的也不一样。就没什么可以多说了。实话说来,在手机端完成一个HTTP Server不难也不简单,看个人想法吧。
HTTP Server 漫谈
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
- 优秀文章推荐 给 Android 开发者的 RxJava 详解http://gank.io/post/560e15...