【忘记系列】39dll早期自带教程
Tifaices 发表于 2015-10-16 03:44:56 949

本帖最后由 Tifaices 于 2015-10-16 15:52 编辑

[align=left][align=left]Winsock dll (39dll) [font=宋体]教程[/font][/align]
[align=left]原作者: 39ster[/align][align=left]翻译:[font=Arial]tifaices[/font][/align]
[align=left] 以一个[font=Arial]2[/font][font=宋体]人乒乓球的例子来说明[/font][font=Arial]dll[/font][font=宋体]的用法(此范例在完整版本39dll,[/font][font=宋体]http://mizukiyo.ys168.com/范例/39个2011年的动漫网络新番.zip[/font])[/align]

[align=left] 这个教程将教会你如何使用我的[font=Arial]winsockdll[/font][font=宋体]插件来制作一个使用[/font][font=Arial]UDP[/font][font=宋体]和[/font][font=Arial]TCP[/font][font=宋体]协议的在线游戏。[/font][/align][align=left][font=宋体]
[/font][/align]
[align=left]UDP[font=宋体]协议的优点[/font][/align][align=left] UDP[font=宋体]的最大优点:则就是低带宽和高速。[/font][font=Arial]UDP[/font][font=宋体]相对[/font][font=Arial]TCP[/font][font=宋体]来说快很多,[/font][font=Arial]UDP[/font][font=宋体]根本不需要连接,仅仅是将数据打包连续发送给正确的[/font][font=Arial]IP[/font][font=宋体]和端口即可。[/font][/align][align=left][font=宋体]
[/font][/align]
[align=left]UDP协议的缺点[/align][align=left] 单单靠[font=Arial]UDP[/font][font=宋体]来传输数据是不可靠的,因为数据发出去了,并不能一定保证能够到达目的。所以,我们同时也必须开一个[/font][font=Arial]TCP[/font][font=宋体]连接,用来发送一些重要的数据,这样的话,重要数据就算通过[/font][font=Arial]UDP[/font][font=宋体]发送不成功,也能保证到达目的地。[/font][/align][align=left] 还有一个问题就是防火墙(防火墙必须开放游戏使用的端口),因此如果你想连接上游戏,防火墙一定要配置好。[/align]

[align=left]初始化[font=Arial]DLL[/font][/align][align=left] 要使用[font=Arial]DLL[/font][font=宋体]的功能,你必须在第一个房间内输入以下代码:[/font][/align][align=left]
dllinit(0, true, false);
第一个参数是数字型的话,函数就默认加载"39dll.dll"这个函数,如果你将39dll.dll这个文件更名的话,那么你这里就填写上相对应的更改的名称(字符型):
Eg. dllinit("mydll.dll",true,false)

[/align][align=left] 第二个参数的作用是我们是否需要使用[font=Arial]winsock[/font][font=宋体]功能(当然!)[/font][/align][align=left] 第三个参数则是我们是否使用[font=Arial]39dll[/font][font=宋体]提供的某些文件函数,在这里我们是用不上的,所以不需要加载以增加游戏执行效率。[/font][/align]
[align=left]设置服务器.[/align][align=left] 要创建一个多人游戏的话,主机需要和其他所有客户端建立连接。[/align][align=left] 使用[font=Arial]dll[/font][font=宋体]来设置你的服务器,先创建一个[/font][font=Arial]obj[/font][font=宋体],用他来控制接收所有来自客户端的连接。[/font][/align][align=left] 然后你制作一个简单的菜单,一个按钮用来“创建”,另一个则是“连接”,用一个全局变量[font=Arial]global.master[/font][font=宋体]来判断,如果用户选择的是“创建”,那么这个用户则是主机,这个时候[/font][font=Arial]global.master = true[/font][font=宋体],这时候我们把程序跳到一个专门的房间,把他命名为“[/font]rmWaiting”(等候室),并且在这里创建一个对象称为:“[font=Arial]objwait[/font][font=宋体]”。并在[/font][font=Arial]create[/font][font=宋体]事件里写入以下代码:[/font][/align][align=left]
listen = tcplisten(14804, 2, true);
if(listen <= 0)
{
  show_message("Failed to listen on port 14804");
  game_end();
}
[/align][align=left] 这段代码的作用是创建一个监听口用来监听这个口(这里是[font=Arial]14804[/font][font=宋体],就是第一个参数)来访的任何连接。这个端口的号是你想用多少都可以([/font][font=Arial]1~65535[/font][font=宋体],并最好未被占用,建议使用大数字)。[/font][/align][align=left] 第二个参数的作用是连接人数的上限。如果超过这个数字,其他的人将在等候室里等候,直到前一个人退出,并且服务器接收他们为止。[/align][align=left]最后一个参数的作用是设置为[font=Arial]true[/font][font=宋体]以后,在你使用[/font]tcpaccept()的同时并没人试图连接你的时候,游戏不会被卡住。[/align][align=left]这个脚本将返回一个数字,如果是大于[font=Arial]0[/font][font=宋体],则是成功了,否则返回值是小于或者等于[/font][font=Arial]0[/font][font=宋体]。[/font][/align][align=left] 如果端口小于或者等于[font=Arial]0[/font][font=宋体],则会出现错误,这样我们将[/font][font=Arial]end game[/font][font=宋体]。[/font][/align]
[align=left]新的来访客户[/align][align=left] 要接收新来的客户端,我们就必须在“[font=Arial]objwait[/font][font=宋体]”对象里的[/font][font=Arial]step[/font][font=宋体]事件里写入以下代码:[/font][/align][align=left]
client = tcpaccept(listen, true);
(listen的由来参见上一节)这一行的作用为检查连接名单,是否有人试图连接,如果没有就返回<1的数字,如果有的话,就返回这个人的id,并将它赋予变量client,现在则可以和这个id之间进行相关的数据的发送和接收,第二个参数是是否无阻塞,当为true的时候,就无论你是否收到对方的消息,游戏也不会卡住(冻结)。
if(client <= 0) exit;
如果连接数小于1,则退出该脚本;接下来的的代码,必须在此行不成立时才能执行;
global.udpsock = udpconnect(14805, true);
这一行用来创建了一个从14805端口发送数据的UDP协议。
global.otherplayer = client;
这一行是将获得的client(新连接的id)赋予global.otherplayer。
global.otherip = lastip();
这行是获取最后收到连接的玩家的ip地址,并将它储存于global.otherip,将用于把这个信息作为UDP消息给其他的玩家,让其他玩家知晓他的ip。
global.otherudpport = 14803;
这一行是把其他电脑将使用哪一个的UDP端口号来进行连接告知对方,也即是将这个变量作为UDP消息发送给他们。
room_goto(rmGame);
这一行将是跳到进行游戏的房间。
[/align]
[align=left]连接到服务器[/align][align=left] 要加入刚才建立的游戏,我们需要连接上它这台服务器,还记得之前房间里创建的简单的菜单吗,这时候,我们将在点击“连接”后将执行的程序,在“连接”里输入以下代码:[/align][align=left]
global.master = false;
server = tcpConnect("127.0.0.1", 14804, true);
if(server <= 0)
{
  show_message("Unable to connect to server");
  game_end();
  exit;
}
global.otherplayer = server;
global.udp = udpconnect(14803, true);
global.otherip = tcpip(server);
global.otherudpport = 14805;
room_goto(rmGame);
[/align][align=left] 由于你点击的是“连接”,那么现在你就是客户端了,第一行的作用就是告诉接下来的程序,你不是主机而是客户端,则将[font=Arial]global.master[/font][font=宋体]设置为假。第二行则是建立一个连接来连接服务器(参见[/font][font=Arial]UDP[/font][font=宋体]的缺点),[/font][font=Arial]tcpConnect[/font][font=宋体]一共有三个参数,第一个是你想连接到的服务器地址,当然这里我们用的是本机的[/font][font=Arial]127.0.0.1[/font][font=宋体]。第二个则是连接的端口号,第三个参数是是否无阻赛,这里就不再解释了。[/font][/align][align=left]如果[font=Arial]tcpConnect[/font][font=宋体]连接成功服务器,这时候[/font]server 这个变量将获得服务器返回的[font=Arial]ID[/font][font=宋体]。否者返回一个小于或者等于[/font][font=Arial]0[/font][font=宋体]的数。[/font][/align][align=left]接下来的[font=Arial]if(server <=0[/font][font=宋体])则是判断错误的,错误就退出脚本,这里也不再解释了。[/font][/align][align=left]如果没有错误发生,紧接着执行以下代码,将[font=Arial]server[/font][font=宋体]这个[/font][font=Arial]id[/font][font=宋体]赋予[/font][font=Arial]glboal.otherplayer([/font][font=宋体]这里是客户端的哦,所以服务器对于客户端来说就是[/font][font=Arial]otherplayer[/font][font=宋体]了。)[/font][/align][align=left] 同时打开[font=Arial]14803[/font][font=宋体]这个端口作为[/font][font=Arial]udp[/font][font=宋体]协议发送数据。[/font][/align][align=left] 接下来的[font=Arial]2[/font][font=宋体]行代码的作用和服务器端一样了:[/font][/align][align=left]
global.otherip = tcpip(server);
global.otherudpport = 14805;
[/align][align=left] 最后,客户端也跳入游戏房间。[/align]
[align=left]发送和接收消息[/align][align=left] 为了让我们游戏能正常运行,我们需要让双方都知道对方的球拍的位置以及球的位置。[/align]
[align=left]发送数据[/align][align=left] 现在是你控制球拍(无论你是客户还是服务器端),你必须将你的球拍在控制的同时,把正确的位置告诉另一个玩家,要做到这一点的话,你必须在[font=Arial]Up[/font][font=宋体]和[/font][font=Arial]Down[/font][font=宋体]的事件里写入以下代码[/font][/align][align=left]
clearbuffer();
writebyte(0);
writeshort(y);
[/align][align=left] 第一行是先清楚缓冲区(必然首先执行,不然的话,每一次待发送的数据,都会在缓冲区,然后缓冲区的数据会全部发送给另一个玩家,这样会造成数据混乱)[/align][align=left] 然后在第二行,首先写一个字节[font=Arial]0[/font][font=宋体],这个[/font][font=Arial]0[/font][font=宋体]好比一个标志,告诉接收方:我接下来发送的数据是球拍的[/font][font=Arial]Y[/font][font=宋体]坐标啦。[/font][/align][align=left]自然,第三行就是将[font=Arial]Y[/font][font=宋体]坐标写入缓冲区。这里使用的[/font][font=Arial]short[/font][font=宋体](短字节),他的数字范围为[/font][font=Arial]-32000 to +32000[/font][font=宋体](如果需要更大的范围或者其他字符范围类型,参考[/font]http://tieba.baidu.com/f?kz=865590635的[font=Arial]26[/font][font=宋体]楼)[/font][/align][align=left] 最后一行则是这段程序的重点,他将所有缓冲区里的数据全部通过端口global.udpsock发送给global.otherip,[font=宋体]第三个参数是告诉对方该用哪个端口来接收。[/font][/align][align=left]现在我们将把球的位置同样发送给另一玩家,当然前提是我们是服务器,为此,我们需要在球对象[font=Arial](ball)[/font][font=宋体]的[/font][font=Arial]step[/font][font=宋体]事件里写入:[/font][/align]
if(!global.master)exit;
clearbuffer();
writebyte(1);
writeshort(x);
writeshort(y);
sendmessage(global.udpsock, global.otherip, global.otherudpport);

[align=left] 第一句话是用来判断我们是否是服务器,如果不是就退出不发送,如果是的话就继续执行下面的内容:[/align][align=left]接下来,我们同样需要先将缓冲区里的数据清理掉,然后写入一个字节[font=Arial]1[/font][font=宋体],这个也是一个标志,是用来告诉接收方:我接下来发送的是球的[/font][font=Arial]X[/font][font=宋体]和[/font][font=Arial]Y[/font][font=宋体]的坐标啦(如果发送的时候是以[/font][font=Arial]x,y[/font][font=宋体]这个顺序来发,接收的时候一定也要以这个顺序接收,否则就会颠倒或者混乱,无论任何数据都必须按照顺序),然后是依次写入[/font][font=Arial]x,y[/font][font=宋体]数据进入缓冲。[/font][/align][align=left] 最后一行就是发送了。[/align]
[align=left]发送对话[/align][align=left] 如果要和对方对话的话,我们使用以下代码:[/align][align=left]
clearbuffer();
writebyte(2);         //chat message id
writestring(“Hello other player”);
sendmessage(global.otherplayer);

[/align]
[align=left] 同样,清理缓冲区,然后写入标志字节[font=Arial]2[/font][font=宋体](最后提醒一下,这个标志是你自己定义的,想多少都可以,但前提你要知道每个数字代表是发送的什么),写入字符到缓冲区,发送。[/font][/align]
[align=left]接收数据[/align][align=left] 因为某一方的球拍都是由另一个玩家控制的,所以我们现在要在另一方的球拍的[font=Arial]step[/font][font=宋体]事件里写入以下内容:[/font][/align][align=left]
var size;
while(true)
首先创建一个无限循环,条件为真。
{
  size = receivemessage(global.udpsock);   
  判断在glboal.udpsock上接收到的信息数量
  if(size <= 0) size = receivemessage(global.otherplayer);
如果在udp上接收到的信息量为0,那改为接收tcp上的信息量。
  if(size < 0) break;
  如果没有收到消息,那么就退出这个循环。
  if(size == 0)
  {
    show_message("The other player left the game");
    game_end();
  }

[/align][align=left] 如果收到的信息量为[font=Arial]0[/font][font=宋体],那么就代表这个玩家已经退出游戏,我们游戏也结束[/font] [/align]
 messageid = readbyte();
[align=left] 如收到信息,首先读取这个信息的第一个字节(即标志字节)[/align]
 switch(messageid)
  {
    case 0:
      y = readshort();
    break;
    如果标志字节是0,就将这个球拍移动到读取到的y上。
    case 1:
      objBall.x = readshort();
      objBall.y = readshort();:
    break;
如果标志字节是1,就将球的坐标移动到读取到的x,y上。
  case 2:
    chatmessage = readstring();
    show_message(chatmessage);
  break;
[align=left]释放[font=Arial]DLL[/font][/align][align=left]记得不使用以后,就使用函数dllfree()释放内存哦。[/align][align=left]
[/align][align=left]关闭连接[/align][align=left]在完成游戏或者结束游戏后,我们使用 closesocket(socketid)来关闭这个连接端口。[/align][/align]
最新回复 (5)
  • 断水 发表于 2015-10-16 05:21:48
    0 2
    惊现蒂法大大的新教程
  • JMJM5656 发表于 2015-10-17 01:29:00
    0 3
    {:5_311:}
  • Tifaices 发表于 2015-10-17 08:26:27
    0 4
    Quote断水 发表于 2015-10-16 17:21
    惊现蒂法大大的新教程


    也不算啦,这个其实是最最最老的..随手翻出来了~发一个备份一下
  • Tifaices 发表于 2015-10-17 08:26:29
    0 5
    Quote断水 发表于 2015-10-16 17:21
    惊现蒂法大大的新教程


    也不算啦,这个其实是最最最老的..随手翻出来了~发一个备份一下