메뉴 건너뛰기

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

서버: 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 자동화된 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 문서 코멘트라는걸...  
184 VC++ 2008 Express Edition에서 문D라이브 링크 [2] A.미스릴 13090   2008-04-17 2014-04-15 17:28
SDK도 최신버전으로 깔아주었고 도구 설정도 적절하게 했습니다 모든 셋팅이 완료되고 링크를 해보니까 아래와 같은 문장이 쭉쭉나오는군요 warning은 굉장히 많이 나오는데 실제로 컴파일을 막아버린 부분은 LINK : fatal error LNK1104: 'LIBC.lib' 파일을 ...  
183 cocos2d-x 터치와 업데이트 활성화 시키기 똥똥배 10656   2011-10-27 2013-09-13 07:29
cocos2d와 달리, 윈도우 환경에서 cocos2d-x 프로그램을 짜보면 상속을 받았음에도 update, ccTouchesBegan 등의 함수가 작동되지 않음을 알 수 있다. 터치 해결법: OnEnter와 OnExit를 추가하고, 다음 같이 적어준다. void HelloWorld::onEnter() { //단일 ...  
182 문D 질문 #2 [1] A.미스릴 8797   2008-06-01 2009-01-07 22:05
이거 도배하는거같은 마음이 드네여 ㅈㅅ 제가 대화창을 다뤄봤는데여 CTextDlg textdialog(50); CTextDlg * pt_textdialog(&textdialog); pt_textdialog->SetDlg(100, 100, 200, 200); pt_textdialog->SetColor(255,255,0); pt_textdialog->SetDlgBox("_d...  
181 MFC 더블 버퍼링 질문 [2] A.미스릴 6602   2008-06-13 2013-11-23 08:43
void CPingpongView::OnDraw(CDC* pDC) { CPingpongDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here // 현재 창 크기와 같은 DC를 만듬 CRect rect; GetClientRect(&rect); int int_client_width = (rect.ri...  
180 문D라이브로 만드는 더블드래곤.. 질문입니다 [4] 하와이안 5550   2009-01-15 2009-01-21 00:16
예전 다른 프로그래밍 언어는 조금 써본 적 있는데 C++은 거의 없어서 헤매고 있습니다. 문D라이브 더블드래곤 강좌에 나온 데로 VC++ 2005 express edition으로 깔고 링크되어 있는 directx sdk 깔고 VC디렉토리에 SDK 링크도 추가해서 moondlib 실행하고 빌...  
179 임의의 점이 다각형 내부에 있는지 검사하는 함수 똥똥배 5358   2008-04-14 2008-04-14 02:21
수학도 기하학도 허접한 제가 짠 거라서 허접하지만 넓은 아량으로 봐 주십시오. struct Point { double x; double y; }; bool LineIn(Point p1, Point p2, Point p3) { double top, bottom; //위 아래 범위 결정 if(p1.y < p2.y) { top = p1.y; bottom = p2.y...  
178 문D라이브로 더블드래곤을 만들자(11) file 똥똥배 4647   2008-05-17 2008-05-17 20:24
 
177 문D라이브로 더블드래곤을 만들자(1) [2] file 똥똥배 4479   2008-04-16 2008-04-17 08:29
 
176 씨언어 질문 (내일 시험 ㄷㄷ) [1] 쿠로쇼우 4331   2008-06-17 2008-06-17 22:10
1 121 12321 1234321 123454321 부탁 ㄷㄷ  
175 문D라이브도 더블버퍼링이 필요한가요? [3] A.미스릴 4223   2008-06-28 2008-06-29 07:06
보니까 backbuffer에 그림을 붙이던데 backbuffer가 배경 버퍼니까 바로 화면에 출력되는건가요? 그런데 그걸 직접 쓰면 MFC의 DC처럼 깜박임과 속도저하가 오는건 아니죠?ㅡ,.ㅡ; 추가로 LoadPicture로 불러온 그림을 다시 안불러온것처럼 되돌리는건 없나요?  
174 문D라이브로 더블드래곤을 만들자(10) file 똥똥배 4065   2008-05-17 2008-05-17 03:07
 
173 #define에 대해 [1] A.미스릴 4061   2008-05-19 2008-05-19 18:57
#define을 너무 많이 쓰면 코드에 불안정화가 온다는데 사실인가여? 그러면 불안정화를 막으러면 어떻게 해야하나여 아니면 enum이나 const를 써야 하나...  
172 클레스들을 담은 헤더들의 혼란 [4] A.미스릴 3892   2008-06-21 2013-11-23 08:43
xxx.h파일 //xxx.h class xxx { ... 어떤 멤버함수 (yyy 뭐시기); ... } yyy.h파일 //yyy.h class yyy { ... 어떤 멤버함수 (xxx 뭐시기); ... } 이 두 클래스의 헤더 파일이 있으면 #include "xxx.h"와 #include "yyy.h"를 어떤 순서로 배치해야 할지 혼란스...  
171 srand에 관해서 [4] A.미스릴 3868   2008-07-15 2008-07-18 05:49
제가 알고 있는 난수의 사용방법은 srand( (unsigned int) time( NULL ) ); 로 srand를 지정하고 rand()를 해서 랜덤난수를 가져오는 것인데 함수값이 어쩌고 하는 원리더군요 그런데 원리를 생각해 보니 이게 정말 난수의 역할을 해낼수 있을까요?ㅡ,.ㅡ; 애...  
170 문D 질문 [5] A.미스릴 3847   2008-05-26 2008-06-12 18:55
1.글자, 문장을 화면상에 출력하는 방법 2.숫자를 스트링으로 형변환할떄 아스키 코드가 아니라 숫자가 옮겨가게 하는법 3.사각형, 원 등을 출력하는 방법(Rectangle 함수같은거 없나여)  
169 흥크립트에 궁금한점 [4] 상상악수 3835   2008-08-21 2008-08-27 04:13
tile1에서 앞쪽에 18칸말고는 사용할수없는 칸인가요? 그림을 그리면 맵에디터에서는 잘나오는데 게임에서는 까만색에 움직일수없는..  
168 C++ 질문 [1] A.미스릴 3818   2008-12-21 2008-12-21 19:24
str_list1[loop_v]가 존재하면 true, 안 존재하면 false 이런 감별이 필요한데 CString str_list1[] = {"","","웹디자인","3D그래픽스"}; int loop_v = 0; while(str_list1[loop_v]) { this->m_ctrl_combobox1.AddString(str_list1[loop_v]); this->m_ctrl_lis...  
167 TinyXML의 한계 [2] 똥똥배 3756   2011-12-11 2013-11-23 06:30
내가 TinyXML을 쓴 것은 이름 그대로 tiny하기 때문이었다. 쓸데없는 기능없이 담백하게 읽고 쓰고 땡. 하지만 써 본 사람들은 잘 알겠지만 유니코드를 지원하지 않는다. 일단 유니코드를 지원하지 않더라도 유니코드(UTF-8)로 된 문서를 읽을 수는 있다. 한글...  
166 문D라이브 2008년 5월 19일 버전 [2] 혼돈 3747   2008-06-02 2015-12-09 08:06
휴가루 웍휴2를 개발하면서 조금씩 수정 중이지만, 미스릴님의 요청이 있어서 올립니다. 대화창에 관련되서 완성되었다는 것과 흥크립트 부분이 많이 변했다는 것이 주요 변경점입니다. 대화창의 경우 기본은 대화창이 없는 상태로 MakeDlgBox를 사용하면 크기...