linux网络编程-2

1.select模型

1.1什么是select模型

select模是一种用于管理和处理多个文件描述符的I/O多路复用技术,尤其在网络编程中非常常见。他的主要作用是允许程序同时监控多个文件描述符,判断它们是否可以进行读写操作或有异常发送。

1.2为什么要用select模型

1)处理多个连接,使用select,服务器可以在单个线程或进程中同时处理多个客户端连接,而无需为每个客户端创建一个线程或进程。极大的简化了编程模型并节省了系统资源

2)避免阻塞,直接在套接字上调用read()或write(),这些操作可能会阻塞,等待数据的到来或发送完成、使用select可以先检测哪些套接字准备好进行I/O操作。这样就可以避免在为准备好的套接字上阻塞

3)可以高效的利用资源,在高并发的情况下,如果为每一个连接都创建一个线程或进程,将要消耗大量的系统资源。select模型通过在单个线程或进程中处理多个I/O操作,能更高效地利用系统资源,特别是在连接数多单每个连接到I/O频率较低的场景。

4)兼容性好,大多数Unix系统和window系统的支持。因此可以跨平台使用。

5)相对其他的I/O多路复用技术,select的API相对简单,易于理解和使用

1.3使用场景

1)小型服务器

2)网络代理

3)非阻塞I/O

1.4函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

参数解释

1.int nfds :文件描述符数量,通常是文件描述符集合中最大的文件描述符加1(max_fd+1)

2.fd_set *readfds:指向一个fd_set类型的指针,代表你感兴趣的可写事件的文件描述符集合,如果某个文件描述符在readfds集合中且有数据可读,select返回时将保留该文件描述符。如果你不关心任何可写事件,可以传递 NULL

3.fd_set *writefds指向一个fd_set类型的指针,代表你感兴趣的异常事件(如带外数据)的文件描述符集合。与前面类似

4.fd_set *exceptfds与前面类似,代表异常事件。

5.struct timeval *timeout:指向一个’timeval‘结构的指针,用于设置select函数的超时时间,0是

timeval 结构体的定义如下

struct timeval {
    long tv_sec;  // 秒
    long tv_usec; // 微秒
};
  • 如果 timeoutNULLselect 会一直阻塞,直到某个文件描述符状态发生变化。
  • 如果 timeouttv_sectv_usec 都为 0,select 将立即返回,不阻塞。

返回值:

成功:返回准备好的文件描述符数量中的总数。

失败返回-1并设置errno,指出错误原因

超时:如果在指定的时间内没有任何文件描述符准备好,则返回0

其他定义与宏解释

fd_set:是一个结构体,用于表示文件描述符的集合。

FD_ZERO(fd_set *set):初始化一个 fd_set 变量,将所有位清零。

FD_SET(int fd, fd_set *set):将指定的文件描述符 fd 添加到文件描述符集合 set 中。也就是将 fd 对应的位设置为 1

FD_CLR(int fd, fd_set *set):将指定的文件描述符 fd 从集合 set 中移除。

FD_ISSET(int fd, fd_set *set):检查 fd 是否在集合 set 中。也就是检查 fd 对应的位是否为 1。如果文件描述符 fd 在集合 set 中,返回非零值;否则返回 0。

1.5工作流程

1)初始化文件描述符集合:用“FD_ZERO”函数初始化一个‘fd_set’结构体,用于存储需要监视的文件描述符。

2)将文件描述符添加到集合中,使用‘FD_SET’函数将感兴趣的文件描述符添加到‘fd_set'中。

3)调用select函数:select返回时会阻塞当前进程,直到监视的文件描述符中至少有一个文件描述符的状态发生变化,或者指定的超时时间到达

4)处理就绪的文件描述符:当select返回时,程序检查哪些文件描述符已经准备好进行I/O操作,并进行相应处理

5)重复上述过程

1.6样例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>       
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <netinet/ip.h> 
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
// 宏定义端口号
#define PROT 8083
int server;

void handle_signal(int sig)
{
    if(sig==SIGINT)
    {
        printf("\nServer shutting down...\n");
        close(server); // 关闭服务器套接字
        exit(0);
    }
}
int main()
{
    char buf[1024];
    struct sockaddr_in sock_addr;
    fd_set readfds,fds;
    int max_fd =0;
    //创建套接字
    server=socket(AF_INET,SOCK_STREAM,0);
    if(server<0)
    {
        perror("socket");
        _exit(EXIT_FAILURE);
    }
    //接收CTRL+C
    signal(SIGINT,handle_signal);
    //配置并绑定ip与端口号
    sock_addr.sin_family=AF_INET;
    sock_addr.sin_port=htons(PROT);
    sock_addr.sin_addr.s_addr=INADDR_ANY;
    if(bind(server,(struct sockaddr *)&sock_addr,sizeof(sock_addr))<0)
    {
        perror("bind");
        _exit(EXIT_FAILURE);
    }
    //监听套接字
    if(listen(server,100)<0)
    {
        perror("listen");
        _exit(EXIT_FAILURE);
    }

    //初始化fds,readfds,max_FD。把server放入fds中
    max_fd =server;
    FD_ZERO(&fds);
    FD_ZERO(&readfds);
    FD_SET(server,&fds);

    while (1)
    {
        //遍历文件描述符,把fd放入readfds中
        for(int fd=0;fd<1024;fd++)
        {
            if(FD_ISSET(fd,&fds))
            {
                FD_SET(fd,&readfds);
                if(fd>max_fd)max_fd=fd;
            }
        }
        //建立模型连接可读文件
        int n=select(max_fd+1,&readfds,NULL,NULL,0);
        if(n<0)
        {
            perror("select");
            _exit(EXIT_FAILURE);
        }
        //遍历发现改变的
        for(int fd=0;fd<=max_fd;fd++)
        {
            if(FD_ISSET(fd,&readfds))
            {
                //接收新的连接
                if(fd == server)
                {
                    int newsock = accept(server,NULL,NULL);
                    FD_SET(newsock,&fds);
                    printf("has a new link\n");
                }
                else{
                    memset(buf,0,sizeof(buf));
                    if(read(fd,buf,1024)>0)
                    {
                        printf("%s\n",buf);
                        write(fd,"recved\n",8);
                    }
                    else{
                        printf("recv a close\n"); 
                        close(fd);
                        FD_CLR(fd,&fds);
                        FD_CLR(fd,&fds);   //理论上不清空这个,但莫名会有bug 
                        max_fd=0;
                    }
                }
            }
        }

    }
    
}

2.UDP通信

2.1什么是UDP通信

UDP是一种无连接的、不可靠的传输层协议。常用于需要快速传输而不需要可靠性的场景。UDP的特点是数据报文的发送和接收是独立的,每个数据报文都包含了源地址和目的地址,并不会因传输问题而重新传输或确认接收

2.2UDP通信的基本步骤

2.2.1服务端

1)创建UDP套接字:使用socket函数

2)绑定地址和端口:使用bind

3)接收数据:recvfrom函数接收客户端发送的数据报文

4)发送数据:sento函数发送数据报文到客户端

2.2.2客户端

1)创建UDP套接字:使用socket函数

2)绑定地址和端口:使用bind(也可以不绑定客户端的地址)

3)设置服务器地址(端口号和ip)

4)接收数据:recvfrom函数接收客户端发送的数据报文

5)发送数据:sento函数发送数据报文到客户端

2.3函数原型

recvfrom函数

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

参数信息

  • sockfd:要接收数据的套接字描述符,通常是使用 socket() 创建的套接字。
  • buf:用于存放接收到的数据的缓冲区指针。
  • len:缓冲区的长度,即可以接收的数据的最大字节数。
  • flags:通常设置为 0,可以使用其他标志来改变函数的行为(如 MSG_PEEK 查看数据但不移除它)。
  • src_addr:指向 sockaddr 结构的指针,用于存放发送数据的源地址信息。如果不需要源地址信息,可以传入 NULL
  • addrlen:指向 socklen_t 类型变量的指针,用于存储 src_addr 的长度。如果 src_addrNULL,这个参数也可以为 NULL

返回值成功时返回收到的字节数,失败时返回-1并设置errno

sendto函数

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数说明

  • sockfd:要发送数据的套接字描述符,通常是使用 socket() 创建的套接字。
  • buf:指向包含要发送的数据的缓冲区的指针。
  • len:要发送的数据的字节数。
  • flags:通常设置为 0,可以使用其他标志来改变函数的行为。
  • dest_addr:指向 sockaddr 结构的指针,指定目标地址。
  • addrlendest_addr 结构的长度。

返回值

  • 成功时,返回实际发送的字节数。
  • 失败时,返回 -1,并设置 errno

2.4代码示例

2.4.1服务端代码

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>       
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/ip.h> 
#include <unistd.h>
#include <signal.h>
//宏定义服务端端口
#define PORT 8080
int server;
//ctrl+C关闭终端释放服务器
void handle_signal(int sig)
{
    if(sig==SIGINT)
    {
        printf("\nServer shutting down...\n");
        close(server); // 关闭服务器套接字
        exit(0);
    }
}

int main()
{

    struct sockaddr_in host_addr,colient_addr;
    socklen_t len =sizeof(colient_addr);
    //接收CTRL+C信号
    signal(SIGINT,handle_signal);
    //生成套接字
    server=socket(AF_INET,SOCK_DGRAM ,0);
    if(server<0)
    {
        perror("socket");
        _exit(EXIT_FAILURE);
    }

    //绑定服务端地址与端口
    host_addr.sin_family=AF_INET;
    host_addr.sin_port=htons(PORT);
    host_addr.sin_addr.s_addr=INADDR_ANY;
    if(bind(server,(struct sockaddr *)&host_addr,sizeof(host_addr))<0)
    {
        perror("bind");
        _exit(EXIT_FAILURE);
    }
    
    while (1)
    {
        //清空输入数组,将接收的写入其中然后打印
        char rxbuf[100];
        memset(rxbuf,0,sizeof(rxbuf));
        int n = recvfrom(server,rxbuf,sizeof(rxbuf),0,(struct sockaddr*)&colient_addr,&len);
        if(n<0)
        {
            perror("recvfrom");
            _exit(EXIT_FAILURE);
        }
        printf("%s\n",rxbuf);
        //接收到后向客户端发送ok
        if(n>0)
        {
            int m= sendto(server,"ok\n",7,0,(struct sockaddr*)&colient_addr,len);
            if(m<0)
            {
                perror("sendto");
                _exit(EXIT_FAILURE);
            }
        } 
    }
   close(server);

}

2.4.2客户端代码

colient.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>       
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/ip.h> 
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
//宏定义服务端IP与客户端端口(客户端端口不能与服务端相同)
#define PORT 8081
#define IP "192.168.137.138"

int main()
{
    int server;
    struct sockaddr_in host_addr,colient_addr;
    socklen_t len=sizeof(host_addr);

    //创建套接字
    if((server=socket(AF_INET,SOCK_DGRAM,0))<0)
    {
        perror("socket");
        _exit(EXIT_FAILURE);
    }
    //绑定客户端地址(也可以不绑定)
    colient_addr.sin_family=AF_INET;
    colient_addr.sin_port=htons(PORT);
    colient_addr.sin_addr.s_addr=INADDR_ANY;
    bind(server,(struct sockaddr*)&colient_addr,sizeof(colient_addr));

    //设置服务端地址与端口(需要直到和谁通信)
    host_addr.sin_family=AF_INET;
    host_addr.sin_port=htons(8080);
    host_addr.sin_addr.s_addr=inet_addr(IP);

    while (1)
    { 
        char rxbuf[100],txbuf[100];
        //清空输出数组,并输入新的信息
        memset(txbuf,0,sizeof(txbuf));
        printf("请输入你要发送的信息:");
        scanf("%s",txbuf);
        //发送信息给服务端
        if(sendto(server,txbuf,sizeof(txbuf),0,(struct sockaddr*)&host_addr,len)<0)
        {
            perror("sendto");
            _exit(EXIT_FAILURE);
        }
        
        //清空输入数组,将接收的写入其中然后打印
        memset(rxbuf,0,sizeof(rxbuf));
        if(recvfrom(server,rxbuf,128,0,(struct sockaddr*)&host_addr,&len)<0)
        {
            perror("recvfrom");
            _exit(EXIT_FAILURE);
        }
        printf("%s",rxbuf);
        

    }
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值