Beej’s网络编程向导

Beej’s网络编程向导

               ——使用因特网套接字

Brian “Beej” Hall

 

目录

 

1.  介绍

1.1          观众

1.2          平台和编译器

1.3          官方的主页

1.4          Solaris/SunOS 程序员手册

1.5          Windows程序员手册

1.6          电子信函政策

1.7          反映

1.8          翻译者手册

1.9          版权和发行

2.  什么是套接字?

2.1           二种类型的因特网套接字

2.2           低水平废话和网络理论

3.  结构和数据处理

3.1           转换和本地

3.2           IP地址和和怎么去处理他们

4.  系统调用或Bust

4.1          socket() – 取得文件描述符!

4.2          bind()  -- 我在使用什么端口?

4.3          connect() -- 喂,你!

4.4          listen() – 某人请Call我?

4.5          accept() – “感谢你呼叫端口 3490

4.6          send() recv() – 宝贝,与我交谈!

4.7          Sendto() recvfrom() – 与我交谈,DGRAM-类型

4.8          Close() shutdown() – 取下我的脸!

4.9          Getpeername() – 你是谁?

4.10       Gethostname() – 我是谁?

4.11       域名服务器DNS – 你说“whitehouse.gov”, 我说“198.137. 240.92”

5.  客户-服务器后台

5.1           一个简单的流式服务

5.2           一个简单的流式客户

5.3           数据报套接字

6.  简单的高级的技术

6.1           阻塞

6.2           Select() – 异步 I/O 多路技术

6.3           处理局部send()s

6.4           数据封闭因子

7.更多参考

 71   操纵页

 72  

 73   Web参考

 74   RFCs

8.常见问题

9.拒绝和请求帮助

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


1.  介绍

 喂!套接字编程使你沮丧?这些材料只是有一点太难去解决操作页?你想做冷静的因特网编程,但你没有时间去熟悉结构去解决,如果你在调用connect()前调用bind(),等等。

 

 好,猜测一下!我已经做了这些繁琐的事情,我将把这部分信息提供给你们每个人!你已经来到一个正确的地方。这个文档将给那些能胜任的C程序员,他需要取得掌握这些网络噪声。

 

1.10       观众

 

 这个文档被写作指南,而不是参考。这个可能是对于那些个别的只是想涉足套接字编程和寻找一个立足处的人是最好的。无论怎么说,这当然不是一个完全的套接字编程指南。

 

 虽然,它有希望只足以操纵页去开始理解。

 

1.11       平台和编译器

 

  包含在这个文档中的代码将被在Linux PC下用Gnu’s gcc编译器编译。无论如何,这将可在任凭平台上使用gcc进行构造。自然地,如果你为Windows编程,这将不能应用。了解windows编程的部分。

 

1.12       官方的主页

 这个文档的官方地址是在芝加哥的加利福利亚州大学,http://www.ecst.csuchico.edu/~beej/guide/net/

 

1.13       Solaris/SunOS 程序员手册

 

 当在Solaris SunOS下编译时,你需要为连接到合适的库指定一些额外的命令行转换。为了实现这些,简单的增加“-lnsl – lsocket –lresolv”到编译命令的结尾,如下:

             $ cc -9 server server.c –lnsl –lsocket –lresolv

 

 如果你仍然得到错误,你应该尝试进一步增加一个“-lxnet”到那个命令行的结尾。我不知道那将做什么,但一些人看起来需要它。

 

 另一个地方你将找到问题是在setsockopt()调用中。这个原型不同于在我的Linux box,所以代替:

     Int      yes = 1;

进入这里:

     Char    yes = ‘ 1’ ;

 

 当我没有Sun box,我没有尝试其它超出的信息,只是人们通过email已经告诉我。

 

1.14       Windows程序员手册

 

 我特别厌恶Windows,鼓励你们使用Linux,BSD, 或者Unix。但可以说,你任可以在Windows下使用这些材料。

 

 首先,忽略恰当的我在这里提到的大量的系统头文件。所有我们需要包含的是:

           #include <winsock.h>

 

等等!你还必须在利用套接字库做任何事情前,调用WSAStartup()函数。这个代码是使用方法如下:

         #include <winsock.h>

        {

                 WSADATA     wsaData;        // if this doesn’t work

                 //WSAData    wsaData;        // then try this instead

 

                     If (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0)

                     {

                           Fprintf(stderr, “WSAStartup failed. /n”);

                            Exit(1);

                     }

                }

 

  你还需要告诉你的编译器使它连接到的Winsock库,通常调用wsock32.libwinsock32.lib或诸如此类的东西。在VC++下,这能在菜单ProjectSettings… 点击Link tab,找到“Object/library 模块”的列表框,增加“wsock32.lib?”到列表中。

 

 或许我听到。

 

 最终,当你通过所有操作使用套接字库,你必须调用WSACleanup()。为了细节注意你的在线帮助。

 

 一旦你这么做了,在指南中的其余的例子应该适用,但也有一些例外。例如,你不能使用closeup()来关闭一个套接字,你必须使用closesocket() 代替。同时,select()只能与套接字描述符一起工作,没有文件描述符(例如0 for stdin)。

 

 这也有一些套接字类你可以使用,CSocket。检查你的编译器帮助以获取更多的信息。

 

 想取得更多关于Winsock的信息,阅读Winsock FAQ和去那儿。

 

 最终,我听说Windows 没有fork()系统调用,不幸地,这将用于我的一些例子中。或许你有一个连接到POSIX库或某些东西可以使它工作,或你能使用CreateProcess()代替fork()没有冲突,CreateProcess()大约有480亿论据。如果你没有达到,CreateThread()比较容易消化。不幸地是在这个文档中,很少讨论关于多线程的情况,我只能讨论这么多了,你知道的!

 

1.15       电子信函政策

 

 我一般使用email问题帮助别人,所以可以很自由的写入,但我不能保证回答。我控制一个恰当的忙碌的生活,我只能回答我有时间回答的问题。当在那些情况下,我总是只删除消息。这是与个人无关的;我只能尽力花时间详细的回答你提出的问题。

 

 作为规则,越复杂的问题,我回答的可能性越小。如果你能在发送前减少你的问题并且保证包含相关的信息(如平台,编译器,你取得的错误消息和其它任何你认为可以对我的回答可以提供帮助的信息),你很可能获取回答。为了更多的要点,阅读ESR’s 文档,怎么去灵活地回答问题。

 

 如果你没有得到回答,更多地去了解它,尝试寻找答案,如果仍然没有理解,然后重新发给我信息你已经找到的和希望这能帮助我找出问题。

 

 现在我已经迫使你如果写信给我和不写,我只是想让你知道我完全的感激所有的一年内收到的赞扬指南。这可以真实的推进士气,而且当知道被很好的使用时这可以使我喜悦。谢谢你们!

 

1.16       反映

 

 你可以通过进入这个镜像网站,无论你公共的还是私人的。如果你公共的镜像这个网站和希望我连接到这个主页,点击beej@piratehaven.org

 

1.17       翻译者手册

 

 如果你想翻译指南到另一种语言,发邮件到beej@piratehaven.org,,我将连接你的翻译到主页中。

 

 可以自由的把你的名字和email地址加入到翻译中。

 

 对不起,但使用空间约束,我不能提供翻译。

 

1.18       版权和发行

 

 Beej’s 网络编程指南Copyright@1995-2001 Brian “Beej” Hall

 

 这个指南或许可在任何媒体上自由再版,只要保证内容没有改变,必须包含现在的内容,并保留版权信息。

 

 教育家特别鼓励推荐或提供这个指南的拷贝给他们的学生。

 

 这个指南可以自由的翻译到任何其他语言,只要翻译是准确的,并且可以完全的再版。翻译版本也可以包含名字和翻译者的联系信息。

 

 这个文档中的C源代码因此同意进行公共领域。

 

 联系beej@piratehaven.org获取更多信息。

 

2.  什么是套接字?

 

 你经常听说“套接字”,可能你会怀疑他们是不是正确的。好,他们这么说:使用标准的Unix文件描述符与其它程序进行通讯的方法

 

 你可能听到一些Unix电脑黑客说,“呀,在Unix中,什么都是文件!:”,当Unix程序使用任何I/O接口时,这些人可能会讨论到,他们通过读和写入文件描述符来实现。文件描述符是一个被关联到一个打开的文件的一个简单的整数。但(这里提到的),那个文件可以是一个网络连接,一个FIFO,一个管道,一个终端,一个真实的磁盘文件,或是其它任何东西。在Unix中的任何东西都是文件!所以当你想通过Internet与另一个程序进行通讯时,你只能通过一个文件描述符来实现,你将得到确认。

 

 “那我在哪儿获得文件描述符用于网络通讯呢,Smarty-Pants先生?”,这可能是你刚刚想到的最后一个问题,但我将在下面回答:你可以调用socket()系统例程。它返回套接字描述符,你通过使用专用的send()recv()来通讯(操纵send,操纵recv)套接字调用。

 

 “但是,喂!”你现在可以这样惊叫,“如果这是一个文件描述符,为什么我不能使用一般的read()write()调用来实现通讯?”。简短的回答是,“你能!”,详细的回答是,“你能,但send()recv()提供更多的控制数据传输的功能。”

 

 下面做什么?这样如何:这有所有类型的套接字。有DARPA Internet地址(Internet 套接字),在本地结点(Unix 套接字)的数径名,CCITT X.25地址(你可以安全地忽略X.25套接字),或许还有很多其它的领带于Unix想让你运行。这个文档只处理第一个:Internet套接字。

 

2.3           二种类型的因特网套接字

 

 这是什么?有两种类型的Internet套接字?是的,好,不是的。我说谎。有更多,但我不想威吓你。我只讨论这两种。除此之外,我将告诉你“Raw Sockets”功能也很强大,你可以查看一下。

 

 好,准备好了。两种类型是什么?一个是“流式套接字”;另一个是“数据报套接字”,今后可能分别的被提及的“SOCK_STREAM”和“SOCK——DGRAM”。数据报套接字有时被称为“无连接套接字”。(虽然如果确实想,他们能connect(),下面看connect()

 

 流式套接字是可靠的双向的面向连接的通讯流。如果你输出两条顺序为“1 2 的消息到套接字中,他们在相反的另一端的到达顺序也是“1 2 。他们也会出现错误。所有错误你遇到的都是你自己疯狂的虚构的事,我们不在这儿讨论。

 

 

 怎么使用流式套接字?好,你听说过远程登录应用程序吧?它就是使用流式套接字的。所有你输入的字符都要以相同的顺序到达,是吧?同时,网页浏览器使用HTTP协议,使用的也是流式套接字来取得页面。甚至,如果你远程登录到一个网站的80端口,输入“GET /”,它把HTML转储于你!

 

 流式套接字是如何实现高可靠性的数据传输?他们使用一个称为“传输控制协议”,称为“TCP”(查看RFC-793获得更多关于TCP的细节)。TCP保证你的数据到达是顺序的和无差错的。你可能在知道“TCP/IP”另一半“IP”标准是“因特网协议”(查看RFC-791)以前,听说过“TCP”。IP主要处理因特网路由,不能保证数据的完整性。

 

 好的。什么是数据报套接字呢?为什么被称之为无连接的呢?在这儿做了什么?为什么是不可靠的?好,这儿有一些原因:如果你发送一个数据报,它可能到达。它可能不是按顺序到达。如果它到达,包中的数据可能有错误。

 

 数据报套接字也使用IP路由,但它不使用TCP;它使用“用户数据报协议”,也称为“UDP”(查看RFC-768)。

 

 为什么是无连接的?好,主要地,是因为你没有像使用流式套接字一样必须维持一个打开的连接。你只要建立一个信息包,把目的信息加入到IP头中,把它发出去。不需要连接。他们一般使用包信息传输。应用实例:tftp,bootp等。

 

 “够了!”你可能叫到,“如果数据报丢失,那些程序怎么工作?”好,我的人类的朋友,在UPD的上层有自己的协议。例如,tfpt协议提到为每个发送和接收的包,每次做一个这样的包应答,“我收到了!“(一个”ACK“包)。如果发送的包没有收到应答,一般,五秒,它将重新发送包直到收到应答为止。在实现SOCK_DGRAM应用中,这个应答程序是很重要的。

 

2.4           低水平废话和网络理论

 

 由于我只提及协议分层,是时候说一个网络是怎么工作的了,讲一些SOCK_DGRAM包怎么建立的例子。特别地,你可能略过这部分。不论如何,有一个好的背景。

 

 嗨,小子,是时候学习数据封装了!这是很重要的。如果你在这儿取得了网络课程,这是可能是你在这儿所学到的。主要地,这么说:一个包是天生的,包是在头(很少在脚)使用第一个协议(如,TFTP协议)封装的(“压缩”),然后所有的事情(包括TFTP头)被使用下一个协议(如,UDP)重新压缩,然后重新使用下一个协议(IP)压缩,然后使用最终的物理层(如,以太网)重新压缩。

 

 当另一台机器接收到包,硬件剥去以太网头,内核剥去IPUPD头,TFTP程序剥去TFTP头,最终得到数据。

 

 现在我可以讨论网络分层模式了。网络分层模型描述了一个功能性的网络系统,比其它的模型有很多优点。例如,你可以写相同的套接字程序,而不管物理层数据是怎么传输的(串和行,以太网,AUI,或其它)。因为程序为你在低层次进行了处理。实现的网络硬件和拓扑对套接字程序员是透明的。

 

 除了更多的发展目标,我将在这儿列出模型中的所有的层次。记住这些:

  应用层

  表现层

  会话层

  传输层

  网络层

  数据链路层

  物理层

 

 物理层是硬件(串行,以太网等)。应用层只是你可以想象的离物理层很远的,它主要用于网络中用户相互影响。

 

 现在,只要你真的想,这个模型你可以像汽车修理指南一样使用,Unix一致的层模型如下:

  应用层(telnet,ftp,etc

  主机到主机传输层(TCPUDP

  网络层(IP和路由)

  网络链路层(以太网,ATM或其它)

 

 现在这个时候,你可能了解到这些层通信过程中的数据封装过程。

 

 了解建立一个简单包需做多少工作?呀!你0必须自己输入包头使用“cat“!仅仅欺骗,所有你要做的只是为流式套接字调用send()发出数据。所有你要做的只是为数据报套接字使用你选择的方法进行封装,并调用sendto()发出去。内核为你构造传输层和网络层,硬件实现数据链路层。啊,现代技术。

 

 所以以网络理论结束我们的大纲。是的,我忘了告诉你我想说的关于路由的东西:没什么!正好,我还不准备讨论它。路由器剥去了包的IP头,参考路由表,无聊无聊无聊。如果你真的想了解,检查IP RFC。如果你不想学,好,你是实在的。

 

3.  结构和数据处理

 

 好,我们最终到这儿了。是时候讨论编程了。在这一章,我将会提及所有套接字接口中使用的数据类型,因为有些很难指出。

 

 首先,简单的:套接字描述符。套接字描述符是如下类型:

          Int

 只是一个规则的整型。

 

 有些事情在这儿是神秘的,因此只要读和忍受我。知道这些:这有两字节的排序:大部分有意义的字节(有时也称之为“八位组”),或最小的有意义的字节。以前称之为“网络字节顺序”。一些机器在网络字节序列中保存他们的数字,一些没有。当我说一些必须有网络字节序列时,你必须调用 一个函数(例如,htons())来改变它。如果我不说“主机字节序列”,然后你必须在主机字节序列中丢弃值。

 

 (由于好奇,“网络字节序列”也称之为“Bib-Endian字节序列)

 

 我的第一个结构-sturct sockadd。这个结构为许多套接字类型保存套接字地址信息。

      

 Sa_family可以是多种类型,但在这个文档中它不可以是AF_INET

 Sa_data包括套接字的目的地址和端口号。这是不实用的,因为你不想冗长面乏味地手工地在sa_data中包装地址。

 

 为处理sockaddr结构,程序员需创建一个类似的结构:struct sockaddr_in(in”为“Internet
)

 

  这个结构使套接字地址的参考基础变得容易。注意sin_zero(包含在结构中的指示struct sockaddr的长度)应该使用函数memset()全部设为0。同时,这是一个重要的位,这个指示器指出sockaddr_in结构能计算出sockaddr结构的指针。所以socket()需要一个struct sockaddr*,你仍然可以使用struct sockaddr_in,并在最后一分钟算出它!同时,注意sin_familystruct sockaddr中协调sa_family,应该被设置为“AF_INET”。最终,sin_portsin_addr必须在网络字节序列中!

   

  好,它被用于联合,但现在不是了。很好的解除了。所以如果你声明inastruct sockaddr_in中,然后ina.sin_addr.s_addr涉及一个4-字节的IP地址(在网络字节序列中)。注意即使这样,如果你的系统为struct in_addr仍然使用God-awful联合,你仍然可以像我上面一样(这需要#defines)以相同的方法涉及4-字节IP地址。

 

3.3           转换和本地

 

 我们现在进入下一章的学习。讨论了太多的关于网络到主机字节序列转换,现在可以行动了。

 

 是的,这有两种类型你可以转换:short (two bytes) and long (four bytes)。这些函数可以无符号变量下工作。如果你想从主机字节序列转换到short。为“host“以”h“开始,紧跟着“to“,然后为“network”加“n”,为“short”加“s”:h-to-n-s,或htons()(读作:“host to network short”)。

 

 这个很简章……

 

 如果你想,“n”,“h”,“s”和“l”,你可以使用所有的组合,不能计算真实的结构。例如,这儿没有stolh()(“short to long host”)函数,没有这部分,无论如何,但有:

  

 现在,你可以想你了解了这些。你可能会想,“如果我想在一个字节中改变字节序列,应该做什么?”然后你可能会想,“哦,没关系。”你或许也会想因为你的68000机器准备使用网络字节序列,你没有必要在你的IP地址中调用htonl()。你是正确的,但如果你尝试连接已经反转了网络字节序列的机器,你的程序将失败。方便的!这是Unix的世界!(否则 Bill Gates 将这么想)记住:在你将它们放入网络前,将你的字节放入网络字节序列中。

 

 最终要点:为什么使用sin_addrsin_port需要在网络字节序列的struct sockaddr_in中,但sin_family不能?答案是:sin_addrsin_port取得在包中取得压缩分别在IPUDP层,因此,他们必须在网络字节序列中,不论如何,sin_family字段中能用于内核来决定结构中包括的地址类型,所以必须在主机字节序列中。同时,因为sin_family没有取得在网络中发送,综可以在主机字节序列中。

 

3.4           IP地址和和怎么去处理他们

 

  你是幸运的,这儿有一批函数让你去操作IP地址。不需要手工地计算,填充他们长期使用操作《。

 

 首先,你有一个struct sockaddr_in ina,你有一个IP地址“10.12. 110.57 ,你想把这个IP存储在这个结构中。你想使用函数inet_addr(),转换IP地址从数据-点符号到无符号长整型。过程如下:

      

 注意inet_addr()返回网络字节序列中的地址,你不一定要调用htonl()。增大!

 

 现在,以上的代码片断不是足够的,因为没有错误检查。如,inet_addr()返回-1表示错误。记得二进制数字?(unsigned-1只发出在协调IP地址255.255.255.255!那是一个广播地址!记得使用合适的错误检查。

 

 事实上,有一个清楚的接口,你可以用来代替inet_addr():它被 inet_aton()调用(“aton”意思为“ascii to network”)

  当你包装struct sockaddr_in,这里有简单的用法,(当你想在bind()connect()中取得片断时,这个例子将提供给你更多的含意。)

 

  Inet_aton(),不像实际上的每一个其它的socket-related函数,返回非零表示成功,返回零表示失败。地址在inp中传递。

 

  不幸地是,不是所有平台实现inet_aton()都是这样的,虽然它的使用是更好的,以前的更一般在这个指南中使用inet_addr()

 

 好的,现在你能转换字符串IP地址到它们的二进制表示。还有其它的方法吗?如果你有struct in_addr,你想以数字-点符号打印它?在这种情况下,你想使用函数inet_ntoa()(“ntoa”意思为:“network to ascii”)如下:

  

  这将打印IP地址。注意inet_ntoa() 以参数的形式取得struct in_addr,不是long,同时注意它返回一个字符型指针。这个指针指向在inet_ntoa()中的一个静态存储的字符数组,所以当你调用 inet_ntoa(),它将写上你要求的IP地址。例如:

    

  如果你需要保存地址,strcpy()到你的字符数组中。

 

  这是现在所有现在所讨论的话题。下面,你将学到转换一个字符,如:“whitehouse.gov”,到它的通讯的IP地址(查看DNS,下面)。

 

4.  系统调用或Bust

 

 

4.12       socket() – 取得文件描述符!

 

4.13       bind()  -- 我在使用什么端口?

 

4.14       connect() -- 喂,你!

 

4.15       listen() – 某人请Call我?

 

4.16       accept() – “感谢你呼叫端口 3490

 

4.17       send() recv() – 宝贝,与我交谈!

 

 

4.18       Sendto() recvfrom() – 与我交谈,DGRAM-类型

 

 

4.19       Close() shutdown() – 取下我的脸!

 

4.20       Getpeername() – 你是谁?

 

 

4.21       Gethostname() – 我是谁?

 

 

4.22       域名服务器DNS – 你说“whitehouse.gov”, 我说“198.137. 240.92”

 

5.  客户-服务器后台

 

5.4           一个简单的流式服务

 

5.5           一个简单的流式客户

 

5.6           数据报套接字

 

6.  简单的高级的技术

 

6.5           阻塞

 

6.6           Select() – 异步 I/O 多路技术

 

6.7           处理局部send()s

 

6.8           数据封闭因子

 

 

7.更多参考

 71   操纵页

 72  

 73   Web参考

 74   RFCs

8.常见问题

9.拒绝和请求帮助

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值