[Python] 17. [Network] threading module, 채팅 서버/클라이언트 제작

2017. 8. 2. 17:38·빅데이터 프로그래밍/Python
728x90
반응형

01.  threading module
- thread란 하나의 프로세스내에서 진행되는 하나의 실행단위를 뜻하며, 
  하나의 프로세스에서 여러 실행단위가 실행되는것을 멀티스레드라고 한다. 
  프로세스와 스레드는 모두 프로그램을 수행된다는 공통점을 가지고 있지만, 
  프로세스는 윈도우에서 여러 응용프로그램을 각각 실행시키는것처럼 독립적으로 실행되어
  독립된 메모리공간을 사용하지만, 멀티스레드는 하나의 프로세스내에서 여러 스레드들이
  프로세스공간의 메모리를 공유하여 사용할수 있다.
  파이썬에서는 threading이라는 멀티스레드 기능을 지원하는 모듈을 제공한다.
  threading모듈에서 자주 사용되는 객체를 살펴보고, 간단한 예제를 통해 threading모듈을 알아보자
 

1. Thread 객체
  - 스레드를 사용하기 위해서는 일반적으로 threading.Thread를 상속받은 클래스객체를
    생성하여 사용하며, 아래 나열된 메서드들을 주로 이용한다. 
    생성자를 재정의하는 경우 반드시 Thread.__init__()을 수행해야 한다.

1) Thread.start(): 
   스레드를 시작할때 사용함
 
2) Thread.run(): 스레드의 주요 동작을 정의함, 개발자가 주로 작업하는 함수
 
3) Thread.join([timeout]): 스레드가 종료되기를 기다린다.
   timeout이 정의된경우, 최대 정의된 시간(초)만큼 기다린다.

 

2.  Lock 객체
- 하나의 프로세스에서 2개 이상의 스레드가 동시에 수행되는 경우,
  스레드간에 프로세스 공간의 메모리를 공유한다. 
  만약 하나의 변수에 대해서 2개이상의 스레드가 변경을 가하고자 한다면, 
  어떤 스레드가 먼저 수행되느냐에 따라 결과가 달라질수 있다. 
  이러한 상태를 경쟁상태(race condition)라고 한다. 
  이러한 상황을 방지하기 위해서 파이썬에서는 Lock이라는 동기화 객체를 지원한다.
 
- Lock객체는 locked와 unlocked의 2가지 상태를 가지며, acquire()(잠금 걸기)와 release()(잠금 해제)의
  두가지 함수만을 제공한다.
  unlocked상태에서 acquire()(잠금 걸기)가 호출되면 locked상태로 바뀌고
  locked상태에서 release()(잠금 해제)가 호출되면 unlocked상태로 바뀌게 된다.
  locked상태에서 다른스레드가 acquire()를 호출하면 locked상태의 스레드가
  release()할때까지 멈추게 된다.
 
  
3. 동기화 실습
- acquire()(잠금 걸기)와 release()(잠금 해제) 메소드를 주석 처리하고 실행을 비교할 것.

[실행 화면]
- Thread 처리를 하지않은 경우

> name=usr1
count: 9
> name=usr2
count: 8
> name=usr3
count: 7
> name=usr3
> name=usr2
> name=usr1
count: 6
count: 5
count: 4
> name=usr1
> name=usr3
> name=usr2
count: 3
count: 2
count: 1
> name=usr3
> name=usr1
> name=usr2
count: 0
count: -1
count: -2
usr1 fixed 4
usr2 fixed 4
usr3 fixed 4
 

- Thread 처리를 한경우

> name=usr1
count: 9
> name=usr1
count: 8
> name=usr2
count: 7
> name=usr1
count: 6
> name=usr3
count: 5
> name=usr1
count: 4
> name=usr3
count: 3
> name=usr2
count: 2
> name=usr1
count: 1
> name=usr3
count: 0
usr1 fixed 5
usr2 fixed 2
usr3 fixed 3
 
▷ network.Thread1.py
-----------------------------------------------------------------------------------
# -*- coding: utf-8 -*-

from threading import Thread, Lock
import time

count=10
lock=Lock()

class Developer(Thread): # Thread 상속
    def __init__(self, name): # 생성자
        Thread.__init__(self)  # 부모 클래스의 생성자 호출
        self.name=name     # instance 변수
        self.fixed=0            # instance 변수 

    def run(self):             # 지정된 메소드 형식, start()에 호출됨.
        global count         # class 밖의 전역 변수 접근
        while 1:                # 무한 루틴
            lock.acquire()    # 잠금 얻기
            if count > 0:
                print ('> name=%s' % self.getName()) # self.getName은 Thread 이름 반환
                count -= 1       # 전역 변수 접근
                print('count: ' + str(count))
                
                lock.release()  # 잠금 해제
                self.fixed += 1  # 객체 멤버 변수, instance, attribute
                time.sleep(1)  # 1: 1초
            else:
                lock.release()   # 잠금 해제
                break  # while 문 종료 -> run() 메소드 종료

dev_list=[]  # list 선언, 사용자 저장
for name in ['usr1','usr2','usr3']:
    dev=Developer(name)
    dev_list.append(dev)  # Developer class의 객체를 list에 추가
    dev.start()    # 스레드 호출, run() 호출

for dev in dev_list:
    dev.join()     # 스레드가 모두 종료될때까지 대기  
    print(dev.name, 'fixed', dev.fixed) # 가변인자로 출력
    
    
    
-----------------------------------------------------------------------------------
 

 

02. 채팅 서버/클라이언트 제작

1. Server
[실행 화면]

C:/CD 201705_python/ws_python/oop/network
python ChatServer1.py
▷ network.ChatServer1.py
-----------------------------------------------------------------------------------
# -*- coding: utf-8 -*-

import socketserver, sys
import threading

lock = threading.Lock()

class UserManager:
    def __init__(self):
        # Dictionary, 변경 가능, 키, 값
        '''
        {"왕눈이":(conection 객체, "172.16.3.1"),
         "아로미":(conection 객체, "172.16.3.2"),
         "가길동":(conection 객체, "172.16.3.3"),
        }
        '''  
        self.users = {} 

    def addUser(self, username, conn, addr):
        if username in self.users:
            # 현재 접속자에게 문자열 송신
            conn.send('이미 등록된 사용자입니다.\n'.encode())
            return None

        # 새로운 사용자를 등록함
        lock.acquire()  # Lock
        self.users[username] = (conn, addr) # Connection, IP tuple 저장
        lock.release()   # Unlock

        # 모든 사용자에게 메시지 전송
        self.sendMessageToAll('[%s]님이 입장했습니다.' %username)
        print('▷ 대화 참여자 수 [%d]' %len(self.users))
        
        return username

    def removeUser(self, username):
        # 삭제하려는 ID가 없으면 아무일도 안함.
        if username not in self.users:
            return

        lock.acquire()  # Lock
        del self.users[username] # Dictionary에서 아이디에 해당하는 항목 삭제
        lock.release()   # Unlock

        self.sendMessageToAll('[%s]님이 퇴장했습니다.' %username)
        print('▷ 대화 참여자 수 [%d]' %len(self.users))

    def messageHandler(self, username, msg):
        if msg.strip() == '/exit':            # 접속자 접속 해제
            self.removeUser(username)  # 사용자 삭제
            return -1
        
        self.sendMessageToAll('[%s] %s' %(username, msg))


    def sendMessageToAll(self, msg):
        # Dictionary 값 2개 추출: Connection, IP로 구성된 tuple
        for conn, addr in self.users.values(): 
            conn.send(msg.encode()) # 각각의 사용자에게 메시지 전송
          

class MyTcpHandler(socketserver.BaseRequestHandler):  # 한번만 객체 생성됨
    userManager = UserManager()  # 사용자 관리 객체 생성
    
    def handle(self):   # 사용자 접속시마다 계속 자동 실행     
        print('▷ [%s] 연결됨' %self.client_address[0])

        try:
            username = self.registerUsername() # 사용자 id 처리
            msg = self.request.recv(1024) # 접속된 사용자로부터 입력대기
            while msg:
                print(msg.decode()) # 서버 화면에 출력
                if self.userManager.messageHandler(username, msg.decode()) == -1:
                    self.request.close() # Connection close
                    break   # 메시지 수신 대기 종료
                msg = self.request.recv(1024) # 메시지 수신 대기
                
        except Exception as e:
            print(e)

        print('▷ [%s] 접속종료' %self.client_address[0])
        self.userManager.removeUser(username)

    def registerUsername(self):  # 접속자의 이름 받기
        while True:
            self.request.send('로그인ID: '.encode()) # 신규 현재 접속자에게 전송
            username = self.request.recv(1024)     # 수신 대기
            username = username.decode().strip()  # strip(): 공백 제거
            if self.userManager.addUser(username, self.request, self.client_address):
                return username

# socketserver.ThreadingMixIn: 독립된 스레드로 처리하도록 접속시 마다 새로운 스레드 생성
# ThreadingMixIn, TCPServer class 상속
class ChatingServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass
        
def runServer(ip):
    print('▷ 채팅 서버를 시작합니다.')
    print('▷ 채텅 서버를 끝내려면 Ctrl-C를 누르세요.')

    try:
        server = ChatingServer((ip, 9009), MyTcpHandler)
        server.serve_forever()          # 무한 실행
    except KeyboardInterrupt:       # Ctrl + C 입력시 종료
        print('▷ 채팅 서버를 종료합니다.')
        server.shutdown()               # 서버 종료
        server.server_close()            # 메모리 해제

''' 
C:
CD 201705_python/ws_python/oop/network
python ChatServer1.py 172.16.7.100
'''
runServer(sys.argv[1])


-----------------------------------------------------------------------------------

 

2. Client
[실행 화면]

C:/CD 201705_python/ws_python/oop/network
python ChatClient1.py 172.16.7.100 9009
▷ network.ChatClient1.py
-----------------------------------------------------------------------------------
# -*- coding: utf-8 -*-

import sys, socket
from threading import Thread # Thread() 함수 import

def rcvMsg(sock):
    while True:   # 연속성 스레드
        try:
            data = sock.recv(1024)  # 서버로부터 문자열 수신
            if not data:  # 문자열 없으면 종료
                break
            print(data.decode())       
        except:
            pass  # 예외 원인 무시

def runChat(host, port):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((host, port))  # 소켓 연결
        # rcvMsg 함수를 연속 실행하는 스레드 생성,
        # sock 소켓 객체를 tuple 형식으로 전달
        t = Thread(target=rcvMsg, args=(sock,))  
        t.daemon = True # 스레드를 생성한 메인 스레드가 종료되면 자동으로 종료됨.
        t.start()               # 스레드 시작

        while True:
            msg = input()        # 키보드 입력
            if msg == '/exit':    # 종료
                sock.send(msg.encode())
                break               # while 종료

            sock.send(msg.encode()) # 입력 메시지 전송

''' 
C:
CD 201705_python/ws_python/oop/network
python ChatClient1.py 172.16.7.100 9009 
'''            
runChat(sys.argv[1], int(sys.argv[2]))



-----------------------------------------------------------------------------------
 

728x90
반응형

'빅데이터 프로그래밍 > Python' 카테고리의 다른 글

[Python] 19. [GUI] wxPython 그래픽 사용자 인터페이스, 다양한 Widget, Menu  (0) 2017.08.02
[Python] 18. [GUI] wxPython 그래픽 사용자 인터페이스, 기본 Widget, Sizer  (0) 2017.08.02
[Python] 16. [Network] socketserver 네트워크 프레임워크, Echo Server 2, 파일 송수신  (0) 2017.08.02
[Python] 15. [Network] Network(네트워크), Socket, Echo Server 1  (0) 2017.08.02
[Python] 14. [IO] 데이터 파일을 읽어 성적, 연봉, 단풍 여행 처리 시스템을 작성하세요.  (0) 2017.08.02
'빅데이터 프로그래밍/Python' 카테고리의 다른 글
  • [Python] 19. [GUI] wxPython 그래픽 사용자 인터페이스, 다양한 Widget, Menu
  • [Python] 18. [GUI] wxPython 그래픽 사용자 인터페이스, 기본 Widget, Sizer
  • [Python] 16. [Network] socketserver 네트워크 프레임워크, Echo Server 2, 파일 송수신
  • [Python] 15. [Network] Network(네트워크), Socket, Echo Server 1
밍글링글링
밍글링글링
mingling - 밍글링, 밍글밍글링. 코드와 어우러지다. IT/ 프로그래밍/소스
    반응형
    250x250
  • 밍글링글링
    mingling
    밍글링글링
  • 전체
    오늘
    어제
    • 밍글링글링 (407) N
      • Flutter (2)
      • 일상생활 (8)
        • 리뷰 (1)
        • 생활정보 (4)
        • 맛집 (0)
        • 여행 (0)
        • 모든정보 (3)
      • JAVA (126)
        • 개념 (6)
        • 예제 (115)
        • Exception (2)
      • C (1)
        • C (1)
        • C++ (0)
        • C# (0)
      • JS (29)
        • JavaScript (18)
        • JQuery (5)
        • AJax (0)
        • NODE.JS (6)
        • Angular.JS 2.0 (0)
      • WEB (87)
        • HTML (6)
        • CSS (61)
        • JSP (20)
        • JSTL (0)
      • FrameWork (8)
        • Spring (8)
        • BootStrap (0)
        • MyBATIS (0)
        • JUnit (0)
      • 외부 라이브러리 (5)
      • 공유 소스 관리 (5)
        • Git (5)
        • SVN (0)
      • 빅데이터 프로그래밍 (37)
        • Python (37)
        • R Programming (0)
      • DB (7)
        • ORACLE (0)
        • MySql (6)
      • Development Tools (7)
        • StarUML (0)
        • eXERD (0)
        • Eclipse (4)
      • SKILL (6)
        • Migration (0)
        • Security (6)
      • MicroSoft (0)
        • Excel (0)
        • Word (0)
      • Android (0)
      • Server (21)
        • Ubuntu (5)
        • Linux (15)
      • IOS (0)
      • XML (0)
      • 미디어 (0)
      • 공지사항 (3)
      • NETWORK (1)
      • 게임 (4)
        • 피파 (1)
        • 리니지M (0)
        • 배틀그라운드 (1)
        • 듀랑고 (2)
      • 세상 이슈 (6)
      • 일렉트론 (0)
      • 대회 소식 (4)
      • 업무 (2)
      • Express, Vue (6)
      • docker (11)
      • svelte (3)
      • 블록체인 (1)
      • IT (10) N
      • Rust (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    자바 클래스
    자바 for문
    jsp include
    lang rust
    java casting
    AI 코딩 에이전트
    css table
    Extension Bisect
    svelte
    리눅스 설치
    vue cli
    css transition
    클론코딩
    nginx
    nginx ssl 적용
    자바 exception
    nginx ssl 설정
    Java Array
    프런트엔드
    servlet class
    rust linux
    API 설계
    proxy pass
    css float
    VS Code 팁
    spring java
    티스토리 자동화
    Node
    React Compiler
    자바 배열
    docker
    에디터 팁
    vscode
    css tb
    css perspective
    ubuntu
    브라우저 자동화
    SSL 인증서
    Rust lang
    vue 설치
    오류
    css block
    자바 생성자
    mysql db
    러스트
    ssl 인증서 발급
    css list
    gitlab 설치
    자바 객체 지향
    jsp parameter
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
밍글링글링
[Python] 17. [Network] threading module, 채팅 서버/클라이언트 제작
상단으로

티스토리툴바