CRLF:回车换行(Carriage-Return Line-Feed)。
CR:回车,ASCII 0x0d,转义字符
,
LF:换行,ASCII 0x0a,转义字符
。
windows下使用
换行,linux使用
换行。
在wireshark抓包中看到,不管是服务器还是客户端,HTTP协议中的换行是0x0d 0x0a,空格是0x20
//客户端步骤
socket()
connect()//阻塞
write()
read()
//服务端步骤
socket()
bind()
listen()
accept()//阻塞
read()
write()
echo服务器重点语句
//客户端
connect()//阻塞
while(fget()) {
write();
if(readline()==0)
error();
fpus();
}
//服务端
listen(sockfd, 1);//listen
while(1) {
if((connfd = accept(sockfd, (struct sockaddr*)&cliaddr, &clilen)) < 0) {//阻塞
if(errno == EINTR) {
printf("accept EINTR
");
continue;
}
else
perror("accept error
");
}
if((childpid = fork()) == 0) {//进程
printf("It's child.
");
close(sockfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
引入IO复用
在上述的服务器和客户端实际运行的过程中,当客户端阻塞在fgets,我们终止服务器程序。这时的客户端还是阻塞在fgets,用户不知道服务器那边发生了什么。为了让服务器关闭时,客户端也相应地关闭,我们需要使用IO复用。
在服务器方面,使用select可以将上述例子由多进程改为单进程,减少创建新进程的开销。
select函数
select函数的参数是值-结果类型
跟select函数一起使用的还有:
FD_SET
FD_ZERO
FD_ISSET
//使用select的客户端
FD_ZERO(&rset);//复位描述字集
while(1) {
FD_SET(fileno(fp), &rset);//置位感兴趣的描述字,一个是标准输入,一个是socket
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
select(maxfdp1, &rset, NULL, NULL, NULL);//阻塞。若描述字集里有就绪好的描述字,就往下执行
if(FD_ISSET(sockfd, &rset)) {//sockfd是否就绪好,若服务器突然关闭,则sockfd会就绪好
if(readline(sockfd, recvline, MAXLINE) == 0) {
exit(1);
}
fputs(recvline, stdout);
}
if(FD_ISSET(fileno(fp), &rset)) {//fp是否就绪好
if(fgets(sendline, MAXLINE, fp) == NULL)
return;
writen(sockfd, sendline, strlen(sendline));
}
}
//使用select的单进程服务器
fd_set rset;
int client[FD_MAX];//客户端描述字集
FD_ZERO();
FD_SET(linstenfd);
while(1) {
select(&rset);//阻塞,当客户与服务器建立连接时,监听描述字就绪
if(FD_ISSET(linstenfd)) {//有新的客户连接进来
connfd = accept();
FD_SET(connfd);//把连接描述字加入感兴趣集
}
for() {//遍历客户描述字集client,找出非负描述字,即目前保持连接状态的客户
if(FD_SET(&rset)){//该客户是否就绪可以读取内容了
read();
write();
}
}
}
poll函数
poll函数的参数不同于select的值-结果类型,poll既有输入参数,也有输出参数。
select需要维护一个client[]数组和select函数所需要的描述字集fd_set,而poll只需要维护一个结构体数组pollfd即可。
//使用poll的单进程服务器
struct pollfd client[FD_MAX];//客户端描述字集
client[0].fd = linstenfd;
client[0].events = POLLRDNORM;
while(1) {
poll(client);//阻塞,当客户与服务器建立连接时,监听描述字就绪
if(client[0].revents & POLLRDNORM) {//有新的客户连接进来
connfd = accept();
client[0].fd = connfd;//把连接描述字加入感兴趣集
client[0].events = POLLRDNORM;//设置感兴趣的事件
}
for() {//遍历客户描述字集client,找出非负描述字,即目前保持连接状态的客户
if(client[i].revents & (POLLRDNORM | POLLRDERR){//该客户是否就绪可以读取内容了
read();
write();
}
}
}