zoukankan      html  css  js  c++  java
  • 网络编程容易出错点-动手才知道

           一般认为网络编程就是socket编程,实际上,socket编程并不只是满足网络间不同主机之间的通信,它也能实现同一台主机上不同进程间的通信需求。其体现在创建socket时的参数的不同:

            int socket(int domain,int type,int protocol);

            对于网络间的通信,domainAF_INET,其创建的socket需要通过IP和端口来进行标识;对于同一台主机的进程间通信,其domainAF_UNIX,其创建的socket则通过文件名来标识。

            对于网络编程初学者而言,网络编程容易出错的地方包括以下:

    1. socket有三种类型,即创建socket接口的第二个参数,         SOCK_STREAM代表TCP,即代表该socket可以像文件字节流那样操作;SOCK_DGRAM代表UDP,其是数据报文方式,在内核中抽象为类似消息队列一样管理;SOCK_RAW是原始socket类型,PING命令就是使用这种类型。我们在创建socket的时候一定要注意这个参数的设置,否则会出现难以捉摸的场景。初学者往往在练习TCP编程后直接拷贝代码改为UDP进行操作,这时如果不改这个类型,那是不能调试成功的。

    2. socket接口的传参问题,先来看以下接口:

    l   int bind (int sockfd, struct sockaddr* addr, int addrLen);

    l   int accept(int sockfd, struct sockaddr *addr, int *addrlen) ;

    l   int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

    l   ssize_t sendto(int socket, void *message, size_t length, int flags, struct sockaddr *dest_addr, int dest_len);

    l   ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, int  *address_len);

    1)  以上接口的最后一个参数都是前一个sockaddr类型参数的字节长度,sockaddr类型参数是一个结构体,包含IP地址和PORT端口。从中可以看到在addrlen在某些接口里面的传参是值传递(如bindconnectsendto),而在acceptrecvfrom接口是地址传递。按一般的思维,地址传递是为了在接口中去改变这个地址所在的值,但是,这里的addrlen在以上接口实现中都不能被改进,它是用来告诉接口前一个参数的字节长度,所以以上所有接口在调用前这个addrlen都要被初始化,如addrlen = sizeof(struct sockaddr), 然后再根据接口的传递要求(值传递还是地址传递)进行传参。

    2)  对于str uct sockaddr* addr这个参数,接口中全部都是地址传递,但意义并不一样。

            Bind接口是服务端的socket绑定自己的地址(IPport),所有addr应该是要先初始化再传参;

            Connect接口是客户端的socket去连接服务端,而接口中的addr就是服务端的地址,因此连接前也要进行初始化;

            SendtoUDP通信模式时源端(客户端/服务器)向对端(服务器/客户端)发送内容,由于UDP在通信前并未建立起连接,所有每次发送内容都要明确指明对端的地址,因此这个接口的addr也要先初始化再调用。

            AcceptTCP模式中处于监听状态的服务端socket去接受客户端的连接,而这个接口的addr就是在连接成功时由系统内核调用填上客户端的地址信息,因此这个addr并不需要初始化;特别地,如果服务端的用户不想知道客户端的详细地址信息,那就传一个NULL进去,表示不care这个信息。如果accpet接口里面不想知道客户端的地址,那它又怎么能把数据传递回给客户端,那是因为accept返回的新的通信socket对应的内核数据结构已经记录到客户端的信息了。前面说服务端传NULL进去不care这个信息,只是说用户不想得到这个信息去做某些事情,例如打印地址信息等等,但对于内核维护的socket对应的数据结构,它是一定会记录客户端的地址信息的。Socket提供一个接口:getpeername(conn_socket_fd, struct sockaddr*),就能够获取对端的地址信息,其跟accept接口直接获得对方的地址是等价的。

             RecvfromUDP模式接收内容的接口,由于UDP模式在通信前不建立连接,要想在收到信息后给对方发送消息,Recvfrom收到信息时就必须要知道对端的地址,addr就是为了获取对端的地址信息的。因此其在使用前不需要初始化。

    特别地,这里所讲的初始化是指初始化为有意义的地址值,如包括实际的IP和端口。对于不需初始化的addr,其在传参前应该将内存清0

    3. sendrecvTCP模式的接口,由于TCP在通信前已经建立起连接,即底层的socket内核数据结构已经记录对端的地址和本段的地址信息,因此sendrecv接口不需传递对端的地址信息。

             sendtorecvfromUDP模式的接口,通信前没有建立连接,所以每次都要填上/获取对方的地址。

         两种模式的发送和接收接口是一一对应的,不能混用。

         由于TCP在底层数据结构中是抽象为文件字节流来操作,所以可以用一般的readwrite操作,其跟recvsend是相对应的,实现同样的功能,理论上是可以相互替代的,但为了代码的可维护性,仍然要遵循对应原则。

    4. connect的阻塞问题。默认创建的socket都是阻塞的,connect也不例外,但在实践的过程中发现connect有时能够立即返回失败,并不阻塞。其原因是:练习时往往是在虚拟机上开一个终端启动客户端程序,其程序中是在连接本机(127.0.0.1)的某个端口,服务端未启动时,即该端口并未处于监听状态,因此客户端的connect能够立即返回失败,即其底层能够立刻得到失败的回复。阻塞的意义是一直等到有意义的应答,失败同样是一种有意义的应答,而并不是说一定要等到服务器来连接它。如果客户端程序去connect一个不存在的IP或者是经过多层路由的IP,那数据包会因为超时(IP包有一个段是标记最大存在时间的,超过这个时间的数据包会被直接丢弃)而返回,这时就可以发现connect确实是阻塞的。

    5. int listen (int sockfd, int backlog) 监听接口的第二个参数表示sockfd的监听队列里面最大能够存在的客户端连接数,有客户端来连接connect,那客户端的socket就会被添加到服务端的监听队列里面,accept接口则是服务端从监听队列里面摘走一个socket,表示接受这个客户端的请求。所以backlog代表服务端暂时未能处理(accept)而存在于监听队列的最大连接数。如果服务端能够及时处理accept,那任意个客户端都能被连接进来,当前前提是不超过linux系统的规定数。很多人以为backlog是指最大允许这么多个客户来跟服务端通信,实际上理解错误的。可以这样做实验,服务端listen之后死循环,不再accept,看这时有多少个连接能够connect成功。

    特别地,不能在同一台机启动客户端和服务端,因为同一台机并没有进行三次握手,connect总是能够返回成功,不受限于backlog

  • 相关阅读:
    【codecombat】 试玩全攻略 第二章 边远地区的森林 一步错
    【codecombat】 试玩全攻略 第十八关 最后的kithman族
    【codecombat】 试玩全攻略 第二章 边远地区的森林 woodlang cubbies
    【codecombat】 试玩全攻略 第二章 边远地区的森林 羊肠小道
    【codecombat】 试玩全攻略 第十七关 混乱的梦境
    【codecombat】 试玩全攻略 第二章 边远地区的森林 林中的死亡回避
    【codecombat】 试玩全攻略 特别关:kithguard斗殴
    【codecombat】 试玩全攻略 第二章 边远地区的森林 森林保卫战
    【codecombat】 试玩全攻略 第二章 边远地区的森林
    实验3 类和对象||
  • 原文地址:https://www.cnblogs.com/yueqian-scut/p/3952309.html
Copyright © 2011-2022 走看看