Netlink通信机制

来源:互联网 发布:2010年nba科比数据 编辑:程序博客网 时间:2024/05/29 10:26

1、Netlink 机制简介

为了完成内核空间与用户空间通信,Linux提供了基于socket的Netlink通信机制,可以实现内核与用户空间数据的及时交换。Netlink是基于socket的通信机制,由于socket本身的双共性、突发性、不阻塞特点,因此能够很好的满足内核与用户空间小量数据的及时交互,因此在Linux 2.6内核中广泛使用,例如SELinux,Linux系统的防火墙分为内核态的netfilter和用户态的iptables,netfilter与iptables的数据交换就是通过Netlink机制完成。

netlink作为一种用户空间和内核空间通信的机制,它不光为了内核和用户通信,还可以作为IPC机制进行进程间通信。它也用到了sk_buff结构体,和网络套接字一样,更好的事情是它并没 有触及sk_buff里面的标准字段,而仅仅用了一个扩展的cb字段,cb在 sk_buff里面的定义是char cb[40];在netlink模块里面NETLINK_CB宏就是取cb字段的,也就是netlink所用的私有字段,这样的话你就可以用 netlink向任何执行实体传输任何数据了,不限于本机。

Linux操作系统中当CPU处于内核状态时,可以分为有用户上下文的状态和执行硬件、软件中断两种。其中当处于有用户上下文时,由于内核态和用户态的内 存映射机制不同,不可直接将本地变量传给用户态的内存区;处于硬件、软件中断时,无法直接向用户内存区传递数据,代码执行不可中断。针对传统的进程间通信 机制,他们均无法直接在内核态和用户态之间使用,原因如下:

通信方法 无法介于内核态与用户态的原因

管道(不包括命名管道) 局限于父子进程间的通信。

消息队列 在硬、软中断中无法无阻塞地接收数据。

信号量 无法介于内核态和用户态使用。

内存共享 需要信号量辅助,而信号量又无法使用。

套接字 在硬、软中断中无法无阻塞地接收数据。

Netlink相对于其他的通信机制具有以下优点:

(1)使用Netlink通过自定义一种新的协议并加入协议族即可通过socket API使用Netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。

(2)Netlink使用socket缓存队列,是一种异步通信机制,而ioctl是同步通信机制,如果传输的数据量较大,会影响系统性能。

(3)Netlink支持多播,属于一个Netlink组的模块和进程都能获得该多播消息。

(4)Netlink允许内核发起会话,而ioctl和系统调用只能由用户空间进程发起。

2、源码分析

2.1 linux\netlink.h

2.1.1 netlink 协议类型

#define NETLINK_ROUTE0/* Routing/device hook*/#define NETLINK_UNUSED1/* Unused number*/#define NETLINK_USERSOCK2/* Reserved for user mode socket protocols */#define NETLINK_FIREWALL3/* Firewalling hook*/#define NETLINK_INET_DIAG4/* INET socket monitoring*/#define NETLINK_NFLOG5/* netfilter/iptables ULOG */#define NETLINK_XFRM6/* ipsec */#define NETLINK_SELINUX7/* SELinux event notifications */#define NETLINK_ISCSI8/* Open-iSCSI */#define NETLINK_AUDIT9/* auditing */#define NETLINK_FIB_LOOKUP10#define NETLINK_CONNECTOR11#define NETLINK_NETFILTER12/* netfilter subsystem */#define NETLINK_IP6_FW13#define NETLINK_DNRTMSG14/* DECnet routing messages */#define NETLINK_KOBJECT_UEVENT15/* Kernel messages to userspace */#define NETLINK_GENERIC16/* leave room for NETLINK_DM (DM Events) */#define NETLINK_SCSITRANSPORT18/* SCSI Transports */#define NETLINK_ECRYPTFS19
可以根据自己的应用,创建新的通信协议。新协议的定义不能和内核已经定义的冲突,同时不能超过MAX_LINKS这个宏的限定,MAX_LINKS = 32。

2.1.2 netlink 地址格式

struct sockaddr_nl{sa_family_tnl_family;/* AF_NETLINK*/unsigned shortnl_pad;/* zero*/__u32nl_pid;/* port ID*/       __u32nl_groups;/* multicast groups mask */};
nl_pad 当前没有使用,因此要总是设置为 0,

nl_pid就是一个约定的通信端口,用户态使用的时候需要用一个非0的数字,一般来说可以直接采用上层应用的进程ID(未必是进程 ID,它只是用于区分不同的接收者或发送者的一个标识,用户可以根据自己需要设置该字段,只要是系统中不冲突的一个数字即可)。对于内核的地址,该值必须用0,即上层通过sendto向内核发送netlink消息,peer addr中nl_pid必须填写0。

nl_groups用于一个消息同时分发给不同的接收者,是一种组播应用。绑定时用于指定绑定者所要加入的多播组,这样绑定者就可以接收多播消息,发送 消息时可以用于指定多播组,这样就可以将消息发给多个接收者。这里nl_groups 为32位的无符号整形,所以可以指定32个多播组,每个进程可以加入多个多播组, 因为多播组是通过“或”操作,如果设置为 0,表示调用者不加入任何多播组。

本质上,nl_pid就是netlink的通信地址。除了通信地址,netlink还提供“协议”来标示通信实体,在创建socket的时候,需要指定netlink的通信协议号。每个协议号代表一种“应用”,上层可以用内核已经定义的协议和内核进行通信,获得内核已经提供的信息。netlink采用“协议号 + 通信端口”的方式构建自己的地址体系。

2.1.3 netlink 头部信息

struct nlmsghdr{__u32nlmsg_len;/* Length of message including header */__u16nlmsg_type;/* Message content */__u16nlmsg_flags;/* Additional flags */__u32nlmsg_seq;/* Sequence number */__u32nlmsg_pid;/* Sending process port ID */};
nlmsg_len 指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小。

nlmsg_type 用于应用内部定义消息的类型,它对netlink 内核实现是透明的,因此大部分情况下设置为 0。

nlmsg_seq 和 nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID。其中pid是Netlink分配的一个ID,不同的值代表不同的socket通道,默认的值是进程PID。在某些情况下,这个值被设置为0,比如消息来自内核空间,或者想要Netlink来设置这个值。

nlmsg_flags 用于设置消息标志,可用的标志包括:

/* Flags values */#define NLM_F_REQUEST1/* It is request message. */#define NLM_F_MULTI2/* Multipart message, terminated by NLMSG_DONE */#define NLM_F_ACK4/* Reply with ack, with zero or error code */#define NLM_F_ECHO8/* Echo this request *//* Modifiers to GET request */#define NLM_F_ROOT0x100/* specify treeroot*/#define NLM_F_MATCH0x200/* return all matching*/#define NLM_F_ATOMIC0x400/* atomic GET*/#define NLM_F_DUMP(NLM_F_ROOT|NLM_F_MATCH)/* Modifiers to NEW request */#define NLM_F_REPLACE0x100/* Override existing*/#define NLM_F_EXCL0x200/* Do not touch, if it exists*/#define NLM_F_CREATE0x400/* Create, if it does not exist*/#define NLM_F_APPEND0x800/* Add to end of list*/

2.1.4 netlink 在skb_buff中的对应信息

struct netlink_skb_parms{struct ucredcreds;/* Skb credentials*/__u32pid;__u32dst_group;kernel_cap_teff_cap;__u32loginuid;/* Login (audit) uid */__u32sessionid;/* Session id (audit) */__u32sid;/* SELinux security id */};

内核态发送数据的时候,在skb_buff存储对应的发送地址等信息,供用户态需要时使用。

2.1.5 netlink 相关的宏

#define NLMSG_ALIGNTO4#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(NLMSG_HDRLEN))#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))//获取nlmsghdr头部之后的数据#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \  (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \   (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \   (nlh)->nlmsg_len <= (len))#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
2.2 net\netlink\af_netlink.c

netlink的实现方法主要在 /net/netlink/af_netlink.c文件中。

2.2.1 netlink 套接字结构

struct netlink_sock {/* struct sock has to be the first member of netlink_sock */struct socksk;u32pid;//内核态pidu32dst_pid;u32dst_group;u32flags;u32subscriptions;u32ngroups;//组数unsigned long*groups;//组号unsigned longstate;wait_queue_head_twait;//等待队列struct netlink_callback*cb;struct mutex*cb_mutex;struct mutexcb_def_mutex;void(*netlink_rcv)(struct sk_buff *skb);//内核态接收用户态信息后的处理函数struct module*module;};
Groups : 对于每一个netlink协议类型,可以使用多播的概念,最多可以有 32个多播组,每一个多播组用一个位表示,netlink 的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多播消息的应用而言,大大地降低了系统调用的次数。

注意:用户态可以使用标准的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 等函数就能很容易地使用 netlink socket,在用户空间可以直接通过socket函数来使用Netlink通信,区别是:

(1)netlink有自己的地址;

创建socket,socket(PF_NETLINK, SOCK_DGRAM, NETLINK_XXX);第一个参数必须是PF_NETLINK或者AF_NETLINK(Linux中实际为一个东西,它表示要使用netlink),第二个参数用SOCK_DGRAM和SOCK_RAW都没问题,第三个参数就是netlink的协议号。

(2)netlink接收到的消息带一个netlink自己的消息头;

用户态发送、接收的时候,需要在自己附带的消息前面要加上一个netlink的消息头:

struct tag_rcv_buf      {              struct nlmsghdr hdr;            //netlink的消息头              netlink_notify_s my_msg;        //通信实体消息      }st_snd_buf;
2.2.2 创建netlink socket
struct sock *netlink_kernel_create(struct net *net, int unit, unsigned int groups,      void (*input)(struct sk_buff *skb),      struct mutex *cb_mutex, struct module *module){struct socket *sock;struct sock *sk;struct netlink_sock *nlk;unsigned long *listeners = NULL;BUG_ON(!nl_table);if (unit < 0 || unit >= MAX_LINKS)return NULL;if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))//创建socket 结构return NULL;/* * We have to just have a reference on the net from sk, but don't * get_net it. Besides, we cannot get and then put the net here. * So we create one inside init_net and the move it to net. */if (__netlink_create(&init_net, sock, cb_mutex, unit) < 0)//创建sock 结构goto out_sock_release_nosk;sk = sock->sk;sk_change_net(sk, net);if (groups < 32)groups = 32;listeners = kzalloc(NLGRPSZ(groups) + sizeof(struct listeners_rcu_head),    GFP_KERNEL);if (!listeners)goto out_sock_release;sk->sk_data_ready = netlink_data_ready;if (input)nlk_sk(sk)->netlink_rcv = input;//设置内核接收Netlink消息的函数if (netlink_insert(sk, net, 0))goto out_sock_release;nlk = nlk_sk(sk);//取得sock嵌入的netlink_sock结构体nlk->flags |= NETLINK_KERNEL_SOCKET;netlink_table_grab();if (!nl_table[unit].registered) {nl_table[unit].groups = groups;nl_table[unit].listeners = listeners;nl_table[unit].cb_mutex = cb_mutex;nl_table[unit].module = module;nl_table[unit].registered = 1;//更新netlink_table结构体信息,每种协议对应一个netlink_table结构} else {kfree(listeners);nl_table[unit].registered++;}netlink_table_ungrab();return sk;out_sock_release:kfree(listeners);netlink_kernel_release(sk);return NULL;out_sock_release_nosk:sock_release(sock);return NULL;}
内核态创建netlink socket的时候,调用 netlink_kernel_create() 函数。

2.2.3 内核态发送数据

内核态向用户态发送数据的时候,调用netlink_unicast() 函数。

int netlink_unicast(struct sock *ssk, struct sk_buff *skb,    u32 pid, int nonblock){struct sock *sk;int err;long timeo;skb = netlink_trim(skb, gfp_any());timeo = sock_sndtimeo(ssk, nonblock);retry:sk = netlink_getsockbypid(ssk, pid); //找到内核中对应该pid和SOCK_DGRAM协议的sock结构,挂载在哈希表中if (IS_ERR(sk)) {kfree_skb(skb);return PTR_ERR(sk);}if (netlink_is_kernel(sk))//若另一端也为内核端,调用其netlink_sock的input函数接受return netlink_unicast_kernel(sk, skb);if (sk_filter(sk, skb)) {err = skb->len;kfree_skb(skb);sock_put(sk);return err;}err = netlink_attachskb(sk, skb, &timeo, ssk);if (err == 1)goto retry;if (err)return err;return netlink_sendskb(sk, skb);//挂载到用户态sock结构的receive_queue队列中, 然后调用sk_data_ready函数,唤醒用户态睡眠函数}
2.2.4 内核态接收数据

在netlink_kernel_create函数中,可以设置接收函数。

2.2.5 用户态发送数据

static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock,   struct msghdr *msg, size_t len){struct sock_iocb *siocb = kiocb_to_siocb(kiocb);struct sock *sk = sock->sk;struct netlink_sock *nlk = nlk_sk(sk);struct sockaddr_nl *addr = msg->msg_name;//目的地址信息u32 dst_pid;u32 dst_group;struct sk_buff *skb;int err;struct scm_cookie scm;if (msg->msg_flags&MSG_OOB)return -EOPNOTSUPP;if (NULL == siocb->scm)siocb->scm = &scm;err = scm_send(sock, msg, siocb->scm);if (err < 0)return err;if (msg->msg_namelen) {if (addr->nl_family != AF_NETLINK)return -EINVAL;dst_pid = addr->nl_pid;dst_group = ffs(addr->nl_groups);if ((dst_group || dst_pid) &&    !netlink_capable(sock, NL_NONROOT_SEND))return -EPERM;} else {dst_pid = nlk->dst_pid;dst_group = nlk->dst_group;}if (!nlk->pid) {err = netlink_autobind(sock);if (err)goto out;}err = -EMSGSIZE;if (len > sk->sk_sndbuf - 32)goto out;err = -ENOBUFS;skb = alloc_skb(len, GFP_KERNEL);//分配一个sk_buff结构,目的是将msghdr结构转化为sk_buff结构if (skb == NULL)goto out;NETLINK_CB(skb).pid= nlk->pid;//本地的pid信息NETLINK_CB(skb).dst_group = dst_group;NETLINK_CB(skb).loginuid = audit_get_loginuid(current);NETLINK_CB(skb).sessionid = audit_get_sessionid(current);security_task_getsecid(current, &(NETLINK_CB(skb).sid));memcpy(NETLINK_CREDS(skb), &siocb->scm->creds, sizeof(struct ucred));/* What can I do? Netlink is asynchronous, so that   we will have to save current capabilities to   check them, when this message will be delivered   to corresponding kernel module.   --ANK (980802) */err = -EFAULT;if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) {//将数据拷贝到sk_buff中kfree_skb(skb);goto out;}err = security_netlink_send(sk, skb);if (err) {kfree_skb(skb);goto out;}if (dst_group) {atomic_inc(&skb->users);netlink_broadcast(sk, skb, dst_pid, dst_group, GFP_KERNEL);}err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);out:return err;}
2.2.6 用户态接收数据
static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,   struct msghdr *msg, size_t len,   int flags){struct sock_iocb *siocb = kiocb_to_siocb(kiocb);struct scm_cookie scm;struct sock *sk = sock->sk;struct netlink_sock *nlk = nlk_sk(sk);int noblock = flags&MSG_DONTWAIT;size_t copied;struct sk_buff *skb, *data_skb;int err;if (flags&MSG_OOB)return -EOPNOTSUPP;copied = 0;skb = skb_recv_datagram(sk, flags, noblock, &err);//从等待队列中接受一个数据包if (skb == NULL)goto out;data_skb = skb;#ifdef CONFIG_COMPAT_NETLINK_MESSAGESif (unlikely(skb_shinfo(skb)->frag_list)) {/* * If this skb has a frag_list, then here that means that we * will have to use the frag_list skb's data for compat tasks * and the regular skb's data for normal (non-compat) tasks. * * If we need to send the compat skb, assign it to the * 'data_skb' variable so that it will be used below for data * copying. We keep 'skb' for everything else, including * freeing both later. */if (flags & MSG_CMSG_COMPAT)data_skb = skb_shinfo(skb)->frag_list;}#endifcopied = data_skb->len;//接收的数据包长度if (len < copied) {msg->msg_flags |= MSG_TRUNC;copied = len;}skb_reset_transport_header(data_skb);//将数据包从sk_buff中拷贝到msghdr结构中err = skb_copy_datagram_iovec(data_skb, 0, msg->msg_iov, copied);if (msg->msg_name) {//如果要求得到kernel的struct netlink_nl结构struct sockaddr_nl *addr = (struct sockaddr_nl *)msg->msg_name;addr->nl_family = AF_NETLINK;addr->nl_pad    = 0;addr->nl_pid= NETLINK_CB(skb).pid;//内核态pidaddr->nl_groups= netlink_group_mask(NETLINK_CB(skb).dst_group);//发送分组msg->msg_namelen = sizeof(*addr);}if (nlk->flags & NETLINK_RECV_PKTINFO)netlink_cmsg_recv_pktinfo(msg, skb);if (NULL == siocb->scm) {memset(&scm, 0, sizeof(scm));siocb->scm = &scm;}siocb->scm->creds = *NETLINK_CREDS(skb);if (flags & MSG_TRUNC)copied = data_skb->len;skb_free_datagram(sk, skb);//释放sk_buff数据包信息if (nlk->cb && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2)netlink_dump(sk);scm_recv(sock, msg, siocb->scm, flags);out:netlink_rcv_wake(sk);//接受唤醒return err ? : copied;}
内核态将数据发送到用户态对应的sock的sk_receive_queue中,并且唤醒睡眠的进程。

3、测试

3.1 用户态与内核态通信

3.2.1 内核代码实现

#include <linux/init.h>#include <linux/module.h>#include <linux/timer.h>#include <linux/time.h>#include <linux/types.h>#include <net/sock.h>#include <net/netlink.h>#define NETLINK_TEST 25#define MAX_MSGSIZE 1024int stringlength(char *s);void sendnlmsg(char * message);int pid;int err;struct sock *nl_sk = NULL;int flag = 0;void sendnlmsg(char *message){    struct sk_buff *skb_1;    struct nlmsghdr *nlh;    int len = NLMSG_SPACE(MAX_MSGSIZE);int slen = 0;    if(!message || !nl_sk)    {        return ;    }    skb_1 = alloc_skb(len,GFP_KERNEL);    if(!skb_1)    {        printk(KERN_ERR "my_net_link:alloc_skb_1 error\n");    }    slen = stringlength(message);    nlh = nlmsg_put(skb_1,0,0,0,MAX_MSGSIZE,0);    NETLINK_CB(skb_1).pid = 0;    NETLINK_CB(skb_1).dst_group = 0;    message[slen]= '\0';    memcpy(NLMSG_DATA(nlh),message,slen+1);    printk("my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh));    netlink_unicast(nl_sk,skb_1,pid,MSG_DONTWAIT);}int stringlength(char *s){    int slen = 0;    for(; *s; s++){        slen++;    }    return slen;}void nl_data_ready(struct sk_buff *__skb) {     struct sk_buff *skb;     struct nlmsghdr *nlh;     char str[100];     struct completion cmpl;     int i=10;     skb = skb_get (__skb);     if(skb->len >= NLMSG_SPACE(0))     {         nlh = nlmsg_hdr(skb);         memcpy(str, NLMSG_DATA(nlh), sizeof(str));         printk("Message received:%s\n",str) ;         pid = nlh->nlmsg_pid;         while(i--)         {                init_completion(&cmpl);                wait_for_completion_timeout(&cmpl,3 * HZ);                sendnlmsg("I am from kernel!");         }         flag = 1;         kfree_skb(skb);     } }int netlink_init(void){    nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 0,                                 nl_data_ready, NULL, THIS_MODULE);    if(!nl_sk){        printk(KERN_ERR "my_net_link: create netlink socket error.\n");        return 1;    }    printk("my_net_link: create netlink socket ok.\n");    return 0;}static void netlink_exit(void){    if(nl_sk != NULL){        sock_release(nl_sk->sk_socket);    }    printk("my_net_link: module exited.\n");}module_init(netlink_init);module_exit(netlink_exit);MODULE_AUTHOR("LRQ");MODULE_LICENSE("GPL");
3.2.2 用户态代码实现
#include <sys/stat.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <sys/socket.h>#include <sys/types.h>#include <string.h>#include <asm/types.h>#include <linux/netlink.h>#include <linux/socket.h>#include <errno.h>#define NETLINK_TEST 25#define MAX_PAYLOAD 1024 // maximum payload sizeint main(int argc, char* argv[]){    int state;    struct sockaddr_nl src_addr, dest_addr;    struct nlmsghdr *nlh = NULL;    struct iovec iov;    struct msghdr msg;    int sock_fd, retval;    int state_smg = 0;    // Create a socket    sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);    if(sock_fd == -1){        printf("error getting socket: %s", strerror(errno));        return -1;}// To prepare binding    memset(&msg,0,sizeof(msg));    memset(&src_addr, 0, sizeof(src_addr));    src_addr.nl_family = AF_NETLINK;    src_addr.nl_pid = getpid(); // self pid    src_addr.nl_groups = 0; // multi cast    retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));    if(retval < 0){        printf("bind failed: %s", strerror(errno));        close(sock_fd);        return -1;    }    // To prepare recvmsgnlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));    if(!nlh){        printf("malloc nlmsghdr error!\n");        close(sock_fd);        return -1;    }    memset(&dest_addr,0,sizeof(dest_addr));    dest_addr.nl_family = AF_NETLINK;    dest_addr.nl_pid = 0;    dest_addr.nl_groups = 0;    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);    nlh->nlmsg_pid = getpid();    nlh->nlmsg_flags = 0;    strcpy(NLMSG_DATA(nlh),"Hello!");    iov.iov_base = (void *)nlh;    iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);// iov.iov_len = nlh->nlmsg_len;    memset(&msg, 0, sizeof(msg));    msg.msg_name = (void *)&dest_addr;    msg.msg_namelen = sizeof(dest_addr);    msg.msg_iov = &iov;    msg.msg_iovlen = 1;    printf("state_smg\n");    state_smg = sendmsg(sock_fd,&msg,0);    if(state_smg == -1)    {        printf("get error sendmsg = %s\n",strerror(errno));    }    memset(nlh,0,NLMSG_SPACE(MAX_PAYLOAD));    printf("waiting received!\n");// Read message from kernel    while(1){        printf("In while recvmsg\n");        state = recvmsg(sock_fd, &msg, 0);        if(state<0)        {            printf("state<1");        }        printf("In while\n");        printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));    }    close(sock_fd);    return 0;}
原创粉丝点击