소켓은 TCP를 위한 구조이다. TCP를 사용하여 응용 프로그램끼리 통신을 하기 위해서는 먼저 연결을 하여야 하는데 연결을 하기 위해서느 연결 끝점(End point)가 있어야 한다. 이 연결끝점이 바로 Socket이다.
Socket 클래스
생성자
설명
Socket (String host, int port)
호스트 이름이 host이고 포트 번호가 port인 새로운 소켓을 생성한다
Socket (InetAddress address, int port)
InetAddress에 기술된 주소로 새로운 소켓을 생성한다
메소드
설명
InputStream getInputStream()
소켓이 사용하는 입력 스트림을 반환한다
OutputStream getOutputStream()
소켓이 사용하는 출력 스트림을 반환한다
Inetaddress getInetAddress()
소켓이 연결되어 있는 인터넷 주소를 반환한다
public int getLocalPort()
소켓이 연결되어 있는 포트 번호를 반환한다
public int getPort()
원격 컴퓨터의 포트번호를 반환한다
pbulic InetAddress getLocalAddress()
소케싱 연결되어 있는 인터넷 주소를 반환한다
ServerSocket 클래스
생성자
설명
public ServerSocket(int port) throws IOException
포트번호 Port에 대해 ServerSocket의 새로운 인스턴스를 만든다. 포트번호 0은 비어있는 포트번호를 사용한다는 의미이다. queue는 서버가 받을 수 있는 입력 연결의 개수를 의미한다. (디폴트는 50연결이다) address는 컴퓨터의 인터넷 주소를 나타낸다.
public ServerSocket(int port, int queue) throws IOException
public ServerSocket(int port, int queue, InetAddress address ) throws IOException
메소드
설명
public socket accept()
접속요청을 받는다
public void close()
ServerSocket을 닫는다
public Inetaddress getInetAddress()
소켓이 연결되어 있는 인터넷 주소를 반환한다
public int getSoTimeout()
소켓에 대한 타임아웃값을 밀리 초로 반환하거나 설정한다.
소켓클래스를 이용한 채팅방
서버)
package network;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
public class TCPIPChatMultichatServer {
// 접속 사용자 정보를 저장 : 메세지를 전체에게 보내기 위해서
// 저장정보 : name, OutputStream
// Map<String, Object>로 저장
HashMap<String, Object> clients;
public TCPIPChatMultichatServer() {
clients = new HashMap<String, Object>();
Collections.synchronizedMap(clients); // 스레드 동기화에 안전한 상태로 해주는메서드
}
public void start() throws IOException {
// Server Socket 생성
// 사용자의 요청이 있으면 Socket 생성해서 연결 > Clients 사용자 정보를 저장
// 연결 > Clients 사용자 정보를 저장 스레드로 처리
ServerSocket serverSocket = new ServerSocket(7777);
System.out.println("Chatting Server Start!");
Socket socket = null;
while(true) {
// 요청이 들어올 때까지 대기 하다가 요청이 들어오면 새로운 소켓을 생성해서 반환
socket = serverSocket.accept();
// map에 정보저장, 접속한 사람들에게 메세지를 전송
System.out.println("["+socket.getInetAddress()+socket.getPort()+"] 사용자 접속");
ServerReceiver receiver = new ServerReceiver(socket);
receiver.start();
}
}
void SendToAll(String msg) {
// 일괄처리 - Map은 순서가 없다 - Map이 가지고 있는 key를 Set으로 만든다.
Set<String> keys = clients.keySet();
Iterator<String> itr = keys.iterator();
while (itr.hasNext()) {
DataOutputStream out = (DataOutputStream) clients.get(itr.next()); // 키값가져오기
try {
out.writeUTF(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 내부클래스 : 데이터 받아서 저장, 메시지 전체 발송
private class ServerReceiver extends Thread {
// Socket, InputStream, OutputStream
Socket socket;
DataOutputStream out;
DataInputStream in;
public ServerReceiver(Socket socket) {
this.socket = socket;
try {
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run() {
String name=null; // 접속한 사용자 이름
try {
name= in.readUTF(); // 이름을 스트림을 통해 받는다
clients.put(name, out); // map에 사용자 정보 저장
SendToAll(">>>>>>>>>> " +name+"님이 접속하셨습니다.");// 내부클래스에서는 외부 클래스의 멤버를 참조할 수 있다.
while(in !=null ) {
SendToAll(in.readUTF());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
clients.remove(name);
System.out.println(name+"님이 나가셨습니다.");
}
}
}
public static void main(String[] args) throws IOException {
new TCPIPChatMultichatServer().start();
}
}
클라이언트)
package network;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import thread.threadMain;
public class TCPIPMulitiChatClient {
public static void main(String[] args) {
String serverIp = "192.168.0.32"; // localhost > 호스트자신의 주소
//Socket연결
try {
Socket socket = new Socket(serverIp, 7777);
// 메세지를 받는 스레드
Thread receiver = new ClientReceiver(socket);
// 메세지를 보내는 스레드
Thread sender = new ClientSenderThread(socket, "포그바");
sender.start();
receiver.start();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ClientSenderThread extends Thread{
// 보내기 스레드는 Socket, OutputStream, name도 필요하다.
Socket socket;
DataOutputStream out;
String name;
public ClientSenderThread(Socket s, String name) {
this.socket = s;
try {
out = new DataOutputStream(s.getOutputStream());
this.name = name;
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
Scanner sc = new Scanner(System.in);
try {
// 접속하면 이름을 서버에 전송
if(out != null) {
out.writeUTF(name);
}
while(out!=null) {
out.writeUTF("["+name+"]"+sc.nextLine());
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
class ClientReceiver extends Thread {
// 메세지를 받아서 콘솔에 출력
// Socket, InputStream, 필요
Socket socket;
DataInputStream in;
public ClientReceiver(Socket socket) {
this.socket = socket;
try {
in = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (in != null){
try {
System.out.println(in.readUTF());
} catch (IOException e) {
}
}
}
}
– 자바에서 스레드를 사용하는 이유는 비동기적 작동 방식(독립스레드)에 대한 개념이 없음 • 프로그래밍 방식이 자바에서는 사용이 가능하지 않음 • 비동기적 작동 방식 구현 : ‘스레딩 기술의 사용 방법’ 숙지 – 자바가 실행되는 기반인 자바가상머신(Java virtual machine, 이하 JVM) 자체가 하나의 프로세스임 • 언어적 차원 : 스레드의 동기화 지원 가능 • 다중 스레드 프로그램을 쉽고 명료하게 만들 수 있음
다중 쓰레드
– 시분할 기법(Time-Shared) • 멀티태스킹 및 멀티스레드를 가능하게 하는 기법 • 아주 짧은 시간 간격을 두고 여러 개의 프로그램을 전환하면서 실행 • 빠른 속도 때문에 두 개 이상의 프로그램이 동시에 실행되는 것처럼 느껴짐 • 프로그램의 실행을 전환하는 것은 OS가 담당함
– 다중 스레드의 이점 • 자원을 효율적으로 사용할 수 있음 • 사용자에 대한 응답성이 향상됨 • 작업이 분리되어 코드가 간결함
– 다중 스레드의 단점 • 동기화에 주의해야 함 • 교착상태가 발생하지 않도록 주의해야 함 • 각 스레드가 효율적으로 고르게 실행될 수 있게 해야 함
–다중 스레드의 사용 예
• 스마트폰 게임 – 메인스레드 : 게임을 하기 위한 UI부분을 그려줌 – 그래픽 부분 담당 코드 : 순차적으로 실행, UI를 그리는 서버통신을 담당하는 소켓부분을 방치하는 수 밖에 없음. – 통신을 담당하는 스레드를 따로 하나를 두어 일정한 시간단위로 체크할 수 있도록 해야 함.
• 영상통신
– 영상을 받아 화면에 출력해 주는 코드와 영상을 생성하여 보내주는 코드를 만드는 경우
> 적어도 2개의 작업이 동시에 일어난다는 것을 알 수 있음 – 두 가지 이상의 일을 구현하기 위해 다중 스레드를 사용함
< 스레드 사용 방법 >
예제 extends Thread
package thread;
// 쓰레드 클래스의 정의는 thread클래스를 상속해서 정의
public class showThread extends Thread {
String threadName;
public showThread(String name) {
this.threadName=name;
}
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println("안녕하세요" + threadName + "입니다.");
try {
// object클래스의 sleep() 메서드 : 현재 스레드를 1/1000초 간격으로 멈춤
sleep(100);// 100/1000초 > 1/10초
} catch (InterruptedException e) {
e.printStackTrace();
}
} System.out.println(threadName + " 종료 ★");
}
}
package thread;
public class threadMain {
public static void main(String[] args) {
// Thread 생성
showThread st1 =new showThread("1번 스레드");
showThread st2 =new showThread("2번 스레드");
st1.start();
st2.start();
for(int i=0; i<100; i++) {
System.out.println("메인스레드");
}
System.out.println("★끝★");
}
}
< 출력 >
main스레드에 Thread.sleep(100)을 똑같이 넣어주면 셋이 번갈아서 실행됨
예제 implements Runnable
package thread;
// 쓰레드 클래스 정의할 때 상속이 필요한 경우 Runnable 인터페이스를 구현해서 스레드를 생성할 수 있다.
public class ShowRunnable implements Runnable {
public void run() {
for(int i=0; i<100; i++) {
System.out.println("러너블스레드");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("★끝★");
}
}
package thread;
public class threadMain2 {
public static void main(String[] args) {
// Runnable 인터페이스를 이용한 스레드
Runnable target = new ShowRunnable();
Thread st3 = new Thread(target);
st3.start();
for(int i=0; i<100; i++) {
System.out.println("메인스레드");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("★끝★");
}
}
<출력 / 100개 반복>
스레드 우선순위 설정
// 우선순위 설정 1-10 : 기본5
st1.setPriority(Thread.MAX_PRIORITY);
st2.setPriority(Thread.MIN_PRIORITY);
<출력> ~ 속도가 빨라서 별차이가 없지만 방법은 ARADUZA ~
t1.join(); // t1 스레드가 종료될때까지 다른 스레드는 멈춘상태
예제)
package thread;
public class Sum {
int num;
Sum(){
num=0;
}
void addNum(int n) {
num+=n;
}
int getNum() {
return num;
}
public void run() {
// TODO Auto-generated method stub
}
}
package thread;
public class Adderthread extends Sum implements Runnable {
int start;
int end;
Adderthread(int n1, int n2){
start=n1;
end=n2;
}
@Override
public void run() {
for(int i=start; i<=end;i++) {
addNum(i);
}
}
}
package thread;
public class AdderThreadMain {
public static void main(String[] args) {
Adderthread at1 = new Adderthread(1, 500000);
Adderthread at2 = new Adderthread(500000, 1000000);
// 쓰래드 생성
Thread t1 = new Thread(at1);
Thread t2 = new Thread(at2);
t1.start();
t2.start();
try {
t1.join(); // t1 쓰래드가 종료될 때 까지 다른 쓰래드는 멈춤상태
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("1~100 합은 : " + (at1.getNum()+at2.getNum()) );
}
}
<출력>
1~100 합은 : 1784793664
멀티스레드 예제
package thread;
import javax.swing.JOptionPane;
public class ThreadTestMain1 {
// 다른 스레드에서도 참조가 가능한 클래스 변수
public static boolean check = false; // 10초 지나면 꺼지게하려고 만듦 (안에서사용하려고)
public static void main(String[] args) {
// String age= JOptionPane.showInputDialog("나이를 입력해주세요");
// int ageNumber=Integer.parseInt(age);
// System.out.println("저의 나이는 " + age + "세 입니다.");
// for(int i=10; i>0; i--) {
// System.out.println(i);
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// } // 나이쓰는 창뜨고 > 숫자셈
InputAgeThread iat= new InputAgeThread();
CountDownThread cdt=new CountDownThread();
iat.start();
cdt.start();
}
}
class InputAgeThread extends Thread{
@Override
public void run() {
System.out.println("10초 안에 입력하세요.");
String age= JOptionPane.showInputDialog("나이를 입력해주세요");
System.out.println("저의 나이는 " + age + "세 입니다.");
ThreadTestMain1.check=true;
}
}
class CountDownThread extends Thread {
@Override
public void run() {
for(int i=10; i>0; i--) {
if(ThreadTestMain1.check) {
break; // 입력되면 숫자세기 STOP
}
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.exit(0);
}
}
스레드 동기화
Note that this implementation is not synchronized API 문서에는 해당 클래스의 인스턴스가 둘 이상의 쓰레드가 동시에 접근을 해도 문제가 발생하지 않는지를 명시하고 있다. 따라서 쓰레드 기반의 프로그래밍을 한다면, 특정 클래스의 사용에 앞서 쓰레드에 안전한지를 확인 해야 한다.
ex) String buffer / builder
쓰레드의 동기화 - synchronized - 한 번에 하나의 쓰레드만 객체에 접근할 수 있도록 객체에 락(lock)을 걸어서 데이터의 일관성을 유지하는 것.
아래 예제)
Sum 클래스의 Void addNum에 synchronized가 없으면 동기화가 되지 않아서 매번 값이 다르다..
그러나 엄청난 성능의 감소를 동반한다.
package thread;
public class Sum {
int num;
Sum(){
num=0;
}
// 동기화~!
synchronized void addNum(int n) {
num+=n;
}
int getNum() {
return num;
}
public void run() {
// TODO Auto-generated method stub
}
}
package thread;
public class AdderThread1 extends Thread {
Sum sum;
int start;
int end;
public AdderThread1(Sum sum, int start, int end) {
this.sum = sum;
this.start = start;
this.end = end;
}
@Override
public void run() {
for (int i = start; i <= end; i++) {
sum.addNum(i);
}
}
}
package thread;
public class AdderThreadhMain2 {
public static void main(String[] args) {
Sum sum = new Sum();
AdderThread1 t1 = new AdderThread1(sum, 1, 5000);
AdderThread1 t2 = new AdderThread1(sum, 5001, 10000);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1-100합 "+ sum.getNum());
}
}
<출력>
1-100합 50005000
동기화블럭
public class Calculator {
int opplCnt=0; // 몇번의 연산을 했나요?
int opmiCnt=0;
public int add(int n1, int n2) {
synchronized (this) { // key
opplCnt++;
}
// opCnt++;
return n1+n2;
}
public int min(int n1, int n2) {
synchronized (obj) { // 참조값이 달라서 다르다.
opmiCnt++;
}
// opCnt++;
return n1-n2;
}
Object obj = new Object(); // 새로운 동기화 키 생성
}
만약 opplCnt / opmiCnt가 아니라 똑같이 opCnt++;를 사용하면 똑같이 this(둘다같은거면OK)를 넣으면 된다
num1도 쓰고 num2도 쓰기 위해(효율성) 두개의 Key로 분리해 주는 것이라고 생각하면 되는듯하다.
package io;
import java.io.File;
public class FileMove {
public static void main(String[] args) {
// File 클래스는 경로를 저장한다.
// 파일의 경로, 폴더의 경로
// 파일 또는 폴더의 속성을 변경하거나 삭제, 생성이 가능하다.
// 현재 존재하는 파일의 경로를 생성
File myFile = new File("C:"+File.separator+"myJava\\my.bin");
// myFile.delete();
if(!myFile.exists()) {
System.out.println("원본 파일이 준비되어 있지 않습니다.");
System.out.println("프로그램을 종료합니다.");
return;
}
System.out.println("파일 존재");
File reDir = new File("c:\\yourJava");
reDir.mkdir(); // 해당 경로에 폴더가 있으면 그대로 유지, 없으면 생성
System.out.println("폴더 생성 성공");
//파일을 이동할 새로운 경로 생성
File refile = new File(reDir, "Your.bin");
System.out.println("파일 존재 유무 : " + refile.exists());
//파일 이동
myFile.renameTo(refile);
if(refile.exists()) {
System.out.println("파일 이동 성공");
}
}
}
package io;
import java.io.Serializable;
public class Circle implements Serializable{
//implements Serializable : 직렬화의 대상이다. 인스턴스 저장이 가능하다.
// 직렬화를 하기 위해서는 반드시 해줘야함..
int x;
int y;
double rad;
public Circle(int x, int y, double r) {
this.x=x;
this.y=y;
this.rad=r;
}
public void showCircleInfo() {
System.out.printf("[%d,%d]", x,y);
System.out.println("rad : " + rad);
}
}
package io;
import java.io.Serializable;
public class Circle2 implements Serializable {
// implements Serializable : 직렬화의 대상이다. 인스턴스 저장이 가능하다.
// 직렬화를 하기 위해서는 반드시 해줘야함..
// transient Point p; 직렬화대상에서 제외
Point p;
double rad;
public Circle2(int x, int y, double r) {
p = new Point(x, y);
this.rad = r;
}
public void showCircleInfo() {
System.out.println("rad : " + rad);
}
}
package io;
import java.io.Serializable;
public class Point implements Serializable {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectSerializable {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// 인스턴스 저장을 위한 스트림 생성
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Object.ser"));
// 인스턴스를 저장
out.writeObject(new Circle(1,1,2.4));
out.writeObject(new Circle(2,2,4.8));
out.writeObject(new String("String class implements Serializable"));
// String 클래스도 직렬화 가능
out.writeObject(new Circle2(3,3,6.9));
out.close(); // 저장~!
// 인스턴스 복원
ObjectInputStream in = new ObjectInputStream(new FileInputStream("Object.ser"));
// Circle c1 = (Circle) in.readObject(); // 형변환
Object o1 = in.readObject();
Circle c1 =null;
if(o1 instanceof Circle) {
c1 = (Circle)o1;
}
Circle c2 = (Circle) in.readObject();
String str= (String) in.readObject();
Object o2 = in.readObject();
Circle2 c3=null;
if(o2 instanceof Circle2) {
c3= (Circle2)o2;
}
// 복원된 인스턴스 출력
c1.showCircleInfo();
c2.showCircleInfo();
System.out.println(str);
c3.showCircleInfo();
}
}
예제2)
package io;
import java.beans.Transient;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class PersonInfo implements Serializable {
String name;
transient String secretInfo;
int age;
transient int secretNum;
public PersonInfo(String name, String secretInfo, int age, int secretNum) {
this.name = name;
this.secretInfo = secretInfo;
this.age = age;
this.secretNum = secretNum;
}
public void showInfo() {
System.out.println("name : " + name);
System.out.println("secretInfo : " + secretInfo);
System.out.println("age : " + age);
System.out.println("secretNum : " + secretNum);
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// 인스턴스 저장 스트림 생성
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("PersonInfo.ser"));
PersonInfo info = new PersonInfo("김태형", "뽀선이애인", 24, 1);
info.showInfo();
// 인스턴스 저장
out.writeObject(info);
out.close();
// 인스턴스 복원을 위한 스트림 생성
ObjectInputStream in = new ObjectInputStream(new FileInputStream("PersonInfo.ser"));
// 복원
PersonInfo reInfo = (PersonInfo) in.readObject();
// 복원된 인스턴스의 정보 출력
reInfo.showInfo();
}
}