Socket Programming
1.1 TCP 소켓프로그래밍
- 이 통신은 서버와 클라이언트 간의 1대1 소켓 통신이다.
- 서버가 먼저 시행되어 클라이언트의 연결 요청을 기다리고 있어야한다.
- 한포트에 하나의 서버 소켓 만 연결할 수 있다. (프로토콜이 다르면 같은 포드를 공유할 수 있다.)
- TCP 통신 절차
- 서버는 서버 소켓으로 특정 포트의 클라이언트 연결요청을 처리할 준비를 한다.(listen)
- 클라이언트는 연결 서버 IP와 포트번호로 소켓을 생성해 서버에 연결 요청한다.
- 요청을 받은 서버는 새로운 소켓을 생성해 클라이어트의 소켓과 연결되도록 한다.(accept)
- 자바에서는
Socket
와 ServerSocket
클래스를 지원한다.
1.1.1 Soket
Socket
은 TCP 클라이언트 API 이고 원격 호스트에 접속할때 사용한다.
ServerSocket
은 TCP 서버 API 이고 클라이언트 Socket들을 연결 승인한다.
- TCP Sockets를 주고받는것은 I/O stream 통해서 이루어진다.
public class Server {
public static void main(String[] args){
try {
ServerSocket serverSocket = new ServerSocket(7777);
System.out.println("서버 실행중...");
while (true){
Socket socket = serverSocket.accept();
DataInputStream in = new DataInputStream(socket.getInputStream());
String msg = in.readUTF();
System.out.println(msg);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeUTF("수신확인");
in.close();
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Client {
public static void main(String[] args){
try {
Scanner sc = new Scanner(System.in);
Socket socket = new Socket("localhost",7777);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeUTF("전송");
DataInputStream in = new DataInputStream(socket.getInputStream());
System.out.println(in.readUTF());
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.0 1대1 채팅
- 1대1 채팅을 하기 위해서는
Thread
가 필요하다(메세지를 수신,송신 각각)
- 이것은 작업을 독립적인 단위 로 나눈다는 의미로 볼 수 있다.
public class Server {
Socket socket;
DataInputStream in;
DataOutputStream out;
Scanner sc;
ServerSocket serverSocket;
public Server() {
try {
ServerSocket serverSocket = new ServerSocket(7777); // listen 상태
socket = serverSocket.accept(); // connect 요청이 들어오면 연결
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
sc = new Scanner(System.in);
revMsg();
sendMsg();
} catch (IOException e) {
}
}
public void revMsg(){
new Thread(()->{
try {
while (true) {
String msg = in.readUTF();
System.out.println("클라이언트 : "+msg);
}
} catch (IOException e) {
}
}).start();
}
public void sendMsg(){
new Thread(()->{
try {
while (true) {
out.writeUTF(sc.nextLine());
}
} catch (IOException e) {
close();
}
}).start();
}
public void close(){
try {
System.out.println("연결끊김");
socket.close();
serverSocket.close();
in.close();
out.close();
} catch (IOException e) {
}
}
public static void main(String[] args){
new Server();
}
}
public class Client {
Socket socket;
DataInputStream in;
DataOutputStream out;
Scanner sc;
public Client() {
try {
this.socket = new Socket("localhost",7777); // 연결 요청
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
sc = new Scanner(System.in);
sendMsg();
revMsg();
} catch (IOException e) {
}
}
public void close(){
try {
System.out.println("연결끊김");
socket.close();
in.close();
out.close();
} catch (IOException e) {
}
}
public void revMsg(){
new Thread(()->{
try {
while (true) {
String msg = in.readUTF();
System.out.println("서버 : "+msg);
}
} catch (IOException e) {
}
}).start();
}
public void sendMsg(){
new Thread(()->{
try {
while (true) {
out.writeUTF(sc.nextLine());
}
} catch (IOException e) {
close();
}
}).start();
}
public static void main(String[] args) {
new Client();
}
}
3.0 다중 클라이언트
- Block 은 메서드가 실행될때까지 멈춰있는 것을 의미한다.
- Block 상태일때는 다른작업이 진행되지 않기 때문에 멀티스레드를 활용한다.
- 멀티스레드 로 클라이언트를 block을 피하기위해 하나의 실행 단위로 바꿔주어야 한다.
3.0.1 다중채팅 구현
- 클라이언트가 메세지를 보내면 서버에서 모든 메서지를 보내주는 콘솔을 구현했다.
- 작은 단위로 보면 클라이언트가 보낸 메세지는 서버가 다시보내주는 형태이다.
- 그렇기 때문에 클라이언트 스레드안에서 개별스레드를 만들필요가 없었다.
- 클라이언트는 1대1채팅과 다르게 메인스레드와 recieve 스레드 만으로 구현했다.
public class Server {
Socket socket;
Scanner sc;
ServerSocket serverSocket;
LinkedList<Socket> socketList;
Map<Socket, Integer> socketMap;
public Server() {
try {
serverSocket = new ServerSocket(7777); // listen 상태 소켓을 생성
socketList = new LinkedList<>();
socketMap = new HashMap<>();
int id = 0; //아이디
System.out.println("서버 실행");
while (true) {
// 클라이언트가 다중으로 접속하기 위한 무한반복문
socket = serverSocket.accept();
// connect 요청이 들어오면 연결 (올때까지 block)
System.out.println("클라이언트 접속");
System.out.println(socket);
socketMap.put(socket,++id);
socketList.add(socket);
// 클라이언트마다 스레드를 생성
// Thread가 없을경우 readUTF() 메서드에서 block이 발생하기 때문에 독립적으로 분할한다.
new Thread(()->{
try {
DataInputStream in = new DataInputStream(socket.getInputStream());
revMsg(in,socket);
} catch (IOException e) {
}
}).start();
}
} catch (IOException e) {
}
}
public void revMsg(DataInputStream in,Socket client){
try {
while (true) {
String msg = in.readUTF();
if(!msg.equals("")) {
sendMsg(client,msg);
}
}
} catch (IOException e) {
}
}
public void sendMsg(Socket client,String msg){
Integer id = socketMap.get(client);
socketList.stream().forEach(so->{
try {
DataOutputStream ou = new DataOutputStream(so.getOutputStream());
ou.writeUTF(id+"번님 : "+msg);
} catch (IOException e) {
}
});
}
public static void main(String[] args){
new Server();
}
}
public class Client {
Socket socket;
DataInputStream in;
DataOutputStream out;
Scanner sc;
public Client() {
try {
this.socket = new Socket("localhost",7777); // 연결 요청
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
sc = new Scanner(System.in);
revMsg();
sendMsg();
// 위의 두개의 메서드의 순서를 바꾸면 block이 발생한다.
} catch (IOException e) {
}
}
public void revMsg(){
new Thread(()->{
try {
while (true) {
String msg = in.readUTF();
System.out.println(msg);
}
} catch (IOException e) {
}
}).start();
}
public void sendMsg(){
try {
while (true) {
String msg = sc.nextLine();
out.writeUTF(msg);
}
} catch (IOException e) {
close();
}
}
public static void main(String[] args) {
new Client();
}
}
출처