메뉴 건너뛰기

창작에 관련된 질문이나 간단한 팁, 예제를 올리는 곳

서버: Lua

클라이언트: Love2d(Lua)

 

서버 구동에는 luasocket 라이브러리가 필요하며, luarocks로 설치할 수 있음.

별도 패키지 관리자가 있는 리눅스 시스템에서는 apt-get install lua-socket 등의 패키지 관리자 명령어로 설치 가능하며

Windows에서 구동시에는 Lua for windows에 기본적으로 luasocket 라이브러리가 탑재되어 있기 때문에 별도로 설치할 필요가 없다.

 

서버측:

socket = require("socket")

 

function newset()

    local reverse = {}

    local set = {}

    return setmetatable(set, {__index = {

        insert = function(set, value)

            if not reverse[value] then

                table.insert(set, value)

                reverse[value] = table.getn(set)

            end

        end,

        remove = function(set, value)

            local index = reverse[value]

            if index then

                reverse[value] = nil

                local top = table.remove(set)

                if top ~= value then

                    reverse[top] = index

                    set[index] = top

                end

            end

        end

    }})

end

 

-- set socket

server = socket.bind("*", 12345)

server:settimeout(0)

 

-- create new set

set = newset()

 

while true do

local client = server:accept()

-- accept new connection

if client ~= nil then

set:insert(client)

print("New client connected!! ".. client:getpeername())

client:send("Hello!\n")

end

 

-- recieve from client

for a = 1, #set do

local readdata, err = set[a]:receive()

if err then

print("Client disconnected: " .. set[a]:getpeername() .. " Reason: " .. err)

set[a]:close()

set:remove(set[a])

else

if readdata ~= nil then

local peer = set[a]:getpeername()

print("[" .. peer .. "]: " .. readdata)

end

end

end

end

 

클라이언트측:

local socket = require "socket"

local timer = 0

 

function love.load()

client = socket.connect("127.0.0.1", 12345)

if client == nil then

print("Couldn't connect to server...")

else

client:settimeout(0)

print("Connected to server! " .. client:getpeername())

end

end

 

function love.update(dt)

if client ~= nil then

local data_recv, err = client:receive()

if data_recv ~= nil then

print("Server sent a data: " .. data_recv)

else

if err ~= "timeout" then

print("Oops. Something happened: " .. err)

client:close()

client = nil

end

end

 

-- once in a 3 secs, send "Hi!\n" to server

timer = timer + dt

if timer >= 3 then

timer = timer - 3

local result = client:send("Hi!\n")

if result ~= nil then

print("Send data to server: Hi!")

else

print("Oops. Something happened: Can't send data to server.")

client:close()

client = nil

end

end

end

end

 

터미널로 데이터를 보내기 때문에 클라이언트측에서는 conf.lua를 만들어 t.console = true 설정을 해줘야 출력값을 볼 수 있다.

 

서버측 설명

 

socket = require("socket")

socket 라이브러리를 불러온다.

 

function newset()은 set 구현을 위한 기능이다.

set 객체는 insert와 remove, 2가지의 함수를 가지는데, 각각 set에 무엇인가를 넣고 뺄 수 있는 함수이다.

 

-- set socket

server = socket.bind("*", 12345)

server:settimeout(0)

server 소켓을 생성하여 모든 주소(*)에서 12345번 포트를 통해 접속할 수 있게 한다.

timeout은 0으로 설정하여 아무런 응답이 없을 경우 바로바로 timeout 처리가 되어 넘어갈 수 있게 한다.

 

set = newset()

이후 접속한 클라이언트 소켓들을 저장하기 위한 set을 생성한다.

이후 while 반복문을 계속 돌면서 여러가지를 처리한다.

 

서버측 while 반복문은 크게 두 부분으로 이루어져 있는데, 클라이언트의 접속을 허용하는 부분과 클라이언트에게서 데이터를 받는 부분이다.

 

local client = server:accept()

-- accept new connection

if client ~= nil then

set:insert(client)

print("New client connected!! ".. client:getpeername())

client:send("Hello!\n")

end

 

client 지역변수에 server:accept() 를 통하여 클라이언트의 접속을 받는다.

접속하고자 하는 클라이언트가 존재하는 상태라면 client 소켓이 생성될 것이고

존재하지 않는 상태라면 nil이 반환될 것이다.

nil일 경우 처리하지 않아도 되고, 소켓이 생성되었다면 set 안에 넣어준다.

그리고 사용자에게 새로운 클라이언트가 생성되었음을 알린다. client:getpeername()으로 클라이언트의 IP와 같은 정보를 알 수 있다.

전송된 클라이언트 소켓에 데이터를 보내고 싶다면 client:send("보낼 데이터") 로 데이터를 보내면 된다.

 

만일 모든 클라이언트 소켓에 브로드캐스트를 보내고 싶다면

모든 클라이언트 소켓의 정보는 set에 저장되어 있으므로, 모든 set을 돌면서 보내면 될 것이다.

 

-- recieve from client

for a = 1, #set do

local readdata, err = set[a]:receive()

if err then

print("Client disconnected: " .. set[a]:getpeername() .. " Reason: " .. err)

set[a]:close()

set:remove(set[a])

else

if readdata ~= nil then

local peer = set[a]:getpeername()

print("[" .. peer .. "]: " .. readdata)

end

end

 

end

클라이언트에게서 데이터를 수신하는 부분이다.

set에 있는 모든 소켓들을 순회한다. 

local readdata, err = set[a]:receive() -> set에 있는 각 소켓에서부터 데이터를 받는다.

수신한 데이터가 있으면 readdata에 데이터가 저장되며, 없으면 nil이 저장된다.

 

에러가 발생한 경우 err에 에러 내용이 문자열 형식으로 저장된다.

에러가 발생한 경우 클라이언트가 접속을 종료하는 등으로 데이터를 보낼 수 없는 상태이므로

소켓을 닫고 set에서 제거한다.

 

에러가 발생하지 않은 경우 readdata에 값이 저장되어 있다면(=데이터를 수신했다면)

사용자에게 데이터를 수신했음을 알려준다.

 

 

클라이언트측 설명

 

local socket = require "socket"

local timer = 0

 

라이브러리를 불러오고 timer를 0으로 맞춘다.

 

function love.load()

client = socket.connect("127.0.0.1", 12345)

if client == nil then

print("Couldn't connect to server...")

else

client:settimeout(0)

print("Connected to server! " .. client:getpeername())

end

end

 

게임이 로드되면 127.0.0.1의 12345 포트로 연결한다.

연결에 성공하면 client에 client 소켓이 생성되고, 실패하면 nil 값이 반환된다.

nil 값이 반환된 경우 서버에 접속할 수 없었다는 메세지를 띄워주고

서버에 성공적으로 접속한 경우 네트워크 처리로 인해 blocking이 일어나지 않도록 timeout을 0으로 설정하고

서버에 연결되었다는 메세지를 띄워준다.

 

love.update 함수는 서버에게서 데이터를 받는 부분과 3초에 한번씩 서버에 Hi! 메세지를 보내는 부분으로 구성되어 있다.

모든 처리는 client가 연결에 성공하여 client에 nil이 아닌 client 소켓이 생성되어 있을 때에만 처리한다.

 

local data_recv, err = client:receive()

if data_recv ~= nil then

print("Server sent a data: " .. data_recv)

else

if err ~= "timeout" then

print("Oops. Something happened: " .. err)

client:close()

client = nil

end

end

 

서버와 마찬가지로 무엇인가를 받으면 data_recv에 값을 저장한다.

nil값이 아닐 경우 무엇인가 들어왔다는 뜻이므로 서버에게서 받은 데이터를 사용자에게 띄워준다.

만일 err에 값이 들어왔을 경우 뭔가 에러가 발생했다는 뜻인데

timeout인 경우 그냥 아무것도 안보냈음이라는 의미고, 이건 정상적이니까 무시하고

그 외 closed 등등의 값이 들어올 경우 오류 메세지를 띄우고 클라이언트 소켓을 닫은 다음 없앤다.

client를 nil값으로 만드는 것은 꼭 필요한 처리는 아니지만, 이 코드에서는 client 소켓이 생성되어 있으면 연결되어 있다는 뜻이고

그렇지 않으면 연결되어 있지 않다는 뜻이므로 client에 닫혀 있는 소켓이 남아있어 나머지 부분에 혼란을 주는 경우를 방지하기 위해 client를 nil로 만들어 준다.

 

-- once in a 3 secs, send "Hi!\n" to server

timer = timer + dt

if timer >= 3 then

timer = timer - 3

local result = client:send("Hi!\n")

if result ~= nil then

print("Send data to server: Hi!")

else

print("Oops. Something happened: Can't send data to server.")

client:close()

client = nil

end

end

 

timer에 delta time을 더해준다.

delta time이 3 이상 쌓였다면 3초가 경과했다는 뜻이므로 3을 빼주고

일단 client 소켓에서 Hi! 값을 보낸 뒤 결과에 result에 저장한다.

전송에 성공했다면 result에 보낸 글자수를 의미하는(정확하지는 않음) 정수값이 반환되고

전송에 실패했다면 nil 값이 반환된다.

 

전송에 성공한 경우 보냈다고 사용자에게 띄워주고

전송에 실패한 경우 데이터를 보낼 수 없었으므로 서버와의 연결이 끊겼음을 감지하고 socket을 닫는다.

 

매번 보낼 때마다 위와 같은 처리를 할 수는 없으니 위와 같은 처리를 하는 함수를 만들고, 그 함수를 통해서 데이터를 보내는 것이 좋을 것 같다.

 

 

더 많은 기능이 있겠지만 그건 그때 가서 만들 일이고, 지금은 필요한 부분만...

 

해결할 부분

내가 뭔가 잘못하고 있는지는 모르겠는데, Send 할 때 맨 마지막에 "\n" 문자가 안 들어가면 전송이 안 되는 것 같음.

그리고 Send("AAA\nBBB\n")로 보내면 "AAA\nBBB\n"이 한번에 오는 것이 아니라, AAA와 BBB로 두 번에 걸쳐서 전달이 되는 것 같음.

-> client:receive()에는 3가지 패턴이 있는데, '*a'는 연결이 끊어질 때 까지 송신, '*l'은 \n이 들어올 때 까지 송신, 임의의 정수는 그 정수의 길이만큼 데이터를 송신하며 기본값은 '*l'이다.

 

계속 서버와 연결을 진행해야 하는 방식에서 '*a'는 적합하지 않은 방식이고 주고받는 데이터의 길이가 고정되어 있지 않으므로 정수형으로 데이터를 송수신하기도 어려우므로 결국 '*l' 모드를 사용해야 할 것 같다.

 

따라서 내가 원하는 대로 동작시키려면 데이터 송신시 '\n' 문자를 임의의 다른 문자로 치환하고 송신할 데이터의 마지막에 \n 문자를 붙여서 송신.

수신시에는 반대로 데이터의 마지막에 있는 \n 문자를 제외하고 다른 문자를 다시 \n 문자로 치환할 필요가 있다.

조회 수 :
21
등록일 :
2023.07.14
11:00:03 (*.168.186.88)
엮인글 :
게시글 주소 :
https://hondoom.com/zbxe/index.php?mid=study&document_srl=821940

노루발

2023.07.22
16:19:09
(*.149.251.187)

위의 코드를 그대로 사용하면 2개 이상의 클라이언트가 동시에 접속할 경우 block이 발생하니

client를 accept하여 새로운 소켓을 만든 시점에서 timeout을 0으로 설정해야 한다.

List of Articles
번호 제목 글쓴이 날짜 조회 수
185 Bootstrap4 container class가 적용된 div의 양 옆에 설정하지 않은 margin이 생김 노루발 2024-02-07 5
184 express.js 세션 적용 후 리다이렉트 시 세션 적용이 제대로 안 되는 문제점 노루발 2024-02-07 4
183 illegal character 방지 [3] 노루발 2023-07-17 22
» Lua-love2d TCP 통신 [1] 노루발 2023-07-14 21
181 PHP로 웹게임 만드는 영상 [1] 노루발 2021-06-25 351
180 certbot을 이용한 HTTPS 인증서 발급 및 적용 노루발 2021-01-12 18
179 Love2d 게임 안드로이드로 패키징하기 노루발 2021-01-11 48
178 자동화된 Lua 스크립트의 문서화 - LDoc 노루발 2021-01-11 43225
177 리캡챠 적용 [1] 노루발 2021-01-08 11
176 Oracle cloud에 Nginx/MariaDB 설치하기 노루발 2020-12-06 93
175 Love2d로 만든 로그라이크 예제 노루발 2020-11-30 252
174 Love2d 이미지 하얗게 그리기 노루발 2020-11-23 42
173 루아 스타일 가이드 노루발 2020-11-19 33
172 턴 기반 시스템 구현에 대한 글 [4] 노루발 2020-11-14 440
171 이쁜 눈나가 유니티 개발 알려주는 재생목록 노루발 2020-11-12 37
170 love2d에서 안드로이드 터치 제스처 인식하기 노루발 2020-11-12 20
169 특정좌표를 기준으로 zoom in/zoom out하기 노루발 2020-11-11 57
168 Lua 클래스 만들고 활용하기 노루발 2020-11-06 23
167 Lua 테이블 안에 함수 저장하기 노루발 2020-11-06 28
166 Lua 인수로 nil값이 들어왔을 경우 처리하기 노루발 2020-11-06 35