05-16 05:32
Recent Posts
Recent Comments
관리 메뉴

miinsun

[토탈 솜루션]Java와 Python로 간단한 서버, 클라이언트 구현하기 본문

Project/2020 토탈솜루션

[토탈 솜루션]Java와 Python로 간단한 서버, 클라이언트 구현하기

miinsun 2021. 11. 25. 14:04

 

오랜만에 네트워크를 다시 접하니 네트워크의 기본 개념조차 다 잊어버렸다. java와 python으로 서버와 네트워크를 구현해보기에 앞서 다시 한번 TCP와 UDP의 개념을 잡고 가야할 거 같다


 

TCP와 UDP는 인터넷 상에서 데이터를 메세지의 형태로 보내기 위해 IP와 함께 사용되는 프로토콜이다.

간단하게 요약하자면, TCP(Transmission Control Protocol)는 연결형 서비스로 UDP에 비해 높은 신뢰성을 보장하며, 데이터의 흐름이 연속적이다. TCP는 데이터의 흐름제어나 혼잡제어와 같은 기능도 하기 때문에 UDP보다 속도가 느리다는 단점이 있다.

TCP서버는 클라이언트와 1대 1로 연결되며, 스트림 전송으로 전송 데이터의 크기에 제한이 없다. 또, 패킷에 대한 응답을 해야하기 때문에 성능이 좋지 않다.

UDP(User Datagram Protocol)는 TCP와 달리 비연결형 프로토콜이다. 즉, 연결을 위해 할당되는 할당되는 경로가 없어, 각각의 패킷은 제각각의 경로로 수신된다. UDP는 수신자가 패킷을 전달 받았는지에 대한 유무를 알 수 없고, 중요하지도 않다. 그래서 TCP에 비해 신뢰성이 낮고, 네트워크 부하가 적어 속도가 빠르다. 이런 특징을 가진 UDP는 주로 실시간 스트리밍 서비스에 사용된다.

 

UDP서버는 클라이언트와 1대 1, 1대 N, N대 N등의 관계로 연결될 수 있고, 전송되는 데이터의 순서가 바뀔 수도 있다. 또, 클라이언트 서버 간에 연결이 없어 소켓의 구분이 없다.

서버 클라이언트 구조

 


 

TCP 소켓 호출 순서와 데이터 흐름은 다음과 같다.

 

출처: webnautes.tistory.com

크게 몇가지 흐름을 정리해보았다.

> 서버 측

서버는 리스닝 소켓을 만들어, 클라이언트의 응답을 기다린다.
서버와 클라이언트가 연결되면 accept()에서 새로운 소켓을 리턴받아 클라이언트와의 통신에서 사용한다.

> 클라이언트 측

서버는 리스닝 소켓을 만들어, 클라이언트의 응답을 기다린다.서버와 클라이언트가 연결되면 accept()에서 새로운 소켓을 리턴받아 클라이언트와의 통신에서 사용한다.

 

버와 클라이언트의 연결이 완료되면, send, recv 함수를 호출하여 데이터를 주고 받는다. 클라이언트가 소켓을 닫아 연결을 종료하면, 서버도 클라이언트와 사용하였던 소켓을 닫는다.


 

Python으로 작성된 코드 (아래 블로그 참고)

> python_server code

import socket

# 접속할 서버 주소, 변경 가능하다.
HOST = '127.0.0.1'
# 클라이언트 접속을 대기하는 포트번호, 이 역시 변경 가능하다.
PORT = 9999        
 
#소켓 객체 생성, 소켓 타입으로 TCP프로토콜을 사용
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

#소켓을 특정 네트워크 인터페이스와 포트 번호에 연결
server_socket.bind((HOST, PORT))

#서버가 클라이언트의 접속을 허용하도록 함
server_socket.listen()

#서버는 accept 함수에서 대기하다가 클라이언트의 응답이 오면 새로운 소켓을 리턴
client_socket, addr = server_socket.accept()

#접속된 클라이언트의 주소를 출력
print('Connected by', addr)

while True:
    #클라이언트의 응답을 기다린다.
    data = client_socket.recv(1024)
    
    #빈 문자열이면 무한루프 종료
    if not data:
        break
        
    #클라이언트가 보낸 문자열 출력
    print('Received from', addr, data.decode())
   
    #받은 문자열을 다시 클라이언트에 전송(에코)
    client_socket.sendall(data)

#클라이언트, 서버 소켓을 닫는다.
client_socket.close()
server_socket.close()

 

> python_client code

import socket
from test.test_wsgiref import hello_app

#연결할 Host, Port 정보
HOST = '127.0.0.1'  
PORT = 9999       

#소켓 생성
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#서버에 접속
client_socket.connect((HOST, PORT))

#서버에 "Hello world"메세지 전송
client_socket.sendall("Hello world".encode()); 

#서버에게서 메시지를 수신(에코)
data = client_socket.recv(1024)
print('Received', repr(data.decode()))

#클라이언트 소켓을 닫는다.
client_socket.close()

 

server 코드를 먼저 실행해주고 client 코드를 실행해준다.

python 코드 실행 결과

 


 

Python으로 작성된 코드 (아래 블로그 참고)

우리 프로젝트는 Java언어로 진행할거라서, 위에 python 예제를 Java로 대체해봤다.

> java_server code

package minsun;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;


public class Java_server {
	//메인 인스턴스 생성
    public static void main(String[] args ) {
        try {
        	
            //서버 소켓 생성
            ServerSocket serverSocket = new ServerSocket(9000);

            //서버는 accept 함수에서 대기하다가 client 접속 accept
            Socket socket = serverSocket.accept();
            
            //접속된 클라이언트의 주소를 출력
            System.out.println("Connected by ( " + socket.getLocalAddress()+ ", "+ socket.getLocalPort()+ ") ");
            
            //client가 보낸 메세지 출력
            //버퍼를 사용하면, 입출력의 효율이 좋아진다.
            //상대방이 보낸 버퍼를 읽으려면 socket에 기능중 Stream을 불러와 InputStreamReader 객체에 담는다.
            BufferedReader bufReader = new BufferedReader( new InputStreamReader( socket.getInputStream()));
            String message = bufReader.readLine();
            System.out.println("Message : " + message );
          
            //client에 데이터 전송
            BufferedWriter bufWriter = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream()));
           
            //받은 문자열을 다시 클라이언트에 전송(에코)
            bufWriter.write(message);
            //버퍼 개행
            bufWriter.newLine();
            //버퍼에 남은 데이터 출력
            bufWriter.flush();
            
            //클라이언트, 서버 소켓을 닫는다.
            socket.close();
            serverSocket.close();
            bufReader.close();
            bufWriter.close();
          }

        //예외처리
          catch( Exception e ) {
            e.printStackTrace();
          }
     }
}

 

> java_client code

package minsun;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class Java_client {
    public static void main(String[] args ) {
        try {
            //서버 접속, localhost 9000번 포트에 접속
            Socket socket = new Socket("127.0.0.1", 9000);

            //Server에 보낼 데이터를 쓴다.
            //버퍼를 사용하면, 입출력의 효율이 좋아진다.
            //socket에 기능중 Stream을 불러와 outputStreamWriter 객체에 담는다.
            BufferedWriter bufWriter = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream()));
            //버퍼 출력
            bufWriter.write("hello world");
            //버퍼 개행
            bufWriter.newLine();
            //남아 있는 데이터 모두 출력
            bufWriter.flush();

            //Server가 보낸 데이터 출력
            BufferedReader bufReader = new BufferedReader( new InputStreamReader( socket.getInputStream()));
            String message = bufReader.readLine();
            System.out.println("Message : " + message );

            socket.close();
            bufReader.close();
            bufWriter.close();
            }
        
        //예외처리
          catch( Exception e ){
            e.printStackTrace();
          }
    }
}

 

python 코드와 마찬가지로 server 코드부터 실행 후, client 코드를 실행해주자.

java 코드실행 결과 값

 

 

- 참고
멘토님께서 해당 블로그를 참고하여 서버, 클라이언트를 만들어 보라고 하셨다!
해당 블로그의 파이썬 코드를 참고해서 작성해보았다
https://webnautes.tistory.com/1381

 

Comments