메뉴 건너뛰기

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

서버: 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
번호 제목 글쓴이 조회 수 추천 수sort 날짜 최근 수정일
185 질문요.C++ [7] 질문자01 2729   2007-05-10 2008-04-03 05:32
도대체 파라미터가 뭔지 모르겠어요.  
184 [re] 혼돈님께 질문 혼돈 2048   2007-12-25 2008-03-17 04:37
옛날에 뼈대를 그려놓고 일단 게임을 만들자는 주의였는데 나중에 완성하고 나면 고치기도 귀찮고, 그 그래픽에 맞춰 만든 거라서 그냥 그대로 쓰게 되는 경우가 많았음. 그래서 그 다음부터는 처음부터 그래픽 소스를 제대로 준비한 뒤 시작하는 식으로 했습...  
183 Bootstrap4 container class가 적용된 div의 양 옆에 설정하지 않은 margin이 생김 노루발 5   2024-02-07 2024-02-07 14:25
문제: <div class="container"> TEST </div> 위와 같은 페이지를 브라우저에서 렌더링 시 div의 양 옆에 설정하지 않은 빈 margin이 생김. 해결: <div class="container-fluid"> TEST </div> margin을 0으로 주고 width를 100%로 주고 등등 별걸 다 해봤는데 ...  
182 express.js 세션 적용 후 리다이렉트 시 세션 적용이 제대로 안 되는 문제점 노루발 4   2024-02-07 2024-02-07 14:23
문제: logout.js router.get('/', (req, res) => { req.session.destroy(); res.redirect('/login'); }); login.js if (result[0] !== undefined) { // 로그인에 성공하였으므로 세션을 할당 req.session.uid = result[0].uid; req.session.us...  
181 illegal character 방지 [3] 노루발 22   2023-07-17 2023-07-19 16:14
문제점: 클라이언트 <-> 서버 통신을 하면서 다음과 같이 메세지를 주고받기로 함 패킷종류|파라미터1|파라미터2 예) MOTDREQ -> 서버에 MOTD를 요청 (파라미터 없음) MOTD|공지사항입니다 -> MOTD는 "공지사항입니다" 임 (파라미터 1개) MSG|김덕배|안녕하세...  
» Lua-love2d TCP 통신 [1] 노루발 21   2023-07-14 2023-07-22 16:19
서버: Lua 클라이언트: Love2d(Lua) 서버 구동에는 luasocket 라이브러리가 필요하며, luarocks로 설치할 수 있음. 별도 패키지 관리자가 있는 리눅스 시스템에서는 apt-get install lua-socket 등의 패키지 관리자 명령어로 설치 가능하며 Windows에서 구동시...  
179 PHP로 웹게임 만드는 영상 [1] 노루발 373   2021-06-25 2022-01-28 03:40
Simple PHP Strategy Game - YouTube  
178 certbot을 이용한 HTTPS 인증서 발급 및 적용 노루발 18   2021-01-12 2021-01-12 16:57
snap 설치 및 업데이트 sudo snap install core; sudo snap refresh core certbot 설치 sudo snap install --classic certbot 심볼릭 링크 생성 sudo ln -s /snap/bin/certbot /usr/bin/certbot nginx에 맞춰 자동 설정 sudo certbot --nginx 알아서 다 해주기...  
177 Love2d 게임 안드로이드로 패키징하기 노루발 48   2021-01-11 2021-02-21 01:45
http://hondoom.com/zbxe/index.php?mid=study&document_srl=797993 버전이 바뀌면서 빌드 방법이 바뀌었기에 다시 정리한다. 1. Android studio 설치 https://developer.android.com/studio/index.html SDK 플랫폼 - Android 11.0 [API 30] SDK 버전 - An...  
176 자동화된 Lua 스크립트의 문서화 - LDoc 노루발 43226   2021-01-11 2021-01-11 11:53
다운로드 https://github.com/lunarmodules/LDoc penlight 설치가 필요 luarocks install penlight 프로젝트가 있는 폴더에서 아래의 명령행을 실행 lua /path/to/ldoc/ldoc.lua $* https://stevedonovan.github.io/ldoc/manual/doc.md.html 문서 코멘트라는걸...  
175 리캡챠 적용 [1] 노루발 11   2021-01-08 2021-01-11 12:15
XE 회원가입 시 구글 리캡챠 인증 추가하기 : 네이버 블로그 (naver.com)  
174 RPG Maker MV 로컬라이징 방법 file 똥똥배 910   2015-10-27 2015-10-27 05:51
 
173 김프로 이미지 맵 만들기 노루발 393   2015-11-11 2015-11-11 08:05
https://docs.gimp.org/en/plug-in-imagemap.html  
172 [번역] gamedev레딧의 Getting Started 문서 번역 [5] priling 1890   2014-12-26 2018-07-24 10:33
처음인 분들을 위한 '게임만들기' 가이드이 글은 [레딧 게임개발 커뮤니티의 /u/LordNed님의 포스팅]을 베이스로 작성한 것입니다. 이 글의 목적은 게임을 만들고 싶어하는 분들이 어떻게 시작할 수 있을지에 대해 명확한 가이드라인을 보여드리는 것입니다.  ...  
171 Love2d 안드로이드 게임 패키징하기 [3] 노루발 546   2014-12-15 2021-01-11 12:11
이 문서는 개발 환경이 갖추어져 있는 상태이고, 빌드를 무사히 마친 뒤라고 가정합니다. 또한 이 문서는 https://bitbucket.org/MartinFelis/love-android-sdl2/wiki/Game%20Packaging#markdown-header-how-to-package-the-apk-with-your-own-love-090-game ...  
170 Love2d 안드로이드 빌드하기 노루발 572   2014-12-15 2014-12-15 00:59
Love2d의 안드로이드 포트는 알파 단계입니다. 차차 개선되어 나가긴 하겠지만 아직 불안정한 부분이 많으며, 일어날 수 있는 오작동과 그로 인한 결과는 일절 책임지지 않습니다.   이 문서는 Windows 사용자 기준입니다. Linux나 Mac을 사용할 정도의 내공...  
169 Love2d 여러 플랫폼으로 빌드 자동화 노루발 470   2014-11-12 2014-11-12 17:35
https://github.com/MisterDA/love-release/blob/master/README.md http://www.ambience.sk/lua-love2d-game-distribution/  
168 구글 인앱 구매 Soomla로 구현해본 후 팁 똥똥배 577   2014-09-20 2014-09-20 18:45
1. 일단 알파 테스트라도 앱을 출시시켜야 인앱이 작동한다. 그리고 APK 업로드 후에 바로 인앱이 적용되지 않는다. 구글에 게시되는 데, 시간이 걸리므로 인앱 등록 - APK 등록을 마친 후 다음날부터 구현하는 게 깔끔하다. 이걸로 모르고 하루종일 왜 안되나...  
167 Love2d 게임 중간에 광고 표시 [1] 노루발 377   2015-11-12 2015-11-17 00:16
http://love2d.org/forums/viewtopic.php?f=11&t=81224  
166 cocos2d-x 2.2.2 문자열 출력 버그 [2] 똥똥배 601   2014-06-10 2014-08-23 22:35
cocos2d-x 2.2.2, 맥에서만 나타는 문제다. 윈도우, 안드로이드에서는 아무 이상 없다. 줄 수가 꽤 긴 문자열을 출력할 경우, 문자가 전부 다 나오지 않고 잘린다. 잘리는 기준은 줄이다. 문자 수가 어떻든 간에 줄 수에 따라서 잘려버린다. 아마 문자열의 줄 ...