# 서버코드
import socket
import argparse
import threading
import time
user_list = {}
def handle_receive(client_socket, addr, user):
while 1:
data = client_socket.recv(1024)
data = data.decode()
if data == "/종료" or data == "/exit" : break
print(user,data)
#받은 데이터를 모든 클라이언트에게 전송한다.
for con in user_list.values():
try:
con.sendall(data.encode())
except:
print("클라이언트 소켓 에러")
del user_list[user]
client_socket.close()
if __name__ == '__main__':
host = "host_ip" #127.0.0.1
port = 4000
#IPv4 체계, TCP 타입 소켓 객체를 생성
# socket.AF_INET: 해당 소켓을 IP version 4 용으로 사용하겠다는 의미
# socket.SOCK_STREAM: 해당 소켓에 TCP 패킷을 받겠다는 의미
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#포트를 사용 중 일때 에러를 해결하기 위한 구문
#기본적으로 socket을 사용하고 나면 사용한 port는 close로 닫아주는게 맞다.
#그래야 해당 port를 다른데서도 사용할 수 있으니깐.
#근데 port를 닫았다고해도 일정시간 동안은 TIME_WAIT 상태로 대기한다고 한다.
#그리고 이 상태에서는 즉각적으로 다시 바로 사용할 수 없다.
#그래서 socket을 쓰고 close를 했는데도 already in use 에러가 발생하게된다.
#이런 에러를 막기위해서 setsockopt을 사용한다. 사용법은 아래와 같다.
#socket.SO_REUSEADDR: 방금 사용하고 close한 port를 즉시 다시 사용한다는 의미
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#ip주소와 port번호를 함께 socket에 바인드 한다.
#포트의 범위: 1~65535
server_socket.bind((host, port))
server_socket.listen(5)# 클라이언트 최대 접속 수
print('채팅서버를 시작합니다.')
while 1:
try:
#클라이언트 함수가 접속하면 새로운 소켓을 반환한다.
client_socket, addr = server_socket.accept()#클라이언트 접속까지 대기함
except:
#의도하지 않은 서버 종료에 대한 예외 처리
#프로그램 중단시 모든 클라이언트 소켓들 닫기.
for user, con in user_list:
con.close()
server_socket.close()
break
user = client_socket.recv(1024)
user = user.decode()
print(user,'서버에 접속하였습니다.')
user_list[user] = client_socket
#daemon 속성은 서브쓰레드가 데몬 쓰레드인지 아닌지를 지정하는 것인데,
#데몬 쓰레드란 백그라운드에서 실행되는 쓰레드로 메인 쓰레드가 종료되면 즉시 종료되는 쓰레드이다.
#반면 데몬 쓰레드가 아니면 해당 서브쓰레드는 메인 쓰레드가 종료할 지라도 자신의 작업이 끝날 때까지 계속 실행된다.
receive_thread = threading.Thread(target=handle_receive, args=(client_socket, addr,user))
receive_thread.daemon = True
receive_thread.start()
import socket
import threading
def handle_receive(client_socket, user):
while True:
try:
data = client_socket.recv(1024)#데이터가 수신될 때까지 대기함
except:
print("연결 끊김")
break
data = data.decode()#바이트 타입의 데이터를 decode()로 문자열로 변환
if not user in data:#자신의 데이터를 제외한 나머지 데이터를 출력함
print(data)
def handle_send(client_socket, user):
while True:
data = input()#사용자로부터 문자열을 입력받는 함수
#데이터는 encode()함수를 이용하여 바이트형으로 변환
client_socket.sendall(data.encode())#소켓을 통해 서버로 데이터 보내기
if data == "/종료" or data == "/exit": break
client_socket.close()
print('소켓 닫기')
if __name__ == '__main__':
port = 4000 # 포트넘버
host = 'server_ip' #서버 IP #127.0.0.1
user = 'user_id'
#IPv4 체계, TCP 타입 소켓 객체를 생성
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 지정한 host와 prot를 통해 서버에 접속합니다.
client_socket.connect((host, port))
# 문자열을 전송할 때 encode()을 이용한다. 파이썬 문자열의 encode() 메소드는 문자열을 byte로 변환해주는 메소드이기 때문이다.
# 파이썬 내부에서 다뤄지는 문자열은 파이썬에서 생성된 객체이고, 이를 바로 트랜스포트에 그대로 싣는 것은 불가능합니다.
# 그러므로 적절한 인코딩을 하여 보내야만 합니다.
# 만약 encode()을 사용하지 않을 경우 에러가 발생 한다.
client_socket.sendall(user.encode())
receive_thread = threading.Thread(target=handle_receive, args=(client_socket, user))
receive_thread.daemon = True
receive_thread.start()
send_thread = threading.Thread(target=handle_send, args=(client_socket, user))
send_thread.daemon = True
send_thread.start()
#join()은 부모쓰레드가 자식 쓰레드가 종료될 때까지 기다려준다.
#즉 전체 프로그램의 실행이 하나의 쓰레드이고 부모쓰레드가 되며,
#먼저 send_thread, receive_thread가 종료된후 프로그램이 종료하게 된다.
send_thread.join()
receive_thread.join()