• TCP三次握手建立连接
  • 错误处理模块:wrap.c,函数声明:wrap.h
  • 并发服务器模型(多进程,多线程)

转换大小写程序

服务端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#define PORT 6799
#define IP "127.0.0.1"
void print_err(char* str){
    perror(str);
    exit(-1);
}
int main(){
    int res_bind=0;
    int cfd=0;
    struct sockaddr_in serverAddr;
    struct sockaddr_in clientAddr;
    socklen_t cli_addr_len;
    int sfd=socket(AF_INET,SOCK_STREAM,0);
    if(sfd==-1) print_err("socket fails\n");
    //把内存清零,在使用结构体赋值前将缓冲区清零
    bzero(&serverAddr,sizeof(serverAddr));
    serverAddr.sin_family=AF_INET;
    serverAddr.sin_port=htons(PORT);
    //serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    inet_pton(sfd,IP,&serverAddr.sin_addr.s_addr);
    res_bind=bind(sfd,(struct sockaddr*)&serverAddr,\
                  sizeof(serverAddr));
    if(res_bind==-1) print_err("bind fails\n");
    listen(sfd,120);
    cli_addr_len=sizeof(clientAddr);
    cfd=accept(sfd,(struct sockaddr*)&clientAddr,&cli_addr_len);
    if(cfd==-1) print_err("accept fails\n");
    /*显示一下哪个客户端连接了*/
    char client_IP[100]={0};
    printf("client IP=%s,client PORT=%d\n",\
            inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,\
                              client_IP,sizeof(client_IP)),\
            ntohs(clientAddr.sin_port));
    /*转换大小写*/
    char buf[30]={0};
    int n=read(cfd,buf,sizeof(buf));
    int i;
    for(i=0;i<n;i++){
        buf[i]=toupper(buf[i]);}
    write(cfd,buf,sizeof(buf));
    close(sfd);
    close(cfd);
    return 0;
}

用户端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#define PORT 6799
#define IP "127.0.0.1"
void print_err(char* str){
    perror(str);
    exit(-1);
}
int main(){
    int cfd=socket(AF_INET,SOCK_STREAM,0);
    if(cfd==-1) print_err("socket fails\n");
    struct sockaddr_in server_addr;
    int connect_res=0;
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(PORT);
    inet_pton(AF_INET,IP,&server_addr.sin_addr.s_addr);
    connect_res=connect(cfd,(struct sockaddr*)&server_addr,\
                                    sizeof(server_addr));
    if(connect_res==-1) print_err("connect fails\n");
    /*执行程序*/
    //把用户输入读到buf
    char buf[100]={0};
    fgets(buf,sizeof(buf),stdin);
    //把buf内容写到用户端
    write(cfd,buf,strlen(buf));
    //把返回结果写回屏幕
    int n=read(cfd,buf,sizeof(buf));
    write(1,buf,sizeof(buf));
    close(cfd);
    return 0;
}

执行结果

程序分析

多进程并发服务器-LMLPHP

  • 每次创建出一个socket,socket的描述符都指向两个缓冲区,一个用来读,一个用来写
  • 两个套接字想进行网络通信,必须通过ip地址+端口号,才能建立网络连接
服务器端
  • 接收端也就是读缓冲区,需要从用户端套接字的写缓冲区读数据

  • 然后本地实现大小写功能后,需要把数据写到用户端的接收端

用户端
  • 首先需要把用户的键盘输入的内容读到自己定义的用户缓冲区buf

  • 然后自己的发送端的内容(客户端写的)写到自己定义的buf,以便输出

  • 最后buf的数据,写出到屏幕上

查看端口的命令

netstat -apn | grep 具体端口号

把错误处理进行封装

wrap.c(没有主函数)

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
void print_err(char* str){
    perror(str);
    exit(-1);
}
/*socket封装*/
int Socket(int domain, int type, int protocol){
    int n=socket(domain,type,protocol);
    if(n==-1) print_err("socket fails\n");
    return n;
    }
    /*bind封装*/
int Bind(int sockfd, const struct sockaddr *addr,\
    socklen_t addrlen){
    int n=bind(sockfd,addr,addrlen);
    if(n==-1) print_err("bind fails\n");
    return n;
}
/*listen封装*/
int Listen(int sockfd, int backlog){
    int n=listen(sockfd,backlog);
    if(n==-1) print_err("listen fails\n");
    return n;
}
/*accept封装*/
int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
    int n=accept(sockfd,addr,addrlen);
    if(n==-1) print_err("accept fails\n");
    return n;
}
/*connect封装*/
int Connect(int sockfd, const struct sockaddr *addr,\
                   socklen_t addrlen){
    int n=connect(sockfd,addr,addrlen);
    if(n==-1) print_err("connect fails\n");
    return n;
}

wrap.h(头文件声明)

#ifndef MY_WRAP
#define MY_WRAP
extern void print_err(char* str);
extern int Socket(int domain, int type, int protocol);
extern int Bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
extern int Listen(int sockfd, int backlog);
extern int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
extern int Connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
#endif

server.c(调用大写程序)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include "wrap.h"
#define PORT 6799
#define IP "127.0.0.1"
int main(){
    int cfd,sfd;
    char buf[30];
    int n;
    struct sockaddr_in serverAddr;
    struct sockaddr_in clientAddr;
    socklen_t cli_addr_len;
    //socket()
    sfd=Socket(AF_INET,SOCK_STREAM,0);
    //bind()
    bzero(&serverAddr,sizeof(serverAddr));
    serverAddr.sin_family=AF_INET;
    serverAddr.sin_port=htons(PORT);
    inet_pton(sfd,IP,&serverAddr.sin_addr.s_addr);
    Bind(sfd,(struct sockaddr*)&serverAddr,\
                  sizeof(serverAddr));
    //listen()
    Listen(sfd,12);
    //accept()
    cli_addr_len=sizeof(clientAddr);
    cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&cli_addr_len);
    /*显示一下哪个客户端连接了*/
    char client_IP[100]={0};
    printf("client IP=%s,client PORT=%d\n",\
            inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,\
                              client_IP,sizeof(client_IP)),\
            ntohs(clientAddr.sin_port));
    /*转换大小写*/
    n=read(cfd,buf,sizeof(buf));
    int i;
    for(i=0;i<n;i++){
        buf[i]=toupper(buf[i]);}
    write(cfd,buf,n);
    close(sfd);
    close(cfd);
    return 0;
}

client.c(调用小写函数)

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include "wrap.h"
#define PORT 6799
#define IP "127.0.0.1"
int main(){
    int n;
     char buf[100];
    int cfd=Socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in server_addr;
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(PORT);
    inet_pton(AF_INET,IP,&server_addr.sin_addr.s_addr);
    Connect(cfd,(struct sockaddr*)&server_addr,\
                                    sizeof(server_addr));
    //把用户输入读到buf
    fgets(buf,sizeof(buf),stdin);
    //把buf内容写到用户端
    write(cfd,buf,strlen(buf));
    //把返回结果写回屏幕
    n=read(cfd,buf,sizeof(buf));
    write(1,buf,n);
    close(cfd);
    return 0;
}

程序运行

accept()函数错误处理

int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
    int n=accept(sockfd,addr,addrlen);
    /*异常处理和错误都会返回-1,所以要单独分析
        ---比如:一个连接被断开了,错误号:ECONNABORTED
        ---比如:因为收到信号被断开连接,错误号:EINTR
        这时候要么重新连接,要么结束连接,这里选“重新连接”
    */
reconnectted:
    if(n==-1){
        if(errno==ECONNABORTED||(errno==EINTR))
           goto reconnectted;
        else 
           print_err("accept fails\n");
    }
    return n;
}

read()函数返回值

  • 正常情况大于0—实际读到字节数

    • 比如buf[1024],read恰好读到那么大,==1024
    • 如果不足,读到多少字节就返回多少字节
  • 恰好返回0----读到了文件末尾,管道被关闭了,socket被关闭

  • 等于-1

    • 正常退出
      • errno==EINTR,被信号终止,退出或重启
      • errno==EAGAIN,非阻塞方式读,但是还没有数据的情况
      • 。。。
    • 异常—(-1,出现错误)
/*read封装*/
ssize_t Read(int fd, void *buf, size_t count){
    int n=read(fd,buf,count);
    if(n==-1){
    again:
        if(errno==EINTR||errno==EAGAIN)
             goto again;
        else 
           print_err("read fails\n");
    }
    return n;
}

TCP协议

TCP的通信时序图

多进程并发服务器-LMLPHP

  • 两条竖线表示通讯的两端
  • 从上到下表示时间的先后顺序
  • 图中的箭头都是斜的—数据从一端传到网络的另一端也需要时间
  1. 首先客户端主动发起连接、发送请求
  2. 然后服务器端响应请求
  3. 然后客户端主动关闭连接

三次握手----建立连接

多进程并发服务器-LMLPHP

miss表示最大段尺寸

在建立连接的同时,双方协商了一些信息,例如双方发送序号的初始值最大段尺寸

数据传输:三次握手后,四次握手前

多进程并发服务器-LMLPHP

  1. 客户端发出段4
  1. 服务器发出段5
  1. 客户端发出段6

在数据传输过程中,ACK和确认序号是非常重要的,

  • 应用程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区
  • 发出数据包给对方之后,只有收到对方应答的ACK段才知道该数据包确实发到了对方
  • 如果因为网络故障丢失了数据包或者丢失了对方发回的ACK段,经过等待超时后TCP协议自动将发送缓冲区中的数据包重发

四次握手----关闭连接

多进程并发服务器-LMLPHP

多进程并发服务器

多进程并发服务器-LMLPHP

父进程应答请求,fork子进程通信

//父进程不断通过复制子进程实现多个连接
    while(1){
       //accept()----别忘了对第三个参数取地址
       cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&client_addr_len);
       //fork()
       pid=fork();
       if(pid >0){//父进程
           close(cfd);//没有请求了,关闭cfd
       }
       else if(pid==0){
            close(sfd);
            break;//跳出进入子进程交互
       }
    }//结束应答
   if(pid==0){//子进程去交互
      while(1){
          //转换大小写
          n=Read(cfd,buf,sizeof(buf));
          if(n==0){//读到了末尾
            close(cfd);
            return 0;
          }
          for(i=0;i<n;i++)
             buf[i]=toupper(buf[i]);
          write(cfd,buf,n);
      }
   }
运行结果(nc)

僵尸进程问题

 #include <sys/types.h>
 #include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);

实现:

void wait_child(int singo){
    //0---所有子进程无差别回收
    while(waitpid(0,NULL,WNOHANG)>0);
    exit(-1);
}
if(pid >0){//父进程
           close(cfd);//没有请求了,关闭cfd
           signal(SIGINT,wait_child);//子进程回收
       }

打印信息:网络字节序to本机

printf("client ip=%s,port=%d\n",inet_ntop(AF_INET,\
             &serverAddr.sin_addr.s_addr,client_IP,sizeof(client_IP)),\
             ntohs(serverAddr.sin_port));
  • inet_ntop(AF_INET**,&地址.s_addr,写入的缓存地址,**缓存长度);
  • ntohs(地址.sin_port);

实现了多进程并发的完整程序

wrap.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
void print_err(char* str){
    perror(str);
    exit(-1);
}
/*socket封装*/
int Socket(int domain, int type, int protocol){
    int n=socket(domain,type,protocol);
    if(n==-1) print_err("socket fails\n");
    return n;
    }
    /*bind封装*/
int Bind(int sockfd, const struct sockaddr *addr,\
    socklen_t addrlen){
    int n=bind(sockfd,addr,addrlen);
    if(n==-1) print_err("bind fails\n");
    return n;
}
/*listen封装*/
int Listen(int sockfd, int backlog){
    int n=listen(sockfd,backlog);
    if(n==-1) print_err("listen fails\n");
    return n;
}
/*accept封装*/
int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
    int n=accept(sockfd,addr,addrlen);
    /*异常处理和错误都会返回-1,所以要单独分析
        ---比如:一个连接被断开了,错误号:ECONNABORTED
        ---比如:因为收到信号被断开连接,错误号:EINTR
        这时候要么重新连接,要么结束连接,这里选“重新连接”
    */
reconnectted:
    if(n==-1){
        if(errno==ECONNABORTED||(errno==EINTR))
           goto reconnectted;
        else 
           print_err("accept fails\n");
    }
    return n;
}
/*connect封装*/
int Connect(int sockfd, const struct sockaddr *addr,\
                   socklen_t addrlen){
    int n=connect(sockfd,addr,addrlen);
    if(n==-1) print_err("connect fails\n");
    return n;
}
/*read封装*/
ssize_t Read(int fd, void *buf, size_t count){
    int n=read(fd,buf,count);
    if(n==-1){
    again:
        if(errno==EINTR||errno==EAGAIN)
             goto again;
        else 
           print_err("read fails\n");
    }
    return n;
}

wrap .o

#ifndef MY_WRAP
#define MY_WRAP
extern void print_err(char* str);
extern int Socket(int domain, int type, int protocol);
extern int Bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
extern int Listen(int sockfd, int backlog);
extern int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
extern int Connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
extern ssize_t Read(int fd, void *buf, size_t count);
#endif

server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>
#include "wrap.h"
#include <sys/wait.h>
#include <signal.h>
#define PORT 6767
void wait_child(int singo){
    //0---所有子进程无差别回收
    while(waitpid(0,NULL,WNOHANG)>0);
    exit(-1);
}
int main(){
    int sfd,cfd;
    struct sockaddr_in serverAddr;
    struct sockaddr_in clientAddr;
    socklen_t client_addr_len;
    pid_t pid;
    char buf[100],client_IP[100];
    int n,i;
    sfd=Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    /*bind()*/
    bzero(&serverAddr,sizeof(serverAddr));
    serverAddr.sin_family=AF_INET;
    serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
    //inet_pton(AF_INET,自己的IP,&地址.sin_adds.s_addr)
    serverAddr.sin_port=htons(PORT);
    Bind(sfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    Listen(sfd,12);
    client_addr_len=sizeof(clientAddr);
    /*利用多进程实现*/
    //父进程不断通过复制子进程实现多个连接
    while(1){
       //accept()----别忘了取地址
       cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&client_addr_len);
       //打印信息:ip+端口号
       printf("client ip=%s,port=%d\n",inet_ntop(AF_INET,\
             &serverAddr.sin_addr.s_addr,client_IP,sizeof(client_IP)),\
             ntohs(serverAddr.sin_port));
       //fork()
       pid=fork();
       if(pid >0){//父进程
           close(cfd);//没有请求了,关闭cfd
           signal(SIGINT,wait_child);//子进程回收
       }
       else if(pid==0){
            close(sfd);
            break;//跳出进入子进程交互
       }
    }
   if(pid==0){//子进程去交互
      while(1){
          n=Read(cfd,buf,sizeof(buf));
          if(n==0){//读到了末尾
            close(cfd);
            return 0;
          }
          for(i=0;i<n;i++)
             buf[i]=toupper(buf[i]);
          write(cfd,buf,n);
      }
   }
   return 0;
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <ctype.h>
#include "wrap.h"
#define PORT 6767
int main(int argc,char** argv){
    int cfd;
    char* ip_ADDR=argv[1];//不可以用char ip_ADDR[100]
    char buf[100];
    struct sockaddr_in serverAddr;
    cfd=Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    bzero(&serverAddr,sizeof(serverAddr));
    serverAddr.sin_family=AF_INET;
    serverAddr.sin_port=htons(PORT);
    inet_pton(AF_INET,ip_ADDR,&serverAddr.sin_addr.s_addr);
    Connect(cfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
    /*程序实现*/
    while(1){
    fgets(buf,sizeof(buf),stdin);
    write(cfd,buf,strlen(buf));
    int n=read(cfd,buf,sizeof(buf));
    write(1,buf,n);}
    return 0;
}

基础补充

典型协议

传输层 常见协议有TCP/UDP协议

  • UDP用户数据报协议(User Datagram Protocol)

应用层常见的协议有HTTP协议,FTP协议

网络层 常见协议有IP协议ICMP协议IGMP协议

  • IGMP协议是 Internet 组管理协议(Internet Group Management Protocol)

网络接口层 常见协议有ARP协议RARP协议、以太网帧协议

  • [RARP]是反向地址转换协议

网络应用程序设计模式

C/S模式

  • 传统的网络应用设计模式,客户机(client)/服务器(server)模式
  • 需要在通讯两端各自部署客户机和服务器来完成数据通信

B/S模式

  • 浏览器()/服务器(server)模式
  • 只需在一端部署服务器,而另外一端使用每台PC都默认配置的浏览器即可完成数据的传输。

优缺点

C/S模式

  1. 优点
  • 将数据缓存至客户端本地,从而提高数据传输效率,可以保证性能
  • 所采用的协议相对灵活,可以在标准协议的基础上根据需求裁剪及定制
  1. 缺点
  • 工作量将成倍提升,开发周期较长
  • 从用户角度出发,需要将客户端安装至用户主机上,对用户主机的安全性构成威胁

B/S模式

  • 没有独立的客户端,使用标准浏览器作为客户端,其工作开发量较小
  • 移植性非常好,不受平台限制

缺点:

  • 协议选择不灵活,必须采用标准协议
  • 缓存数据慢,传输数据量受限制

分层模型

OSI七层模型

TCP/IP四层模型

多进程并发服务器-LMLPHP

目的主机收到数据包后,如何经过各层协议栈最后到达应用程序

多进程并发服务器-LMLPHP

  1. 以太网驱动程序根据以太网首部中的“上层协议”字段确定该数据帧的有效载荷是IP、ARP还是RARP协议的数据报
  1. 假如是IP数据报,IP协议再根据IP首部中的“上层协议”字段确定该数据报的有效载荷是TCP、UDP、ICMP还是IGMP
  2. 假如是TCP段或UDP段,TCP或UDP协议再根据TCP首部或UDP首部的“端口号”字段确定应该将应用层数据交给哪个用户进程
  • 虽然IP、ARP和RARP数据报都需要以太网驱动程序来封装成

    • 但是从功能上划分,ARP和RARP属于链路层,IP属于网络层
  • 虽然ICMP、IGMP、TCP、UDP的数据都需要IP协议来封装成数据报

    • 但是从功能上划分,ICMP、IGMP与IP同属于网络层,TCP和UDP属于传输层。
11-29 13:39