回顾:
多进程的问题:数据共享。
多进程的问题: 进程的上下文环境(context)
文件描述符号是整数以及对应上下文环境
多进程的问题:上下文环境共享
一.SELECT TCP服务器编程模式
1.select函数
int select(
int fds,//建议是监控的文件描述符号的最大值+1
fd_set *readfds,//读文件描述符号集合
//该参数既是输入,也是输出
//输入:被监控的描述符号
//输出:有数据的描述符号
fd_set *writefds,
fd_set *errfds,
struct timeval*timeout);//指定阻塞时间限制
//为NULL,永久
返回:
>0:发生改变的文件描述符号个数
=0:时间限制过期
=-1:异常
// select用法
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/select.h>
main()
{
fd_set fds;
int r;
char buf[100];
while(1)
{
FD_ZERO(&fds); // 清空
FD_SET(0,&fds); // 将描述符0加入
r=select(1,&fds,0,0,0); // 监控
printf("有数据输入!\n");
r=read(0,buf,99);
}
}
异步IO就是通过信号工作.
3.应用使用select
4.使用select实现TCP的多客户连接与处理
看个小例子:
// chatServer.c
// 聊天服务器端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include < string.h>
#include <sys/socket.h>
#include <netinet/ in.h>
#include <arpa/inet.h>
#include <sys/select.h>
main()
{
int sfd; // 服务器描述符号
int fdall[100]; // 客户描述符号
int count; // 客户个数
int r; // 返回值(异常处理)
struct sockaddr_in dr; // IP地址与端口
fd_set fds; // 被select监控的描述符号集合
int maxfd; // 最大文件描述符号
int i,j; // 循环变量
char buf[1024]; // 客户聊天数据
// 1.建立socket
sfd=socket(AF_INET,SOCK_STREAM,0);
if(sfd==-1) printf("1:%m\n"),exit(-1);
printf("socket ok!\n");
// 2.绑定地址与端口
dr.sin_family=AF_INET;
dr.sin_port=htons(8866);
inet_aton("192.168.180.92",&dr.sin_addr);
r=bind(sfd,( struct sockaddr*)&dr, sizeof(dr));
if(r==-1) printf("2:%m\n"),close(sfd),exit(-1);
printf("bind ok!\n");
// 3.监听
r=listen(sfd,10);
if(r==-1) printf("3:%m\n"),close(sfd),exit(-1);
printf("listen ok!\n");
// 初始化
count=0;
maxfd=0;
FD_ZERO(&fds);
for(i=0;i<100;i++)
{
fdall[i]=-1;
}
while(1)
{
// 4.构造监听的描述符号集合
// 4.1.清空
FD_ZERO(&fds);
maxfd=0;
// 4.2.加入服务器描述符号
FD_SET(sfd,&fds);
maxfd=maxfd>=sfd?maxfd:sfd;
// 4.3.加入客户描述符号
for(i=0;i<count;i++)
{
if(fdall[i]!=-1)
{
FD_SET(fdall[i],&fds);
maxfd=maxfd>=fdall[i]?maxfd:fdall[i];
}
}
// 5.使用select循环控制描述符号集合
r=select(maxfd+1,&fds,0,0,0);
if(r==-1)
{
printf("服务器崩溃!\n");
break;
}
// 6.分两种情况处理:
// 6.1.有客户连接:服务器描述符号
if(FD_ISSET(sfd,&fds))
{
fdall[count]=accept(sfd,0,0);
if(fdall[count]==-1)
{
printf("服务器崩溃!\n");
// 释放所有客户
break;
}
printf("有客户连接!\n");
count++;
}
// 6.2.有客户发送数据:客户描述符号
for(i=0;i<count;i++)
{
// 判定改变描述符号是否存在
if( fdall[i]!=-1 &&
FD_ISSET(fdall[i],&fds))
{
// 读取数据
r=recv(fdall[i],buf,1023,0);
if(r==0){
printf("有客户退出!\n");
close(fdall[i]);
fdall[i]=-1;
}
if(r==-1){
printf("网络故障!\n");
close(fdall[i]);
fdall[i]=-1;
}
if(r>0)
{
// 广播数据
buf[r]=0;
printf("广播数据:%s\n",buf);
for(j=0;j<count;j++)
{
if(fdall[j]!=-1)
{
send(fdall[j],buf,r,0);
}
}
}
}
}
}
}
int poll(
struct pollfd *fds,//监控的描述符号
int nfds,//监控的描述符号的个数
int timeout ); //阻塞超时
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/poll.h>
main()
{
struct pollfd fds[1];
int r;
char buf[100];
fds[0].fd=0;
fds[0].events=POLLIN;
while(1)
{
r=poll(fds,1,-1);
if(fds[0].revents & POLLIN)
{
printf("有数据输入!\n");
r=read(0,buf,99);
}
}
}
1.socket有哪些选项可以设置
ARP
|
IP
|
|-----------------|
UDP TCP
通用选项:
SOL_SOCKET
SO_BROADCAST 广播
SO_RCVBUF 描述符号的缓冲的大小
SO_SNDBUF 描述符号的缓冲的大小
SO_REUSEADDR 地址反复绑定
SO_TYPE 描述符号类型SOCK_STREAM SOCK_DGRAM?
ICMP选项
IPPTOTO_ICMP
ICMP_FILTER
IP选项(干预系统生成IP头)
IPPROTO_IP
......
......
UDP选项
IPPROTO_UDP
......
TCP选项
IPPROTO_TCP
......
setsockopt设置选项
getsockopt获取选项
案例:
判定一个socket的数据类型AF_INET:SOCK_STREAM SOCK_DGRAM SOCK_RAW
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ in.h>
main()
{
int fd;
int type;
int len;
len= sizeof(type);
fd=socket(AF_INET,SOCK_DGRAM,0);
getsockopt(fd,SOL_SOCKET,SO_TYPE,&type,&len);
printf("%u:%u\n",SOCK_STREAM,type);
if(type & SOCK_STREAM)
{
printf("流!\n");
}
if(type & SOCK_DGRAM)
{
printf("报文!\n");
}
}
// 获得缓冲大小
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ in.h>
main()
{
int fd;
int type;
int len;
len= sizeof(type);
fd=socket(AF_INET,SOCK_DGRAM,0);
getsockopt(fd,SOL_SOCKET,SO_RCVBUF,&type,&len); // 在这里把参数SO_TYPE变成SO_RCVBUF即可
printf("缓冲大小:%u\n",type);
}
使用选项进行数据广播.
cast_A发送
建立socket
设置广播选项
发送数据(广播方式发送)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ in.h>
#include <arpa/inet.h>
main()
{
int fd;
int opt=1;
int r;
struct sockaddr_in dr;
// 1.选项设置
fd=socket(PF_INET,SOCK_DGRAM,0);
if(fd==-1) printf("1:%m\n"),exit(-1);
r=setsockopt(fd,SOL_SOCKET,SO_BROADCAST,
&opt, sizeof(opt));
if(r==-1) printf("2:%m\n"),exit(-1);
dr.sin_family=AF_INET;
dr.sin_port=htons(9999);
// 2.使用广播IP地址
dr.sin_addr.s_addr=inet_addr("192.168.180.255");
r=sendto(fd,"Hello",5,0,
( struct sockaddr*)&dr, sizeof(dr));
if(fd==-1) printf("3:%m\n");
close(fd);
}
建立socket
设置地址可重用选项
绑定地址
接收数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ in.h>
#include <arpa/inet.h>
main()
{
int fd;
int opt=1;
char buf[100];
int r;
struct sockaddr_in dr;
fd=socket(PF_INET,SOCK_DGRAM,0);
if(fd==-1) printf("1:%m\n"),exit(-1);
// 1.选项
r=setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,
&opt, sizeof(opt));
if(r==-1) printf("2:%m\n"),exit(-1);
dr.sin_family=AF_INET;
dr.sin_port=htons(9999);
// 2.广播地址
dr.sin_addr.s_addr=inet_addr("192.168.180.255");
r=bind(fd,( struct sockaddr*)&dr, sizeof(dr));
if(r==-1) printf("3:%m\n"),exit(-1);
r=recv(fd,buf,100,0);
if(r>0)
{
buf[r]=0;
printf("广播数据:%s\n",buf);
}
close(fd);
}
优先数据(带外数据)
send(,MSG_OOB);
recv(,MSG_OOB);
案例:
oob_server.c
recv MSG_OOB
oob_client.c
send MSG_OOB
// oobServer
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <fcntl.h>
int fd,cfd;
void handle( int s)
{
char data[100];
int r;
if(s==SIGURG)
{
r=recv(cfd,data,100,MSG_OOB);
data[r]=0;
printf("$$%s\n",data);
}
}
main()
{
int opt=1;
char buf[100];
int r;
struct sockaddr_in dr;
fd=socket(PF_INET,SOCK_STREAM,0);
if(fd==-1) printf("1:%m\n"),exit(-1);
printf("1\n");
dr.sin_family=AF_INET;
dr.sin_port=htons(10000);
dr.sin_addr.s_addr=inet_addr("192.168.180.92");
r=bind(fd,( struct sockaddr*)&dr, sizeof(dr));
if(r==-1) printf("2:%m\n"),exit(-1);
printf("2\n");
r=listen(fd,10);
if(r==-1) printf("3:%m\n"),exit(-1);
printf("3\n");
signal(SIGURG,handle);
cfd=accept(fd,0,0);
fcntl(cfd,F_SETOWN,getpid());
if(cfd==-1) printf("4:%m\n"),exit(-1);
printf("4\n");
while(1)
{
r=recv(cfd,buf,100,0);
if(r>0)
{
buf[r]=0;
printf("接收数据:%s\n",buf);
}
else
{
break;
}
}
close(cfd);
close(fd);
}
// oobClient
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ in.h>
#include <arpa/inet.h>
main()
{
int fd;
int opt=1;
char buf[100];
int r;
struct sockaddr_in dr;
fd_set fds;
fd=socket(PF_INET,SOCK_STREAM,0);
if(fd==-1) printf("1:%m\n"),exit(-1);
printf("1\n");
dr.sin_family=AF_INET;
dr.sin_port=htons(10000);
dr.sin_addr.s_addr=inet_addr("192.168.180.92");
r=connect(fd,( struct sockaddr*)&dr, sizeof(dr));
if(r==-1) printf("2:%m\n"),exit(-1);
while(1)
{
FD_ZERO(&fds);
FD_SET(fd,&fds);
select(fd+1,0,&fds,0,0);
send(fd,"Hello",5,MSG_OOB);
}
close(fd);
}
OOB总结:
1.OOB数据只能一个字符
2.普通数据使用一般方式接收与发送,OOB数据使用MSG_OOB接收与发送
3.一个数据使用MSG_OOB,则最后一个是OOB,其他非OOB数据
4.问题:OOB数据是优先数据。优先体现在什么地方? 接收OOB数据的时候,会产生一个URG信号
四.HTTP协议以及应用
1.HTTP协议版本HTTP1.0 HTTP1.1
2.HTTP是应用协议
3.HTTP协议分成:
请求协议
响应协议
4.请求协议的格式:
请求行(请求方法 请求资源 协议版本)
请求体(请求头:请求值)
空行
数据(querystring:key=value&key=value)
#include <stdio.h>
#include <stdlib.h>
#include < string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ in.h>
#include <arpa/inet.h>
main()
{
int fd;
struct sockaddr_in dr;
char strreq[1024];
char buf[10*1024];
int r;
// 建立socket
fd=socket(AF_INET,SOCK_STREAM,0);
// 连接服务器192.168.0.72
dr.sin_family=AF_INET;
dr.sin_port=htons(80);
dr.sin_addr.s_addr=inet_addr("192.168.0.72");
r=connect(fd,( struct sockaddr*)&dr, sizeof(dr));
// 构建http请求字符串
sprintf(strreq,
"GET /index.php HTTP/1.1\r\n"
"Host: 192.168.0.72:80\r\n"
"User-Agent: Tarena5.0\r\n"
"Accept: text/html,image/png\r\n"
"Accept-Language: zh-cn\r\n"
"Accept-Charset: gb2312,utf-8\r\n"
"Keep-Alive: 300\r\n"
"Connection: keep-alive\r\n"
"\r\n");
// 发送http请求字符串
r=send(fd,strreq,strlen(strreq),0);
// 等待服务器响应
// while(1)
// {
r=recv(fd,buf,1024,0);
// if(r<=0) break;
printf("========================\n");
printf("%s\n",buf);
printf("========================\n");
// }
close(fd);
}
5.响应协议的格式
响应行(协议版本 响应码 响应码的文本描述)
响应体(响应头: 响应值)
空行
数据(普通数据/分块数据)
响应码:
1XX 正在处理
2XX 响应成功(200表示完全成功)
3XX 继续处理
4XX 客户错误
5XX 服务器错误
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <fcntl.h>
#include < string.h>
int fd,cfd;
main()
{
char buf[1024];
int r;
struct sockaddr_in dr;
char strres[1024];
fd=socket(PF_INET,SOCK_STREAM,0);
if(fd==-1) printf("1:%m\n"),exit(-1);
printf("1\n");
dr.sin_family=AF_INET;
dr.sin_port=htons(10000);
dr.sin_addr.s_addr=inet_addr("192.168.180.92");
r=bind(fd,( struct sockaddr*)&dr, sizeof(dr));
if(r==-1) printf("2:%m\n"),exit(-1);
printf("2\n");
r=listen(fd,10);
if(r==-1) printf("3:%m\n"),exit(-1);
printf("3\n");
cfd=accept(fd,0,0);
if(cfd==-1) printf("4:%m\n"),exit(-1);
printf("4\n");
sprintf(strres,
"HTTP/1.1 200 OK\r\n"
"Server: tarena2.0\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 28\r\n"
"Connection: keep-alive\r\n"
"\r\n"
"<font color=red>靓崽!</font>");
while(1)
{
r=recv(cfd,buf,1024,0);
if(r>0)
{
buf[r]=0;
printf("接收数据:%s\n",buf);
send(cfd,strres,strlen(strres),0);
}
else
{
break;
}
}
close(cfd);
close(fd);
}
五.ioctl函数
实现ifconfig工具
总结:
重点:
select
广播
了解:
OOB数据
HTTP协议
应用:
独立编写TCP服务器端的select模式
编写广播
能够请求一个网页,并且解析响应
作业:
1.把聊天程序使用poll实现
2.使用UDP的广播,发送一个文件
3.随意挑选网站,把主页下载并保存成html文件