【1】poll
函数
引入poll的目的:
因为select最多只能监听1024个文件描述符;
#include <poll.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
fds:
struct pollfd {
int fd; /* file descriptor */ 需要关心的文件描述符
short events; /* requested events */ 产生的事件
short revents; /* returned events */
};
监听数据是从键盘来的还是从鼠标来的?用poll实现;
1. 创建文件描述符集合;
struct pollfd fds[2];
2. 把关心的文件描述符加入到集合当中;
fds[0].fd = 0; //键盘
fds[0].events = POLLIN;
fds[1].fd = fd; //鼠标
fds[1].events = POLLIN;
3. 调用poll函数
poll(fds,2,1000); //1000表示1秒,以毫秒为单位。不想设置超时时间 -1;
4. if(fds[0].revents == POLLIN)
if(fds[1].revents == POLLIN)
任务:使用poll,想监听标准输入0,还想监听sockfd,acceptfd用于数据通信的套接字;
【2】服务器模型
1、循环服务器:
依次只能处理一个客户端,等这个客户端所有请求都完成后(客户端关闭),
才能处理下一个客户端
缺点: 循环服务器所处理的客户端,不能做耗时动作
socket(...);
bind(...);
listen(...);
while(1)
{
accept(...);
while(1)
{
recv(...);
process(...);
send(...);
}
close(...);
}
2、并发服务器:
可以同时处理多个客户端的请求,创建子进程/子线程来处理跟客户端的请求
流程如下:
void handler(int sigo)
{
while (waitpid(-1, NULL, WNOHANG) > 0); //一个SIGCHLD可能对应多个僵尸进程,循环收尸
}
int sockfd = socket(...);
bind(...);
listen(...);
signal(SIGCHLD, handler); //注册SIGCHLD信号,当产生SIGCHLD信号时调用handler函数
while(1) {
int connfd = accept(...);
if (fork() == 0) {
close(sockfd);
while(1)
{
recv(...);
process(...);
send(...);
}
close(connfd);
exit(...);
}
close(connfd);
}
效果: 父进程--只做链接 子进程--跟客户端的通信(耗时)
【3】三次握手和四次挥手
ACK: 确认包;握手
SYN: 同步包;握手
FIN: 结束包;挥手
三次握手:
在建立连接的时候进行。客户端主动方,连接服务器;
第一次握手(connect),有客户端向服务器发送SYN同步包,告诉服务器我要连接了
第二次握手,服务器收到SYN后,要回复一个确认包,告诉客户端已经收到了,
并发送同步包(确认一下服务器发的包是否能发出去);
第三次握手,客户端发送确认包给服务器;
四次挥手:
TCP套接字是全双工的;close时,两端都要关闭
客户端和服务器都可以作为主动方;
第一次挥手;客户端调用close,发送FIN包;
第二、三次挥手;服务器端,先回复确认包,确认已经收到断开连接的请求,
并且再发FIN结束包;
第四次挥手;客户端再次发送ACK确认包;
【4】setsockopt:
NAME
getsockopt, setsockopt - get and set options on sockets
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#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 : sockfd (socket()的返回值)
level:
SOL_SOCKET : 应用层;
IPPROTO_IP : IP层/网络层
IPPRO_TCP : 传输层
optname:
SO_REUSEADDR 允许重用本地地址和端口
optval:
&值
optlen:
第四个参数值的大小
===================================
//设置允许重用本地地址和端口
int on = 1;
if (0 > setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
{
perror("setsockopt");
return -1;
}
【5】超时检测
1、select poll可以通过参数设置超时时间 struct timeval ;
2. 通过setsockopt设置超时时间
SO_RCVTIMEO 接收超时 struct timeval
设置接收超时时间为两秒,TCP代码来写;
2秒
struct timval tv = {2,0};
#if 0
struct timeval tv = {1};
if (0 > setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)))
{
perror("setsockopt");
return -1;
}
3、 alarm(5),使用闹钟定时器实现超时检测;
void handler(int sign)
{
printf("timeout....\n");
}
struct sigaction act;
sigaction(SIGALRM, NULL, &act); //获取SIGALRM原有的处理动作
act.sa_handler = handler; //相当于signal第二个参数
act.sa_flags &= ~SA_RESTART; //关闭重启
sigaction(SIGALRM, &act, NULL); //设置SIGALRM的处理动作
while(1)
{
alarm(5);
if (recv(,,,) < 0) ……
}