目录
三、select、setsockopt/getsockopt函数说明
一、服务器模型
服务器模型分为两种:循环服务器、并发服务器
循环服务器:在同一个时刻只能响应一个客户端的请求。
并发服务器:在同一时刻可以响应多个客户端的请求。
TCP服务器默认就是一个循环服务器,受两类阻塞函数accept和recv之间的影响。
UDP服务器默认就是一个并发服务器,因为只有一个会造成阻塞的函数(recvfrom)。
二、TCP并发服务器
在有些场景下我们即需要保证数据可靠,又需要支持并发,这样就需要用到TCP并发服务器了。
2.1使用多线程实现TCP并发服务器
思路:主线程负责accept等待客户端连接,一旦有客户端连接就创建一个子线程专门用于和当前的客户端通信。
服务器代码:
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define PRINT_ERR(msg) \
do { \
printf("%s %s:%d\n", __FILE__, __func__, __LINE__); \
perror(msg); \
return -1; \
} while (0)
typedef struct MSG {
char txt[128];
int acceptfd;
struct sockaddr_in clientaddr;
} msg_t;
// 线程体
void* deal_read_write(void* arg)
{
pthread_detach(pthread_self()); // 将线程设置成被动分离状态
msg_t buff = *(msg_t*)arg;
// 定义结构体保存客户端信息
int nbytes = 0; // 记录recv函数的返回值
int acceptfd;
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddr_len = sizeof(clientaddr);
acceptfd = buff.acceptfd;
clientaddr = buff.clientaddr;
while (1) {
// 收发数据
memset(&buff, 0, sizeof(buff));
if (-1 == (nbytes = recv(acceptfd, &buff, sizeof(buff), 0))) {
perror("recv error");
printf("%s %s:%d\n", __FILE__, __func__, __LINE__);
pthread_exit(EXIT_FAILURE);
} else if (0 == nbytes) {
printf("客户端[%s:%d]断开了连接..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
break;
}
if (!strcmp(buff.txt, "quit")) {
printf("客户端[%s:%d]退出了..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
break;
}
printf("[%s:%d]:[%s]\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buff.txt);
// 组装应答回复客户端
strcat(buff.txt, "--server");
if (-1 == send(acceptfd, &buff, sizeof(buff), 0)) {
perror("send error");
printf("%s %s:%d\n", __FILE__, __func__, __LINE__);
pthread_exit(EXIT_FAILURE);
}
}
close(acceptfd);
pthread_exit(EXIT_SUCCESS);
}
int main(int argc, const char* argv[])
{
// 入参合理性检查
if (3 != argc) {
printf("Usage : %s <ip> <port>\n", argv[0]);
exit(-1);
}
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
PRINT_ERR("socket error");
}
// 2.填充服务器网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
// 设置端口复用
int flag = 1;
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) {
printf("setsockopt error");
}
// 3.将套接字与服务器的网络信息结构体绑定
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
PRINT_ERR("bind error");
}
// 4.将套接字设置成被动监听状态
if (-1 == listen(sockfd, 5)) {
PRINT_ERR("listen error");
}
// 定义结构体保存客户端信息
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddr_len = sizeof(clientaddr);
int nbytes = 0;
msg_t buff;
// 5.阻塞等待客户端连接
pthread_t tid;
int acceptfd = 0;
int fd = 0;
char file[128] = { 0 };
while (1) {
// 主线程收发数据
printf("正在等待客户端连接..\n");
if (-1 == (acceptfd = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len))) {
PRINT_ERR("accept error");
}
printf("客户端[%s:%d]连接到服务器..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
buff.acceptfd = acceptfd;
buff.clientaddr = clientaddr;
// 创建子线程
if ((errno = pthread_create(&tid, NULL, deal_read_write, (void*)&buff)) != 0)
PRINT_ERR("pthread create error");
}
// 关闭套接字
close(sockfd);
return 0;
}
客户端代码:
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define PRINT_ERR(msg) \
do { \
printf("%s %s:%d\n", __FILE__, __func__, __LINE__); \
perror(msg); \
return -1; \
} while (0)
typedef struct MSG {
char txt[128];
int acceptfd;
struct sockaddr_in clientaddr;
} msg_t;
int main(int argc, const char* argv[])
{
// 入参合理性检查
if (3 != argc) {
printf("Usage : %s <ip> <port>\n", argv[0]);
exit(-1);
}
// 1.创建流式套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
PRINT_ERR("socket error");
}
// 2.填充服务器网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET; //ipv4协议
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
// 3.与服务器建立连接
if (-1 == connect(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
PRINT_ERR("connect error");
}
printf("与服务器连接成功..\n");
// 收发数据
msg_t buff;
int fd, ret = 0;
while (1) {
//从终端读取需要发送的消息
memset(&buff, 0, sizeof(buff));
fgets(buff.txt, 128, stdin);
buff.txt[strlen(buff.txt) - 1] = '\0';
if (!strcmp(buff.txt, "quit"))
break;
// 发送数据
if (-1 == send(sockfd, &buff, sizeof(buff), 0))
PRINT_ERR("send error");
// 接收应答消息
memset(&buff, 0, sizeof(buff));
if (-1 == recv(sockfd, &buff, sizeof(buff), 0))
PRINT_ERR("recv error");
printf("server:[%s]\n",buff.txt); //打印服务器消息
}
// 关闭套接字
close(sockfd);
return 0;
}
makefile:
all:server client
server:server.o
gcc $^ -o $@ -lpthread
client:client.o
gcc $^ -o $@ -lpthread
%.o:%.c
gcc -c $^ -o $@ -lpthread
clean:
rm server client *.o
2.2使用多进程实现TCP并发服务器
思路:父进程负责accept等待客户端连接,一旦有客户端连接就创建一个子进程专门用于和当前的客户端通信。
服务器代码:
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define PRINT_ERR(msg) \
do { \
printf("%s %s:%d\n", __FILE__, __func__, __LINE__); \
perror(msg); \
return -1; \
} while (0)
typedef struct MSG {
char txt[128];
int acceptfd;
struct sockaddr_in clientaddr;
} msg_t;
// 信号处理函数
void sig_function(int signo)
{
if (SIGUSR1 == signo) {
wait(NULL); // 不关心子进程exit的返回值
}
}
int main(int argc, const char* argv[])
{
// 入参合理性检查
if (3 != argc) {
printf("Usage : %s <ip> <port>\n", argv[0]);
exit(-1);
}
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
PRINT_ERR("socket error");
}
// 2.填充服务器网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET; // ipv4协议
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
// 设置端口复用
int flag = 1;
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) {
printf("setsockopt error");
}
// 3.将套接字与服务器的网络信息结构体绑定
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
PRINT_ERR("bind error");
}
// 4.将套接字设置成被动监听状态
if (-1 == listen(sockfd, 5)) {
PRINT_ERR("listen error");
}
// 定义结构体保存客户端信息
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddr_len = sizeof(clientaddr);
int pid;
msg_t buff;
int nbytes = 0;
int acceptfd = 0;
// 使用signal函数回收子进程资源
signal(SIGUSR1, sig_function);
// 5.阻塞等待客户端连接
while (1) {
// 主程收发数据
printf("正在等待客户端连接..\n");
if (-1 == (acceptfd = accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len))) {
PRINT_ERR("accept error");
}
printf("客户端[%s:%d]连接到服务器..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
buff.acceptfd = acceptfd;
buff.clientaddr = clientaddr;
// 创建子进程
pid = fork();
if (-1 == pid) {
PRINT_ERR("fork error");
} else if (0 == pid) {
// 子进程
while (1) {
// 收发数据
memset(&buff, 0, sizeof(buff));
if (-1 == (nbytes = recv(acceptfd, &buff, sizeof(buff), 0))) {
PRINT_ERR("recv error");
} else if (0 == nbytes) {
printf("客户端[%s:%d]断开了连接..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
break;
}
if (!strcmp(buff.txt, "quit")) {
printf("客户端[%s:%d]退出了..\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
break;
}
printf("[%s:%d]:[%s]\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buff.txt);
// 给客户端回消息
strcat(buff.txt, "--server");
if (-1 == send(acceptfd, &buff, sizeof(buff), 0)) {
PRINT_ERR("send error");
}
}
// 给父进程发送子进程退出的信号
kill(getppid(), SIGUSR1);
close(acceptfd);
exit(EXIT_SUCCESS); // 子进程退出
} else {
close(acceptfd);
}
}
// 关闭套接字
close(sockfd);
return 0;
}
客户端代码:
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define PRINT_ERR(msg) \
do { \
printf("%s %s:%d\n", __FILE__, __func__, __LINE__); \
perror(msg); \
return -1; \
} while (0)
typedef struct MSG {
char txt[128];
int acceptfd;
struct sockaddr_in clientaddr;
} msg_t;
int main(int argc, const char* argv[])
{
// 入参合理性检查
if (3 != argc) {
printf("Usage : %s <ip> <port>\n", argv[0]);
exit(-1);
}
// 1.创建流式套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
PRINT_ERR("socket error");
}
// 2.填充服务器网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
// 3.与服务器建立连接
if (-1 == connect(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
PRINT_ERR("connect error");
}
printf("与服务器连接成功..\n");
// 收发数据
msg_t buff;
int fd, ret = 0;
while (1) {
memset(&buff, 0, sizeof(buff));
fgets(buff.txt, 128, stdin);
buff.txt[strlen(buff.txt) - 1] = '\0';
if (!strcmp(buff.txt, "quit"))
break;
// 发送数据
if (-1 == send(sockfd, &buff, sizeof(buff), 0))
PRINT_ERR("send error");
// 接收应答消息
memset(&buff, 0, sizeof(buff));
if (-1 == recv(sockfd, &buff, sizeof(buff), 0))
PRINT_ERR("recv error");
printf("server:[%s]\n",buff.txt);
}
// 关闭套接字
close(sockfd);
return 0;
}
makefile:
all:server client
server:server.o
gcc $^ -o $@
client:client.o
gcc $^ -o $@
%.o:%.c
gcc -c $^ -o $@
clean:
rm server client *.o
2.3使用多路IO复用实现TCP并发服务器
思路:TCP服务中有两类阻塞函数(recv和accept)是造成TCP无法并发的罪魁祸首,其本质就是sockfd和acceptfd之间的影响,只要解决了这两类文件描述符之间的相互影响就解决了TCP并发的问题,我们可以使用多路IO复用来解决这个问题。
多路IO复用基本思想:构造一个关于文件描述符的表,将要监视的文件描述符都放在这个表里,将这表交给一个函数 (select poll epoll),这个函数默认也是阻塞的,当监视的文件描述符中,有一个或多个准备就绪的时候,这个函数会返回,返回的时候会通知我们哪些文件描述符准备就绪可以进行IO操作了我们再去执行相应的IO操作 就不会阻塞了。
服务器代码:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define PRINT_ERR(msg) \
do { \
printf("%s %s:%d\n", __FILE__, __func__, __LINE__); \
perror(msg); \
return -1; \
} while (0)
typedef struct MSG {
char txt[128];
int acceptfd;
struct sockaddr_in clientaddr;
} msg_t;
int main(int argc, const char* argv[])
{
// 入参合理性检查
if (3 != argc) {
printf("Usage : %s <ip> <port>\n", argv[0]);
exit(-1);
}
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
PRINT_ERR("socket error");
}
// 2.填充服务器网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
// 设置端口复用
int flag = 1;
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) {
printf("setsockopt error");
}
// 3.将套接字与服务器的网络信息结构体绑定
if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
PRINT_ERR("bind error");
}
// 4.将套接字设置成被动监听状态
if (-1 == listen(sockfd, 5)) {
PRINT_ERR("listen error");
}
// 创建监视文件描述符的集合
fd_set rfds; // 母本
fd_set rfds_temp; // 副本
int max_fd = 0; // 最大文件描述符
FD_ZERO(&rfds);
FD_ZERO(&rfds_temp);
FD_SET(sockfd, &rfds); // 将套接字添加到需要监视的文件描述符集中
max_fd = max_fd > sockfd ? max_fd : sockfd; // 更新最大文件描述符
msg_t buff;
int ret = 0, i = 0, nbytes = 0;
int acceptfd = 0;
定义结构体保存客户端信息(如果需要给指定客户端回复需要保存信息,会用到MSG结构体第三个参数)
// struct sockaddr_in clientaddr;
// memset(&clientaddr, 0, sizeof(clientaddr));
// socklen_t clientaddr_len = sizeof(clientaddr);
// 5.阻塞等待客户端连接
while (1) {
rfds_temp = rfds; // 每次调用select函数都需要提前重置一下副本
if (-1 == (ret = select(max_fd + 1, &rfds_temp, NULL, NULL, NULL)))
PRINT_ERR("select error");
for (i = 3; i < max_fd + 1 && ret != 0; i++) {
// 遍历判断哪一个文件描述符处于活跃状态
if (FD_ISSET(i, &rfds_temp)) {
if (i == sockfd) { // 有新的客户端连接
if (-1 == (acceptfd = accept(i, NULL, NULL)))
PRINT_ERR("accept error");
printf("[%d]客户端连接到服务器\n", acceptfd);
// 将新的文件描述符添加到监视集合中(母本中)
FD_SET(acceptfd, &rfds);
max_fd = max_fd > acceptfd ? max_fd : acceptfd; // 更新最大文件描述符
} else { // 有客户端发来消息
memset(&buff, 0, sizeof(buff));
if (-1 == (nbytes = recv(i, &buff, sizeof(buff), 0))) {
PRINT_ERR("recv error");
}
// 处理客户端异常退出
if (0 == nbytes) {
printf("客户端[%d]断开了连接..\n", i);
FD_CLR(i, &rfds);
close(i);
continue;
}
// 处理客户端主动退出
if (!strcmp(buff.txt, "quit")) {
printf("客户端[%d]退出了..\n", i);
FD_CLR(i, &rfds);
close(i);
continue;
}
printf("[%d]:[%s]\n", i, buff.txt);
// 给客户端组装应答消息
strcat(buff.txt, "--😀");
if (-1 == send(i, &buff, sizeof(buff), 0)) {
PRINT_ERR("send error");
}
}
ret--;
}
}
}
// 关闭套接字
close(sockfd);
return 0;
}
客户端代码:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define PRINT_ERR(msg) \
do { \
printf("%s %s:%d\n", __FILE__, __func__, __LINE__); \
perror(msg); \
return -1; \
} while (0)
typedef struct MSG {
char txt[128];
int acceptfd;
struct sockaddr_in clientaddr;
} msg_t;
int main(int argc, const char* argv[])
{
// 入参合理性检查
if (3 != argc) {
printf("Usage : %s <ip> <port>\n", argv[0]);
exit(-1);
}
// 1.创建流式套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd) {
PRINT_ERR("socket error");
}
// 2.填充服务器网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET; //ipv4协议
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
// 3.与服务器建立连接
if (-1 == connect(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
PRINT_ERR("connect error");
}
printf("与服务器连接成功..\n");
// 收发数据
msg_t buff;
int fd, ret = 0;
while (1) {
memset(&buff, 0, sizeof(buff));
fgets(buff.txt, 128, stdin);
buff.txt[strlen(buff.txt) - 1] = '\0';
// 发送数据
if (-1 == send(sockfd, &buff, sizeof(buff), 0))
PRINT_ERR("send error");
if (!strcmp(buff.txt, "quit"))
break;
// 接收应答消息
memset(&buff, 0, sizeof(buff));
if (-1 == recv(sockfd, &buff, sizeof(buff), 0))
PRINT_ERR("recv error");
printf("server:[%s]\n", buff.txt); //打印服务器消息
}
// 关闭套接字
close(sockfd);
return 0;
}
makefile:
all:server client
server:server.o
gcc $^ -o $@
client:client.o
gcc $^ -o $@
%.o:%.c
gcc -c $^ -o $@
clean:
rm server client *.o
三、select、setsockopt/getsockopt函数说明
3.1 selsct函数说明:
功能:
实现多路IO复用
头文件:
#include <sys/select.h>
函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数:
nfds: 要监视的最大的文件描述符 +1
readfds: 要监视的读文件描述符集合 如果不关心 可以传NULL
writefds: 要监视的写文件描述符集合 如果不关心 可以传NULL
exceptfds: 要监视的异常文件描述符集合 如果不关心 可以传NULL
timeout: 超时时间 如果传 NULL 则永久阻塞
返回值:
成功 就绪的文件描述符的个数
超时 0
失败 -1 重置错误码
四个宏函数:
void FD_CLR(int fd, fd_set *set); //将文件描述符在集合中删除
int FD_ISSET(int fd, fd_set *set); //测试文件描述符是否还在集合中
// 返回0 不在 返回非0 还在
void FD_SET(int fd, fd_set *set); //将文件描述符添加到集合中
void FD_ZERO(fd_set *set); //清空文件描述符集合
注意事项:
1.select只能用来监视小于 FD_SETSIZE 1024 的文件描述符,poll没有这个限制
2.select每次返回都会将没有就绪的文件描述符在集合中擦除
因此在循环中每次调用select之前,都要重置集合
3.大部分场景下我们只关心读文件描述符集合 readfds
3.2关于selset函数的几个问题
1.select函数fd_set集合的本质及为什么要限制最大文件描述符:
因为select函数的作用是为了设置或者获取文件描述符的状态;
文件描述符的状态只有就绪和非就绪两个状态,
所以在集合中我们首先需要保存这两个状态;
其次select函数可以同时监视多个文件描述符,
所以在集合中我们也需要保存不同的文件描述符;
为了节省空间我们用一个比特位的0和1来表示文件描述符的状态,用不同的比特位来表示不同的文件,
所以要监视的文件描述符的集合是一个long int xxx[16]的数组;
通过不同的比特位来表示不同文件描述符([0~1023]一共1024位),
通过每个比特位的0和1来表示文件描述符的状态。
2.select返回值有什么用?
select函数返回的是就绪的文件描述符的个数,
在调用select函数的整个循环过程中,select函数调用之后我们需要对就绪的文件描述符进行IO处理
如果已知就绪的文件描述符的个数,在遍历所有IO处理的过程中就可以减少遍历的个数,
判断的时候没必要从第一个文件描述符一直判断到最后一个文件描述符
3.select函数对应的四个宏函数的原理。
本质都是位运算
void FD_CLR(int fd, fd_set *set); //将文件描述符在集合中删除
也就是将个比特位置零(置为非就绪)int FD_ISSET(int fd, fd_set *set); //测试文件描述符是否还在集合中
// 返回0 不在 返回非0 还在
判断某一个比特位是否为1(是否为就绪态)void FD_SET(int fd, fd_set *set); //将文件描述符添加到集合中
将某一位的比特位置1(设置为就绪态)void FD_ZERO(fd_set *set); //清空文件描述符集合
将整个文件描述符集合置零(都为非就绪态)
4.为什么select参数中表示最大文件描述符的参数要+1.
因为在select函数中判断某一个比特位是否为1(判断哪个进程为就绪态)使用了类似for循环的方式
for(i = 0, i < nfds; i++)...要想把每一位都遍历到,需要让nfds+1
5.为什么每次调用select函数中文件描述符的集合都要重置。
因为每一次select函数调用完毕都会将传入的文件描述符集合中没有就绪的文件描述符置零
所以在每次循环判断各个文件描述符的状态之前都要将文件描述符的集合重置
3.3setsockopt/getsockopt函数说明
功能:
设置或者获取套接字的属性
头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数原型:
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
参数:
sockfd:要操作的套接字
level: 选项的级别
套接字API级别 SOL_SOCKET
TCP级别 IPPROTO_TCP
IP级别 IPPROTO_IP
optname:选项的名字 不同的选项在不同的级别中
套接字API级别:
SO_BROADCAST 是否允许发送广播
SO_RCVBUF 接收缓冲区大小
SO_SNDBUF 发送缓冲区大小
SO_RCVTIMEO 接收超时时间
SO_SNDTIMEO 发送超时时间
使用 struct timeval 结构体
如果超时返回-1 错误码置为 EAGAIN
SO_REUSEADDR 是否允许端口复用
TCP级别:
TCP_NODELAY 设置关闭nagle算法
IP级别:
IP_ADD_MEMBERSHIP 设置加入多播组
optval:选项的值
optlen:optval的大小
返回值:
成功 0
失败 -1 重置错误码