利用TCP协议自己编写服务器

Livealone

发布日期: 2018-11-05 11:37:17 浏览量: 327
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

一、 需求分析

  • 在 socket 客户端实验的基础上,编写自己的服务器

  • 实现的功能包括:

    • 客户端上传文件并自动用 RSA 算法加密/服务器接收文件
    • 客户端下载文件并自动解密(拥有密钥)/服务器发送文件
    • 客户端获得服务器上的文件列表
  • 本次实验中数据的传输采用 TCP 协议

    • 服务端 IP 地址为本机 IP 地址
    • 端口为 10086
  • 考虑服务器并发性,依次尝试阻塞式服务器、并发式服务器、 异步式服务器,并给出特征分析

二、操作环境

  • 操作系统:Mac OS

  • 编写语言:Java

  • 编译软件:Eclipse

三 、 概要设计

3.1 客户端

3.1.1 Client 类的基本操作

  • public void put() throws Exception

    • 操作结果:将本地文件发送到服务器
  • public String listAll() throws Exception

    • 操作结果:返回服务器保存的文件列表,包括可供下载的文件和用户上传的文件
  • public int get(String file_name) throws Exception

    • 传入参数:要下载文件的名称
    • 操作结果:得到下载的文件,下载成功返回 1,失败返回 0(文件不存在)
  • public String pre_list() throws Exception

    • 操作结果:为方便得到服务器上传和下载的文件列表,此函数保留以便今后添加新功能
  • public Client()

    • 操作结果:构造函数,连接服务器

3.1.2 Frame 类

主要为显示客户端主界面,包括各种空间和各种事件处理函数,调用 Client 类 的各种方法,实现图形界面

3.1.3 ListAll 类

主要为显示服务器上的文件列表,包括可供下载的文件和用户上传的文件,调用 Client 类的各种方法,实现图形界面

3.1.4 download 类

主要为显示服务器上可供下载的文件,输入相应文件名后可自动下载,调用 Client 类的各种方法,实现图形界面

3.1.5 Encrypt 类

采用静态方法 public static byte[] encrypt(byte[] data, String filename) throws Exception,传入加密文件转换成的 byte 数组和加密文件的文件名,返回得到经 RSA 算法加密得到的 byte 数组(加密后的文件),其中,私钥保存在本地。

3.1.6 Decrypt 类

采用静态方法 public static byte[] decrypt(byte[] data, String filename) throws Exception,传入从服务器收到加密后文件转换成的 byte 数组和加密文件的文件名,返回得到经 RSA 算法解密得到的 byte 数组(解密后的文件),其中,自动提 取本地私钥,若私钥不存在,则无法解密,返回加密文件。

3.2 服务器

3.2.1 Sever 类

包括绑定服务器端口,选择服务器保存文件的目录(以供接受客户端文件),依次测试阻塞式服务器、并发式服务器、异步式服务器。

3.2.2 Socket_connect 类

  • public Socket_connect() throws Exception

    • 操作结果:构造函数,传入连接成功的 socket,进行初始化
  • public void choose() throws Exception

    • 操作结果:根据客户端传来指令的类型,自动选择发送文件、接受文件、 显示客户端文件列表功能
  • public void success_message(String str) throws Exception

    • 传入参数:成功时需要显示的信息
    • 操作结果:服务器成功操作后返回给客户端信息
  • public void error_message(String str) throws Exception

    • 传入参数:失败时需要显示的信息
    • 操作结果:服务器成功失败后返回给客户端信息
  • public void send_list() throws Exception

    • 操作结果:服务器发送给客户端文件列表
  • public void receive_file() throws Exception

    • 操作结果:服务器接受客户端发送过来的文件(已加密)
  • public void send_file() throws Exception

    • 操作结果:服务器发送给客户端文件,若文件不存在,返回失败信息

四 、服务器并发性能分析

目前常用的服务器模式共分为三种:

  • 第一种是阻塞式服务器,是最好实现的服务器,也是问题最多的服务器。客户端发送到服务器的请求,服务器会进行排队,依次处理请求。前一个请求没有处理完成,服务器不会处理后面的请求。也就相当于使用一个 while(true)循环,依次调用 seversocket.accept()函数,每次只接受一个客户端访问,知道访问完成后才处理下一个客户端的请求。这种服务器很容易进行攻击,只需要向服务器发送一个处理时间很长的请求,就会将其他的请求堵在门外,导致其他请求无法得到处理,所以,这种服务器更多的是作为理论模型,实际应用并不多。

  • 第二种是并发式服务器,这种服务器处理请求时,每接收到一个请求,就启动一个线程处理该请求,这种模式的服务器,好处是不会出现阻塞式服务器请求被拥堵的情况,但是也是存在问题的,服务器启动线程是有一定的开销的,请求数量不多的时候,服务器启动线程没 有问题,但是请求过多时,将会导致服务器的资源耗尽。所以,会存在一种方式——建立线程池来处理请求,每当请求到来时,向线程池申请线程进行处理,这样,线程池开放多少线程是固定的,不会导致系统资源耗尽,但是依然会有一些问题,当线程池被占用满时,还是 有可能出现请求被阻塞的情况,所以这种方式是一种折中的方式。但是,对于并发请求不是很多的场景来说, 使用这种方式是完全可以的。本次客户端最终版本就是采用这种方法编写的。

  • 第三种方式是异步服务器,使用该种方式,一般要借助于系统的异步 IO 机制,如 select 或 poll,这种方式,当一个请求到达时,我们可以先将请求注册,当有数据可以读取时,会得到通知,这时候我们处理请求,这样,服务器进程没有必要阻塞处理,也不会存在很大 的系统开销,因此,目前对于并发量要求比较高的服务器,一般都是采用这种方式。

对于三种服务器,本人都进行了测试:其中最初版本是阻塞服务器,当一个客户端连接请求到达时,其他客户端都无法进行连接,需要依次排队等候,效率比较低;后来我采用多线程方法,实现了并发服务器,然而发现有些进程无法执行完毕,经检查发现是被系统自动回收了,因此创建了 ArryList 来保存每个客户端连接的进程,以防被回收。

在尝试第三种服务器时,发现使用的方法与前两种大不相同:前两种用的是 java.net 包,主要是 socket 和 seversocket 完成通讯;异步服务 器使用的是 java.nio 包,主要是socketchanel 和 seversocketchanel 完成通讯。有很多差别,因此在实现上无法做到预期的设想,仅仅是做了简单的字符串传送通信,如果想要实现文件上传下载,还需要阅读相关资料深入学习,尤其是 buff 缓冲区的各种操作。所以最终的客户端选用第二种-并发服务器来完成。对其进行改善,建立线程池来处理请求,每当请求到来时,向线程池申请线程进行处理,这样不会导 致系统资源耗尽。这些在代码中都已实现。

对于异步服务器,基本格式如下:

  1. public class Server
  2. {
  3. String IP = "127.0.0.1";
  4. int PORT = 10086;
  5. public void startServer(String serverIP, int serverPort) throws IOException{
  6. ServerSocketChannel serverChannel = ServerSocketChannel.open();
  7. InetSocketAddress localAddr = new InetSocketAddress(IP, Port);
  8. //服务器绑定地址
  9. serverChannel.bind(localAddr);
  10. //设置为非阻塞
  11. erverChannel.configureBlocking(false);
  12. //注册到 selector,会调用 ServerSocket 的 accept,用 selector 监听 accept 能否返回
  13. Selector selector = Selector.open();
  14. serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  15. while (true) {
  16. //调用 select,阻塞在这里,直到有注册的 channel 满足条件
  17. selector.select();
  18. //可以通过 selector.selectedKeys().iterator()拿到符合条件的迭代器
  19. Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
  20. //处理满足条件的 keys
  21. while (keys.hasNext()) {
  22. //取出一个 key 并移除
  23. SelectionKey key = keys.next();
  24. keys.remove();
  25. try {
  26. if (key.isAcceptable()) {
  27. //取得可以操作的 channel
  28. ServerSocketChannel server = (ServerSocketChannel)key.channel();
  29. SocketChannel channel = server.accept();
  30. //注册进 selector,当可读或可写时将得到通知,select 返回
  31. channel.register(selector, SelectionKey.OP_READ);
  32. }
  33. else if (key.isReadable()) {
  34. //有 channel 可读,取出可读的 channel
  35. SocketChannel channel = (SocketChannel)key.channel();
  36. //创建读取缓冲区,一次读取 1024 字节
  37. ByteBuffer buffer = ByteBuffer.allocate(1024);
  38. channel.read(buffer);
  39. //锁住缓冲区,缓冲区使用的大小将固定
  40. buffer.flip();
  41. //附加上 buffer,供写出使用
  42. key.attach(buffer);
  43. key.interestOps(SelectionKey.OP_WRITE);
  44. }
  45. else if (key.isWritable()) {
  46. //有 channel 可写,取出可写的 channel
  47. SocketChannel channel = (SocketChannel)key.channel();
  48. //取出可读时设置的缓冲区
  49. ByteBuffer buffer = (ByteBuffer)key.attachment();
  50. //将缓冲区指针移动到缓冲区开始位置
  51. buffer.rewind();
  52. //读取为 String
  53. String recv = new String(buffer.array());
  54. //清空缓冲区
  55. buffer.clear();
  56. buffer.flip();
  57. //写回数据
  58. byte[] sendBytes = recv.toUpperCase().getBytes();
  59. channel.write(ByteBuffer.wrap(sendBytes));
  60. }
  61. }
  62. catch (IOException e) {
  63. //当客户端 Socket 关闭时,会走到这里,清理资源
  64. key.cancel();
  65. try {
  66. key.channel().close();
  67. }
  68. catch (IOException e1) {
  69. e1.printStackTrace();
  70. }
  71. }
  72. }
  73. }
  74. }
  75. }

此框架的设计经阅读并参考资料编写,对于具体情况需要改变 buff 缓冲区的读 写方式,可以成功发送接受信息,完成通信。异步服务器的好处在于,服务器没 有工作可做的时候,会等在 select 调用上,不会占用系统资源,而当不同的条 件满足时,又可以第一时间被唤醒,执行相应的操作,所以无论从资源的利用上, 还是从响应的及时性上都优于前两种。另外,如果 write 和 read 的时间比较长, 处理也可以放到线程中处理,这样就结合了并发服务器的优势。

五、程序测试

客户端运行时的主界面

点击“上传文件”,显示文件选择器,选取要上传的文件, 进行上传

发送后会显示成功上传

依次上传本地几个文件,生成的私钥会被保存

此时服务器上的文件均为加密文件,不可查看

点击“查看列表”,会显示服务器上可供下载的文件和服务 器上已有的文件,可以看到刚刚上传的文件

点击“下载文件”,会显示服务器上可供下载的文件和服务器上已有的文件,可以看到刚刚上传的文件,输入要下载的文 件的全部名称(复制即可),单击下载

选择文件目录,保存下载文件。下载成功后返回信息

本地的文件已经自动解密,可以正常使用

上传的附件 cloud_download 利用TCP协议自己编写服务器.7z ( 1.41mb, 4次下载 )
error_outline 下载需要6点积分

发送私信

只有过着不安稳得日子,日子才能不纠结

8
文章数
8
评论数
最近文章
eject