프로그램이 네트워크에서 데이터를 송수신할 수 있도록, "네트워크 환경에 연결할 수 있게 만들어진 연결부"가 바로
"네트워크 소켓(Socket)"이다.

그런데 네트워크에 연결하기 위한 소켓 또한 정해진 규약,
즉, 통신을 위한 프로토콜(Protocol)에 맞게 만들어져야 한다. 
보통은 OSI 7 Layer(Open System Interconnection 7 Layer)의 네 번째 계층인 TCP(Transport Control Protocol) 상에서 동작하는 소켓을 주로 사용하는데, 이를 "TCP 소켓" 또는 "TCP/IP 소켓"이라고 부른다.

소켓 생성
서버 측에 연결 요청
서버 소켓에서 ok 하면 데이터를 송수신
끝나면 close
1) 클라이언트 소켓 생성을 통한 서버접속
Socket socket = new Socket("127.0.0.1", 8000);  // IP주소, 포트번호
2) 데이터 송수신을 위한 input/output 스트림 생성
InputStream in = socket.getInputStream( );
OutputStream out = socket.getOutputStream( );
3) output 스트림을 통한 데이터 송신 (클라이언트 → 서버)
String outputMessage = "보낼메시지";
out.write(outputMessage.getBytes( ));
out.flush( );
4) input 스트림을 통한 데이터 수신 (서버 → 클라이언트)
byte[ ] inputData = new byte[100];
int length = in.read(inputData);
String inputMessage = new String(inputData, 0, length);
5) 통신 종료
socket.close( );
소켓 생성
생성한 소켓에 서버 ip, port 번호 바인딩 (정보 입력)
클라이언트로부터 연결 요청이 들어오는지 listen
요청이 성공적으로 수신되면 accept 후 데이터 통신을 위한 소켓을 별도로 생성
일단 새로운 소켓을 통해 연결이 수립(ESTABLISHED)되면, 클라이언트와 마찬가지로 데이터 송수신(send/recv) 가능
데이터 송수신이 끝나면 소켓 close
1) 서버소켓 생성
ServerSocket serverSocket = new ServerSocket(8000);  // 포트번호
2) 클라이언트 접속 대기
Socket socket = serverSocket.accept( );
3) 데이터 송수신을 위한 input/output 스트림 생성
InputStream in = socket.getInputStream( );
OutputStream out = socket.getOutputStream( );
4) input 스트림을 통한 데이터 수신 (클라이언트 → 서버)
byte[ ] inputData = new byte[100];
int length = in.read(inputData);
String inputMessage = new String(inputData, 0, length);
5) output 스트림을 통한 데이터 송신 (서버 → 클라이언트)
String outputMessage = "보낼메시지";
out.write(outputMessage.getBytes( ));
out.flush( );
6) 통신 종료
socket.close( );
serverSocket.close( );
데이터 전송의 기본단위는 바이트(byte)로 문자의 경우 데이터 전송시 문자를 바이트로, 수신시 바이트를 문자로 변환이 필요하다. 이를 위해서 앞에서는 String클래스의 생성자와 getBytes메소드를 이용했다.
자바에는 이 변환을 편리하게 해주는 클래스로 InputStreamReader와 OutputStreamWriter가 있으며 charset을 지정하여 사용할 수 있다. 또한 데이터 입출력의 효율을 위해 바로 전달하지 않고 중간에 버퍼를 이용하기 위해서 BufferedReader와 BufferedWriter 클래스를 함께 사용할 수 있다.
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream( )));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream( )));
데이터 출력의 경우에는 출력 포맷을 편리하게 해주는 기능이 있는 PrintWriter 또는 PrintStream 클래스를 사용할 수 있다.
PrintWriter out = new PrintWriter(socket.getOutputStream( ));
PrintStream out = new PrintStream(socket.getOutputStream( ));
public class MultiServer {
	
	public static void main(String[] args) {
		MultiServer multiServer = new MultiServer();
		multiServer.start();
	}
	
	public void start() {
		ServerSocket serverSocket = null;
		Socket socket = null;
		try {
			serverSocket = new ServerSocket(8000);
			while (true) {
				System.out.println("[클라이언트 연결대기중]");
				socket = serverSocket.accept();
				
				// client가 접속할때마다 새로운 스레드 생성
				ReceiveThread receiveThread = new ReceiveThread(socket);	
				receiveThread.start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (serverSocket!=null) {
				try {
					serverSocket.close();
					System.out.println("[서버종료]");
				} catch (IOException e) {
					e.printStackTrace();
					System.out.println("[서버소켓통신에러]");
				}
			}
		}
	}
}
class ReceiveThread extends Thread {
	
	static List<PrintWriter> list = 
			Collections.synchronizedList(new ArrayList<PrintWriter>());
	
	Socket socket = null;
	BufferedReader in = null;
	PrintWriter out = null;
			
	public ReceiveThread (Socket socket) {
		this.socket = socket;
		try {
			out = new PrintWriter(socket.getOutputStream());
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			list.add(out);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
		
	@Override
	public void run() {
		String name = "";
		try {
			// 최초1회는 client이름을 수신
			name = in.readLine();
			System.out.println("[" + name + " 새연결생성]");	
			sendAll("[" + name + "]님이 들어오셨습니다.");
			
			while (in != null) {
				String inputMsg = in.readLine();
				if("quit".equals(inputMsg)) break;
				sendAll(name + ">>" + inputMsg);
			}
		} catch (IOException e) {
			System.out.println("[" + name + " 접속끊김]");
		} finally {
			sendAll("[" + name + "]님이 나가셨습니다");
			list.remove(out);
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		System.out.println("[" + name + " 연결종료]");
	}
	
	private void sendAll (String s) {
		for (PrintWriter out: list) {
			out.println(s);
			out.flush();
		}
	}
}
public class MultiClient {
	
	public static void main(String[] args) {
		MultiClient multiClient = new MultiClient();
		multiClient.start();
	}
	
	public void start() {
		Socket socket = null;
		BufferedReader in = null;
		try {
			socket = new Socket("localhost", 8000);
			System.out.println("[서버와 연결되었습니다]");
			String name = "user" + (int)(Math.random()*10);
			Thread sendThread = new SendThread(socket, name);
			sendThread.start();
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			while (in != null) {
				String inputMsg = in.readLine();
				if(("[" + name + "]님이 나가셨습니다").equals(inputMsg)) break;
				System.out.println("From:" + inputMsg);
			}
		} catch (IOException e) {
			System.out.println("[서버 접속끊김]");
		} finally {
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		System.out.println("[서버 연결종료]");
	}
}
class SendThread extends Thread {
	Socket socket = null;
	String name;
	Scanner scanner = new Scanner(System.in);
	
	public SendThread(Socket socket, String name) {
		this.socket = socket;
		this.name = name;
	}
	@Override
	public void run() {
 		try {
			// 최초1회는 client의 name을 서버에 전송
			PrintStream out = new PrintStream(socket.getOutputStream());
			out.println(name);
			out.flush();
			
			while (true) {
				String outputMsg = scanner.nextLine();
				out.println(outputMsg);
				out.flush();
				if("quit".equals(outputMsg)) break;
			}
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}
}