一. 复习ANSI C IO函数
1. printf 和 scanf .各种格式都要复习到
2. fprintf 和 fscanf
3. sscanf 和 sprintf ,snprintf
4. fgets 和 fputs
5. gets 和 puts
6. vsnprintf 可变参数列表
7. putc 和 getc
8. putchar 和 getchar
其他全部看笔记
二. 信号
重点在APUE信号一章
结合笔记
一些好的文章
三.linux网络相关命令的总结
1.ifconfig
2.route
3.ping
4.traceroute
5.netstat
6.host
6.nslookup
7.telnet/ftp/ssh
8.tcpdump 重点
四.书上重点
1. 客户/服务器程序示例
2. signal函数,原型,以及signal函数的简化
3. 处理SIGCHLD信号的三个注意点
4. wait 和 waitpid
5. 信号处理函数的编写. 图5-11的例子
6. P101 正常启动时,边启动服务器和客户端,边用linux命令查看状态.这套流程多熟悉几遍
五.并发服务器最终源代码
head.h
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <sys/wait.h>
//some universal value
#define MAXSIZE 8392
#define MAXLINE 8392
#define SERV_PORT 8888
#define LISTENQ 100
typedef struct sockaddr SA;
typedef void Sigfunc(int);
//wrong proceedure
void err_str(const char* str)
{
printf("<%s> is error!!\n",str);
exit(0);// !> we also exit !
}
//wrap function
//socket
int Socket(int family,int type,int protocol)
{
int sockfd;
if((sockfd = socket(family,type,protocol)) < 0)
err_str("socket");
return sockfd;
}
int Connect(int sockfd,const struct sockaddr* sa,socklen_t len)
{
int ret;
if((ret = connect(sockfd,sa,len)) < 0)
err_str("connect");
return ret;
}
int Bind(int sockfd,const struct sockaddr* sa,socklen_t len)
{
int ret;
if((ret = bind(sockfd,sa,len)) < 0)
err_str("bind");
return ret;
}
int Listen(int sockfd,int backlog)
{
int ret;
if((ret = listen(sockfd,backlog)) < 0)
err_str("listen");
return ret;
}
int Accept(int sockfd,struct sockaddr* cliaddr,socklen_t* len)
{
int connfd;
if((connfd = accept(sockfd,cliaddr,len)) < 0)
err_str("accept");
return connfd;
}
// IO
//....no buffer version
ssize_t readn(int sockfd,char* buf,size_t nbytes)
{
ssize_t nread;
size_t nleft = nbytes;
char* p = buf;
while(nbytes)
{
if((nread = read(sockfd,p,nleft)) < 0)
{
if(errno == EINTR)
nread = 0;
else
return -1;
}
else if(nread == 0)
break;
nleft -= nread;
p += nread;
}
return (nbytes - nleft);
}
ssize_t writen(int sockfd,const char* buf,size_t nbytes)
{
ssize_t nwrite;
size_t nleft = nbytes;
const char* bufp = buf;
while(nleft > 0)
{
if((nwrite = write(sockfd,bufp,nleft)) <= 0)
{
if(nwrite < 0 && errno == EINTR)
nwrite = 0;
else
return -1;
}
nleft -= nwrite;
bufp += nwrite;
}
return nbytes;
}
ssize_t readline(int sockfd,char* buf,size_t n)
{
ssize_t nread;
char* p = buf;
char c;
size_t i;
for(i = 1; i < n; ++i)
{
if((nread = read(sockfd,&c,1)) == 1)
{
*p++ = c;
if(c == '\n')
break;
}else if(nread == 0){
*p = '\0';
return (i - 1);
}else{
if(errno == EINTR)
continue;
return -1;
}
}
*p = '\0';
return i;
}
//change address
int Inet_pton(int family,const char* strptr,void* addrptr)
{
int ret;
if((ret = inet_pton(family,strptr,addrptr)) < 0)
err_str("inet_pton");
return ret;
}
//get information
int Getsockname(int sockfd,struct sockaddr* sa,socklen_t* addrlen)
{
int ret;
if((ret = getsockname(sockfd,sa,addrlen)) < 0)
err_str("getsockname");
return ret;
}
int Getpeername(int sockfd,struct sockaddr* sa,socklen_t* addrlen)
{
int ret;
if((ret = getpeername(sockfd,sa,addrlen)) < 0)
err_str("getpeername");
return ret;
}
int Getnameinfo(const struct sockaddr* addr,socklen_t addrlen,
char* host,socklen_t hostlen,char* service,socklen_t servlen,int flags)
{
int ret;
if((ret = getnameinfo(addr,addrlen,host,hostlen,service,servlen,flags)) < 0)
err_str(gai_strerror(errno));
return ret;
}
int Getaddrinfo(const char* host,const char* port,const struct addrinfo* hints,struct addrinfo** res)
{
int ret;
if((ret = getaddrinfo(host,port,hints,res)) != 0)
err_str(gai_strerror(errno));
return ret;
}
char * sock_ntop(const struct sockaddr* sa,socklen_t len)
{
char portstr[8];
static char hoststr[128];
switch(sa->sa_family)
{
case AF_INET:{
struct sockaddr_in* sin = (struct sockaddr_in *)sa;
if(inet_ntop(AF_INET,&sin->sin_addr,hoststr,sizeof(hoststr)) == NULL)
return NULL;
if(ntohs(sin->sin_port) != 0)
{
snprintf(portstr,sizeof(portstr),":%d",ntohs(sin->sin_port));
strcat(hoststr,portstr);
}
return hoststr;
}
case AF_INET6:{
struct sockaddr_in6* sin6 = (struct sockaddr_in6* )sa;
if((inet_ntop(AF_INET6,&sin6->sin6_addr,hoststr,sizeof(hoststr))) == NULL)
return NULL;
if(ntohs(sin6->sin6_port) != 0)
{
snprintf(portstr,sizeof(portstr),":%d",ntohs(sin6->sin6_port));
strcat(hoststr,portstr);
}
return hoststr;
}
}
return hoststr;
len = len;//no use !
}
int open_client(const char* host,const char* serv)
{
int sockfd;
struct addrinfo hints,*listp,*p;
bzero(&hints,sizeof(hints));
hints.ai_family = AF_UNSPEC;//for both ipv4 && ipv6
hints.ai_socktype = SOCK_STREAM;
Getaddrinfo(host,serv,&hints,&listp);
for(p = listp; p != NULL; p = p->ai_next)
{
if((sockfd = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) < 0)
continue;
if((connect(sockfd,p->ai_addr,p->ai_addrlen)) == 0)
break;
close(sockfd); //dont forget close sockfd.
}
freeaddrinfo(listp);
return sockfd;
}
//open serv .we can use ipv4 or ipv6
//flags = 0 ,universal.flags = 1,IPV4,flags = 2,IPV6
int open_serv(const char* host,const char* serv,const int flags)
{
int sockfd;
struct addrinfo hints,*listp,*p;
const int on = 1;
bzero(&hints,sizeof(hints));
hints.ai_flags = AI_PASSIVE;
if(flags == 0)
hints.ai_family = AF_UNSPEC;
else if(flags == 1)
hints.ai_family = AF_INET;
else
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
Getaddrinfo(host,serv,&hints,&listp);
for(p = listp; p ; p = p->ai_next)
{
if((sockfd = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) < 0)
{
continue;
}
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
if( ( bind(sockfd,p->ai_addr,p->ai_addrlen) ) == 0)
{
//printf("bind to %d successful!\n",sockfd);
break;
}
close(sockfd);
}
if( p == NULL)
err_str("No available address");
Listen(sockfd,LISTENQ);
freeaddrinfo(listp);
return sockfd;
}
int Getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen)
{
int ret;
if((ret = getsockopt(sockfd,level,optname,optval,optlen)) < 0)
err_str("getsockopt");
return ret;
}
int Setsockopt(int sockfd,int level,int optname,const void* optval,socklen_t optlen)
{
int ret;
if((ret = setsockopt(sockfd,level,optname,optval,optlen)) < 0)
err_str("setsockopt");
return ret;
}
void client_simple_echo(int sockfd)
{
char sendline[MAXLINE];
char recvline[MAXLINE];
while(fgets(sendline,MAXLINE,stdin) != NULL)
{
//read (from stdin) -> write (to serv) -> read(from serv) ->write(to stdout)
writen(sockfd,sendline,strlen(sendline));
if(readline(sockfd,recvline,MAXLINE) == 0)//serv closed
break;
fputs(recvline,stdout);
}
}
void serv_simple_echo(int sockfd)
{
//read (from serv) ->write(to serv)
char buf[MAXLINE];
ssize_t nread;
while(( nread = read(sockfd,buf,MAXLINE)) > 0)
writen(sockfd,buf,nread);
return;
}
//signal process
//SIGCHLD
void sig_chld(int signo)
{
int stat;
pid_t pid;
while((pid = waitpid(-1,&stat,WNOHANG)) > 0)
printf("process %d terminated!\n",signo);
return;
}
serv.c
#include "head.h"
int main(int argc,const char**argv)
{
if(argc != 2)
{
fprintf(stderr,"enter 1(ipv4 only),2(ipv6 only),0(both) to start service!\n");
exit(0);
}
struct sockaddr_in cli;
socklen_t len;
int sockfd,connfd;
pid_t childpid;
sockfd = open_serv(NULL,"8888",(*argv[1] - '0'));
signal(SIGCHLD,sig_chld);
printf("*********************wait for connection*************\n");
for( ; ; )
{
len = sizeof(cli);
if((connfd = accept(sockfd,(SA*)&cli,&len)) < 0)
{
if(errno == EINTR)
continue;
else
err_str("accept error");
}
Getpeername(connfd,(SA*)&cli,&len);
char *cli_info = sock_ntop((SA*)&cli,len);
printf("%s connected!\n",cli_info);
if((childpid = fork()) == 0)
{
close(sockfd);
serv_simple_echo(connfd);
exit(0);
}
close(connfd);
}
return 0;
}
client.c
#include "head.h"
int main(int argc,char** argv)
{
if(argc < 3){
printf("Please enter IP address and port.\n");
exit(0);
}
struct sockaddr_storage ss;
socklen_t len;
int sockfd; //client sockfd
sockfd = open_client(argv[1],argv[2]);
len = sizeof(ss);
if(getpeername(sockfd,(SA*)&ss,&len) < 0)
err_str("getpeername");
printf("You have connected to %s\n",sock_ntop((SA*)&ss,len));
// now do some echo
//demand client to enter some words
client_simple_echo(sockfd);
return 0;
}
六.特殊情况分析–使服务器变的健壮
复习TCP/IP卷一的TCP状态图
1.正常启动
2.正常终止
3.服务器进程终止
1.启动服务器,正常连接后,客户端发送hello world,得到回显,说明一切正常
2.接下来,直接杀掉serv端负责与客户端通信的子进程,接着查看端口状态.
(注意到此时client等待读取键盘的输入,即阻塞在fgets()系统调用里)
(server端父进程依旧在监听.
子进程被杀死,子进程会关闭所有打开的文件描述符,connfd自然也会被关闭,
因为先前父进程已经关闭过connfd,所以此时connfd引用计数为0,connfd彻底关闭,
这将导致发送FIN给客户TCP,客户TCP会响应ACK
此时客户进入close_wait状态,服务器进入fin_wait_2状态
服务端子进程的SIGCHLD被正确的接收和处理了)
3.接下来,我们继续通过客户端给服务器发信息.
这是被允许的.因为此时处于半连接状态,服务器端不能发数据,可以接收数据.
客户端不会接收到服务器端的数据,却可以向服务器端发送数据.
客户端调用client_simple_echo(sockfd),接着调用writen(),发送一行数据
服务器TCP接收到来自客户的数据之后,因为先前的进程已经没了,会响应一个RST
我们使用tcpdump来抓取这个RST,验证课本和我们的服务器
flags标识为R,标识RST
4.客户进程收不到这个RST,因为它继续调用readline,
由于早先收到的FIN,TCP会直接使得readline返回0(EOF)
我们的客户随之退出.(退出原因在第4点)
随后客户端所有文件描述符被关闭.
原因分析:客户阻塞在fgets调用上时,无法读取FIN,致使对fin的处理只是简单的由TCP自动返回了个ACK,却没有进一步的处理,实际上,我们应当在收到这个FIN后,关闭与客户端的连接了.
即为,服务器进程终止时,客户TCP得到了通知,但是客户进程没有得到通知.
解决办法: IO复用.当数据早于FIN到来时,便去处理数据.当FIN早于数据到来时,便关闭连接.
4.SIGPIPE
写 一个已经收到RST的sockfd会引发SIGPIPE信号,该信号默认终止进程.需要记录日志时,可以捕获该信号.
5.服务器主机崩溃
1.服务器主机崩溃,客户端收不到任何提示
2.客户的TCP会持续重传分节.当客户TCP放弃时,会给客户进程传递一个错误.
3.收不到响应,ETIMEDOUT,主机不可达是: EHOSTUNREACH
6.服务器主机崩溃后重启
重启后,服务器TCP丢失了所有的连接信息.当然会对发来的数据响应RST.