Linux网络编程

1 网络基础

  1. 网络应用程序设计模式:
  • C/S -Client/Server架构
  • 优点:协议选用灵活,可以缓存数据
  • 缺点:对用户安全构成威胁,开发工作量大,调试困难。
  • B/S-browser/server
  • 优点:跨平台
  • 缺点:只能使用http
  1. 协议的概念
  • 规则:数据传输和数据解释的规则
  • 原始协议---->(改进、完善)------>标准协议
  • 典型协议:TCP/UDP HTTP FTP IP ARP
  1. 分层模型

OSI七层模型

  • 物理层–双绞线、光纤
  • 数据链路层–数据的传输和错误检测
  • 网络层—为数据包选择路由
  • 传输层—提供端对端的接口
  • 会话层—解除或建立与别的节点的联系
  • 表示层–数据格式化,代码转换,数据加密
  • 应用层—文件传输,电子邮件,文件服务,虚拟终端。

TCP/IP四层模型

  • 数据传输层—以太网帧协议
  • 网络层—IP协议
  • 传输层—TCP/UDP
  • 应用层—ftp,http,ssh,telent

在这里插入图片描述
4. 协议格式
在这里插入图片描述
根据IP地址获取mac地址,再根据mac地址和ip地址进行发送数据包。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1 网络通信过程

在这里插入图片描述
在这里插入图片描述
以太网帧协议:
ARP请求:通过IP获取目的地址的MAC地址。
以太网帧协议:根据MAC地址,完成数据包传输。

IP协议:
在这里插入图片描述

  • 版本:IPv4/IPv6。
  • TTL:time to live。设置数据包在路由节点中的跳转上限。每经过一个路由节点,该值-1。当减为0的路由,有义务将该数据包丢弃。
  • 源IP: 32位----4字节 192.168.1.108 —点分十进制 IP地址(string)———> 二进制在网络中传输
  • 目的IP:32位----4字节

UDP协议:

  • 16位:源端口号。 2^16 = 65536
  • 16位:目的端口号。

TCP协议:
在这里插入图片描述

  • 16位源端口号。
  • 16位目的端口号
  • 32位序号
  • 32位确认序号
  • 6个标志位。
  • 16位的窗口大小。
对比C/SB/S
优点缓存大量数据、协议选择灵活、速度快安全性、跨平台、开发工作量较小
缺点安全性、开发工作量大不能缓存大量数据、严格遵守http

1.2 TCP协议

tcp时序
在这里插入图片描述
三次握手

  • 主动发送连接请求端:发送SYN标志位,请求建立连接。携带序列号、数据字节数(0)、滑动窗口大小
  • 被动接受连接请求端,发送ACK标志位,同时携带SYN请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小。
  • 主动发送连接请求端,发送ACK标志位,应答服务器连接请求。携带确认序号,

四次挥手

  • 主动关闭连接请求端,发送FIN标志位。
  • 被动关闭连接请求端,应答ACK标志位。------半关闭完成
  • 被动关闭连接请求端,发送FIN标志位。
  • 主动关闭连接请求端,应答ACK标志位。-------连接全部关闭

滑动窗口实现流量控制
发送给连接对象,本端的缓冲区大小(实时),保证数据不会发送丢失。
在这里插入图片描述

错误处理函数的封装思想
需要对所有的函数返回值进行判断,以确保程序出错时的有错误信息。

1.3 TCP状态转换

在这里插入图片描述
CLOSED:表示初始状态。

LISTEN:该状态表示服务器端的某个SOCKET处于监听状态,可以接受连接。

SYN_SENT:这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,随即进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。

SYN_RCVD: 该状态表示接收到SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂。此种状态时,当收到客户端的ACK报文后,会进入到ESTABLISHED状态。

ESTABLISHED:表示连接已经建立。

FIN_WAIT_1: FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。区别是:
FIN_WAIT_1状态是当socket在ESTABLISHED状态时,想主动关闭连接,向对方发送了FIN报文,此时该socket进入到FIN_WAIT_1状态。

FIN_WAIT_2状态是当对方回应ACK后,该socket进入到FIN_WAIT_2状态,正常情况下,对方应马上回应ACK报文,所以FIN_WAIT_1状态一般较难见到,而FIN_WAIT_2状态可用netstat看到。
FIN_WAIT_2:主动关闭链接的一方,发出FIN收到ACK以后进入该状态。称之为半连接或半关闭状态。该状态下的socket只能接收数据,不能发。

TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,等2MSL后即可回到CLOSED可用状态。如果FIN_WAIT_1状态下,收到对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。

CLOSING: 这种状态较特殊,属于一种较罕见的状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。

CLOSE_WAIT: 此种状态表示在等待关闭。当对方关闭一个SOCKET后发送FIN报文给自己,系统会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,察看是否还有数据发送给对方,如果没有可以 close这个SOCKET,发送FIN报文给对方,即关闭连接。所以在CLOSE_WAIT状态下,需要关闭连接。

LAST_ACK: 该状态是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,即可以进入到CLOSED可用状态。
在这里插入图片描述
在这里插入图片描述
TCP状态时序图:

结合三次握手、四次挥手 理解记忆。


1. 主动发起连接请求端:	CLOSE -- 发送SYN -- SEND_SYN -- 接收 ACK、SYN -- SEND_SYN -- 发送 ACK -- ESTABLISHED(数据通信态)

2. 主动关闭连接请求端: ESTABLISHED(数据通信态) -- 发送 FIN -- FIN_WAIT_1 -- 接收ACK -- FIN_WAIT_2(半关闭)

			-- 接收对端发送 FIN -- FIN_WAIT_2(半关闭)-- 回发ACK -- TIME_WAIT(只有主动关闭连接方,会经历该状态)

			-- 等 2MSL时长 -- CLOSE 

3. 被动接收连接请求端: CLOSE -- LISTEN -- 接收 SYN -- LISTEN -- 发送 ACK、SYN -- SYN_RCVD -- 接收ACK -- ESTABLISHED(数据通信态)

4. 被动关闭连接请求端: ESTABLISHED(数据通信态) -- 接收 FIN -- ESTABLISHED(数据通信态) -- 发送ACK 

			-- CLOSE_WAIT (说明对端【主动关闭连接端】处于半关闭状态) -- 发送FIN -- LAST_ACK -- 接收ACK -- CLOSE


重点记忆: ESTABLISHED、FIN_WAIT_2 <--> CLOSE_WAIT、TIME_WAIT(2MSL)

netstat -apn | grep  端口号

2MSL时长:

一定出现在【主动关闭连接请求端】。 --- 对应 TIME_WAIT 状态。

保证,最后一个 ACK 能成功被对端接收。(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求。)

端口复用
在server的TCP连接没有完全断开之前不允许重新监听是不合理的。因为,TCP连接没有完全断开指的是connfd(127.0.0.1:6666)没有完全断开,而我们重新监听的是lis-tenfd(0.0.0.0:6666),虽然是占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。
端口复用:

int opt = 1;		// 设置端口复用。

setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));

半关闭:

通信双方中,只有一端关闭通信。  --- FIN_WAIT_2

close(cfd);

shutdown(int fd, int how);	

	how: 	SHUT_RD	关读端

		SHUT_WR	关写端

		SHUT_RDWR 关读写

shutdown在关闭多个文件描述符应用的文件时,采用全关闭方法。close,只关闭一个。

2 Socket编程

2.1网络套接字socket

在通信过程中,套接字一定是成对出现的。
套接字的通信原理如下图:
在这里插入图片描述
sfd,cfd为文件描述符。发送缓冲区、和接收缓冲区。
一端的发送缓冲区对应另一端的接收缓冲区。我们使用同一个文件描述符发送缓冲区和接收缓冲区。
一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)

2.2 网络字节序

小端法:高位存高地址。低位存低地址。计算机存储一般用小端法。(inter)
大端法: 高位存低地址,低位存高地址。网络数据流采用大端字节序(IBM)
函数:
uint32_t htonl(uint32_t hostlong); //本地字节序转网络字节序,转32位的。(IP)转IP
uint16_t htons(uint16_t hostshort); //本地字节序转网络字节序,转16位。(port)转端口号
uint32_t ntohl(uint32_t netlong); //网络转本地 (IP)
uint16_t ntohs(uint16_t netshort);//网络转本地(port)

IP地址转换函数:
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst); //本地字节序转网络字节序

  • af:版本,AF_INET(IPv4),AF_INET6(IPv6)
  • src:传入参数,IP地址(点分十进制)
  • dst:传出参数,转换后的网络字节序的 IP地址。
  • 返回值:成功返回1,异常返回0,说明src指向的不是一个有效的IP地址。失败返回-1,设置errono

const char *inet_ntop(int af, const void *src,char *dst, socklen_t size); //网络字节序转本地字节序

  • af:版本,AF_INET(IPv4),AF_INET6(IPv6)
  • src:传入参数,网络字节序IP(二进制)
  • dst:传出参数,本地字节序的IP
  • size:dst的大小
  • 返回值:成功dst,失败返回NULL。

2.3sockaddr地址结构

struct sockaddr_in addr; //定义有效的数据类型
addr.sin_family = AF_INET/AF_INET6;
addr.sin_port = htons(9527);
addr.sin_addr.s_addr = htonl(INADDR_ANY); //取出系统中有效的任意IP地址。二进制类型。
bind(fd,(struct sockaddr )&addr,size); //但是传参要强制类型转换为sockaddr类型
在这里插入图片描述
struct sockaddr_in
{
sa_family_t sin_family; /
address family: AF_INET /
in_port_t sin_port; /
port in network byte order /
struct in_addr sin_addr; /
internet address /
};
/
Internet address. /
struct in_addr { uint32_t s_addr; /
address in network byte order */
};

2.4网络套接字函数

socket函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol); //创建一个 套接字

  • domain:AF_INET、AF_INET6、AF_UNIX//ipv4 ipv6
  • type:数据传输协议。SOCK_STREAM(流式协议)、SOCK_DGRAM(报式协议)
  • protocol:所选协议中的代表协议。一般填0。流式协议-TCP。报式协议–UDP
  • 返回值:成功返回新套接字对应的文件描述符。失败返回-1,设置errno。

bind函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //给socket绑定一个地址结构(IP+port)

  • sockfd:socket函数返回值
    • struct sockaddr_in addr;
    • addr.sin_family = AF_INET;
    • addr.sin_port = htons (8888);
    • addr.sin_addr.s_addr = htonl(INADDR_ANY);
  • addr : (struct sockaddr *)&addr
  • addrlen:sizeof(addr) //地址结构的大小

listen函数-----设置同时与服务器建立连接的上限数。(可以同时进行三次握手的客户端数量)
int listen(int sockfd, int backlog);

  • sockfd:socket 函数返回值
  • backlog:上限数值。最大值128。
  • 成功0,失败-1,设置errno

accept函数—阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的socket文件描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  • sockfd:socket函数返回值
  • addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)
    • socklen_t clit_addr_len = sizeof(addr);
  • addrlen:传入传出参数。入:addr的大小。出:客户端addr的实际大小。
    • &clit_addr_len
  • 返回值:成功返回能与服务器进行数据通信的socket对应的文件描述符。失败返回-1,设置errno

connct函数 --------使用现有的socket与服务器建立连接
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

  • sockfd:socket函数返回值
  • addr:传入参数。服务器的地址结构
  • addrlen:sizeof(addr)服务器地址结构的大小
  • 返回值:成功返回0,失败-1,设置errno。

如果不使用bind绑定客户端地址结构,采用“隐式绑定”。

2.5 代码实例

在这里插入图片描述
一个客户端和一个服务器进行通信,一共会有三个套接字
TCP通信流程分析:
server:

  • socket()创建socket
  • bind() 绑定服务器地址结构
  • listen()设置监听上限
  • accept() 阻塞监听客户端连接
  • read(fd)读socket获取客户端数据
  • 小写—>大写
  • write(fd)
  • close()

client:

  • socket() 创建socket
  • connect() 与服务器建立连接
  • write() 写数据到socket
  • read() 读转换后的数据
  • 显示读取结果
  • close()

服务器端:

 1 #include <stdio.h>                                                                                                   
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <sys/stat.h>
  6 #include <string.h>
  7 #include <arpa/inet.h>
  8 #include <ctype.h>
  9 
 10 int main(int argc, const char* argv[])
 11 {
 12     // 创建监听的套接字
 13     int lfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
 14     if(lfd == -1)
 15     {
 16         perror("socket error");
 17         exit(1);
 18     }
 19 
 20     // lfd 和本地的IP port绑定
 21     // 创建本地端口
 22     struct sockaddr_in server;
 23     memset(&server, 0, sizeof(server));
 24     server.sin_family = AF_INET;    // 地址族协议 - ipv4
 25     server.sin_port = htons(8888);  //端口号8888,htos函数将主机字节顺序转换为网络字节顺序,s---短整型
 26     server.sin_addr.s_addr = htonl(INADDR_ANY);
 27     int ret = bind(lfd, (struct sockaddr*)&server, sizeof(server)); //bind,将本地IP和端口与套接字绑定
 28     if(ret == -1)
 29     {
 30         perror("bind error");
  31         exit(1);
 32     }
 33 
 34     // 设置监听
 35     ret = listen(lfd, 20);
 36     if(ret == -1)
 37     {
 38         perror("listen error");
 39         exit(1);
 40     }
 41 
 42     // 等待并接收连接请求
 43     // 创建客户端
 44     struct sockaddr_in client;
 45     socklen_t len = sizeof(client);
 46     int cfd = accept(lfd, (struct sockaddr*)&client, &len); //阻塞等待客户端连接请求,并接受连接
 47     if(cfd == -1)
 48     {
 49         perror("accept error");
 50         exit(1);
 51     }
 52 
 53     printf(" accept successful !!!\n");
 54     char ipbuf[64] = {0};
 55     printf("client IP: %s, port: %d\n", 
 56            inet_ntop(AF_INET, &client.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
 57            ntohs(client.sin_port));
 58     // 一直通信
 59     while(1)
 60     {  
  61         // 先接收数据                                                                                                
 62         char buf[1024] = {0};
 63         int len = read(cfd, buf, sizeof(buf));
 64         if(len == -1)
 65         {
 66             perror("read error");
 67             exit(1);
 68         }
 69         else if(len == 0)
 70         {
 71             printf(" 客户端已经断开了连接 \n");
 72             close(cfd);
 73             break;
 74         }
 75         else
 76         {
 77             printf("recv buf: %s\n", buf);
 78             // 转换 - 小写 - 大写
 79             for(int i=0; i<len; ++i)
 80             {
 81                 buf[i] = toupper(buf[i]);
 82             }
 83             printf("send buf: %s\n", buf);
 84             write(cfd, buf, len);
 85         }
 86     }
 87 
 88     close(lfd);
 89 
 90     return 0;
 91 }

客户端:

 1 #include <stdio.h>                                                                                                   
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <sys/stat.h>
  6 #include <string.h>
  7 #include <arpa/inet.h>
  8 
  9 int main (int argc, const char * argv[])
 10 {
 11     if (argc <2)
 12     {
 13         printf("eg:./a.out port\n");
 14         exit(1);
 15     }
 16     int port = atoi(argv[1]);
 17     // 创建套接字
 18     int fd = socket(AF_INET,SOCK_STREAM,0);
 19 
 20     //链接服务器
 21     struct sockaddr_in serv;
 22     memset(&serv,0,sizeof(serv));
 23     serv.sin_family = AF_INET;
 24     serv.sin_port = htons(port);
 25     //serv.sin_addr.s_addr = htonl();
 26     inet_pton(AF_INET,"127.0.0.1",&serv.sin_addr.s_addr);
 27 
 28     connect(fd,(struct sockaddr*)&serv,sizeof(serv));
 29     //通信
 30     while(1)
 31     {
 32         //发送数据
 33         char buf[1024];
 34         printf("请输入要发送的字符串:\n");
 35         fgets(buf,sizeof(buf),stdin);
 36         write(fd,buf,strlen(buf));
 37 
 38         //等待接受数据
 39         int len = read(fd,buf,sizeof(buf));
 40         if(len == -1)
 41         {
 42             perror("read error");
 43             exit(1);
 44         }
 45         else if (len == 0)
 46         {
 47             printf("服务器端关闭来连接\n");
 48             break;
 49         }
 50         else 
 51         {
 52             printf("recv buf:%s\n",buf);
 53         }
 54 
 55     }
 56     close (fd);
 57 
 58     return 0;
 59 
 60 }
 

3多进程并发服务器

3.1 设计思路

多进程并发服务器
server.c

1. Socket();		创建 监听套接字 lfd
2. Bind()	绑定地址结构 Strcut scokaddr_in addr;
3. Listen();	
4. while (1) {

	cfd = Accpet();			接收客户端连接请求。
	pid = fork();
	if (pid == 0){			子进程 read(cfd) --- 小-》大 --- write(cfd)

		close(lfd)		关闭用于建立连接的套接字 lfd

		read()
		小--大
		write()

	} else if (pid > 0) {	

		close(cfd);		关闭用于与客户端通信的套接字 cfd	
		contiue;
	}
  }

5. 子进程:

			close(lfd)
	
			read()
	
			小--大
	
			write()	

   父进程:

			close(cfd);
	
			注册信号捕捉函数:	SIGCHLD
	
			在回调函数中, 完成子进程回收
	
				while (waitpid());

多线程并发服务器
server.c

1. Socket();		创建 监听套接字 lfd

2. Bind()		绑定地址结构 Strcut scokaddr_in addr;

3. Listen();		

4. while (1) {		

	cfd = Accept(lfd, );

	pthread_create(&tid, NULL, tfn, (void *)cfd);
	//pthread_join()阻塞回收子线程

	pthread_detach(tid);  				// pthead_join(tid, void **);  新线程---专用于回收子线程。
  }

5. 子线程:

	void *tfn(void *arg) 
	{
		// close(lfd)			不能关闭。 主线程要使用lfd

		read(cfd)

		小--大

		write(cfd)

		pthread_exit((void *)10);	
	}

3.2 实现多进程并发服务器

3.3 实现多线程并发服务器

其中首字母大写的函数为经过封装判断返回值的自定义函数。
服务器端:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <wrap.h>
#include <pthread.h>
#include <stdint.h>
#define srv_port 9977
#define MAXLINE 8192

void * do_work(void *arg)
{
    int n,i;
    int cfd = (int)(intptr_t)arg;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];

    while(1) {
        n = Read(cfd,buf,MAXLINE);
        if (n == 0 )
        {
            printf("the client cfd = %d closed...\n",cfd);
            break ;
        }
        //printf("received from %s at Port %d\n",inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipbuf,sizeof(ipbuf)),ntohs(client_addr.sin_port));
        for (i = 0; i<n ;i++)
        {
            buf[i] = toupper(buf[i]);
        }
        Write(STDOUT_FILENO,buf,n);
        Write(cfd,buf,n);
    }
    
}

int main()
{
    int lfd,cfd;
    //int socket(int domain, int type, int protocol);
    lfd = Socket(AF_INET,SOCK_STREAM,0);

    //int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
    struct sockaddr_in srv_addr;
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(srv_port);
    srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    Bind(lfd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));

    //int Listen(int fd, int backlog);
    int ret;
    pthread_t tid;
    ret = Listen(lfd,128);

    //int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    while(1){
    cfd = Accept(lfd,(struct sockaddr *)&client_addr,&client_addr_len); //阻塞等待client连接
    char ipbuf[64] = {0};
    printf("client IP:%s, port:%d\n",inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipbuf,sizeof(ipbuf)),ntohs(client_addr.sin_port));
    pthread_create(&tid,NULL,do_work,(void *)(intptr_t)cfd);
    pthread_detach(tid);
    }
    return 0;
}

客户端可用
nc 192.168.1.77 9977 ,来与服务端建立连接。

3.4 select多路IO转接服务器

响应式–多路IO转接服务器

在这里插入图片描述
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

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

  • nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态

  • readfds: 监控有读数据到达文件描述符集合,传入传出参数

  • writefds: 监控写数据到达文件描述符集合,传入传出参数

  • exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数

  • timeout: 定时阻塞监控时间,3种情况
    1.NULL,永远等下去
    2.设置timeval,等待固定时间
    3.设置timeval里时间均为0,检查描述字后立即返回,轮询
    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);
    select多路IO转接:

    原理: 借助内核, select 来监听, 客户端连接、数据通信事件。

    void FD_ZERO(fd_set *set); — 清空一个文件描述符集合。

      fd_set rset;
    
      FD_ZERO(&rset);
    

    void FD_SET(int fd, fd_set *set); — 将待监听的文件描述符,添加到监听集合中

      FD_SET(3, &rset);	FD_SET(5, &rset);	FD_SET(6, &rset);
    

    void FD_CLR(int fd, fd_set *set); — 将一个文件描述符从监听集合中 移除。

      FD_CLR(4, &rset);
    

    int FD_ISSET(int fd, fd_set *set); — 判断一个文件描述符是否在监听集合中。

      返回值: 在:1;不在:0;
    
      FD_ISSET(4, &rset);
    

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

      nfds:监听的所有文件描述符中,最大文件描述符+1
    
      readfds: 读 文件描述符监听集合。	传入、传出参数
    
      writefds:写 文件描述符监听集合。	传入、传出参数		NULL
    
      exceptfds:异常 文件描述符监听集合	传入、传出参数		NULL
    
      timeout: 	> 0: 	设置监听超时时长。
    
      		NULL:	阻塞监听
    
      		0:	非阻塞监听,轮询
      返回值:
    
      	> 0:	所有监听集合(3个)中, 满足对应事件的总数。
    
      	0:	没有满足监听条件的文件描述符
    
      	-1: 	errno
    

3.4.1 思路

思路分析:

int maxfd = 0;

lfd = socket() ;			创建套接字

maxfd = lfd;

bind();					绑定地址结构

listen();				设置监听上限

fd_set rset, allset;			创建r监听集合

FD_ZERO(&allset);				将r监听集合清空

FD_SET(lfd, &allset);			将 lfd 添加至读集合中。

while(1) {

	rset = allset;			保存监听集合

	ret  = select(lfd+1, &rset, NULL, NULL, NULL);		监听文件描述符集合对应事件。

	if(ret > 0) {							有监听的描述符满足对应事件
	
		if (FD_ISSET(lfd, &rset)) {				// 1 在。 0不在。

			cfd = accept();				建立连接,返回用于通信的文件描述符

			maxfd = cfd;

			FD_SET(cfd, &allset);				添加到监听通信描述符集合中。
		}

		for (i = lfd+1; i <= 最大文件描述符; i++){

			FD_ISSET(i, &rset)				有read、write事件

			read()

			小 -- 大

			write();
		}	
	}
}

select优缺点:

缺点: 监听上限受文件描述符限制。 最大 1024.

	检测满足条件的fd, 自己添加业务逻辑提高小。 提高了编码难度。

优点: 跨平台。win、linux、macOS、Unix、类Unix、mips

3.4.2 代码实现

3.5 epoll实现多路IO转接服务器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值