Chinaunix

标题: 代理服务器只能给客户端发送第一组消息 [打印本页]

作者: 方兆国    时间: 2013-06-21 22:58
标题: 代理服务器只能给客户端发送第一组消息
最近作一个代理服务器程序,使用来转发视频的,可是他只能给我把第一条请求转发过来,剩下的数据就发布回来了,然后视频就无法播放了
  1. package com.liujiaqi.proxy;

  2. import java.io.BufferedReader;
  3. import java.io.File;
  4. import java.io.FileNotFoundException;
  5. import java.io.FileReader;
  6. import java.io.IOException;
  7. import java.io.PrintWriter;

  8. public class proxy {
  9.         private static File file = new File("proxy.ini");       //配置文件
  10.         private static String host = "http://localhost/";  //Web服务器地址
  11.         private static int port = 9090;

  12.         public static void createFile() {                          //使用默认参数创建配置文件的函数
  13.                 try {
  14.                         file.createNewFile();
  15.                         PrintWriter writer = new PrintWriter(file);
  16.                         writer.println("<host>" + host);
  17.                         writer.println("<port>" + port);
  18.                         writer.flush();
  19.                         writer.close();
  20.                 } catch (IOException e) {
  21.                         e.printStackTrace();
  22.                 }
  23.         }

  24.         public static void readFile() {                            //读取配置文件中的参数
  25.                 try {
  26.                         String temp = null;
  27.                         BufferedReader reader = new BufferedReader(new FileReader(file));
  28.                         while ((temp = reader.readLine()) != null) {
  29.                                 switch (temp.substring(temp.indexOf("<") + 1,
  30.                                                 temp.lastIndexOf(">"))) {
  31.                                 case "host":
  32.                                         host = temp.substring(temp.lastIndexOf(">") + 1);
  33.                                         break;
  34.                                 case "port":
  35.                                         port = Integer.parseInt(temp.substring(temp
  36.                                                         .lastIndexOf(">") + 1));
  37.                                         break;
  38.                                 default:
  39.                                         break;
  40.                                 }
  41.                         }
  42.                         reader.close();
  43.                 } catch (FileNotFoundException e) {
  44.                         e.printStackTrace();
  45.                 } catch (IOException e) {
  46.                         e.printStackTrace();
  47.                 }
  48.         }

  49.         public static void main(String[] args) {
  50.                 if (file.exists()) {                            //如果配置文件存在,则从配置文件中载入启动参数
  51.                         proxy.readFile();
  52.                 } else {                                        //如果配置文件不存在,则创建配置文件,然后使用默认的参数来启动
  53.                         proxy.createFile();
  54.                 }
  55.                 ProxyServer server = new ProxyServer(host, port);
  56.                 server.start();
  57.         }
  58. }
复制代码

作者: 方兆国    时间: 2013-06-21 22:59
这是用来监听代理端口的类
  1. package com.fangzhaoguo.proxy;

  2. import java.io.IOException;
  3. import java.net.ServerSocket;
  4. import java.net.Socket;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;

  7. public class ProxyServer {
  8.         private static ServerSocket serverSocket;
  9.         private boolean isAlive = true;
  10.         private String host = null;
  11.         private int port = 0;

  12.         public ProxyServer(final String host, final int port) {
  13.                 try {
  14.                         this.host = host;
  15.                         this.port = port;
  16.                         serverSocket = new ServerSocket(this.port);
  17.                 } catch (IOException e) {
  18.                         e.printStackTrace();
  19.                 }
  20.         }

  21.         public void start() {
  22.                 ExecutorService service = Executors.newFixedThreadPool(20);   //创建线程池
  23.                 while (isAlive) {
  24.                         try {
  25.                                 Socket socket = serverSocket.accept();
  26.                                 service.execute(new ProxyConnect(host, socket));    //使用线程池中的线程处理任务
  27.                         } catch (IOException e) {
  28.                                 e.printStackTrace();
  29.                         }
  30.                 }
  31.                 try {
  32.                         serverSocket.close();
  33.                 } catch (IOException e) {
  34.                         e.printStackTrace();
  35.                 }
  36.         }

  37.         public void close() {
  38.                 isAlive = false;
  39.         }
  40. }
复制代码

作者: 方兆国    时间: 2013-06-21 23:03
这是存放视频数据内容的表
  1. create table if not exists Data
  2. (
  3.         id int not null auto_increment,
  4.         name varchar(100) not null,
  5.         data blob not null,
  6.         size int not null,
  7.         log timestamp not null default now(),
  8.         primary key (id)
  9. );
复制代码
id是自动增长的序号,以免数据其他字段的重复导致数据被误刷新
name是视频片断的名称
data是视频片断的数据内容
size是数据片断的大小
log是数据写入日期
作者: 方兆国    时间: 2013-06-21 23:05
这是存放视频名称的表
  1. create table if not exists Information
  2. (
  3.         id int not null auto_increment,
  4.         url char(100) not null,
  5.         log timestamp not null default now(),
  6.         primary key (id)
  7. );
复制代码
id是自动增长的序号
url是视频的名称,旧版本中使用的是url地址,因此保留了这个名称
log是视频最后一次被浏览的日期

作者: 方兆国    时间: 2013-06-21 23:08
这是用来关联视频名称和视频片断的表
  1. create table if not exists DataIndex
  2. (
  3.         file_id int not null,
  4.         block_id int not null,
  5.         primary key (file_id,block_id),
  6.         foreign key(file_id) references Information(id),
  7.         foreign key(block_id) references Data(id)
  8. );
复制代码
file_id是视频名称的id,外键,关联至视频名称表Information的id字段
block_id是视频片断的id,外键,关联至视频数据内容表 Data的id字段
作者: 方兆国    时间: 2013-06-21 23:09
这是视频名称表Information上的存储函数,用来插入新行并且返回id
  1. CREATE function fun_Information(address char(100))
  2. RETURNS int
  3. BEGIN
  4.         DECLARE file_id int;
  5.         select count(*) into file_id from Information where url=address;
  6.         if file_id < 1  then
  7.                 insert into Information(url) values(address);
  8.         end if;
  9.         select id into file_id from Information where url=address;
  10.         return file_id;
  11. END
复制代码

作者: 方兆国    时间: 2013-06-21 23:10
这是视频数据内容表Data上的存储函数,用来插入新行并且返回id
  1. CREATE FUNCTION fun_Data(nValue char(100),dValue blob,sValue int,fValue int)
  2. RETURNS int
  3. BEGIN
  4.         DECLARE bValue int;
  5.         select count(*) into @Value from Data where name=nValue;
  6.         if @Value<1  then
  7.                 insert into Data(name,data,size) values(nValue,dValue,sValue);
  8.         end if;
  9.         select id into bValue from Data where name=nValue;
  10.         select count(*) into @Value from DataIndex inner join Data on Data.id=DataIndex.block_id where Data.name=nValue and DataIndex.file_id=fValue;
  11.         if @Value<1  then
  12.                 insert DataIndex(file_id,block_id) values(fValue,bValue);
  13.         end if;       
  14.         return bValue;
  15. END
复制代码

作者: 方兆国    时间: 2013-06-21 23:11
接下来就是最关键的内容,也就是客户端和代理服务器之间通信的函数了,问题就出在这儿,代理服务器只能接受并且正常返回第一条请求,剩下的就都出问题了
作者: 方兆国    时间: 2013-06-21 23:15
ProxyConnect 使用了 Runnable
run()如下所示,我自己对这一块儿也很糊涂,关键就是http请求和响应的问题
  1. public void run() {
  2.                 int file_id = -1;
  3.                 int[] block_id = null;
  4.                 String filename = this.read();
  5.                 String sql = "{?=call fun_Information(?)}";
  6.                 file_id = database.Rrocedure(sql, filename);
  7.                 sql = "update Information set log=now() where id=" + file_id;
  8.                 database.Write(sql);

  9.                 while (isAlive) {
  10.                         sql = "select Data.id,Data.size from Data inner join DataIndex on Data.id=DataIndex.block_id inner join Information on Information.id=DataIndex.file_id where Information.id="
  11.                                         + file_id + " and Data.name='" + filename + "';";
  12.                         rs = database.Read(sql);
  13.                         try {
  14.                                 if (rs.next()) {
  15.                                         block_id = new int[rs.getRow()];
  16.                                         rs.beforeFirst();
  17.                                         int i = 0, length = 0;
  18.                                         while (rs.next()) {
  19.                                                 block_id[i] = rs.getInt(1);
  20.                                                 length += rs.getInt(2);
  21.                                                 i++;
  22.                                         }
  23.                                         rs.close();
  24.                                         database.ResultSetClose();
  25.                                         this.SendHead(length);
  26.                                         for (i = 0; i < block_id.length; i++) {
  27.                                                 this.WorkDB(block_id[i]);
  28.                                                 try {
  29.                                                         out.flush();
  30.                                                 } catch (IOException e) {
  31.                                                         e.printStackTrace();
  32.                                                 }
  33.                                         }
  34.                                 } else {
  35.                                         rs.close();
  36.                                         database.ResultSetClose();
  37.                                         this.WorkNet(file_id, filename);
  38.                                 }
  39.                         } catch (SQLException e) {
  40.                                 e.printStackTrace();
  41.                         }
  42.                         filename = this.read();
  43.                 }

  44.                 this.close();
  45.         }
复制代码

作者: 方兆国    时间: 2013-06-21 23:24
这是从web服务器读取数据的函数,并且对数据进行处理
  1. private String read() {
  2.                 String buffer = null;
  3.                 System.out.println("I am the " + Thread.currentThread().getId() + "\t"
  4.                                 + Thread.currentThread().getName());      //用来显示该线程是第几个线程
  5.                 try {
  6.                         do {
  7.                                 buffer = in.readLine();
  8.                                 if (buffer.startsWith("Connection: ")) {                                         //如果是以Connection开头,则记录时Keep-Alive还是Close
  9.                                         connection = buffer.substring(buffer.indexOf(" ") + 1);
  10.                                         System.out.println(connection);
  11.                                         if (connection.equals("close")) {
  12.                                                 isAlive = false;
  13.                                         }
  14.                                 }
  15.                                 if (buffer.startsWith("null")) {
  16.                                         isAlive = false;
  17.                                 }
  18.                         } while (!buffer.startsWith("GET") && isAlive);
  19.                         buffer = buffer.substring(5, buffer.lastIndexOf(" "));
  20.                 } catch (IOException e) {
  21.                         e.printStackTrace();
  22.                 }
  23.                 return buffer;
  24.         }
复制代码
System.out.println("I am the " + Thread.currentThread().getId() + "\t"+ Thread.currentThread().getName());     
用来显示该线程是第几个线程,之前对浏览器的请求有疑惑,因此加入了调试代码


if (buffer.startsWith("Connection: ")) {                                       
                                        connection = buffer.substring(buffer.indexOf(" ") + 1);
                                        System.out.println(connection);
                                        if (connection.equals("close")) {
                                                isAlive = false;
                                        }
如果是以Connection开头,则记录时Keep-Alive还是Close
但是我还是对这个Keep-Alive和Close的区别不是很理解,Close好像是本次会话结束之后就关闭的意思
而且使用buffer.substring(buffer.indexOf("HTTP"));检索的时候会看到有一会儿是HTTP 1.0 有些时候是HTTP 1.1
更诡异的是User-Agent字段,有些时候是GPAC/0.5.0-rev4065,有些时候是 Lavf52.33.0,用的是同一个播放器
作者: 方兆国    时间: 2013-06-21 23:25
这个函数是用来给播放器响应HTTP响应头的
  1. private void SendHead(long length) {
  2.                 String status = "HTTP:/1.0 200 OK\r\n";
  3.                 String mine = "MIME-Version: 1.0\r\n";
  4.                 String contentType = "Content-type: text/html\r\n";
  5.                 String contentLength = "Content-Length: " + length + "\r\n";
  6.                 try {
  7.                         out.write(status.getBytes());
  8.                         out.write(mine.getBytes());
  9.                         out.write(contentType.getBytes());
  10.                         out.write(contentLength.getBytes());
  11.                         out.write("\r\n".getBytes());
  12.                 } catch (IOException e) {
  13.                         e.printStackTrace();
  14.                 }
  15.         }
复制代码
我还是对那个"HTTP:/1.0 200 OK\r\n";有些疑惑,我应该随着客户端变呢,还是要固定一种协议呢
作者: 方兆国    时间: 2013-06-21 23:28
这个函数是从web服务器中读取数据的函数
  1. private void WorkNet(int file_id, String filename) {
  2.                 String address = filename, sql = null;
  3.                 URL url = null;
  4.                 int length = 0, sum = 0, num = 0;
  5.                 int[] block_id = new int[100];
  6.                 try {
  7.                         while (isAlive) {
  8.                                 sum = 0;
  9.                                 num = 0;
  10.                                 url = new URL(host + address);
  11.                                 is = url.openStream();
  12.                                 while ((length = is.read(bytes)) > 0) {
  13.                                         sum += length;
  14.                                         data = this.bytecopy(bytes, length);
  15.                                         sql = "{?=call fun_Data(?,?,?,?)}";
  16.                                         block_id[num++] = database.Rrocedure(sql, filename, data,
  17.                                                         length, file_id);

  18.                                 }
  19.                                 this.SendHead(sum);
  20.                                 for (int i = 0; i < num; i++) {
  21.                                         this.WorkDB(block_id[i]);
  22.                                 }
  23.                                 is.close();
  24.                         }
  25.                 } catch (MalformedURLException e) {
  26.                         e.printStackTrace();
  27.                 } catch (IOException e) {
  28.                         e.printStackTrace();
  29.                 }
  30.         }
复制代码
每次读取数据结束后,将数据存入数据库中,然后调用WorkDB函数,将数据返回给播放器
作者: 方兆国    时间: 2013-06-21 23:29
这是从数据库中读取数据的函数
  1. private void WorkDB(int block_id) {
  2.                 String sql = "select data from Data where id=" + block_id + ";";
  3.                 int length = 0;
  4.                 rs = database.Read(sql);
  5.                 try {
  6.                         if (rs.next()) {
  7.                                 is = rs.getBinaryStream(1);
  8.                         }
  9.                         rs.close();
  10.                         database.ResultSetClose();
  11.                         while ((length = is.read(bytes)) > 0) {
  12.                                 data = this.bytecopy(bytes, length);
  13.                                 out.write(data);
  14.                         }
  15.                         is.close();
  16.                 } catch (SQLException e) {
  17.                         e.printStackTrace();
  18.                 } catch (IOException e) {
  19.                         e.printStackTrace();
  20.                 }
  21.         }
复制代码

作者: 方兆国    时间: 2013-06-21 23:31
在这个程序中使用了MySQL的Blob字段,通过从控制台看到的数据显示,能够将读取的数据保存到数据库中,但是只能保存前两个数据段,后面的视频就无法读入了,不知道问题出现在哪里,怎么解决问题
作者: 方兆国    时间: 2013-06-21 23:33
而且还在java程序中调用了MySQL的函数
就是Database类Rrocedure函数
Database类是做得用来存取数据库中数据的类
Rrocedure函数用来调用MySQL函数,并且负责绑定参数
Rrocedure函数被重载过
作者: 方兆国    时间: 2013-06-21 23:37
这是程序运行过程中的一部分日志,我只打开了一个播放器窗口,并且输入url地址+回车后,没有做任何操作,可是还是有这么多的线程被启用,而且那个HTTP协议版本也不一致,User-Agent: 也不一致
  1. I am the 10        Thread-0
  2. Reading……        GET /starry-tales.mpd HTTP/1.0
  3. 修改前GET /starry-tales.mpd HTTP/1.0
  4. 修改后starry-tales.mpd
  5. I am the 10        Thread-0
  6. Reading……        Host: localhost
  7. Reading……        User-Agent: GPAC/0.5.0-rev4065
  8. Reading……        Accept: */*
  9. Reading……        Connection: Keep-Alive
  10. Reading……        Accept-Language: zh
  11. Reading……        Icy-Metadata: 1
  12. Reading……       
  13. Reading……        null
  14. Exception in thread "Thread-0" java.lang.NullPointerException
  15.         at com.liujiaqi.proxy.ProxyConnect.read(ProxyConnect.java:178)
  16.         at com.liujiaqi.proxy.ProxyConnect.WorkNet(ProxyConnect.java:127)
  17.         at com.liujiaqi.proxy.ProxyConnect.Work(ProxyConnect.java:82)
  18.         at com.liujiaqi.proxy.ProxyConnect.run(ProxyConnect.java:49)
  19.         at java.lang.Thread.run(Thread.java:724)
  20. I am the 12        Thread-1
  21. Reading……        GET /starry-tales.mpd HTTP/1.1
  22. 修改前GET /starry-tales.mpd HTTP/1.1
  23. 修改后starry-tales.mpd
  24. I am the 12        Thread-1
  25. Reading……        User-Agent: Lavf52.33.0
  26. Reading……        Accept: */*
  27. Reading……        Range: bytes=0-
  28. Reading……        Host: localhost:9090
  29. Reading……        Authorization: Basic
  30. Reading……        Connection: close
  31. Bye
  32. 修改前starterror end
  33. 修改后error
  34. I am Closing
  35. Exception in thread "Thread-1" java.lang.NullPointerException
  36.         at com.liujiaqi.proxy.ProxyConnect.close(ProxyConnect.java:60)
  37.         at com.liujiaqi.proxy.ProxyConnect.run(ProxyConnect.java:51)
  38.         at java.lang.Thread.run(Thread.java:724)
  39. I am the 13        Thread-2
  40. Reading……        GET /starry-tales.mpd HTTP/1.1
  41. 修改前GET /starry-tales.mpd HTTP/1.1
  42. 修改后starry-tales.mpd
  43. I am the 13        Thread-2
  44. Reading……        User-Agent: Lavf52.33.0
  45. Reading……        Accept: */*
  46. Reading……        Range: bytes=0-
  47. Reading……        Host: localhost:9090
  48. Reading……        Authorization: Basic
  49. Reading……        Connection: close
  50. Bye
  51. 修改前starterror end
  52. 修改后error
  53. I am Closing
  54. Exception in thread "Thread-2" java.lang.NullPointerException
  55.         at com.liujiaqi.proxy.ProxyConnect.close(ProxyConnect.java:60)
  56.         at com.liujiaqi.proxy.ProxyConnect.run(ProxyConnect.java:51)
  57.         at java.lang.Thread.run(Thread.java:724)
  58. I am the 14        Thread-3
  59. Reading……        GET /starry-tales.mpd HTTP/1.0
  60. 修改前GET /starry-tales.mpd HTTP/1.0
  61. 修改后starry-tales.mpd
  62. I am the 14        Thread-3
  63. Reading……        Host: localhost
  64. Reading……        User-Agent: GPAC/0.5.0-rev4065
  65. Reading……        Accept: */*
  66. Reading……        Connection: Keep-Alive
  67. Reading……        Accept-Language: zh
  68. Reading……        Icy-Metadata: 1
  69. Reading……       
  70. I am the 15        Thread-4
  71. Reading……        GET /starry-tales_dash.mp4 HTTP/1.0
  72. 修改前GET /starry-tales_dash.mp4 HTTP/1.0
  73. 修改后starry-tales_dash.mp4
  74. I am the 15        Thread-4
  75. Reading……        Host: localhost
  76. Reading……        User-Agent: GPAC/0.5.0-rev4065
  77. Reading……        Accept: */*
  78. Reading……        Connection: Keep-Alive
  79. Reading……        Accept-Language: zh
  80. Reading……        Icy-Metadata: 1
  81. Reading……       
  82. Reading……        GET /starry-tales-$RepresentationID$-$Number$1.m4s HTTP/1.0
  83. 修改前GET /starry-tales-$RepresentationID$-$Number$1.m4s HTTP/1.0
  84. 修改后starry-tales-$RepresentationID$-$Number$1.m4s
  85. http://localhost/starry-tales-$RepresentationID$-$Number$1.m4s
  86. com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '2-2' for key 'PRIMARY'
  87.         at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  88.         at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
  89.         at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  90.         at java.lang.reflect.Constructor.newInstance(Constructor.java:414)
  91.         at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
  92.         at com.mysql.jdbc.Util.getInstance(Util.java:386)
  93.         at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1041)
  94.         at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4187)
  95.         at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4119)
  96.         at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2570)
  97.         at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2731)
  98.         at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2809)
  99.         at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2758)
  100.         at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:894)
  101.         at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:732)
  102.         at com.liujiaqi.proxy.Database.Write(Database.java:44)
  103.         at com.liujiaqi.proxy.ProxyConnect.WorkNet(ProxyConnect.java:111)
  104.         at com.liujiaqi.proxy.ProxyConnect.Work(ProxyConnect.java:82)
  105.         at com.liujiaqi.proxy.ProxyConnect.run(ProxyConnect.java:49)
  106.         at java.lang.Thread.run(Thread.java:724)
  107. I am the 15        Thread-4
  108. Reading……        Host: localhost
  109. Reading……        User-Agent: GPAC/0.5.0-rev4065
  110. Reading……        Accept: */*
  111. Reading……        Connection: Keep-Alive
  112. Reading……        Accept-Language: zh
  113. Reading……        Icy-Metadata: 1
  114. Reading……       
复制代码

作者: 方兆国    时间: 2013-06-21 23:39
上面的报错日志中的最末尾就停在那儿了
可是视频一点儿都看不到
结束之后察看数据库,在Data表中只有两个记录
因为用到了mpd格式,因此第一个记录时mpd文件的内容,第二个是视频片断的内容,在控制台下当然是乱码
作者: 方兆国    时间: 2013-06-21 23:41
这些都是在PC端测试的,用手机测试的话,也是这样,一直有问题




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2