UNP 学习笔记 第五章

一. 复习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

printf跟着敲一遍
可变参数列表看看敲敲

其他全部看笔记

二. 信号

重点在APUE信号一章

结合笔记

一些好的文章

信号概览

三.linux网络相关命令的总结

1.ifconfig
2.route
3.ping
4.traceroute
5.netstat
6.host
6.nslookup
7.telnet/ftp/ssh
8.tcpdump  重点

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.

7.服务器主机关机

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值