2014-12-19 3 views
0
이 같은 하나의 연결에 SocketChannel를 사용하고

:Java에서 소켓을 사용하여 빠른 데이터 읽기를 만드는 방법은 무엇입니까?

int sampleBufferSize = 50; 
StringBuilder data = new StringBuilder(); 
ByteBuffer bf = ByteBuffer.allocate(sampleBufferSize); 
SocketChannel sc = new SocketChannel(); 
while(true) 
    if(sc.read(bf) > 0){ 
     bf.flip(); 
     while(bf.hasRemaining()) 
      data.append((char) bf.get()); 
     bf.clear(); 
    }else{ 
     fireDataReceived(data.toString()); 
     data.delete(0, data.length()); 
    } 

이 코드는 매우 효율적 아니지만, 그것은 0.05 초에서 같은 PC에서의 HTTP POST 요청 1백30킬로바이트를 읽습니다. 이제 비슷한 기능을하지만 Socket을 사용하여 클래스를 작성하려고합니다. 여기에 코드입니다 :

private static final int TIMEOUT_MILLIS = 50; 
private boolean reading = false; 
private long readBeginTime = 0; 
private StringBuilder buffer = new StringBuilder(); 
private Thread thread = new Thread(){ 
    public void run(){ 
     while(!isInterrupted()){ 
      try { 
       int b = getInputStream().read(); 
       if(b == -1){ 
        if(reading) 
         fireDataReceived(); 
        close(); 
       }else{ 
        if(!reading){ 
         reading = true; 
         readBeginTime = System.currentTimeMillis(); 
         setSoTimeout(TIMEOUT_MILLIS); 
        } 
        buffer.append((char) b); 
       } 
      } catch (SocketTimeoutException e){ 
       fireDataReceived(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
       if(reading) 
        fireDataReceived(); 
       close(); 
      } 
     } 
    } 
}; 
private void fireDataReceived(){ 
    BufferedSocketEvent e = new BufferedSocketEvent(this, System.currentTimeMillis() - readBeginTime, buffer.toString()); 
    buffer.setLength(0); 
    reading = false; 
    try { 
     setSoTimeout(0); 
    } catch (SocketException e1) { 
     e1.printStackTrace(); 
    } 
    for(BufferedSocketListener listener: listeners) 
     listener.dataReceived(e); 
} 

그리고 문제는이 같은 요청에 대해 0.4 초 걸린다 내가 그렇게 오랜 시간이 걸리지 않는 이유를 모르겠어요. 제 코드에 무엇이 잘못된 지 설명하십시오.

+1

'TIMEOUT_MILLIS = 50' 왜이 시간 제한이 너무 작 :

I는 다음과 같이 시작하는 것? 인터넷을 통해 50ms의 지연이있는 것은 드문 일이 아닙니다. –

+0

@Banthar, 그 예입니다. 나는 1 대의 PC 범위에서 그것을 테스트했다. localhost는 거대한 시간 초과입니다. –

+0

30 초로 설정하면 프로그램이 느리게 작동합니까? –

답변

0

스트림 코드의 문제는 한 번에 한 바이트 씩 읽는 것입니다. 느리고 한 번에 하나씩 StringBuilder에 덧붙입니다. 시도해보십시오 :

BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()); 
char[] chars = new char[8192]; 
int count; 
while ((count = in.read(chars)) > 0) 
{ 
    buffer.append(chars, 0, count); 
    fireDataReceived(); 
} 

요청을 분리하는 수단으로 읽기 시간 제한을 사용하는 것은 올바르지 않습니다. 이를 위해 데이터를 구문 분석해야합니다. TCP는 메시지 경계가없는 스트리밍 프로토콜이며 별도로 수신되는 별도의 전송에 대한 보장이 없습니다.

+0

BufferedReader는 좋은 해결책이지만, 줄 바꿈/캐리지 리턴 기호가없는 잘못된 요청을하면 그 데이터를받지 못합니다. –

+0

그리고 SocketTimeoutException catch 절에서 fireDataReceived에 대해 이야기하면 소켓 타임 아웃이 0보다 큰 경우에만 예외가 발생합니다. 타임 아웃은 TIMEOUT_MILLIS로 설정됩니다. 이것은 이미 버퍼링 된 데이터가 없을 때 SocketTimeoutException 절에 도달 할 수 없음을 의미합니다. –

+0

@AntonOnipko 당신은 착각했습니다. 이 코드는'readLine()'을 호출하지 않기 때문에 캐리지 리턴이나 줄 바꿈에 의존하지 않습니다. – EJP

0

readLine() 또는 이와 유사한 방법을 사용해야합니다. HTTP 헤더의 끝은 빈 줄로 표시됩니다. 선을 감지하지 못하면 데이터 읽기를 중단 할시기를 알 수 없습니다. 타임 아웃이나 TCP 프래그먼트에 의존하는 것은 올바른 해결책이 아닙니다. 요청에 새 줄이 포함되어 있지 않으면 하나가 나타나거나 연결이 종료되거나 시간이 초과 될 때까지 기다려야합니다. 최소한 2 초 정도 기다려야합니다.

나는 또한 그 청취자들을 제거 할 것이다. 단일 소켓에 대해 여러 리스너를 추가 할 수 있다는 것은 좋은 생각처럼 보일 수 있지만 HTTP 헤더로 처리하는 것이 현명한 방법은이를 파싱하는 것입니다. 파서는 또한 독자에게 독서를 중단 할 때 알려야합니다.

ServerSocket serverSocket = new ServerSocket(8888); 
Socket clientSocket = serverSocket.accept(); 
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "ASCII")); 
String[] r = reader.readLine().split(" "); 
String type = r[0]; 
String resource = r[1]; 
String version = r[2]; 
Map<String, String> headers = new HashMap<String,String>(); 
while(true) { 
    String line = reader.readLine(); 
    if(line.isEmpty()) { 
     break; 
    } 
    String headerLine[] = line.split(":",2); 
    headers.put(headerLine[0],headerLine[1].trim()); 
} 
processHeader(type, resource, version, headers); 
+0

'readLine()'은 모두가 standarts를 따르고 악의적 인 사람들이 없다면 좋은 선택이 될 것입니다. 제 질문은 꽤 간단하게 시작되었습니다. 일단 웹 인터페이스를 사용하여 응용 프로그램을 만들고 HTTP POST의 본문 뒤에 줄이 끝나지 않았 음을 알았습니다. 그리고 나서 "클라이언트가 잘못된 길이의 헤더를 보낼 수 있고 전신을 읽을 수 없거나 오래되지 않은 휴식 데이터를 기다리는 데 방해가 될 것"이라고 생각했습니다. 그리고 나서 저는 "OMG, 누구든지 끝내 줄 요청을하지 못하고" "매달릴 것"이라고 생각했습니다. 그래서 포맷에 관계없이 최소한의 데이터 만 읽어야합니다. –

+0

걸려있는 연결에 대해 걱정이된다면 제한 시간을 몇 초로 설정하고 느린 연결 만 삭제하면됩니다. 아파치는 이것을 좋아한다 (기본적으로 60 초의 타임 아웃으로). 현재의 접근 방법에서 모든 클라이언트는 LAN 또는 전화 접속의 경우 동일한 시간을 기다려야합니다. 느린 클라이언트가 작동하도록 시간 초과를 늘리면 빠른 클라이언트의 성능이 저하됩니다. 빨리 작동하지 않습니다. –

+0

HTTP POST 본문 뒤에 줄 끝이 없어도됩니다. 이 답변은 헤더에만 관련된 것입니다. 이 대답에 따라 헤더를 읽었 으면 내 대답에 따라'read()'를 사용해야합니다. – EJP