SOCKET及其编程_第1页
SOCKET及其编程_第2页
SOCKET及其编程_第3页
SOCKET及其编程_第4页
SOCKET及其编程_第5页
已阅读5页,还剩30页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、SOCKET及其编程1. 从套接字上得到扩展的更为可靠的出错信息在前一篇中,我们提到在对端主机上没有创建指定的UDP套接字时,我们向其发送一个UDP包,会得到一个目的端口不可达的ICMP出错报文。但内核在处理完该报文后,给应用程序仅仅返回一个ECONNREFUSED错误号,所以应用程序能知道的全部信息就是连接被拒绝,至于为什么被拒绝,没有办法知道。我们可以通过套接字选项的设置,让内核返回更为详细的出错信息,以利于调试程序,发现问题。下面是通过套接字选项传递扩展出错信息的一个示例程序。关于内核原理的分析,在下一篇给出。#include <sys/socket.h>#include &

2、lt;linux/types.h>#include <linux/errqueue.h>#include <sys/ioctl.h>#include "my_inet.h"#include <stdio.h>#include <errno.h>#include <string.h>#include <arpa/inet.h>#include <unistd.h>int ip_control_msg( struct cmsghdr *msg )    int

3、 ret = 0;    switch( msg->cmsg_type )    case IP_RECVERR:                    struct sock_extended_err *exterr;           

4、exterr = (struct sock_extended_err *)(CMSG_DATA(msg);            printf("ee_errno: %un", exterr->ee_errno );            printf("ee_origin: %un", exterr->ee_origin )

5、;            printf("ee_type: %un", exterr->ee_type );            printf("ee_code: %un", exterr->ee_code );         

6、60;  printf("ee_pad: %un", exterr->ee_pad );            printf("ee_info: %un", exterr->ee_info );            printf("ee_data: %un", exterr->ee_dat

7、a );                ret = -1;        break;    default:        break;        return ret;int control_msg( struct msg

8、hdr *msg )    int ret = 0;    struct cmsghdr *control_msg = CMSG_FIRSTHDR( msg );    while( control_msg != NULL )        switch( control_msg->cmsg_level )        case SOL_IP: &#

9、160;          ret = ip_control_msg( control_msg );            break;        default:            break; &#

10、160;              control_msg = CMSG_NXTHDR( msg, control_msg );        return ret;int main()    int i;    struct sockaddr_in dest;    dest.sin_family = MY_PF_INET;&

11、#160;   dest.sin_port = htons(16000);    dest.sin_addr.s_addr = 0x013010AC;    int fd = socket( MY_PF_INET, SOCK_DGRAM, MY_IPPROTO_UDP );    if( fd < 0 )        perror("socket: ");   

12、;     return -1;        if( connect( fd, (struct sockaddr*)&dest, sizeof(dest) ) < 0 )        perror("connect: ");        return -1;      

13、  int val = 1;    if( setsockopt( fd, SOL_IP, IP_RECVERR, &val, sizeof(val) ) = -1 )        perror("setsockopt: ");        return -1;        int bwrite = send( fd, &q

14、uot;abcdefg", 7, 0 );    if( bwrite = -1 )        perror("send: ");        return -1;        char buf1024;    char control_buf1024;    struc

15、t msghdr msg;    struct iovec iov = buf, 1024 ;    memset( &msg, 0, sizeof(msg) );    msg.msg_iov = &iov;    msg.msg_iovlen = 1;    msg.msg_control = &control_buf;    msg.msg_controllen = 1024; 

16、;   int bread = recvmsg( fd, &msg, MSG_ERRQUEUE );    if( bread = -1 )        perror("recv: ");        return -1;        if( control_msg( &msg ) >= 0 )

17、60;       printf("successed!n");    else        printf("failed!n");    close( fd );    return 0;执行结果:    ee_errno: 111      &

18、#160;    /ECONNREFUSED    ee_origin: 2            /SO_EE_ORIGIN_ICMP    ee_type: 3              /目的不可达    ee_code: 3       &#

19、160;      /端口不可达    ee_pad: 0    ee_info: 0    ee_data: 0    failed!2. 从套接字上得到扩展的更为可靠的出错信息(续)    接着前一篇,我们来看这个应用程序背后,内核真正做了一些什么事情。    代表MY_INET域套接字的结构体struct inet_sock有一个成员recverr,它占1bit长度,可能的取值是1

20、或0,当为0时表示socket上出错时,只通过系统调用向应用程序返回错误号,不提供进一步的详细信息。当取值为1时,则表示socket上出错时,则向struct inet_sock的成员sk_error_queue(一个sk_buff的队列)存入一个特殊的struct sk_buff,在sk_buff的成员cb中放入详细的错误信息,应用程序通过特定的系统调用可以取得详细的出错信息。    recverr的值可以通过套接字选项操作进行设置,它是一个IP层的选项,对应的选项名是IP_RECVERR。下面的代码就是将它的值设为1(打开选项):  

21、60;     int val = 1;        if( setsockopt( fd, SOL_IP, IP_RECVERR, &val, sizeof(val) ) = -1 )            ;/deal with error    当打开了这个选项后,我们在该socket上发送UDP数据报,按照前面文章提及的测试环境运

22、行,继续会收到ICMP目的不可达报文,在差错数据报处理时,会达到函数myudp_err,该函数会设置socket的成员sk_err,同时,它也会检查recverr成员,如果为1,则要在sk_error_queue队列中放入一个特殊的出错信息sk_buff。该sk_buff保留了出错的那个源UDP数据报,同时在它的cb成员中保存了一个结构体struct sock_exterr_skb,该结构体记录了详细的出错信息,下面是其定义:        struct sock_exterr_skb 

23、60;                  union             struct inet_skb_parm    h4;#if defined(CONFIG_IPV6) | defined (CONFIG_IPV6_MODULE)     

24、;       struct inet6_skb_parm   h6;#endif            header;            struct sock_extended_err    ee;     &#

25、160;      u16             addr_offset;            u16             port;     

26、;   ;    addr_offset和port是出错UDP数据报的地址和端口号,ee的定义如下:    struct sock_extended_err            _u32   ee_errno;       /错误号。        _u8 

27、   ee_origin;      /产生错误的源,我们的环境下,产生错误的源为一个ICMP包。        _u8    ee_type;        /ICMP类型。        _u8    ee_code;   &

28、#160;    /ICMP代码。        _u8    ee_pad;        _u32   ee_info;        /用于EMSGSIZE时找到的MTU。        _u32   ee_dat

29、a;    ;    我们保存了出错信息,应用程序要取得这个出错信息,必须使用特定的系统调用,recvmsg可以获得详细的出错信息,同时,调用接口上必须使用标志MSG_ERRQUEUE表示取错误队列,下面是recvmsg的定义:        ssize_t recvmsg(int s, struct msghdr *msg, int flags);    flags置MSG_ERRQUEUE,msg结构控制信息成员msg_contro

30、l和msg_controllen需要分配一个缓存,用于辅助信息的传递。关于接收,可以查看前面一篇的源代码和man recvmsg,这里不再重复。3. 用于表示socket的结构体(1)用户使用socket系统调用编写应用程序时,通过一个数字来表示一个socket,所有的操作都在该数字上进行,这个数字称为套接字描述符。在系统调用的实现函数里,这个数字就会被映射成一个表示socket的结构体,该结构体保存了该socket的所有属性和数据。在内核的协议中实现中,关于表示socket的结构体,是一个比较复杂的东西,下面一一介绍。    struct socket。

31、0;   这是一个基本的BSD socket,我们调用socket系统调用创建的各种不同类型的socket,开始创建的都是它,到后面,各种不同类型的socket在它的基础上进行各种扩展。struct socket是在虚拟文件系统上被创建出来的,可以把它看成一个文件,是可以被安全地扩展的。下面是其完整定义:    struct socket         socket_state        &

32、#160;   state;        unsigned long           flags;        const struct proto_ops  *ops;        struct fasync_struct  &

33、#160; *fasync_list;        struct file             *file;        struct sock             *sk;   

34、;     wait_queue_head_t       wait;        short                   type;    ;    state用于表示socke

35、t所处的状态,是一个枚举变量,其类型定义如下:    typedef enum         SS_FREE = 0,            /该socket还未分配        SS_UNCONNECTED,         /未连向

36、任何socket        SS_CONNECTING,          /正在连接过程中        SS_CONNECTED,           /已连向一个socket       

37、SS_DISCONNECTING        /正在断开连接的过程中    socket_state;    该成员只对TCP socket有用,因为只有tcp是面向连接的协议,udp跟raw不需要维护socket状态。    flags是一组标志位,在内核中并没有发现被使用。    ops是协议相关的一组操作集,结构体struct proto_ops的定义如下:    st

38、ruct proto_ops         int     family;        struct module   *owner;        int (*release)(struct socket *sock);        int (*bin

39、d)(struct socket *sock, struct sockaddr *myaddr, int sockaddr_len);        int (*connect)(struct socket *sock, struct sockaddr *vaddr, int sockaddr_len, int flags);        int (*socketpair)(struct socket *sock1, struct socket *soc

40、k2);        int (*accept)(struct socket *sock,struct socket *newsock, int flags);        int (*getname)(struct socket *sock, struct sockaddr *addr,int *sockaddr_len, int peer);        unsigned in

41、t (*poll)(struct file *file, struct socket *sock,                        struct poll_table_struct *wait);        int (*ioctl)(struct socket *sock, uns

42、igned int cmd, unsigned long arg);        int (*listen)(struct socket *sock, int len);        int (*shutdown)(struct socket *sock, int flags);        int (*setsockopt)(struct socket *sock, int le

43、vel,                        int optname, char _user *optval, int optlen);        int (*getsockopt)(struct socket *sock, int level,   &#

44、160;                    int optname, char _user *optval, int _user *optlen);        int (*sendmsg)(struct kiocb *iocb, struct socket *sock,     &

45、#160;                  struct msghdr *m, size_t total_len);        int (*recvmsg)(struct kiocb *iocb, struct socket *sock,         

46、0;              struct msghdr *m, size_t total_len, int flags);        int (*mmap)(struct file *file, struct socket *sock,struct vm_area_struct * vma);        ssize_

47、t (*sendpage)(struct socket *sock, struct page *page,                        int offset, size_t size, int flags);    ;    协议栈中总共定义了三个strcut proto_ops类型的变量,

48、分别是myinet_stream_ops, myinet_dgram_ops, myinet_sockraw_ops,对应流协议, 数据报和原始套接口协议的操作函数集。    type是socket的类型,对应的取值如下:    enum sock_type         SOCK_DGRAM  = 1,        SOCK_STREAM = 2,   

49、;     SOCK_RAW    = 3,        SOCK_RDM    = 4,        SOCK_SEQPACKET  = 5,        SOCK_DCCP   = 6,     &#

50、160;  SOCK_PACKET = 10,    ;    sk是网络层对于socket的表示,结构体struct sock比较庞大,这里不详细列出,只介绍一些重要的成员,    sk_prot和sk_prot_creator,这两个成员指向特定的协议处理函数集,其类型是结构体struct proto,该结构体也是跟struct proto_ops相似的一组协议操作函数集。这两者之间的概念似乎有些混淆,可以这么理解,struct proto_ops的成员操作struct socket层次上的

51、数据,处理完了,再由它们调用成员sk->sk_prot的函数,操作struct sock层次上的数据。即它们之间存在着层次上的差异。struct proto类型的变量在协议栈中总共也有三个,分别是mytcp_prot,myudp_prot,myraw_prot,对应TCP, UDP和RAW协议。    sk_state表示socket当前的连接状态,是一个比struct socket的state更为精细的状态,其可能的取值如下:    enum        

52、 TCP_ESTABLISHED = 1,        TCP_SYN_SENT,        TCP_SYN_RECV,        TCP_FIN_WAIT1,        TCP_FIN_WAIT2,        TCP_TIME_W

53、AIT,        TCP_CLOSE,        TCP_CLOSE_WAIT,        TCP_LAST_ACK,        TCP_LISTEN,        TCP_CLOSING,   &#

54、160;    TCP_MAX_STATES    ;    这些取值从名字上看,似乎只使用于TCP协议,但事实上,UDP和RAW也借用了其中一些值,在一个socket创建之初,其取值都是TCP_CLOSE,一个UDP socket connect完成后,将这个值改为TCP_ESTABLISHED,最后,关闭sockt前置回TCP_CLOSE,RAW也一样。    sk_rcvbuf和sk_sndbuf分别表示接收和发送缓冲区的大小。sk_receive_queue和sk_w

55、rite_queue分别为接收缓冲队列和发送缓冲队列,队列里排列的是套接字缓冲区struct sk_buff,队列中的struct sk_buff的字节数总和不能超过缓冲区大小的设定。4.用于表示socket的结构体(2)接着上一篇,继续介绍struct sock。    sk_rmem_alloc, sk_wmem_alloc和sk_omem_alloc分别表示接收缓冲队列,发送缓冲队列及其它缓冲队列中已经分配的字节数,用于跟踪缓冲区的使用情况。    struct sock有一个struct sock_common成员,因为str

56、uct inet_timewait_sock也要用到它,所以把它单独归到一个结构体中,其定义如下:    struct sock_common         unsigned short      skc_family;        volatile unsigned char  skc_state;     &#

57、160;  unsigned char       skc_reuse;        int         skc_bound_dev_if;        struct hlist_node   skc_node;      

58、;  struct hlist_node   skc_bind_node;        atomic_t        skc_refcnt;        unsigned int        skc_hash;      

59、  struct proto        *skc_prot;    ;    struct inet_sock。    这是INET域专用的一个socket表示,它是在struct sock的基础上进行的扩展,在基本socket的属性已具备的基础上,struct inet_sock提供了INET域专有的一些属性,比如TTL,组播列表,IP地址,端口等,下面是其完整定义:    

60、60;   struct inet_sock             struct sock     sk;#if defined(CONFIG_IPV6) | defined(CONFIG_IPV6_MODULE)            struct ipv6_pinfo   *pinet6;#e

61、ndif            _u32           daddr;          /IPv4的目的地址。            _u32   

62、60;       rcv_saddr;      /IPv4的本地接收地址。            _u16           dport;          /目的端口。 &

63、#160;          _u16           num;            /本地端口(主机字节序)。            _u32   &#

64、160;       saddr;          /发送地址。            _s16           uc_ttl;         /单播的

65、ttl。            _u16           cmsg_flags;            struct ip_options   *opt;        

66、60;   _u16           sport;          /源端口。            _u16           id;   

67、          /单调递增的一个值,用于赋给iphdr的id域。            _u8            tos;            /服务类型。 &

68、#160;          _u8            mc_ttl;         /组播的ttl            _u8      &

69、#160;     pmtudisc;            _u8            recverr:1,                   

70、         is_icsk:1,                            freebind:1,           &

71、#160;                hdrincl:1,      /是否自己构建ip首部(用于raw协议)                        

72、;    mc_loop:1;      /组播是否发向回路。            int             mc_index;       /组播使用的本地设备接口的索引。    

73、        _u32           mc_addr;        /组播源地址。            struct ip_mc_socklist   *mc_list;   /组播组列表。

74、0;           struct                 unsigned int        flags;             

75、   unsigned int        fragsize;                struct ip_options   *opt;                struct

76、 rtable       *rt;                int                 length;         

77、       u32                 addr;                struct flowi        fl; 

78、60;          cork;        ;    struct raw_sock    这是RAW协议专用的一个socket的表示,它是在struct inet_sock基础上的扩展,因为RAW协议要处理ICMP协议的过滤设置,其定义如下:    struct raw_sock      

79、;   struct inet_sock   inet;        struct icmp_filter filter;    ;    struct udp_sock    这是UDP协议专用的一个socket表示,它是在struct inet_sock基础上的扩展,其定义如下:    struct udp_sock    

80、0;    struct inet_sock inet;        int             pending;        unsigned int    corkflag;        _u16

81、           encap_type;        _u16           len;    ;    struct inet_connection_sock    看完上面两个,我们觉得第三个应该就是struct tcp_so

82、ck了,但事实上,struct tcp_sock并不直接从struct inet_sock上扩展,而是从struct inet_connection_sock基础上进行扩展,struct inet_connection_sock是所有面向连接的socket的表示,关于该socket,及下面所有tcp相关的socket,我们在分析tcp实现时再详细介绍,这里只列出它们的关系。    strcut tcp_sock    这是TCP协议专用的一个socket表示,它是在struct inet_connection_sock基础进行扩展,主

83、要是增加了滑动窗口协议,避免拥塞算法等一些TCP专有属性。    struct inet_timewait_sock    struct tcp_timewait_sock    在struct inet_timewait_sock的基础上进行扩展。    struct inet_request_sock    struct tcp_request_sock    在struct inet_request_sock的

84、基础上进行扩展。5.创建一个socket一个socket代表了通信链路的一端,存储或指向与链路有关的所有信息。Linux提供了创建socket的一个系统调用,通过该系统调用,能够得到一个用来访问套接字的描述符:         #include <sys/types.h>         #include <sys/socket.h>      

85、0;  int socket( int domain, int type, int protocol );      内核中的系统调用函数原型是在net/socket.c 1180行:         asmlinkage long sys_socket( int family, int type, int protocol );      该函数主要做了两件事情:创建一个代表通讯端点的结构体st

86、ruct socket,将这个结构映射到一个文件描述符上,最后将这个描述符返回,也就是我们调用socket得到的套接字描述符。 下面是Linux内核中对结构socket的定义(不同操作系统间,对该结构的定义会有差异): struct socket      socket_state            state;     unsigned long   

87、        flags;     struct proto_ops        *ops;     struct fasync_struct    *fasync_list;     struct file        &

88、#160;    *file;     struct sock             *sk;     wait_queue_head_t       wait;     short       &

89、#160;           type;                 state是一个内部状态标志:       typedef enum            SS_FRE

90、E = 0,         /* 未分配 */           SS_UNCONNECTED,      /* 未连接 */           SS_CONNECTING,       /* 正在连接当

91、中 */           SS_CONNECTED,        /* 已经连向一个套接字 */           SS_DISCONNECTING     /* 正在断开连接 */       socket_state;

92、       flags也是一个标志,下面是它的取值:      #define SOCK_ASYNC_NOSPACE 0      #define SOCK_ASYNC_WAITDATA 1      #define SOCK_NOSPACE  2      #define SOCK_PASSCRED

93、60; 3            ops是协议相关的一系例操作的集合,包括listen, bind, connect等常用socket操作,struct proto_ops结构体在include/linux/net.h 123行。            fasync_list是一个异步唤醒的列表,结构体struct fasync_struct在include/linux/fs.

94、h 733行            sk是一个网络层的套接字表示,关于结构体struct sock,下文会有专门介绍。      type是套接字的类型:      enum sock_type            SOCK_STREAM = 1,   &#

95、160;    /*可靠字节流服务套接字,TCP*/           SOCK_DGRAM = 2,  /*传输层数据报服务, UDP*/           SOCK_RAW = 3,  /*网络层数据报服务, ICMP, IGMP,  原始IP*/   

96、0;       SOCK_RDM = 4,  /*可靠的数据报服务*/           SOCK_SEQPACKET = 5, /*可靠的双向记录流服务*/           SOCK_PACKET = 10,  /*已废弃*/  

97、    ;       暂时放一下struct sock,先来看看sys_socket的第一步创建struct socket中究竟做了些什么(描述越过了一些不是很重要的步骤):      首先,检查传入的用来标识域的协议族变量family是否在合法范围内,关于family,我们只关心其中的几个值,PF_INET表示因特网协议,PF_UNIX是unix文件系统套接字。      然后,对于(family =

98、PF_INET && type = SOCK_PACKET )的情况,因为是已废弃的,给出警告信息。      net_families是一个数组,所有的协议族都在这个数组中注册,数组的项是一个结构体:      struct net_proto_family            int  family;    

99、0;      int  (*create)(struct socket *sock, int protocol);           short  authentication;           short  encryption;    &

100、#160;      short  encrypt_net;           struct module *owner;      ;       对于我们要创建的family,我们必须确保能在这个数组中找到相应的项(即内核支持该域)。      

101、; 在内存中创建一个struct socket,并将其type赋值为传入的type值。       调用net_familiesfamily->create完成最后的创建工作。返回。       至此,一个socket就创建成功了。但还有两个问题没有明确:struct sock结构体的内容,以及net_familiesfamily->create如何完成对socket的创建。下一篇将结合inet域的实际例子进行分析。6. struct sock详解结构体sock是

102、套接口在网络层的表示,在代码include/net/sock.h 174行定义,下面是其内容:    struct sock         struct sock_common  _sk_common;#define sk_family       _sk_common.skc_family#define sk_state        _sk_commo

103、n.skc_state#define sk_reuse        _sk_common.skc_reuse#define sk_bound_dev_if     _sk_common.skc_bound_dev_if#define sk_node         _sk_common.skc_node#define sk_bind_node     &#

104、160;  _sk_common.skc_bind_node#define sk_refcnt       _sk_common.skc_refcnt        unsigned char       sk_shutdown : 2,             

105、60;              sk_no_check : 2,                            sk_userlocks : 4;   

106、0;    unsigned char       sk_protocol;        unsigned short      sk_type;        int         sk_rcvbuf;  

107、0;     socket_lock_t       sk_lock;        wait_queue_head_t   *sk_sleep;        struct dst_entry    *sk_dst_cache;      

108、0; struct xfrm_policy  *sk_policy2;        rwlock_t        sk_dst_lock;        atomic_t        sk_rmem_alloc;        ato

109、mic_t        sk_wmem_alloc;        atomic_t        sk_omem_alloc;        struct sk_buff_head sk_receive_queue;        struct

110、sk_buff_head sk_write_queue;        int         sk_wmem_queued;        int         sk_forward_alloc;        unsigne

111、d int        sk_allocation;        int         sk_sndbuf;        int         sk_route_caps;     

112、;   int         sk_hashent;        unsigned long       sk_flags;        unsigned long           sk_l

113、ingertime;        struct             struct sk_buff *head;            struct sk_buff *tail;        sk_backlog; 

114、0;      struct sk_buff_head sk_error_queue;        struct proto        *sk_prot;        struct proto        *sk_prot_creator; 

115、0;      rwlock_t        sk_callback_lock;        int         sk_err,                &

116、#160;   sk_err_soft;        unsigned short      sk_ack_backlog;        unsigned short      sk_max_ack_backlog;        _u32  &

117、#160;        sk_priority;        struct ucred        sk_peercred;        int         sk_rcvlowat;    

118、;    long            sk_rcvtimeo;        long            sk_sndtimeo;        struct sk_filter  

119、      *sk_filter;        void            *sk_protinfo;        struct timer_list   sk_timer;        struct

120、 timeval      sk_stamp;        struct socket       *sk_socket;        void            *sk_user_data;        struct page     *sk_sndmsg_pa

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论