2011년 6월 9일 목요일

java.nio 패키지 New I/O 강좌 - 14

본 자료의 출처는 getJAVA™ 입니다.


14. ServerSocketChannel 클래스와 SocketChannel 클래스
이제 실전에 들어가기 전에 ServerSocketChannel 클래스와 SocketChannel 클래스에 대해 먼저 알아보자. 이들은 net패키지의 ServerSocket클래스와 Socket클래스를 채널로서 다루고자 할 때 쓰는 SelectableChannel이다. 이들 네트워크 관련 채널들은 독자적으로 소켓의 역할을 대처하지는 않는다. 대신 소켓 클래스를 내부에 가지고 있으면서 이들의 기능을 채널화하는데 적절히 이용하게 된다.
1> ServerSocketChannel 클래스
① ServerSocketChannel 클래스 생성
ServerSocketChannel을 얻으려면 open()메서드를 사용한다. 단 이때 얻는 채널은 내부의 소켓이 아직 bind되지 않은 상태이기 때문에 적절한 IP주소와 포트 번호로 binding시켜줘야 한다. 일반적으로 다음과 같은 순서로 채널을 얻고 binding 한다.
1. ServerSocketChannel 얻기.
ServerSocketChannel server=ServerSocketChannel.open();
2. 내부 소켓을 얻는다.
ServerSocket socket=server.socket();
3. binding 한다.
SocketAddress addr=new InetSocketAddress(포트번호);
socket.bind(addr);
② ServerSocketChannel 클래스 주요 메서드
ㅁ public abstract SocketChannel accept() : 이 채널의 소켓에 대한 접속을 받아들여 SocketChannel을 리턴한다. 이때 ServerSocketChannel이 Blocking I/O 모드라면 accept()는 Blocking되지만 Non-Blocking I/O 모드라면 Blocking되지 않는다. 따라서 당장 어떤 접속요구가 없다면 null을 리턴한다. 하지만 리턴된 SocketChannel은 ServerSocketChannel이 Blocking I/O 모드이든 아니든 상관없이 무조건 Blocking I/O 모드로 시작한다.
ㅁ public static ServerSocketChannel open() : ServerSocketChannel를 얻는다. 이때 리턴된 ServerSocketChannel는 아직 bind되지 않은 상태이므로 소켓의 bind 메소드를 사용해 특정의 주소에 binding을 해주어야 한다.
ㅁ public abstract ServerSocket socket() : 내부 소켓을 얻는다.
ㅁ public final int validOps() : 현재 채널이 할 수 있는 해당 동작(ops)을 리턴한다. 서버소켓채널은 SelectionKey.OP_ACCEPT 만 할 수 있다.
2> SocketChannel 클래스
① SocketChannel로 접속하기
소켓채널을 얻기 위해서는 open()메서드를 사용하면 되는데 open()에는 인자가 있는 것과 없는 것이 있다. 만약 인자 없이 open()를 사용한다면 접속이 되지 않는 소켓채널을 리턴하므로 connect()메서드를 이용해서 접속을 해주어야 한다. 인자로 SocketAddress 객체를 준다면 접속이 된 소켓채널을 얻을 수 있다. 두가지 경우를 다 보자.
* 접속된 소켓채널 얻기
SocketAddress addr=new InetSocketAddress("ip주소", 포트번호);
SocketChannel socket=SocketChannel.open(addr);
* connect() 사용해서 접속하기
SocketAddress addr=new InetSocketAddress("ip주소", 포트번호);
SocketChannel socket=SocketChannel.open();
socket.connect(addr);
* SocketChannel에서 Non-Blocking I/O 모드일 경우에는 open(SocketAddress addr)해도 되지만, open()해서 connect(SocketAddress addr)했을 경우에는 즉시 연결작업이 끝나지 않을 수 있어서 이 메서드가 false를 리턴하게 되므로, finishConnect()로 연결작업을 끊어줘야 한다. 만약 연결이 제대로 안되었다면 false가 리턴된다. Blocking I/O 모드일 때는 connect() 호출이 Blocking 되면 연결이 끊길때까지 지속된다.
② SocketChannel 클래스 주요 메서드
ㅁ public abstract boolean connect (SocketAddress remote) : 인자로 들어온 SocketAddress 객체 정보를 가지고 현재 채널에 소켓을 접속한다. 연결이 제대로 안되면 false를 리턴한다.
ㅁ public abstract boolean finishConnect () : 소켓채널의 접속 처리를 완료한다.
ㅁ public abstract boolean isConnected () : 채널소켓이 접속이 되었는지 유무를 리턴.
ㅁ public abstract boolean isConnectionPending () : 이 채널상에서 접속 조작이 진행중인지 어떤지를 판단. 즉 접속이 시작되고 완료하지 않은 경우, finishConnect()가 호출되고 있지 않는 경우 true를 리턴.
ㅁ public static SocketChannel open () : 접속되지 않는 소켓채널을 리턴.
ㅁ public static SocketChannel open (SocketAddress remote) : 접속된 소켓채널을 리턴.
ㅁ read()류 메서드
ㅁ public abstract int read (ByteBuffer dst)
ㅁ public final long read (ByteBuffer [] dsts)
ㅁ public abstract long read (ByteBuffer [] dsts, int offset, int length)

ㅁ write()류 메서드
ㅁ public abstract int write (ByteBuffer src)
ㅁ public final long write (ByteBuffer [] srcs)
ㅁ public abstract long write (ByteBuffer [] srcs, int offset, int length)
3> 예제
간단한 채팅을 만들어보자. 단순하게 서버에 클라이언트가 접속, 메세지를 보내면 서버가 메세지를 읽어 출력하고 서버도 메세지를 보내는 소스이다. 우선 순서를 보자.
<< 서버측 >>
  1. 채널들을 관리할 Selector를 얻는다.
  2. Selector selector=Selector.open();
  3. ServerSocketChannel를 얻는다.
  4. ServerSocketChannel server=ServerSocketChannel.open();
  5. 내부 소켓을 얻는다.
  6. ServerSocket socket=server.socket();
  7. binding 한다.
  8. SocketAddress addr=new InetSocketAddress(포트번호);
  9. socket.bind(addr);
  10. ServerSocketChannel을 Selector에 등록시킨다. ServerSocket는 OP_ACCEPT동작만 할 수 있다.
  11. server.register(selector, SelectionKey.OP_ACCEPT);
  12. 클라이언트의 접속을 대기한다. 이때 접속이 되면 accept()에 의해 상대방 소캣과 연결된 SocketChannel의 인스턴스를 얻는다. 이 채널는 읽기(OP_READ),쓰기(OP_WRITE),접속(OP_CONNECT) 행동을 지원한다.
  13. SocketChannel socketChannel=serverChannel.accept();
  14. 접속된 SocketChannel를 Selector에 등록한다.
  15. socketChannel.register(selector, 소켓채널의 해당행동);
  16. 채널이 취할 수 있는 3가지 동작(읽기(OP_READ),쓰기(OP_WRITE),접속(OP_CONNECT) )에 대해서 검사한다. 이때는 다음과 같은 메서드를 이용해서 체크를 한다.
  17. isConnectable() : true이면 상대방 소켓과 새로운 연결이 됐다는 뜻이다. 이때 Non-Blocking I/O 모드일 경우에는 연결과정이 끝나지 않는 상태에서 리턴될 수도 있다. 그러므로 SocketChannel 의 isConnectionPending()메서드가 true를 리턴하는지 아닌지를 보고 true를 리턴한다면 finishConnection()를 명시적으로 불러서 연결을 마무리짓도록 한다.
  18. isReadable() : true이면 읽기(OP_READ) 동작이 가능하다는 뜻으로 SocketChannel로부터 데이터를 읽는 것에 관한 정보를 받을 준비가 되었다는 뜻이다. 따라서 버퍼에 읽어들이기만 하면된다.
  19. isWritable() : true이면 쓰기(OP_WRITE) 동작이 가능하다는 뜻으로 SocketChannel에 데이터를 쓸 준비가 되었다는 뜻이다. 따라서 버퍼의 데이터를 이 채널에 write()하면 된다.

<< 클라이언트측 >>
  1. SocketChannel를 얻어서 접속한다.
  2. SocketAddress addr=new InetSocketAddress("localhost", 8080);
  3. SocketChannel socket=SocketChannel.open(addr);
  4. 버퍼를 만들고 서버에서 들어온 데이터를 읽고 쓰기를 한다.

자, 이제 소스를 보자. 먼저 서버측 소스인 serverSocketChannel .java를 보자.
serverSocketChannel.java
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class serverSocketChannel implements Runnable {
Selector selector;
int port=8080;

private void Log(Object obj) {
System.out.println(obj.toString());
}

public serverSocketChannel()
throws IOException {
initServer();
Log("****************************************");
Log("클라이언트의 접속을 기다리고 있습니다");
Log("****************************************");
}
private void initServer()
throws IOException, ClosedChannelException {
serverRegister(socketBind(selectorOpen()));
}
private void serverRegister(ServerSocketChannel server)
throws IOException, ClosedChannelException {
server.configureBlocking(false);
int validOps = server.validOps();
Log("ServerSocketChannel.validOps() : " + validOps);
Log(", " + (validOps == SelectionKey.OP_ACCEPT));
server.register(selector, SelectionKey.OP_ACCEPT);
}
private ServerSocketChannel socketBind(ServerSocketChannel server)
throws IOException {
ServerSocket socket = server.socket();
SocketAddress addr = new InetSocketAddress(port);
socket.bind(addr);
Log(server);
return server;
}
private ServerSocketChannel selectorOpen() throws IOException {
selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
Log(server);
return server;
}
public void run() {
int socketOps = SelectionKey.OP_CONNECT |
SelectionKey.OP_READ |
SelectionKey.OP_WRITE;
while(true) {
try {
selector.select();
} catch(IOException ioe) {
ioe.printStackTrace();
}
keyIterator(selector.selectedKeys().iterator(), socketOps);
}
}
private void keyIterator(Iterator iter, int socketOps) {
while (iter.hasNext()) {
try {
SelectionKey selected = (SelectionKey)iter.next();
iter.remove();
SelectableChannel channel = selected.channel();

if (channel instanceof ServerSocketChannel) {
ServerSocketChannel serverChannel = (ServerSocketChannel)channel;
SocketChannel socketChannel = serverChannel.accept();
if (socketChannel == null) {
Log(" # null server socket");
continue;
}
Log(" # socket accepted : " + socketChannel);
socketChannel.configureBlocking(false);
int validOps = socketChannel.validOps();
Log("SocketChannel.validOps() : " + validOps);
Log(", " + (validOps == socketOps));
socketChannel.register(selector, socketOps);
}
else {
SocketChannel socketChannel = (SocketChannel)channel;
if (selected.isConnectable())
isConnectable(socketChannel);
if (selected.isReadable())
isReadable(socketChannel);
if (selected.isWritable())
isWritable(socketChannel);
}
} catch(IOException ioe) {
ioe.printStackTrace();
}
}
}
private void isWritable(SocketChannel socketChannel)
throws IOException {
ByteBuffer buf = ByteBuffer.allocate(20);
String s = "Hello Client!!";
byte[] bytes = s.getBytes();
buf.put(bytes);
buf.clear();
socketChannel.write(buf);
System.out.println(" # socket write : " + s);
}
private void isReadable(SocketChannel socketChannel)
throws IOException {
ByteBuffer buf = ByteBuffer.allocate(20);
socketChannel.read(buf);
buf.clear();
Log("# socket read :");
while (buf.hasRemaining()) {
System.out.print((char)buf.get());
}
}
private void isConnectable(SocketChannel socketChannel)
throws IOException {
Log(" # socket connected");
if (socketChannel.isConnectionPending()) {
Log(" # Connection is pending");
socketChannel.finishConnect();
}
}
public static void main(String[] args)
throws IOException {
new Thread(new serverSocketChannel()).start();
}
}


clientSocketChannel.java -->클라이언트측
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class clientSocketChannel {
private SocketChannel socket = null;

public static void main(String[] args)
throws Exception {
clientSocketChannel client = new clientSocketChannel();
client.exec();
}
public void exec()
throws IOException {
open();
while (true) {
getMesage(socket);
sendMessage(socket);
}
}
private void open()
throws IOException {
SocketAddress addr = new InetSocketAddress("localhost", 8080);
socket = SocketChannel.open(addr);
System.out.println(socket);
System.out.println("# isBlocking() : " + socket.isBlocking());
}
private void getMesage(SocketChannel socket) throws
IOException {
ByteBuffer buf = ByteBuffer.allocate(20);
int read = 0;
socket.read(buf);
buf.clear();
System.out.print("# socket read :");
while (buf.hasRemaining()) {
System.out.print((char)buf.get());
}
buf.clear();
}
private void sendMessage(SocketChannel socket)
throws IOException {
ByteBuffer buf = ByteBuffer.allocate(20);
String msg = "Hello Server!!";
byte[] bytes = msg.getBytes();
buf.put(bytes);
buf.clear();
socket.write(buf);
System.out.println("\n" + "# socket write : " + msg);
buf.clear();
}
}

댓글 없음:

댓글 쓰기

블록체인 개요 및 오픈소스 동향

블록체인(block chain) 블록체인은 공공 거래장부이며 가상 화폐로 거래할때 발생할때 발생할 수 있는 해킹을 막는 기술. 분산 데이터베이스의 한 형태로, 지속적으로 성장하는 데이터 기록 리스트로서 분산 노드의 운영자에 의한 임의 조작이 불가...