版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五版装修工程合同范本:合同生效与解除条件2篇
- 2024跨区域电网工程建设与运营管理合同
- 二零二五版家居行业导购员聘用与考核合同3篇
- 二零二五年餐饮行业食堂承包合作协议范本3篇
- 二零二五版家庭住家保姆综合能力培训聘用合同3篇
- 2025年度新能源出租车特许经营合同3篇
- 二零二五年度跨境电商进口商品代理销售合同9篇
- 二零二五年股权质押贷款担保合同3篇
- 二零二五按揭房离婚财产分割与子女监护协议范本3篇
- 2024淘宝店铺加盟合作协议范本3篇
- 2025新北师大版英语七年级下单词表
- 2024公路沥青路面结构内部状况三维探地雷达快速检测规程
- 《智慧城市概述》课件
- 2024年北京市家庭教育需求及发展趋势白皮书
- GB/T 45089-20240~3岁婴幼儿居家照护服务规范
- 中建道路排水工程施工方案
- 拆机移机合同范例
- 智能停车充电一体化解决方案
- 化学验室安全培训
- 天书奇谭美术课件
- GB/T 18916.15-2024工业用水定额第15部分:白酒
评论
0/150
提交评论