实现一个基本的流式套接字客户端/服务器通信程序,客户端和服务器按如下步骤交互: (1)客户端向服务器发出日期时间请求字符串,如:%D %Y %A %T等。
(2)服务器从网络接收到日期时间请求字符串后,根据字符串格式生成对应的日期时间值返回给客户端。
为了在一个套接字上使用信号驱动 I/O 操作,下面这三步是所必须的。 (1)一个和 SIGIO信号的处理函数必须设定。 (2)套接字的拥有者必须被设定。一般来说是使用 fcntl 函数的 F_SETOWN 参数来 进行设定拥有者。 (3)套接字必须被允许使用异步 I/O。一般是通过调用 fcntl 函数的 F_SETFL 命令,O_ASYNC为参数来实现。虽然设定套接字为异步 I/O 非常简单,但是使用起来困难的部分是怎样在程序中断定产生 SIGIO信号发送给套接字属主的时候,程序处在什么状态。
在UDP 协议上使用异步 I/O 非常简单.这个信号将会在这个时候产生:
(1)套接字收到了一个数据报的数据包。 (2)套接字发生了异步错误。 当我们在使用 UDP 套接字异步 I/O 的时候,我们使用 recvfrom()函数来读取数据报数据或是异步 I/O 错误信息。
2.TCP 套接字的 SIGIO 信号 不幸的是,异步 I/O 几乎对 TCP 套接字而言没有什么作用。因为对于一个 TCP 套接字来说,SIGIO 信号发生的几率太高了,所以 SIGIO 信号并不能告诉我们究竟发生了什么事情。 (1)在 TCP 连接中, SIGIO 信号将会在这个时候产生: (2)在一个监听某个端口的套接字上成功的建立了一个新连接。 (3)一个断线的请求被成功的初始化。 (4)一个断线的请求成功的结束。 (5)套接字的某一个通道(发送通道或是接收通道)被关闭。 (6)套接字接收到新数据。 (7)套接字将数据发送出去。 (8)发生了一个异步 I/O 的错误。
二、设置套接字工作于信号驱动I/O模式
为了让套接字描述符可以工作于信号驱动I/O模式,应用进程必须完成如下三步设置:
(1)注册SIGIO信号处理程序
(2)使用fcntl的F_SETOWN命令,设置套接字所有者为当前进程
(3)使用fcntld的F_SETFL命令,置O_ASYNC标志,允许套接字使用信号驱动I/O。
注意:必须保证在设置套接字所有者之前,向系统注册信号处理程序,否则就有可能在fcntl调用后,信号处理程序注册前内核向应用交付SIGIO信号,导致应用丢失此信号,下面的程序片段描述了怎么样为套接字设置信号驱动I/O。
void do_sigio(int sig)
{
//SIGIO处理程序代码
}
...
int sockfd;//套接字
struct sigaction sigio_action;
...
sigio_action.sa_handler=do_sigio;//信号处理程序
if(sigaction(SIGIO,&sigio_action,NULL)==-1)
{
//出错
}
//设置套接字所有者为当前进程
if((flags=fcntl(sockfd,F_SETOWN,getpid()))<0)
{
//出错
}
//获取当前套接字的flags
if((flags=fcntl(sockfd,F_GETFL,0))<0)
{
//出错
}
//设置信号驱动和非阻塞模式
//注意:在设置套接字工作于非驱动模式时,要先取得原有状态标志,然后通过逻辑或来增加新的标志,\
不能直接设置套接字为信号驱动和非阻塞模式,这样会清除套接字的原有其它工作模式
flags|=O_ASYNC|O_NONBLOCK;
if (fcntl(s, F_SETFL,flags)<0)
{
//出错
}
使用信号驱动模式重写了前面的时间服务器程序,在信号处理程序中读取套接字接收到的UDP数据报,然后将数据存入一个消息队列,再由应用的主循环从队列中取出数据报进行处理。
/*
UDP服务器
说明:用于在sigio信号处理程序中接收来自数据报客户端发来的数据报,接到的数据报存放
在一个队列中,随后程序主循环将从此队列中读取数据并进行处理。
用法:./server ip portnumber
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define QUESIZE 16 //队列大小
#define BUFSIZE 1024
struct request
{
char *reqstr; //接收缓存字符串
size_t reqlen; //字符串长度
struct sockaddr_in *peer; //客户端地址
socklen_t sklen;
};
staticvoidbail(constchar*on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what, stderr);
fputc('\n',stderr);
exit(1);
}
static struct request req_queue[QUESIZE]; //用户空间的缓存队列
static int idx_in;
static int idx_out;
static int nqueue;
int s;
struct sockaddr_in peer_addr;//服务器套接字
static socklen_t socklen=sizeof(peer_addr);
//sigio信号处理函数
void do_sigio(int signum)
{
long z;
struct request *p_req;
for (; ; )
{
//保存下一个客户端请求数据报的位置索引
p_req=&req_queue[idx_in];
if (nqueue>=QUESIZE)
{
write(STDOUT_FILENO,"request queue is full!\n",23);
return;
}
z=recvfrom(s, p_req->reqstr, BUFSIZE, 0, (struct sockaddr*)p_req->peer, &socklen);
if (z<0)
{
//设置服务器套接字工作于非阻塞模式
if(errno==EWOULDBLOCK)
break;
else
{
write(STDOUT_FILENO, "recvfrom error!\n", 16);
exit(1);
}
}
p_req->reqstr[z]=0;
p_req->reqlen=z;
nqueue++;
if (++idx_in>=QUESIZE)
idx_in=0;
}
}
//初始化队列
void init_queue()
{
for (int i=0; i
{
if ((req_queue[i].reqstr=(char *)malloc(BUFSIZE))==NULL)
bail("init_queue");
if((req_queue[i].peer=(struct sockaddr_in *)malloc(sizeof(sockaddr_in)))==NULL)
bail("init_queue");
req_queue[i].sklen=socklen;
}
idx_in=idx_out=nqueue=0;
}
//注册sigio信号处理程序
static void install_sigio()
{
struct sigaction sigio_action;
memset(&sigio_action, 0, sizeof(sigio_action));
sigio_action.sa_flags=0;
sigio_action.sa_handler=do_sigio;
if (sigaction(SIGIO, &sigio_action, NULL)==-1)
perror("failed to set SIGIO");
}
//设置套接字为信号驱动I/O和非阻塞模式
void set_sockopt(int s,int flags)
{
fcntl(s, F_SETOWN,getpid());
if ((flags=fcntl(s, F_GETFL,0))<0)
bail("F_GETFL error");
flags|=O_ASYNC|O_NONBLOCK;
if (fcntl(s, F_SETFL,flags)<0)
bail("F_SETFL error");
}
int main(int argc,char **argv)
{
long z;
char *srvr_addr=NULL;
int len_inet;
int portnumber;
int flags;
struct sockaddr_in srvaddr;//服务器地址
char dtfmt[BUFSIZE]; //日期-时间结果
time_t td; //当前时间和日期
struct tm tv; // 日期时间结构体
sigset_t zeromask,newmask,oldmask;
struct request *p_req;
/*
若命令行提供了作为服务器地址和端口的参数,则使用参数作为地址和端口,否则使用默认的地址和端口
*/
if (argc>2)
{
srvr_addr=argv[1];
if ((portnumber=atoi(argv[2]))<0)
{
fprintf(stderr, "port error");
exit(1);
}
}
else
{
srvr_addr="0";
portnumber=9000;
}
//创建数据报套接字
s=socket(AF_INET,SOCK_DGRAM,0);
if (s==-1)
bail("socket()");
init_queue();//初始化应用数据报接收队列
install_sigio();//注册sigio信号处理程序
set_sockopt(s, flags);//设置非阻塞和sigio驱动I/O模式
//初始化套接字地址
memset(&srvaddr, 0, sizeof(srvaddr));
srvaddr.sin_family=PF_INET;
srvaddr.sin_port=htons(portnumber);
if (!inet_aton(srvr_addr, &srvaddr.sin_addr))
bail("bad address.");
len_inet=sizeof(srvaddr);
//绑定套接字到指定地址和端口,于是客户端可以连接到该服务器
z=bind(s, (struct sockaddr*)&srvaddr, len_inet);
if (z==-1)
bail("bind()");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigemptyset(&oldmask);
sigaddset(&newmask, SIGIO);
// sigprocmask(SIG_BLOCK, &newmask, &oldmask);
for (; ; )
{
while (nqueue==0)
sigsuspend(&zeromask); //挂起进程,直到被任何信号唤醒
if (idx_out>QUESIZE)
idx_out=0;
p_req=&req_queue[idx_out++];
time(&td);
tv=*localtime(&td);
strftime(dtfmt, sizeof(dtfmt), p_req->reqstr, &tv);
z=sendto(s, dtfmt, strlen(dtfmt), 0, (struct sockaddr*)p_req->peer, p_req->sklen);
if (z<0)
bail("sendto()");
//更新临界变量,必须阻塞sigio信号
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
nqueue--;
sigprocmask(SIG_SETMASK, &oldmask, NULL);
}
return 0;
}
/*
UDP客户端
用法:./client ip port
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFSIZE 1024
staticvoidbail(constchar*on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what, stderr);
fputc('\n',stderr);
exit(1);
}
int main(int argc,char **argv)
{
long z;
int sockfd;
char *srv_addr=NULL;
struct sockaddr_in server_addr;//服务器地址
struct sockaddr_in _addr;
int portnumber;
char dgram[BUFSIZE];
//从命令行获取服务器地址字符串
srv_addr=argv[1];
if((portnumber=atoi(argv[2]))<0)
{
fprintf(stderr,"error");
exit(1);
}
//创建服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
if (!inet_aton(srv_addr, &server_addr.sin_addr))
bail("bad address");
//创建UDP套接字
sockfd=socket(AF_INET,SOCK_DGRAM, 0);
if (sockfd==-1)
bail("socket()");
for (; ; )
{
fputs("\nEnter format string:",stdout);
if (!fgets(dgram,sizeof(dgram), stdin))
break;
z=strlen(dgram);
if (z>0 && dgram[--z]=='\n')
dgram[z]=0; //添加NULL结束标记
if(z==0)
continue;
//发送请求字符串
z=sendto(sockfd, dgram, strlen(dgram), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (z<0)
bail("sendto()");
printf("send over...ans dgram=%s\n",dgram);
//如果输入了退出命令‘quit’
if (!strcasecmp(dgram,"QUIT"))
break;
//等待服务器应答
socklen_t size=sizeof(_addr);
z=recvfrom(sockfd, dgram, sizeof(dgram), 0, (struct sockaddr*)&_addr, &size);
if (z<0)
bail("recvfrom()");
dgram[z]=0;
printf("result from %s port %u:\n\t '%s'\n",inet_ntoa(_addr.sin_addr),ntohs(_addr.sin_port),dgram);
}
printf("\n exits from loop.\n");
close(sockfd);
return 0;
}