回顾:
多进程的问题:数据共享。
多进程的问题:进程的上下文环境(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:异常
IO能否发出信号?
异步IO就是通过信号工作。
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
void handle(int s)
{
char buf[200];
int r;
r=read(0,buf,199);
buf[r]=0;
printf("::%s",buf);
}
main()
{
fcntl(0,F_SETFL,O_ASYNC);//O_ASYNC 异步
fcntl(0,F_SETOWN,getpid());//告诉异步处理后信号发送给谁
signal(SIGIO,handle);
while(1);
}
#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);
r=select(1,&fds,0,0,0);
printf("有数据输入!\n");
r=read(0,buf,99);
}
}
2. 应用使用select
3. 使用select实现TCP的多客户连接与处理
chatServer - 2.IO的异步模式(select模式/poll模式)
#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);
}
}
}
}
}
}
}
chatClient - 2.IO的异步模式(select模式/poll模式)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <curses.h>
#include <signal.h>
WINDOW*winfo,*wmsg;
int fd;
int r;
struct sockaddr_in dr;
int isover=1;
int initSocket();
void initUI();
void destroy();
void handle(int s)
{
int status;
wait(&status);
destroy();
exit(-1);
}
main()
{
//printf("网络初始化成功!\n");
initUI();
r=initSocket();
if(r==-1) exit(-1);
signal(SIGCHLD,handle);
if(fork())
{
//输入,发送
char buf[256];
while(1)
{
mvwgetstr(wmsg,1,1,buf);
//buf[r]=0;
send(fd,buf,strlen(buf),0);
//wclear(wmsg);
//box(wmsg,0,0);
refresh();
wrefresh(wmsg);
wrefresh(winfo);
}
}
else
{
//接收,显示
char buf[256];
int line=1;
while(1)
{
r=recv(fd,buf,255,0);
if(r==-1) break;
if(r==0) break;
buf[r]=0;
mvwaddstr(winfo,line,1,buf);
line++;
if(line>=(LINES-3))
{
wclear(winfo);
line=1;
box(winfo,0,0);
}
wmove(wmsg,1,1);
touchwin(wmsg);
refresh();
wrefresh(winfo);
wrefresh(wmsg);
}
exit(-1);
}
destroy();
}
void destroy()
{
close(fd);
endwin();
}
void initUI()
{
initscr();
winfo=derwin(stdscr,(LINES-3),COLS,0,0);
wmsg=derwin(stdscr,3,COLS,LINES-3,0);
keypad(stdscr,TRUE);
keypad(wmsg,TRUE);
keypad(winfo,TRUE);
box(winfo,0,0);
box(wmsg,0,0);
refresh();
wrefresh(winfo);
wrefresh(wmsg);
}
int initSocket()
{
fd=socket(AF_INET,SOCK_STREAM,0);
if(fd==-1) return -1;
dr.sin_family=AF_INET;
dr.sin_port=htons(8866);
dr.sin_addr.s_addr=inet_addr("192.168.180.92");
r=connect(fd,(struct sockaddr*)&dr,sizeof(dr));
if(r==-1)
{
close(fd);
return -1;
}
return 0;
}
4. poll模式
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);
}
}
}
二、 Socket选项设置
1. socket有哪些选项可以设置
ARP
|
IP
|
|---------------------|
UDP TCP
Socket通用选项:(man 7 socket)
SOL_SOCKET
SO_BROADCAST 广播
SO_RCVBUF 描述符号的缓冲的大小
SO_SNDBUF 描述符号的缓冲的大小
SO_REUSEADDR 地址反复绑定
SO_TYPE 描述符号类型SOCK_STREAM SOCK_DGRAM?
ICMP选项(man 7 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_RCVBUF,&type,&len);//SOL_SOCKET就是你想获得的类型宏,直接替换就行,比如IPPROTO_IP
printf("缓冲大小:%u\n",type);
/*
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");
}
*/
}
案例:
使用选项进行数据广播.
cast_A发送
建立socket
设置广播选项
发送数据(广播方式发送)
case_B接收
建立socket
设置地址可重用选项
绑定地址
接收数据
cast_A发送
#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);
}
case_B接收
#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,//设置重用IP
&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);
}
三、 OOB数据(TCP)(带外数据)
优先数据
send(,MSG_OOB);
recv(,MSG_OOB);
案例:
oob_server.c
recv MSG_OOB
oob_client.c
send MSG_OOB
- OOB数据只能一个字符
- 普通数据使用一般方式接收与发送,OOB数据使用MSG_OOB接收与发送
- 一个数据使用MSG_OOB,则最后一个是OOB,其他非OOB数据
- 问题:OOB数据是优先数据。优先体现在什么地方?
所有的事情会停下来优先处理带外数据,而且文件的描述符会指在带外数据的位置。
我理解,带外数据的优先级是依托于信号的机制。由于带外数据是和SIGURG信号绑在一起的,所以优先级较高。带外数据过多,原来的带外数据会失效。
oob_server.c
#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());//只要cfd文件有信号,就会马上获取到,发送给当前进程。默认是发送给跟进程的
if(cfd==-1) printf("4:%m\n"),exit(-1);
printf("4\n");
while(1)
{
r=recv(cfd,buf,100,0);
//r=recv(cfd,buf,100,MSG_OOB);
if(r>0)
{
buf[r]=0;
printf("接收数据%s\n",buf);
}
else
{
break;
}
}
close(cfd);
close(fd);
}
oob_client.c
#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);
}
四、 HTTP协议以及应用
- HTTP协议版本HTTP1.0 HTTP1.1
- HTTP是应用协议
- HTTP协议分成:
请求协议
响应协议
- 请求协议的格式:
请求行(请求方法 请求资源 协议版本)
请求体(请求头:请求值)
空行
数据(querystring:key=value&key=value)
- 响应协议的格式
响应行(协议版本 响应码 响应码的文本描述)
响应体(响应头: 响应值)
空行
数据(普通数据/分块数据)
1XX 正在处理
2XX 响应成功200
3XX 继续处理
4XX 客户错误
5XX 服务器错误
http___Client 打开一个网页,网址就是服务器那块
#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);
}
http_Server 通过网页访问该服务器,并接收该服务器的值,注意网址
http://192.168.180.92:10000/index.html
#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>Hello!</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模式
编写广播
能够请求一个网页,并且解析响应