嵌入式Linux网络编程——网络高级编程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/q1449516487/article/details/81591679

      在实际情况中,人们往往遇到多个客户端连接服务端的情况。由于connect()、recv()和send()等都是阻塞性函数,如果资源没有准备好,则调用该函数的进程将进入睡眠状态,这样就无法处理I/O多路复用的情况。以下给出两个解决办法:

1.fcntl()

(1)fcntl()回顾:

fcntl()函数语法要点
所需头文件

#include <sys/types.h>

#include <unistd.h>

#include <fcntl.h>

函数原型 int fcntl(int fd,int cmd,struct flock *lock)
函数传入值 fd:文件描述符
cmd F_DUPFD:复制文件描述符
F_GETFD:获得fd的close-on-exec标志,若标志未设置,则文件经过exec()函数之后仍保持打开状态
F_SETFD:设置close-on-exec标志,该标志由参数arg的FD_CLOEXEC位决定
F_GETFL:得到open设置的标志
F_SETFL:改变open设置的标志
F_GETLK:根据lock参数值,决定是否上文件锁
F_SETLK:设置lock参数值的文件锁
F_SETLKW:这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。在无法获取锁时,会进入睡眠状态;如果可以获取锁或者捕捉到信号则会返回
lock:结构为flock,设置记录锁的具体状态
函数返回值 0:成功
-1:出错

lock的结构如下所示:

struct flock
{
    short l_type;
    //F_RDLCK:读取锁(共享锁)
    //F_WRLCK:写入锁(排斥锁)
    //F_UNLCK:解锁
    off_t l_start;            //相对位移量(字节)
    short l_whence;           //相对位移量的起点
    //SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小
    //SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量
    //SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小
    off_t l_len;              //加锁区域的长度
    pid_t l_pid;
}

(2)函数fcntl()针对socket编程提供了如下的编程特性:

(1)非阻塞I/O:可将cmd设置为F_SETFL,将lock设置为O_NONBLOCK。
(2)异步I/O:可将cmd设置为F_SETFL,将lock设置为O_ASYNC。

下面是用fcntl()将套接字设置为非阻塞I/O的实例代码:

/*net_fcntl.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>

#define PORT             1234  //端口号
#define BUFFER_SIZE      1024
#define MAX_QUE_CONN_NM  5

int main()
{
        struct sockaddr_in server_sockaddr,client_sockaddr; /* Structure describing an Internet (IP) socket address. */
        int sin_size,recvbytes,flags;
        int sockfd,client_fd;//sockfd=文件描述符
        char buf[BUFFER_SIZE];

        /*建立socket连接*/
        if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)//AF_INET=IPv4,SOCK_STREAM=TCP
        {
                perror("socket");
                exit(1);
        }
        printf("Socket id = %d\n",sockfd);

        /*设置sockaddr_in结构体中相关参数*/
        server_sockaddr.sin_family = AF_INET;  //sin_family=地址族,AF_INET=IPv4
        server_sockaddr.sin_port = htons(PORT);//sin_port=端口号,htons()主机字节序>转化
        server_sockaddr.sin_addr.s_addr = INADDR_ANY;//s_addr=IP地址
        bzero(&(server_sockaddr.sin_zero),8);//填充0以保持与struct sockaddr同样大小

        int i=1;//允许重复使用本地地址与套接字进行绑定
        if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))) < 0)//用于任意类型、任意状态套接口的设置选项值
        {
                perror("setsockopt");
                exit(1);
        }
        /*sockfd = 标识一个套接口的描述字
          level  = 选项定义的层次:支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
          optname = 需设置的选项
          optval  = 指针,指向存放选项待设置的新值的缓冲区
          optlen  = optval缓冲区长度*/

        /*绑定函数bind()*/
        if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)//server_sockaddr = 本地地址
        {                                                                                                                                                                 //sizeof(struct sockaddr) = 地址长度
                perror("bind");
                exit(1);
        }
        printf("Bind success!\n");

        /*调用listen()函数,创建未处理请求的队列*/
        if(listen(sockfd,MAX_QUE_CONN_NM) == -1)//MAX_QUE_CONN_NM=请求队列中允许的>最大请求数,大多数系统默认值为5
        {
                perror("listen");
                exit(1);
        }
        printf("Listening...\n");
        flags = fcntl(sockfd,F_GETFL);
        if(flags < 0 || fcntl(sockfd,F_SETFL,flags|O_NONBLOCK) < 0)
        {
                perror("fcntl");
                exit(1);
        }

        while(1)
        {
                sin_size = sizeof(struct sockaddr_in);
                /*调用accept()函数,等待客户端的连接*/
                if((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size)) == -1)//client_sockaddr = 客户端地址
                {                                                                                                                                                                   //地址长度
                        perror("accept");
                        exit(1);
                }

                /*调用recv()函数接收客户端的请求*/
                if((recvbytes = recv(client_fd,buf,BUFFER_SIZE,0)) == -1)//返回接收
的字节数
                {
                        perror("recv");
                        exit(1);
                }
                printf("Received a message:%s\n",buf);
        }

        close(sockfd);
        exit(0);

}

2.select()

(1)select()回顾

扫描二维码关注公众号,回复: 4092243 查看本文章
select()函数语法要点
所需头文件

#include<sys/types.h>

#include<sys/time.h>

#include<unistd.h>

函数原型 int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exeptfds,struct timeval *timeout)
函数传入值 numfds:该参数值为需要监视的文件描述法的最大值加1
readfds:由select()监视的读文件描述符集合
writefds:由select()监视的写文件描述符集合
exeptfds:由select()监视的异常处理文件描述符集合
timeout NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止
具体值:struct timeval 类型的指针,若等待了timeout时间还没有检测到任何文件描述符准备好,就立即返回
0:从不等待,测试所有指定的描述符并立即返回
函数返回值 大于0:成功,返回准备好的文件描述符的数量
0:超时
-1:出错

对文件描述符进行处理的宏函数:

select()文件描述符处理函数
FD_ZERO(fd_set *set) 清除一个文件描述符集合
FD_SET(int fd,fd_set *set) 将一个文件描述符加入文件描述符集合
FD_CLR(int fd,fd_set *set) 将一个文件描述法从文件描述符集中清除
FD_ISSET(int fd,fd_set *set) 如果文件描述法fd为fd_set集合中的一个元素,则返回非零值,可以用于调用select()之后测试文件描述符集合中的文件描述符是否有变化

PS:在使用select()之前,首先使用FD_ZERO()和FD_SET()来初始化文件描述符集合;

        在使用select()时,循环使用FD_ISSET()来测试描述符集合;

        在执行完相关描述符的操作后,使用FD_CLR()来清除描述符集合;

select()函数中的timeout是一个struct timeval类型的指针,结构体如下:

struct timeval
{
    long tv_sec;     /* 秒 */
    long tv_unsec;   /* 微妙 */
}

(2)下面是使用select()函数的服务器端源代码:

/*net_select.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>

#define PORT             4321  //端口号
#define BUFFER_SIZE      1024
#define MAX_QUE_CONN_NM  5
#define MAX_SOCK_FD          FD_SETSIZE

int main()
{
        struct sockaddr_in server_sockaddr,client_sockaddr; /* Structure describing an Internet (IP) socket address. */
        int sin_size,count;
        fd_set inset,tmp_inset;
        int sockfd,client_fd,fd;//sockfd=文件描述符
        char buf[BUFFER_SIZE];

        /*建立socket连接*/
        if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)//AF_INET=IPv4,SOCK_STREAM=TCP
        {
                perror("socket");
                exit(1);
        }
        //printf("Socket id = %d\n",sockfd);

        /*设置sockaddr_in结构体中相关参数*/
        server_sockaddr.sin_family = AF_INET;  //sin_family=地址族,AF_INET=IPv4
        server_sockaddr.sin_port = htons(PORT);//sin_port=端口号,htons()主机字节序>转化
        server_sockaddr.sin_addr.s_addr = INADDR_ANY;//s_addr=IP地址
        bzero(&(server_sockaddr.sin_zero),8);//填充0以保持与struct sockaddr同样大小

        int i=1;//允许重复使用本地地址与套接字进行绑定
        if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))) < 0)//用于任意
类型、任意状态套接口的设置选项值
        {
                perror("setsockopt");
        exit(1);
        }
        /*sockfd = 标识一个套接口的描述字
          level  = 选项定义的层次:支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
          optname = 需设置的选项
          optval  = 指针,指向存放选项待设置的新值的缓冲区
          optlen  = optval缓冲区长度*/

        /*绑定函数bind()*/
        if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)//server_sockaddr = 本地地址
        {                                                                                                                                                                 //sizeof(struct sockaddr) = 地址长度
                perror("bind");
                exit(1);
        }
        printf("Bind success!\n");

        /*调用listen()函数,创建未处理请求的队列*/
        if(listen(sockfd,MAX_QUE_CONN_NM) == -1)//MAX_QUE_CONN_NM=请求队列中允许的>最大请求数,大多数系统默认值为5
        {
                perror("listen");
                exit(1);
        }
        printf("Listening...\n");

        /*将调用socket()函数的描述符作为文件描述符*/
        FD_ZERO(&inset);//将inset清零使集合中不含任何fd
        FD_SET(sockfd,&inset);//将fd加入inset集合
        while(1)
        {
                tmp_inset = inset;
                sin_size = sizeof(struct sockaddr_in);
                memset(buf,0,sizeof(buf));
                /*调用select()函数*/
                if(!(select(MAX_SOCK_FD,&tmp_inset,NULL,NULL,NULL)) > 0)
                {
                        perror("select");
                }
                for(fd=0;fd<MAX_SOCK_FD;fd++)
                {
                        if(FD_ISSET(fd,&tmp_inset) > 0)
                        {
                                if(fd == sockfd)
                                {
                                        /*服务端接收客户端的连接请求*/
                                        /*调用accept()函数,等待客户端的连接*/
                                        if((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size)) == -1)//client_sockaddr = 客户端地址
                                        {                                                                                                                                                                   //地址长度
                                                perror("accept");
                                                exit(1);
                                        }
                                        FD_SET(client_fd,&inset);
                                        printf("New connection from %d(socket)\n",client_fd);
                                }
                                else
                                {
                                        /*调用recv()函数接收客户端的请求*/
                                        if((count = recv(fd,buf,BUFFER_SIZE,0)) > 0)//返回接收的字节数
                                        {
                                                printf("Received a message from %d:%s\n",fd,buf);
                                        }
                                        else
                                        {
                                                close(fd);
                                                FD_CLR(fd,&inset);
                                                printf("Client %d(socket) has left\n",fd);
                                        }
                                }
                        }
                }
        }
        close(sockfd);
        exit(0);

}

猜你喜欢

转载自blog.csdn.net/q1449516487/article/details/81591679