MudOS LPC Sockets Tutorial - version 1.0
1992 October 20
by Cynosure (Dave Richards)
// 做一个粗略的助读翻译. by Find.

Minor update 1994 Sept 16 by Robocoder (Anthon Pang)

One of the enhancements added to MudOS between 0.8.14 and 0.9.0 was the
inclusion of Internet sockets in LPC.  It has been an ongoing dream of
the MudOS and TMI researchers to provide more tightly integrated MUDs
communicating over the Internet.  Socket efuns (or LPC sockets) provide
the first level of MUD integration by allowing LPC developers to write
Internet sockets-based applications.  For example, LPC objects already
exist for telnet, remote MUD finger, remote MUD tell, inter-MUD mail
delivery, and participation in the MUDWHO system.

MudOS 0.8.14 和 0.9.0 增加了 LPC 语言里的 internet socket.MudOS 和 TMI
的研究人员为了提供基于 internet 上的更加紧固完整的 MUDs 通讯而不断的努力.
Socket efuns(或称 LPC sockets)给那些 LPC 开发者创作基于 Internet socket
的应用程序提供基础的支持.例如:已存在的 LPC 物件: telnet, 远端 MUD finger,
远端 MUD tell, MUD 际邮件投递, 共享的 MUDWHO 系统.

This document is intended as a tutorial on how to use LPC sockets to
write network-based intercommunicating objects.  It is intended for
intermediate to advanced LPC programmers, who already understand the
fundementals of LPC programming, and wish to write network-based LPC
services.

这份文件可以成为运用 LPC sockets 创作网际通讯物件的指南.这份文件主要为
已经理解 LPC 程序的开发并且想要开发 LPC 网络服务的人而写.

Socket Modes
------------

There are five different modes of communication, or socket modes:
MUD, STREAM, DATAGRAM, STREAM_BINARY, and DATAGRAM_BINARY.  Definitions
for these modes can be obtained by including <socket.h> from the mudlib.

有五种不同的通讯模式.MUD, STREAM, DATAGRAM, STREAM_BINARY 和 DATAGRAM_BINARY.
这些模式定义在 mudlib 的 socket.h 头文件里.

MUD Mode
--------

MUD mode is a connection-oriented communication mode where LPC data types
may be passed across the network to another MUD.  For example, in MUD mode
one could send structured data, like arrays or mappings, across the network
to another MUD which is using a MUD mode socket.  All LPC data types _except_
"objects" may be sent and received using MUD mode.

MUD 模式是一种面向连接的通讯模式,LPC 类型的数据可以由此通过网络传递到另一个 MUD.
例如:在 MUD 模式中可以传递结构型数据,例如:阵列和映射,可以通过网络传递给另外一个
也使用 MUD 模式 socket的 MUD.所有的 LPC 数据类型除了 'object' 都可以用MUD模式收发.

STREAM Mode
-----------

STREAM mode is also a connection-oriented communication mode.  It differs from
MUD mode however, in that all data is sent and received as strings.  So,
using STREAM mode one can send streams of data across the network to other
MUDs.  STREAM mode sockets are less powerful in that they do not
transparently send and receive all LPC data types.  However, many
applications like telnet, for example, do not need to send data as
integers, or arrays.  Instead, telnet views data as a stream of characters
going in each direction.

STREAM 模式也同样是一种面向连接的通讯模式.尽管它和 MUD 模式稍有些不同,在这里收发
的数据类型都是 strings(字符串).所以 STREAM 模式只能用过网络向另一个 MUD 发送
数据流.STREAM 模式用于收发所有的 LPC 数据类型上能力较差.尽管如此,想 telnet 这样
的应用程序并不需要发送整数,阵列这样的数据类型,telnet把所有的数据都视为字符流.

MUD mode sockets are effectively implemented as STREAM mode sockets with
special code to send and receive LPC data types.  Therefore, it behooves
one to only use MUD mode if the application requires this extra data
abstraction.  MUD mode is inherently slower and uses more memory buffer
space than STREAM mode.  Note that when using STREAM mode, there is
no guarantee that the string being sent will arrive all at once;
instead, it may arrive in pieces which the receiving side may then have
to reassemble (the pieces will arrive in order).

MUD 模式可以视为替代 STREAM 模式收发 LPC 数据类型里的特殊代码的有力工具.因此
它适合于应用程序只需要提取额外的数据类型时的应用.MUD 模式相比较与 STREAM 模式
速度慢并且更多的花费内存缓冲区.注意:当使用 STREAM 模式不能保证发送的字符串能够
立刻全部到达目的地;它到达的是数据片(piece)接收端再对 piece 进行重新组合.

DATAGRAM Mode
-------------

Unlike MUD and STREAM modes, DATAGRAM mode is connectionless.  No connection
is established between MUDs to transfer data.  Instead, each piece of data
is sent to the destination MUD in a message called a datagram.

DATAGRAM 模式并不像 MUD 模式和 STREAM 模式,它在传输数据的两个 MUD 之间并没有连接.
他发出的是自带目的 MUD 寻址信息的数据包.

Because no connection is estsblished in DATAGRAM mode, it is possible that
the network could lose the DATAGRAM and neither MUD would realize it!  For
example, if TMI sent a datagram to Portals using DATAGRAM mode and the network
lost the datagram, Portals would never receive the datagram and would be
ignorant of the datagram ever being sent at all.  And TMI won't realize the
datagram was lost because no error is received if the datagram is lost.

由于在 DATAGRAM 模式里没有建立连接,所以发送的数据包有可能在网络中丢失并且发送端
和接收端都不知道.例如: TMI 用 DATAGRAM 模式向 ES2 发送了一个数据包在网络中丢失
了, ES2 永远不会收到这个数据包并且也不知道曾经有过这么一个向它发送来的数据包.
TMI 也不会知道这个数据包已丢失因为丢失的数据包不会返回任何错误信息.

TCP and UDP
-----------

In MUD and STREAM mode, a TCP connection is established between the two MUDs.
TCP is a protocol that will re-transmit data if it detects that data has been
lost.  It uses algorithms that send data, measure how long it takes to get a
reply, wait that long and re-transmit the data until an acknowledgement is
received.  TCP also guarantees that the data packets arrive in order and
are not spuriously duplicated.  (This is a very superficial description of
TCP, but does indicate the sort of work involved in making data transfer
reliable).

在 MUD 模式和 STREAM 模式,在收发端之间要建立一个 TCP 连接.是一种能够发现数据丢失
并重新发送丢失部分的协议.TCP 运用这样的算法:发送数据,测量返回信息需要多长时间,等
待这段时间如果还没有收到确认就重新发送这个数据.TCP 同时能保证到达数据包的完整并且
没有被伪造重复.(这只是关于 TCP 的非常浅显的描述,但还是可以说明这种数据传输是可靠的)

DATAGRAM sockets, on the other hand, use a datagram oriented protocol called
UDP.  UDPs send datagrams between MUDs without the overhead of connections,
retransmission, etc.   Now, since DATAGRAM mode is unreliable why would one
want to use it?  Clearly TCP is better because it guarantees that data is
retransmitted if it doesn't arrive, and it deals with all the ugliness that
a network can throw at it.  Simply stated, some applications really don't
care if all data arrives at the other end.  Then why send it?  Okay, okay.
This is a good question, but now is too soon to talk about it.  Just take
it on faith that there is a need for DATAGRAM mode and we can fill in the
details a bit later.

Creating Sockets
----------------

Ok, so let's start off by creating a MUD mode socket.  We can write an
object to do this:

让我们开始建立一个 MUD 模式的 socket.我们来写一个物件来做这个.

	#include <socket.h>

	void
	create()
	{
		int s;

		s = socket_create(MUD, "close_callback");
		if (s < 0) {
			write("socket_create: " + socket_error(s) + "\n");
			return;
		}

		write("Created socket descriptor " + s + "\n");

		socket_close(s);
	}

	void
	close_callback(int s)
	{
		write("socket " + s + " has closed\n");
	}

Let's analyze this object to see how a socket is created.  Be forewarned,
we have a long way to go before we can send data on a socket, and creation is
only one step along this trail.  So be patient and be sure to understand
each example before moving.

让我们来分析这个物件从而了 socket 解的建立.首先要说明的是,我们距离用 socket 发
送数据还很遥远, creation 只是最初的一个步骤.所以要有耐心并且要确认透彻的了解了
每一个例子再往下进行.

The first thing we do is #include <socket.h>.  All socket definitions
are contained in socket.h, remember?  #defines exist, for example, for
MUD, STREAM and DATAGRAM.  Although each name maps to a number, a well
written application will use this name instead; partly because the
#defines may one day change, but mostly because it is more obvious what
you mean to do.

We declare an integer variable s.  In many sockets applocations s is used
as an abbreviation for socket.  Then we call socket_create() with two
arguments.  The first argument is the socket mode (which we discussed above),
and note that we use the synbolic name MUD for MUD mode.  The second argument
in the above example is called the close callback function.  It is the name
of a function within the object that MudOS will call when the connection is
closed.  Callbacks are used often in LPC sockets efuns to notify the
object when important network events occur.  Note, by the way, that we could
have passed STREAM or DATAGRAM to socket_create() to create STREAM or
DATAGRAM sockets.

我们宣布一个整数变量 s .在多数 socket 应用程序里把 s 用作 socket 的缩写.
接下来我们呼叫 socket_create() 函数并传入两个参数.第一个参数是 socket 模式.
注意我们用符号 MUD 代表 MUD 模式.第二个参数叫 close_callback 函数.这是次物
件中的一个函数名,当连接关闭的时候 MudOS 会呼叫这个函数.callback 经常用于 LPC
socket 函数向物件通报重要的网络事件的发生.我们也同样可以向 socket_create() 
函数传入 STREAM 和 DATAGRAM 来建立 STREAM 和 DATAGRAM socket.

All socket efuns return an exit status or return value.  This value indicates
the completion status of the function.  By convention all values less than 0
indicate errors or warnings.  When an error is returned the application must
decide how to respond to it.  In many cases there is no possibility for
success unless the MUD administrator makes changes to local config file or
the MudOS driver itself, so in many cases the application may decide to
just return on failure.  In the above example, if an error is returned
(s is less than 0) then we use the socket efun socket_error() to write
an error message on the screen.  This is useful during debugging, but should
probably be converted to log_file() calls eventually so the errors can be
logged and fixed.

所有的 socket 函数都返回一个退出状态或者返回值,这个值可以指示函数的完成状态.通常的
约定数值小于 0 说明有错误或者警告.当返回一个错误的时候应用程序必须决定如何做出反应.
一般情况下除非 MUD 管理者改动了本地的配置文件或 MudOS driver 应该不会 success 后
发生什么情况.所以大多数情况应用程序只需要对返回错误做出处理.在上一个例子里,如果返
回了一个错误( s 小于 0 ),那么我们用 socket_error() 这个 socket 函数在屏幕上写出
错误信息.这在程序调试过程中非常有用.但最后也许应该修改成 log_file() 函数使错误信
息能够被记录下来用于修正.

If socket_create() succeeds, it returns an integer greater than or equal to
0.  This integer is known as a socket, a socket descriptor or a file
descriptor.  All three names have their origins in UNIX terminology.  If
socket_create() returns a value less than 0 then an error has occurred and
no socket has been created.  There really aren't any good reasons for
getting an error.  The most common errors would be specifiying an incorrect
socket mode (which should not happen if you use the socket mode definitions
in socket.h) and out of sockets.  The MUD administrator can configure the
number of LPC sockets that can be used by MudOS.  By default, this number
is 16, but should be changed to fit the MUD's requirements.  Increasing this
number will make more LPC sockets available.  Note, that each active LPC
socket takes away one socket that could be used to handle a player login
or an open file.  If the mud needs to handle many players and many open
sockets simultaneously, the machine adminstrator may need to be convinced
to increase the maximum number of open file descriptors allowed to a process.

如果 socket_create() 成功,那么它会返回一个大于或等于 0 的整数,这个整数被看作
一个 socket,一个 socket 描述符或者是一个文件描述符.这三个名字来源于 UNIX 的
术语.如果 socket_create() 返回一个小于 0 的值那么说明发生了错误,并没有建立任
何 socket.一般来说没有任何理由会发生错误.通常的错误是定义了一个错误的 socket 模
式(如果你使用socket.h里定义的模式将不会发生)和超出了 sockets, MUD 管理者可以配
置 MudOS 可用的 LPC socket 数量,缺省的值是 16,你可以改变来适应 MUD 的需要.增加
这个数目可以提供更多可用的 LPC socket,注意:每一个活动的 socket 都要使处理玩家
连线进入的或打开文件的 socket 减少一个.如果游戏需要处理很多的玩家和很多同时打开
的 socket,那么服务器的管理者就要增加最多可以打开文件数目的描述来允许这些操作.

All socket objects should be careful not to "lose" sockets.  Sockets
are not like other LPC objects, there are only a finite number of them.
So in the above example, if we were able to create a socket, we close it
afterwards.  Losing track of a researce is called "leaking".  Socket
leaks occur when object create sockets, use them for a while and then
stop using them without closing them so that they can be used by other
objects.  When an object is destructed, all LPC sockets are automatically
closed.  One other thought about sockets: each socket in MudOS has it's own
unique socket descriptor (or socket number).  So if one object created a
socket, and another created a second socket, neither object would receive
the same socket descriptor.  Object may use this knowledge to their advantage.
It is common, for example, to use the socket descriptor as an index into
a mapping that notes various information for each open socket.  Remember,
however, that once the socket is closed, it becomes available for re-use
by other socket_create() calls.

所有的 socket 物件一定要小心不要"丢失" socket, Socket 不同于其它的 LPC 物件,
他们的数目使有限的.所以在上一个例子中,如果我们成功的建立了一个 socket,我们再
关闭它.失去轨迹叫做 "leaking" (失漏?), 当我们建立了一个 socket ,使用一段时
间以后没有关闭 socket 就停止了这个过程,这个 socket 也不能用于其它的物件,这就
发生了 socket leak. 当一个物件被摧毁,所有它制造的 socket 都会被自动关闭.关于
socket 的另一个想法: 在 MudOS 里每一个 socket 都有它自己的唯一的 socket 描
述符( socket number ).所以一个物件创造了一个 socket,另一个物件创造了第二个
socket,这两个物件决不会收到相同的 socket 描述符.例如:通常可以用 socket 的描
述符作为映射的索引项来存储关于每个打开的 socket 的各种不同的信息.尽管如此也要
注意,一旦 socket  被关闭, 它对于其它的 socket_create() 函数就成为可用状态.


Client/Server Model
-------------------

Before continuing with the rest of the socket efuns now is probably a good
time to stop and review some basic networking concepts.  Connection-oriented
communication is normally described in terms of the client/server model.  In
this model each connection has a client and a server.  The client is the
subject that initiates the connection and solicits some sort of service.
The server on the other hand waits for connection requests from a client,
and when they arrive provides the service requested.  Ftp, for example,
operates this way.  A user initiates a request by connecting to a server.
The server than acts on the requests of the client.  A server
is unlike a client however in that is may be serving more than one client
at any point in time.

连接导向的通讯方式通常是指client/server模式,在这种模式里每一个连接都有一个
客户端和一个服务端,客户端引发这个连接并请求一些服务,另一边的服务端等待客户
端的连接请求,并等待这个服务请求到达.例如FTP就是用的这种方式,一个用户引发一
个连接请求到服务器,服务器依据这个客户端的请求操作,服务器并不像一个客户端,

MUD and STREAM mode sockets use the client/server model.  The client and
server each using slightly different calls to establish a connection.
Later we will discuss the peer-to-peer model when discussing DATAGRAMS,
which is slightly different than the client/server model and uses an
again slightly different method for establishing communication.

MUD 和 STREAM 模式是使用 client/server 模式.每一个客户端和服务器用只有很小
差别的呼叫方式来建立一个连接.以后我们讨论 DATAGRAMS 方式时再讨论 peer-to-peer
模式.

It is possible to have many different services available.  Each service is
identify ied by a "well-known port".  A port is simply an integer in the
range 1 to 65535.  Most MUDs, however, can only use the range 1025 to
65535 because the first 1023 are reserved for applications like telnet,
ftp, etc that are standardized.  In order for a client and server to
cummunicate the server my first create a socket, bind to a well-known port
and listen for connection requests.  The client on the other hand must
create a socket and connect to the well-known port.  The client connects
to the same port as the server has bound and listened on.  That is why
it is called a "well-known" port.  Because clients know the port a priori
and can therefore connect to it.  In the Internet Request for Comments
documentation, for example, the port number is actually specified with
the text of the standard.

也许有很多不同的服务方式,每一个服务都由"well-known port"鉴别.port 的范围是
由 1 到 65535.大多数 MUD 只能使用 1025 到 65535,因为 1-1023 是为象 TELNET,
FTP等这类标准应用程序保留的.为了客户端和服务端的互相通讯,服务端要先创建一
个 socket,并捆绑到 well-known port 开始接收连接请求.另一端的客户端也要建立
一个 socket 并连接到 well-known port.因为客户端预先知道这个端口所以可以连接
到它.

We will continue our discussion then in the order that socket efun
calls would normally be executed to establish a connection between the
client and server.  The server must do some preperation before the
client can initiate a request, so we will start with the server.


Binding to a Port 捆绑一个 port
-----------------

当server通过 socket_create()(并且要核对它的返回值要大于或等于0) 创建了
一个 socket 后,下一个逻辑步骤是要捆绑一个port,这要由 socket_bind()函数
来完成.让我们在上面的例子加入一些编码.首先我们新宣布一个叫 error 的整数
型变量,用来取得socket_bind()函数的返回值.

After the server has created a socket with socket_create() (and has verified
that the return value is greater than or equal to 0) the next logical step
is to bind to a port.  This is done with socket_bind().  Let's add some more
code to the example above.  First let's declare a new integer variable
called error, which is used to hold the return value from socket_bind().


	int error;

// Now let's add a call to socket_bind():

	error = socket_bind(s, 12345);
	if (error != EESUCCESS) {
		write("socket_bind: " + socket_error(error) + "\n");
		socket_close(s);
		return;
	}

This should be added above socket_close(s).  What does it do?  Well
the first argument is no suprise, it's just the socket descriptor we
got back from socket_create().  We will have to pass s into every socket
call from now on so that MudOS knows which socket we are referring to.
Remember servers can and often do service more than one client socket at
one time, so we need to be able to keep them straight.  The second argument is
simply the port number.  Recall, it should be in the range 1024 to 65535.
(Actually 0 is legal too, but 0 will be discussed a bit later.)

这些要加在 socket_close(s) 的上面,他做了些什么呢?第一个参数毫无疑问,它只是
我们从 socket_create() 函数取回的值.从现在开始我们要在每一个 socket 呼叫里
传入 s,使 Mudos 能知道我们要操作的是哪一个 socket.记住服务器经常要在同一时
间对多个客户端 socket 提供服务,所以我们需要直接的控制它们.第二个参数是port号,
再次说明,port的范围为 1024-65535(事实上 0 也是合法的,我们以后再讨论.)

After calling socket_bind() we check the return value to see if an error
occurred.  In this case, however, we compare against EESUCCESS.  In most
cases (excluding socket_create()) EESUCCESS indicates that the socket efun
completed successfully.  Like socket_create() above, if an error is returned
we call socket_error() to display the error as a string.  After writing the
error we simply return, right?  Wrong!  We discussed leaks above.  If we
were to return, then an object would exist that is no longer using a socket
but stops others from using it.  If we decided we cannot use the socket
anymore CLOSE IT so others can.  Once socket_create() has been called and
until socket_close() has been called the socket remains open.  So remember
to be a good socket citizen and close sockets when you are finished.

呼叫 socket_bind() 后我们要检查它的返回值看是否发生了错误.在这里我们是把它和
EESUCCESS 进行比较,在多数情况下(除了socket_create()) EESUCCESS 是指示 socket
类超越函数是否成功的完成.象上面的 socket_create(),如果返回了错误值,我们就呼叫
socket_error() 函数把错误信息用字符串显示出来.在显示完错误信息后就简单的返回,
对吗? 错了!我们再上面讨论过这个漏洞,如果我们返回,这个物件会一直存在,再也不会
用于 socket 也禁止其他的程序使用它.如果我们确定再也不会用这个socket,一定要关
闭它使其他的程序可以使用,每当呼叫 socket_create() 函数被呼叫后一直到呼叫socket_close()
函数以前,这个 socket 是一直打开的.所以要记住使用一个 socket 在使用结束后一定要关闭他.

socket_bind() is notorious for returning EEADDRINUSE.  What does it mean?
If one socket binds to port 12345 and another socket attempts to bind to the
same port (i.e. 12345) the second socket will fail to bind.  This is simple
enough to understand.  Once a socket binds to a port that socket owns that
port.  Other attempts to bind to the same port will fail with EEADDRINUSE.
This is a very common error and can occur if two folks attempt to run the
same server demo, for example.  The correct resolution to this problem is
to 1) determine if the same service is being started twice, in which case,
DON'T, once is fine, or 2) more than one developer has chosen the same
port number for multiple services.  This won't work.  One way to avoid this
is to have one port administrator that assign ports.  Unfortunately, since
networks are generally not isolated, this port assignment must be agreed
upon by all MUDs that you intend to communicate with.

当一个 socket 捆绑了一个 port 同时另一个 socket 也试图去捆绑同一个 port 第二
个 socket 会捆绑失败,这非常易于理解,每一个 socket 捆绑了一个 port 也就拥有了
这个 port,其他试图去捆绑同一个 port 就会返回 EEADDRINUSE 这个错误信息.这是一
个通常的错误,对这个问题的正确的解决办法是: 1)是否有相同的服务被引发两次,
2) 不同的开发者为多种服务选择了相同的端口,这将无法工作,为避免这种情况,应该有
一个port管理者去分派端口.不幸的是,由于网络并不是孤立的建立起来的,端口分配必须
得到你要与之通讯的 MUDS 的共同认可.

Security  安全
--------

Before going on and to satiate those with crimical intent we should answer
a few questions about invalid sockets descriptors.  What happens if we were
to pass in a bad socket descriptor value, just to be mean?  Don't worry MudOS
will catch you doing it and tell you so.  For example, if you passed in
a value that was less then 0 or greater or equal to the total number of
possible sockets in the driver, then MudOS would know you were lying though
your teeth and would return EEFDRANGE.  Now if you were more sneaky you
might try to pass in a legal socket descriptor but one that was not
currently is use.  MudOS would catch you again and would return EBADF.  Ok
sneak that you are, you found out about a socket that in use by some other
object.  What then?  Well there is a 2-level security system built into
LPC sockets.

如果我们传送了一个错误的 socket 描述的值将会发生什么情况呢?不用担心, Mudos
会捕获到你的错误并告诉你.例如:你传送了一个小于 0 或大于了driver提供的可用的
socket 的总数,那么 Mudos 将会知道你的误操作并返回 EEFDRANGE.更糟糕的你可以试
着传送一个合法的 socket 描述但这个 socket 现在不可用,Mudos 将会再次捕获到并
返回 EBADF,你就会知道这个 socket 正在被其它的物件使用.
在 LPC sockets 里内建了两级安全检查系统.

The first level of security uses the master object to validate which
objects can and cannot use sockets.  It might make sense on some MUDs, for
example, for some developers to have access to LPC sockets and some others
not too.  Or perhaps one developer is abusive of network priviledges and
should be banned from socket use.  (In the latter case such a developer
should probably asked to leave.)  To enforce such a policy, MudOS invokes
a function called valid_socket().  valid_socket() should return 0 or 1
indicating whether the requested socket operation should be allowed.
If no valid_socket() function exists, then the value 0 is assumed and all
LPC socket access is *denied*.  On most MUDs however master.c would
contain the following valid_socket().

第一级安全检查系统是使用 master 物件来确定那些物件可以或不可以使用 socket,
在一些 MUD 里可以作为侦测手段,例如例如分配一些开发者有权使用 socket 而另一
些却不允许.也许是一些开发者滥用网络优先级将会被禁止使用 socket(接着这种开发
者也许就要被开除了).为了实现这种规则, Mudos 调用一个叫 valid_socket() 的函数,
valid_socket() 用返回 0 或者 1 来确定 socket 操作是否被允许.如果没有
valid_socket() 函数存在, 那么 Mudos 会假定返回值是 0 并中断所有的 LPC socket
操作.很多的 MUD 里 valid_socket() 函数都是这样写的:

   int
   valid_socket(object eff_user, string fun, mixed *info)
   {
       return 1;
   }

The 2nd level of security is more rigid and is used to stop one
object from interfering with another object.  When a socket is created
the socket becomes "owned" by the object that called socket_create().
Each time a socket efun is called the calling object is compared to the
owner object.  If they are not the same, then the socket efun call is
aborted.  Thus, heinous code like:

第二级的检查将更加的严格,当一个物件经由 socket_create() 函数创建了一个
socket 并成为这个 socket 的"拥有者"以后,每次呼叫 scket 类超越函数的物件
都会和这个 socket 的拥有者进行比较,如果并不是同一物件,那么此类呼叫将被
中止,如以下这类恶劣的程序码:

	int s;

	for (s = 0; s < 100; s++)
		socket_close(s);

will not succeed in closing all sockets on the MUD.  It will close all
sockets owned by that calling object, but all other sockets are
protected by tge 2nd-level security policy.

不会关闭 MUD 里的所有 socket.它可以关闭所有由呼叫物件创立的 socket,但其它
的socket是受二级安全检查保护的.

In either case if the 1st or 2nd level security policy is violated, then
EESECURITY is returned to the caller indicating that the socket efun was
aborted because of such a violation.  If you encounter this error when
writing LPC socket code the mostly like reason would be that you passed in
the incorrect socket descriptor.  This happens.

无论违反了一级还是二级检查,都会把返回 EESECURITY 返回给呼叫者来告诉它 socket
类超越函数的操作由于没有通过安全检查而被中止.如果你在写 LPC socket 类的程序码
时遇到了这个错误信息,多数的原因是你传入了一个不正确的 socket 描述.

Listening for Connections
-------------------------

Once a socket has been created and a port has been bound, a server must
begin listening for connections.  This is done with socket_listen().
Like socket_bind() the first argument is the socket on which to listen.
The second argument is the "listen callback" function.  Recall the
close callback function from socket_create()?  socket_listen() specifies
a function within the object that will be called when a connection
request is received from a client.  Within the listen callback function
a server can either accept the connection from the client or close it.
In most servers there is really no good reason for ever just closing a
connection.  It is considered rude and should be avoided.  If the
client and server implement some sort of authentication protocol
(i.e. password checking) the server should return some indication as
to why the socket is being closed.  This is more a question of style,
of course, but it is difficult to debug a problem with a client or server
when a connection is made then dropped immediately.  If you must do this,
be sure to log some indication as to why in a logfile so that an administrator
or developers can determine the cause and resolve the problem.

每当一个 socket 建立并捆绑上一个 port,server 必须开始收听这个连接,这是通过
socket_listen() 函数实现的,象 socket_bind() 函数一样,第一个参数是你要收听
哪一个 socket,第二个参数是 "listen callback" 函数.是否回忆起了 socket_create()
函数的 close callback 函数? socket_listen() 函数在物件内部指定一个函数,当
从客户端收到一个连接请求时就会呼叫这个函数.在 listen callback 函数里既可以
接收一个客户端的连接也可以关闭它.在大多数的服务器里不应当只是关闭一个连接.
这会被认为是粗鲁的应该避免的.如果 client 端和 server 端要完成一系列的安全认证
协议(比如password 检查),服务器应该返回一些确认信息来指示为什么这个 socket 要
被关闭.这是设计风格的问题.当然,要想排除client 和 server 刚建立了连接有理科失
去这种问题事非常困难的.如果你一定要这么做,一定要在记录文件里记录下这么做的原
因,使管理者或开发者能够知道这个问题的原因来确定解决的办法.

The following code starts listening for connection requests on a socket
that has been created and bound to a port.

下面这段程序是在一个已经建立好并捆绑上 port 的 socket 上开始收听连接请求.

	error = socket_listen(s, "listen_callback");
	if (error != EESUCCESS) {
		write("socket_listen: " + socket_error(error) + "\n");
		socket_close(s);
		return;
	}

It is really just more of the same code, right?  We call the socket_listen()
efun, check the return value for success, if an error occurs write an
error message out which includes a description of the specific error,
close the socket because we're done with it and return.  In reality much
of the code necessary in sockets applications follow this pattern.

Now obviously the next thing to do is discuss the listen_callback function,
right?  Right.  But we won't.  Instead we'll change gears here and look at
the client code for a bit.  The reason for this is simple.  Before a client
can initiate a connection to a server the steps we have discussed now for
the server must have occured.  Until the client *does* initiate a connection
though, no more code will be executed within the server.  So it makes sense
to digress and discuss the client for a moment.

Clients
-------

So far we have discussed a connection from the server's perspective.  Now
let's back up and walk through the client.  Just like the server a client
must call socket_create() to create a socket.  Since a client does not
intend that another client connect to it there is no need to bind the
port to socket.  Does this mean that it cannot do so?  No.  It is possible
for either a client or server to bind to a port.

就象 server 一样,client 一样要呼叫 socket_create() 函数来建立一个 socket,
由于一个 client 并不要另一个 client 与他连接,所以不需要 socket 与 port 捆
绑.这是不是意味着不能这样做?不,client 和 server 都捆绑一个 port 是完全可
能的.

But why would a client wish to do so?  Well the truth of the matter is this,
every socket must be bound before a connection can be established.  Every one.
However, since clients don't really care what port they are bound to, a
special bind is used.  It was alluded to above, we're just catching up to it
now.  If a client calls socket_bind() with a second argument of 0, this
indicates that the caller doesn't care what port is selected, just pick
any one that is available.  And this makes it easy for a client.  If the
caller did bind to a specific port, what happens if another client is already
bound to it?  The bind fails.  So why not let the system do the work of
choosing the port?

那么为什么 client 不这么做呢? 真实的原因是这样的:为了建立一个连接,在此之前,
每一个 socket 都要捆绑,由于 client 并不在意他们被捆绑在哪一个端口上,所以使
用一个特殊的捆绑.我们再前面暗示过,这里论述一下.如果一个 client 第二参数用
0 来呼叫 socket_bind() 函数,这就说明呼叫者并不在意选择哪一个端口捆绑,只在
可用端口里任意选择一个给它.这对于 client 是非常容易的.如果呼叫者捆绑了一个
指定的端口,另一个已经捆绑到这个端口的 client 会发生什么呢? 捆绑会失败,那么
为什么不让系统去做这个选择工作呢?

Now there is one more trick up our sleeve, however.  The operating system
is pretty smart.  It knows whether a socket is bound or not.  It knows when
you do a connect (It knows when you've been bad or good).  So, seeing how
common it would be for a client to wish to connect to a server the designers
of the 4.2/4.3BSD networking system put in a neat feature.  If you connect
on a socket, and the socket is not yet bound, the system will do a
socket_bind(s, 0) for you automatically! In fact if you read BSD networking
applications you will notice that almost no sockets that are used to
initiate connect requests on ever bother doing the bind call.  Laziness is
bliss.

操作系统是非常聪明的,他知道 socket 是否已经被捆绑.

如果你连接一个 socket,同时这个 socket 还没有被捆绑,系统会自动为你呼叫
socket_bind(s,0),

Initiating a Connect 开始连接
--------------------

Once a client has created a socket with socket_create() and optionally
bound to a port with socket_bind(), can then called socket_connect() to
initiate a connection request.  socket_connect() requires four parameter:
1. the socket on which the connection to be performed, 2. the address and
port to connection to, 3. the read callback function, and 4. the write callback
function.  There are several new concepts we need to cover so let's go
slowly and review each argument in tern.

每当 client 通过 socket_create() 建立一个 socket 并通过 socket_bind() 函数
随意的捆绑上一个 port,然后就可以呼叫 socket_connect() 函数开始一个连接请求.
socket_connect() 函数需要四个变量:1. socket 在哪个连接上执行. 2. 要去连接
的端口的地址. 3. read callback 函数. 4. write callback 函数.这里涉及到了一
些新的概念.

The first argument is old hat by now.  It is just the socket or (socket
descriptor) that was returned from socket_create().  The second argument,
however, is new and exciting.  It is a string representating of the address
and port to which we want to connect.  Rather than waste your time talking
about Internet standard dot notation, and address classes, etc, lets just
say this about Internet (or IP) addresses.  You have probably seem them
before.  They are 4-byte addresses, with each byte being separated by a ".".
For example, the Internet address for eng3.sequent.com is 138.96.19.14.
There are many ways to find the IP address for a machine.  The mud list
supplies the host name and IP address for the MUD machine (which do change
from time to time).  The UNIX ping and nslookup commands can be used, as well.
From this point on, we will just assume you can determine the IP address
for a destination machine.  One thing to think about though is that in
general most applications to not embed IP addresses within the code.  It is
more common that the user would provide tha address as an argument to the
application, so don't panic.

第一个参数我们已经知道了,他只是 socket_create() 函数的返回的 socket.
第二个参数是一个新的概念.他是一个表示要去连接的地址和端口的字符串.
我们并不愿意浪费你的时间去讲解 Internet 的标准标识符和地址 class 等,
我们只来说明Internet的地址(或 ip).你大概已经明白了这些.地址是由四位
组成,中间以"."分割,例如: eng3.sequent.com 的Internet地址为138.96.19.14
有很多的方法可以找到一台机器的 ip 地址.mud list 提供一台运行 mud 的机器
的域名和 ip 地址.UNIX 的 ping 和 nslookup 命令可以一样的使用.从这点看,
我们假定你可以确定目标机器的 ip 地址,在很多通常的应用程序并没有把 ip 地
址嵌入他们的程序码.更多的情况是由使用者来提供地址作为参数.所以不必慌张.

We discussed ports above when we talked about binding.  The port that
you specify to socket_connect() is the same "well-known" port number that
the server bound to above.  That's the whole point, by the way, of binding.
The server and client rendezvous so to speak at the port.  So how should
the second argument appear?  Let's assume we wanted to connect to
eng3.sequent.com port 12345.  We would write the following additional code:

我们在上面叙述捆绑的时候讨论过端口,你指定给 socket_connect() 函数的端口应该
和上面服务端捆绑的端口相同.server 和 client 这样约定在端口上会话.那么第二个
参数是怎么出现的?让我们假设你要连接 eng3.sequent.com 端口是 12345,我们要加
入以下的程序码:

	string address;

	address = "138.95.19.14 12345";
	error = socket_connect(s, address, "read_callback", "write_callback");
	if (error != EESUCCESS) {
		write("socket_connect: " + socket_error(error) + "\n");
		socket_close(s);
		return;
	}

Notice the address variable.  It is a string and we asssign it the
Internet address and port number with a space in between to seperate
them.  This is the format that socket_connect() expects addresses and
ports to be specified.

注意第二个变量,他是一个字符串,Internet地址和端口中间用一个空格分开.
这是 socket_connect() 函数预先规定好的地址和端口的接受格式.

So what are the read and write callback functions?  Well we have already
talked about callbacks in general.  MudOS calls these function when some
network event occurs.  In these cases, MudOS calls the applications when
data becomes available to read (i.e. read callback) or that it is now
okay to write data (write callback).  We will discuss how these callbacks
should work in just a bit.

read 和 write callback 函数是什么东西呢?我们已经一般性的介绍过callbacks,
当一些网络事件发生 Mudos 就会呼叫这些函数.当数据成为有用的可读数据( read callback )
已经准备好写数据( write callback )时 Mudos 呼叫应用程序.我们将讨论
callbacks 要如何的工作.

The point is, once socket_connect() is called, the client initiates a
connect request to the server.  Because of the way MudOS works this is
all we can do for now.  The network has work to do.  We have just requested
the network to send a connect request to a remote machine.  That remote
machine will then inform the application that a connect request has
been received and that application will decided what to do about it.  The
point is, all of this takes time.  And while all this is going on MudOS
has other work to do.  So rather than stop MudOS from doing useful work,
MudOS simply return EESUCCESS.  Does this mean that the connection has
been made?  No.  Does it mean the remote machine will connect with us?
No.  Do we even know if the remote machine is up?  No.  Do we know
anything?  Yes, a little.  We know that three possible things can happen
in the near future.  1. the read callback function could be called
indicating the arrival of data from the remote application, 2. the write
callback could be called indicating that it is okay to send data, or 3. the
close callback function (that was provided way back in socket_create())
could be closed indicating that the remote machine did not accept of
connection request.

每当 socket_connect() 函数被呼叫,client 就发给 server 一个连接请求.我们需
要网络发送给远端机器一个连接请求.之后远端机器告诉应用程序已经收到了一个
连接请求,由应用程序对他进行处理.Mudos 不得不停止正在进行的工作,对此返回
EESUCCESS,这就意味着连接已经建立了吗?不,意味着远端机器已经和我们连接上了
吗?不,我们能知道远端机器是否已启动?不,我们能知道些什么呢?一点点,我们只知道
未来可能出现的三种可能性.1.当远端应用程序发送过来的数据到达后 read callback
函数可以被呼叫. 2. 当指示准备好发送数据时 write callback 函数可以被呼叫.
3. 当远端机器不接受连接请求时 close callback 函数可以被关闭.

Before going one step further though, make sure this is all clear.
socket_connect() tells TCP to start a connection request.  When
socket_connect() returns we don't know anything about the state of
the connection as of yet.  MudOS will eventually call back one of the
functions so we know what happened.  This sort of programming is called
asynchronuous programming.  It's opposite is known as synchronous
programming.  In synchronous programming your application would wait
until the connection is either accepted or closed before returning.
But we do not have the leisure of synchronous programming in MUDs because
while we wait for the network, other things are being ignored which should
not be.  So to be fair to everyone we use this asynchronous model.  Which is
not really that complicated once you get the hang of it.

在进行下一步之前一定要把这些搞清楚. socket_connect() 函数告诉 TCP 开始一个
连接请求.当 socket_connect() 函数返回时我们并不知道这个连接的状态. 最终 Mudos
会呼叫其中的一个函数我们才能知道到底发生了什么.这一系列程序编制我们叫做"以字
节为单位的不受时间限制的传输模式"(asynchronous)编程,相对于我们知道的同步编程.在同步编程里,
你的应用程序会同步的等待直到连接被接受或中止时才返回.但在 MUD 里同步程序并
没有空闲的时间,因为在我们等待网络时,其他的事情会被忽略不做.因此明智的做法是
使用非同步编程.一旦你掌握它后他并不是很复杂.

Of course, in the above discussion we assume you check the return value
from socket_connect() for EESUCCESS.  If socket_connect() does not return
EESUCCESS then the connection request failed and no callbacks will be
called.  A common mistake is to forget to check the return value and assume
one of the callbacks will eventually be called.  Be careful.  As in all cases
above if the connection request fails then be write an error message out to
the display, close the socket so it can be re-used and return.

当然以上的讨论我们是假定 socket_connect() 函数的返回值是 EESUCCESS.如果
socket_connect() 的返回值不是 EESUCCESS,那么连接请求失败,就不会呼叫任何
callback,通常的错误是忘了检查返回值并且假定了其中一个 callback 最终会被
呼叫.这一定要小心,以上的那些情况如果连接失败应该显示一段错误信息并关掉
这个 socket 以便其他的地方可以再使用它

Now we wait.  Some time in the future either the read, write or close
callback will be called...

现在只有等待,过一段时间 read ,write 或者 close callback 的某项将会被呼叫.

Accepting a Connection	接受连接
----------------------

Meanwhile (back at the server) a connection request is received from the
client.  When this occurs our listen callback function will be called.
This function was specified in the socket_listen() function, remember?
MudOS calls the listen callback with a single argument, that being the
socket descriptor of the socket that, socket_create(), socket_bind() and
socket_listen() was done on.  This is useful if an object is listening
on multiple sockets at the same time.

我们回过头再说服务器.
与此同时将会从client端受到一个连接请求,此时 listhen callback 函数将会被
呼叫.Mudos 用这个 socket 的 socket 描述作为唯一的参数来呼叫 listhen callback,


The responsibility of the listen callback function is too either
accept the connection or close it.  In general though, as was mentioned
above, we always accept incoming connection requests.  The following
code accepts an incoming connection request:

listhen callback 函数的作用也是接受或关闭这个连接.一般的情况我们通常是
接受一个连接请求.下面的编码就是接受一个连接请求:

	void
	listen_callback(int s)
	{
		int ns;

		ns = socket_accept(s, "read_callback", "write_callback");
		if (ns < 0 && ns != EENOSOCKS) {
			write("socket_connect: " + socket_error(error) + "\n");
			return;
		}
	}

Okay, we know what s is, right?  It was passed as an argument to us.
It is the socket on which the connection indication came in on.  So what
is ns?  Aha!  ns is an abbreviation for new socket.  When a connection is
established a new socket is created for that connection.  So what is s used
for?  It is used to accept connections on.  s will never be used, for
example, to actually send or receive data.  Instead, it is used to tell
the socket application when connection requests arrive.  ns, however, is
a socket that can communicate with the client.  The server can send and
receive data on it etc.  Make sense?

我们应该已经知道了 s 是什么,它作为一个参数传入.它是允许连接接入的所指向的那个
socket,那么 ns 是什么? ns 是 new socket 的缩写,当连接建立,一个新的 socket 会
为这个连接而建立.那么 s 做什么用呢? 它只用来接受连接,并不做其他的用处.例如
发送和接受数据.当一个连接请求到达时它负责告诉 socket 应用程序请求的到达.
ns 才是一个和 client 端通讯的 socket.server 可以在上面发送和接受数据.是否
清楚了?

Now if you recall, we passed read and write callback function names to
the socket_connect() efun.  We are doing the same thing here.
We haven't discussed them yet so don't worry, we'll get there.  For now
just realize that the read and write callbacks are used for the same
purpose for socket_accept() as they are for socket_connect().  And don't
get too impatient we are almost ready to discuss them in gory detail.

是否记得?我们把 read write callback 函数的名字送入 scoket_connect() 超越函
数.这里也要做同样的事情.我们并没有讨论过这些,所以要是不明白并不用担心.这里
我们将要提及.现在只需要了解 read write callback 用于 socket_accept() 函数
的目的和用于 socket_connect() 函数是一样的.我们将会详细的讨论它.

It's important to note that our error handling is different here than in
other cases.  If socket_accept() returns a value greater than or equal to
0 then the efun succeeded just like socket_create().  This means that the
connection has been established!  This is major progress.  However if it
did not succeed there is one case that is worth making an exception for.
Recall from way, way back that there are a limited number of sockets that
MudOS can use?  Well, what would happen if all sockets are in use when a
connection request arrived?  Well simple, stated the connection could not
be accepted because there are no sockets to accept it on.  This is a shame
but it can happen.  If it does EENOSOCKS is returned.  This is more of
a warning than a really bad error.  Sure the connection was closed because
not sockets were available, but if some other socket becomes free a new
connection could be established in the future, so this is an example of
a temporary error.  In this case, it may make sense to just return.
However, in all other cases, the listen socket is closed.  This means that
no new connections can be accepted, until the server is restarted.  As
a result be sure to display an error message so an administrator knows
to restart the server!

有一点很重要要特别注意,这里的错误操作(error handling)和其他情况是不同的.
和 socket_connect() 一样当 socket_accept() 函数成功时返回一个大于或等于
0 的数值,意思是连接已经建立.这是大多数的结果.如果没有成功他只返回一个异
常信息.是否还记得 Mudos 可用的 socket 数量是有限的.当连接请求到达时如果
所有的 socket 都在使用中会发生什么情况呢?很简单,就是说明连接无法被接受
的原因是因为没有了 socket 可以接受它.这当然是一件很没面子的事情,但也是
可能发生的.如果这样就会返回一个 EENOSOCKS.这的确是一个错误信息,确定没有
可用的 socket 所以关闭这个连接.一旦有其他的 socket 被释放,以后在接受的连
接就可以建立了.所以这是一个暂时性的错误的例子.这种情况下只需要简单的返回
就可以了.但在其他的情况里 listhen socket 会被关闭.这意味着再也不会接受新
的连接,除非 server 重新启动.所以这类结果必须要显示一个错误信息以便管理者
可以知道需要重新启动服务器.

----------------------------------------------------------------------------

Flow Control 流控制
------------

Before talking about data transfer which is sort of the climactic
section anyway, we need to discuss another paradigm.  If we were
to look at networking technology today networks run at several orders
of magnitude of difference in performance.  FDDI, for example, which is the
fiber opitc network standard of today runs at around 80 million bits per
second, IBM Token Ring runs around 16 million bits per second, Ethernet
around 10 millions bits per second, high-speed syncronous serial runs at
about 56 thousand bits per second and finally consumer asynchronous
modems run from 1.2 thousand to 14.4 thousand bits per second.  These are
raw data rates, and one certainly cannot expect to use the entire
bandwidth of the various media.  The point is this, if you ever expected
to find a hetrogeneous operating environment, networking is it.  The same
protocols operate correctly at various data rates, and with different network
technologies.  This is what the Internet model is all about.

在我们开始讲述最关键的:数据传输之前,我们需要先讨论另一个主题.我们纵览现在的
网络技术,今天的网络是运行在不同的规范上,每种规范的运行方式也是不同的.
相同的协议可以在不同的数据传输速率和不同的网络技术中正确的操作.这正是
Internet 模式的意义所在.

Because of the variety in networks today it is difficult to make assumption
about how long things might take on networks.  Recall from about that when
we initiated a connect request from a client we checked to be sure that
socket_connect() return EESUCCESS and then just returned and waited?  This
was not just a cute metaphor.  In reality we were.  On an Ethernet we
probably waited about 3/1000s of a second for a reply, not really all that
long.  So short in fact, we could have probably just hung around for the
response and delayed further processing within MudOS.  But what if the
reply were to take several seconds, which is is likely to do on a SLIP or
Point-to-Point link?  We don't know a priori how long things will take and
so must be prepared for the worst when dealing with networks.

由于今天的网络的多样性.要想假定数据在网络中的传输时间是非常困难的.时否
还记得我们介绍当检查一个客户端的连接请求并确定 socket_connect() 返回
EESUCCESS 然后就需要返回并等待?这并不是一个聪明的办法.在以太网中我们等
待回应大概需要 3/1000 秒,也并不是都需要这么长时间.实际上是这么短暂,我们
大概可以在等待期间挂起系统延迟一下 Mudos 的工作.但是如果回应要想 SLIP
和点对点连接那样要持续好几秒呢?由于我们预先并不知道要花多长时间,所以必
须为网络处理的最坏情况做准备.

There really is a point to all of this discussion.  Computers are pretty
fast and are getting faster.  Desktop computer can runs millions of
instructions per second.  At best a similarly priced modem for such a
computer could run at around 19.2 thousand bits per second.  That works
out to less than 2 thousand bytes per second.  So if we compare the computer
speed to the network speed we find a vast difference in speed.  The
conclusion one should reach is this: A computer can generate data much
faster than a network can send it.  Therefore I could write a program
that sat in a loop and pounded the network with data, and it's very likely
that I would eventually run into a case where I had data ready to send and
the network is not ready to accept it.  What should we do in this situation?
Well if we follow our previous example (i.e. socket_connect()) we would just
wait until the network is ready for more data.  And so we do.

在这个讨论里有一点是必须为大家指出的,计算机现在已经非常快了并会越来越快,
桌面计算机每秒可以运行几百万条指令,一个同档次的最好的 modem 可以每秒传输
56k 位,也就是7k 字节每秒.我们把计算机速度和网络速度相比较会发现他们有巨大
的差距.可以得出这样的结论:计算机所产生的数据的速度远远快于网络所能传输这
些数据的速度.

Realizing the difference in speed between computers and networks the
inventor of network software have designed the following kind of interface.
Each socket has a reasonably-sized memory buffer (typically around 4k
bytes of data) that is used to temporary hold data while is waits for the
network to send it.  This temporary buffer will eventually fill, of course,
if we were to send data faster than the network can handle.  When it does the
socket is said to be "flow controlled".  This mean we are told that there is
no more room in this temporary buffer to hold any more data, so we should
stop sending more data.  This flow controlled notion affects is directly
when writing sockets code.  We have to be smart enough to send when we
can and wait when we cannot send.  This may sound complicated; luckily
we can reduce this down to a few very simple rules.

由于网络速度和计算机速度的差别,网络软件的发明者设计了下面这种界面:
每一个 socket 都适当的分配一个内存缓冲区(通常可放4k的数据)用来作为
等待网络发送的数据的暂存区.当然,如果我们发送数据的速度高于网络的处
理速度,暂存区最重要被装满.这时 socket 就会难过的说"flow controlled"
这个意思就是告诉我们在暂存区再也没有更多的地方存放数据了.我们应该停
止在发送更多的数据.这个flow controlled的提示会使写 socket 的编码受到
直接的影响.我们必须明智的决定什么时候可以发送数据而什么时候不能.这应
该是非常复杂的.幸运的是我们可以通过一些非常简单的规则来降低这难度.

Remember we said that socket_connect() generates a connection request and
returns immediately without waiting for the reply?  This is true it does.
But what we didn't mention back then is that during the connection request
your application cannot send any data.  This makes sense right?  If the
connection has not been established then how can data be sent.  This is
like dialing a phone number and starting to talk while the phone rings!
Well if we cannot send data after socket_connect(), when can we.  We talked
about this before, remember?  Once the connection is established then
either the read, write or close callback will be called.

还记得我们前面的说的 socket_connect() 建立一个连接请求并且并不等待任何
回应而立刻返回.这就是它真是的做法.但是我们前面没有说到的是在连接请求期
间你的应用程序不能发送任何数据.
那么连接建立以后数据是如何发送的呢?这就象拨一个电话号码在铃响的时候就开
始说话!好吧, socket_connect() 以后我们并不能发送数据,什么时候可以呢?
我们在以前说过,还记得吗?一个连接建立以后,read write 和 close callback
之一就会被呼叫.

So if we want to send data what should we do?  Wait for the write callback
procedure!  Simple enough, right?  Let's forget about the close callback
for the moment because we know why it gets called and it not important any-
more.  Let's just focus on the read and write callback functions.  We said
when the connection has been established either the read or write callback
function will be called.  Well gee, if we are waiting for the connection
to come up so we can send data and our read callack gets called what should
we do, send the data?  No.  Because no matter what, once the connection
comes up the write callback is guaranteed to be called at least once.
In other words, there are two directions of communcation the read direction
(data coming across the network towards us) and the write direction (data we
send out the network towards the other MUD).  If we stick to our guns
and think of each direction as seperate we will avoid confusion.

如果我们要发送数据,我们该怎样做呢?等待write callback 程序.
现在让我们忘记 close callback 因为我们知道它为什么才会被呼叫并且
一点也不重要.让我们把目光集中在 read write callback 函数.当一个连
接建立以后 read 或者 write callback 函数之一将会被呼叫.如果我们正
在等待一个连接的进入并且我们的 read callback 被呼叫我们该做些什么
呢?发送数据?不,当一个连接进入 write callback 保证要至少被呼叫一次,
换句话说,有两个通讯方向,一个是 read 方向(数据通过网络到达我们这里),
另一个是 write 方向(我们法送给网络的要传到其它 MUD 的数据).如果我们
把每个方向看成是分开的就会避免混乱.

Now remember the term flow-controlled?  It means that we cannot send any
more data because we are waiting for the network to catch up.  Well
after a connection request (i.e. socket_connect()) we are flow-
controlled.  Once the connection has been accepted by the server then
our write callback function is called and we become, not flow-controlled!
This is our cue!  Send data!  Make sense?

还记得 flow-controlled 这个名词?它的意思是我们无法发送任何数据因为正在
等待网络速度赶上来.当一个连接请求 ( 如socket_connect() ) 之后我们就
flow-controlled 了,每当一个连接请求被服务器接受以后我们的 write callback
函数就会被呼叫,我们就成为了非 flow-controlled 状态.这就是开始,可以发送数据.

Ok, so we take our chance and start sending data like mad.  The network
sends this, and this, and this too.  At some point the buffer inside is
going to fill up and we are going to become flow-controlled again.  What do
we do then?  Same as before.  Wait for our write callback function to be
called again so we can start sending more data.  Sending on a network is
like send bits of data in bursts.  We send, we wait, we send some more.
A correctly written socket application then is one who keeps track
of whether it can or not.  And only sends when MudOS says it can, and
waits when MudOS says it cannot.

好了,总算有机会疯狂的发送数据了,要指出的是内部的缓冲区会很快的被填满我
们又再次会成为 flow-controlled 状态.然后我们该做什么呢?象以前一样,等待
write callback 函数再次被呼叫使我们能发送更多的数据.
一个正确的socket应用程序的写法应该可以跟踪是否可写的状态变化.只有在 Mudos
允许的时候进行写.在 Mudos 不允许时等待.

Before you go off and get frustrated with LPC sockets, remember this: the
rules for flow control are not difficult to understand, but violate ing them
can end in disaster.  Make sure you understand this flow control model.  It
is fundamental to the asyncrhonous programming model.  If it does not make
sense then please re-read this section or ask a sockets-savvy friend for
help, because this is very important.

流控制的规则并不难理解,但违反它的后果是灾难性的.确定你已经明白了流控制模式,
它是 "以字节为单位的不受时间限制的传输模式" 编程模式 (asyncrhonous programming model)
的基本原则.

Sending data
------------

Okay, now that we understand the constraints within which we live, it's time
to discuss actually sending data.  It's been a long time in coming, but we
have convered a lot of important information along the way.

当我们了解了这些限制,我们就可以讨论实际的数据发送.

After a client's write callback is called the client is no longer
flow-controlled, which means it can begin writing data.  What about the
server?  Is the server flow-controlled after doing socket_accept()?  This
is a good question.  The answer is no, it is not.  This is another difference
between clients and server.  The client must wait for a write callback
before it can begin sending data, the server can begin as soon as the
connection has been accepted.  Ignoring this final detail though, there
is no difference between client and servers send and receiving data.  Both
client and server use the same socket efuns in the same way.

当客户端的 write callback 被呼叫客户端就不再 flow-controlled,这意味着可以
开始写数据.server 端呢? 当做完 socket_accept() server 还是 flow-controlled 吗?
这是一个好问题,答案是不.这是另一个 client 和 server 的不同点. client 必须等待
write callback 之后才能发送数据.server 端只要连接一被 accept 就可以发送数据.
可是忽略这些细节server 和 client,接受和发送数据就没有区别了. server 和 client
都用同样的方式使用相同的 socket 超越函数.

So let's do it, let's send a number in MUD mode.

	error = socket_write(s, 0);

Incredibly enough that's all it takes to send the LPC integer 0 to
the other MUD.  One could do:

	error = socket_write(s, "Hello you other MUD1");

to send a hello message as a string to the other MUD.  In fact in MUD mode
you can send any LPC data type except objects.  This means arrays, mappings,
integers, etc.  This is very powerful indeed!  Ok we sent it, but what happened
on the other side so they can receive it?  Remember the read callback
function, we specified it in the socket_connect() and socket_accept()
efuns?  The read callback function is called when the network delivers to
the data to the socket.  For example:

发送一个字符串类型的问候信息到另一个 MUD,事实上 MUD 模式可以发送出物件以外
的所有的 LPC 数据类型.如阵列,映射,整数等等,是非常强大的.我们发送了这条信息,
在另一端发生了什么?他们是怎么接受这条信息的?我们在 socket_connect() 和
socket_accept() 函数里定义了 read callback 函数,当网络交付数据给 socket
read callback 函数会被呼叫.例如:

	void
	read_callback(int s, mixed message)
	{
		write("Received " + message + "\n");
	}

would write the receive data!  The read callback then is used by MudOS
to tell an object when new data arrives.  In the above example we used
a second parameter of mixed.  This is because in MUD mode any data type
can be sent across the network.  It is the object's responsibility to make
sure the object type is correct.  In fact, once you are able to send and
receive data correctly, you have solved the immediate problem of communication
and have opened up a whole new problem called protocol engineering.  This
involves designing networking protocols that are reliable and can
interoperate with many type of computers, but that unfortunately is far
beyond the scope of the tutorial.  Alas.

当新的数据到达时 Mudos 用 read callback 函数告诉一个物件数据的到达.在上一个
例子中第二个参数我们定义成 mixed 类型,因为 MUD 模式可以传送很多类型的数据.


So have we finished with socket_write()?  I hope you don't think so.
You should have noticed that we didn't check error after calling
socket_write().  Why is this?  Well to be blunt, because there are four
different class es of return codes that socket_write() can return and I
figured we'd ease into the nitty gritty details.  So here we go.

你应该注意到在呼叫 socket_write() 函数后我们并没有检测错误.为什么呢?
因为 scoket_write() 函数可以返回四种不同的返回码,我们可以很容易的
得到详细的信息.

There are four things socket_write() can return.  1) because it sent
the data along and everything is just fine, 2) the data has been saved in
a buffer and will eventually be sent, but no more data can be accomodated
at the moment, 3) the data has not been saved in a buffer and will not
be sent since no more data can be accomodated at the moment, and
4) socket_write() is very confused and doesn't know what to do now.  In the
first case, socket_write() has sent the data and is ready for more.  It will
not call the write callback function because there is no need to, we are not
flow-controlled.  In other words, we should remember that it is still okay
to send data.  In the second case the data will be sent, it's sitting in a
memory buffer ready to be sent when the network can send it, but the buffer
is now full.  Which means we are now flow-controlled.  It would be
inappropriate for us to try to send more at this point, so we should remember
we cannot send any more right now.  When the network empties the memory buffer
the write callback function will be called and we can then start sending data
again.  In the third case, the data couldn't be sent at that moment,
possibly due to socket traffic or network congestion.  We are not flow-
controlled so the write callback function must be called explicitly, to
attempt resending the data.  A call_out() is a better choice here over
a direct function call since the delay gives the system a chance to recover,

reduces the possibility of max eval'ing, and better simulates a callback.

In the final case, some error has occurred either on the network connect or
within the operating system such that the write can not be performed.  In
that case, however, it is likely nothing can be done to rectify the problem.
In general, the best thing to do is simply close the connection.  In general,
this never happens.

socket_write() 可以返回四种类型信息: 1) 正确的发送出了数据.
2) 数据被保存在缓冲区以后发送.这时再也没有地方容纳数据了
3) 数据并没有存在缓冲区以后也不会被发送,因为没有地方容纳数据了
4) socket_write() 不知该做什么.
第一种情况 socket_write() 将数据发送出去并准备好接受其它的数据.
它不会呼叫 write callback 函数因为并不需要,我们并没有成为 flow-controlled
状态.也就是说我们还可以发送数据.
第二种情况 数据将会被发送,数据停留在内存缓冲区内做好发送的准备等待网络
把他们发送出去,但此时内存缓冲区已经满了.也就是我们已经进入了flow-controlled
状态,此时我们就不能再发送更多的数据,当网络将内存缓冲区内的数据发送完,
write callback 函数将会被呼叫我们就可以再次发送数据.
第三种情况 数据此时无法被发送,只有等到 write callback 函数被正确的呼叫
再尝试去重新发送这些数据.在这里一个直接的函数呼叫使用 call_out() 函数是
一个很好的方法用它的延时来给系统一个恢复的时间.
第四种情况 在网络连接或操作系统中发生了错误使写操作无法进行,我们无法去纠
正这个错误,通常的办法是关闭这个连接,一般情况下这是不会发生的.

Okay so much for the abstract, let's look at the specifics.  socket_write()
returns EESUCCESS in case (1) above.  This means that further writes to the
socket are still possible.  If socket_write() returns EECALLBACK, this
indicates that the data has been buffered for output, but that further writes
should be suspended until the write callback function is called.  Conversely,
if socket_write() returns EEWOULDBLOCK, this indicates that the data has
not been buffered for output, and that the write callback function must be
called again manually (to resend) before further writes are attempted.
EEALREADY means that the object has violated the flow control model,
i.e. a write was done while the socket was flow-controlled.  In this event,
the data is *not* buffered and the caller should again wait for the write
callback function.  Of course, well written application will not see
EEALREADY.  Any other return value should probably be interpreted as a fatal
error and the socket closed.

第一种情况 socket_write() 返回 EESUCCESS,
正确发送,还可以继续写

第二种情况 socket_write() 返回 EECALLBACK,
数据放入缓冲区等到下一次 write callback 被呼叫时发送.

第三种情况 socket_write() 返回 EEWOULDBLOCK,
数据没有放入缓冲区,在发送其它的数据以前需要重新发送这个数据.

第四种情况 socket_write() 返回 EEALREADY,
说明此时正在 flow-controlled 状态,是违反 flow control 模式规则的,
数据没有放入缓冲区,呼叫者需要等待 write callback 函数,当然,在写作
正确的应用程序里是不会看到 EEALREADY 的.

其它的返回值就要关闭 socket 了.


int socket_write(int, mixed, string|void);
int socket_release(int, object, string);
int socket_acquire(int, string, string, string);
string socket_address(int);
void dump_socket_status(void);
