{C++系列} UNIX高级编程 day14 网络基础,基于TCP的网络编程,并发服务器的实现
in C/C++ with 0 comment

{C++系列} UNIX高级编程 day14 网络基础,基于TCP的网络编程,并发服务器的实现

in C/C++ with 0 comment

复习

一,网络基础

#一,基于TCP的编程

Struct sockaddr结构体

通用地址格式

但是在具体的通讯中,使用具体的地址格式。
了解具体的地址结构体有哪些?

IPV4地址家族
IPV6地址家族

用于其它的通讯家族 socket(2) domain
为了了解IPV4地址家族的具体情况
Man in.h

#include<netinet/in.h>
In_port_t         uint16_t        无符号的短整形
Int_addr_t        uint32_t         无符号的长整形   

Sa_family_t //typedef unsigned short sa_family_t

Struct in_addr{
          … ;
            Int_addr_t  s_addr;
            …  ;   
};
Struct sockaddr_in{ //internet环境下的套接字地址形式
        。。。;
Sa_family_t    sin_family   // AF_INET;
 In_port_t    sin_port    //port number
Struct in_addr    sin_addr //ip    address       
 。。。;    
        
};
typedef unsigned short sa_family_t;
struct sockaddr { //通用套接字地址
        sa_family_t     sa_family;    /* address family, AF_xxx       */
        char            sa_data[14];    /* 14 bytes of protocol address */

}

truct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下 套接字的地址形式,二者长度一样,都是16个字节。二者是并列结构,指向sockaddr_in 结构的指针也可以指向sockaddr。一般情况下,需要把sockaddr_in结构强制转换成 sockaddr结构再传入系统调用函数中。

是IPV4的具体地址结构
这个结构体中的sin_addr.s_addr 是无符号的长整形。这个成员中存放的是ip地址。我们能使用的ip地址是点分十进制字符串
需要在无符号长整型和字符串之间做转换。

INADDR_ANY //IPv4 local host address

在socket相关的函数中使用的是通用地址结构,这时候需要将具体的地址结构转换为通用的地址结构

1,完成ip地址从字符串和数字格式的相互转换。

inet_pton(3) inet_ntop(3)

#include <arpa/inet.h>
int     inet_pton(int af, const char * restrict src, void * restrict dst);

功能:将地址从字符串转换为二进制
参数

Af;    AF_INET        AF_INET6
Src:    指定点分十进制的字符串IP地址
Dst:    指定存放转换后的结果的缓冲区的地址

返回值:
1 成功
0 如果字符串无效 返回 0

如果指定的地址家族无效 错误 返回-1. Errno被设置

#include <arpa/inet.h>
const char *     inet_ntop(int af, const void * restrict src, char * restrict dst ,   socklen_t size);

功能:binary —》text
参数

af:指定地址家族. AF_INET        AF_INET6
Src:指定了源地址       struct    in_addr
Dst:指定了存放结果字符串的首地址
size:指定了存放结果字符串的缓存大小

返回值:
非空的字符串 成功返回字符串的首地址
NULL 错误 errno被设置

主机字节序 小端 一般是
网络字节序 大端 完全是
主机字节序到网络字节序的相互转换

port 16位 短
S_addr 32位 长

 #include <arpa/inet.h>
 uint32_t     htonl(uint32_t hostlong);
uint16_t     htons(uint16_t hostshort);
uint32_t     ntohl(uint32_t netlong);
uint16_t     ntohs(uint16_t netshort);

h        host
n        net
l         long
s        short

完成程序
1,修改r_net.h

1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/socket.h>
  4 #include<ctype.h>
  5 #include<unistd.h>
  6 #include "../net/r_net.h"
  7 int main(){
  8         struct sockaddr addr;
  9         char buf[128];
           //SA_4 typedef struct sockaddr_in SA_4
 10         SA_4 serv;
 11         //创建一个通讯设备
 12         int s_fd,conn_fd;
 13         s_fd=socket(AF_INET,SOCK_STREAM,0);
 14         if(s_fd==-1){
 15                 perror("socket");
 16                 return -1;
 17         }
 18         //需要初始化serv的ip地址和端口号
 19         serv.sin_family=AF_INET;
 20         serv.sin_port=htons(9999);
            //INADDR_ANY Ipv4 local host address..
 21         serv.sin_addr.s_addr=htonl(INADDR_ANY);
 22         //将通讯设备和服务器的地址和端口号绑定
 23         int b= bind(s_fd,(SA *)&serv,sizeof(serv));
 24         if(b==-1){
 25                 perror("bind");
 26                 return -1;
 27         }
 28         //将s_fd设置为被动连接
 29         listen(s_fd,5);
 30         while(1){//在循环中,从未决连接队列中取出一个连接进行处理。
 31                 conn_fd = accept(s_fd,NULL,NULL);
 32                 if(conn_fd==-1){
 33                         perror("accept");
 34                         return -1;35                 }
 36                 //已经和客户端建立连接
 37                 //读取客户端的请求信息
 38                 int r = read(conn_fd,buf,128);
 39                 //处理客户端的请求
 40                 for(int i=0;i<r;i++){
 41                         buf[i]=toupper(buf[i]);
 42                 }
 43                 //响应客户端
 44                 write(conn_fd,buf,r);
 45                 //关闭和客户端的连接
 46                 close(conn_fd);
 47         }
 48         return 0;
 49 }

客户端:

1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/socket.h>
  4 #include<string.h>
  5 #include<unistd.h>
  6 #include "../net/r_net.h"
  7 int main(){
  8         char buf[128];
  9         SA_4 serv;
 10         char* msg = "this is a test!\n";
 11         //创建一个socket
 12         int s_fd;
 13         s_fd=socket(AF_INET,SOCK_STREAM,0);
 14         if(s_fd == -1){
 15                 perror("socket");
 16                 return -1;
 17         }
 18         //初始化服务器地址和端口号
 19         serv.sin_family=AF_INET;
 20         serv.sin_port=htons(9999);
 21         //连接的是本机的服务器 127.0.0.1
 22         inet_pton(AF_INET,"127.0.0.1",&serv.sin_addr);
 23         //连接s_fd到服务器的ip地址和端口号
 24         int c=connect(s_fd,(SA *)&serv,sizeof(serv));
 25         if(c==-1){
 26                 perror("connect");
 27                 return -1;
 28         }
 29         //向服务器发送消息
 30         write(s_fd,msg,strlen(msg));
 31         //阻塞等待服务器的响应消息
 32         int r = read(s_fd,buf,128);
 33         //将获取到的响应信息输出到显示器
 34         write(1,buf,r);
35         //中止本次通讯
 36         close(s_fd);
 37         return 0;
 38 }

客户端和服务器端的一次连接,可以进行多次通讯,知道客户端输入exit或者quit的时候才终止和服务器器的连接。结束本次通讯。

#三,并发服务器的实现

实现并发服务器可以有三种方式

多进程实现服务器的并发
创建进程 父进程 子进程

举例

使用多进程实现服务器的并发

servers.c

#include <stdio.h>
#include <r_net.h>
#include "doit.h"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void){
    SA4 cli;
    int s_fd,conn_fd,cli_len=sizeof(cli);
    char buf[128],IP[128];

    s_fd=r_listen(9999,5);
    while(1){//在循环中从未决连接队列中取出一个连接进行处理
        conn_fd=r_accept(s_fd,(SA *)&cli,&cli_len);
        if(conn_fd==-1)exit(0);
        printf("%s\n",\
            inet_ntop(AF_INET,&cli.sin_addr,IP,128));
        //创建子进程
        pid_t pid=fork();
        if(pid==-1){
            perror("fork");
            return -1;
        }
        if(pid==0){//子进程负责的任务
            //关闭s_fd
            close(s_fd);
            while(1){
                //已经和客户端建立起连接
                int dc=do_client(conn_fd);
                if(dc==-2)break;
            }
            //关闭和客户端的连接
            close(conn_fd);
            exit(0);//终止子进程
        }else{//父进程负责的任务
            close(conn_fd);
            waitpid(-1,NULL,WNOHANG);
        }
    }
    return 0;
}

r_net.c

#include <stdio.h>
#include <r_net.h>
/*
 * 功能:
 * 参数:
 * 返回值:
 * */
int r_listen(unsigned short port,int backlog){
    int s_fd;
    SA4 serv;
    
    //创建一个通讯设备
    s_fd=socket(AF_INET,SOCK_STREAM,0);
    if(s_fd==-1){
        perror("socket");
        return -1;
    }
    //需要初始化serv的ip地址和端口号
    serv.sin_family=AF_INET;
    serv.sin_port=htons(port);
    //INADDR_ANY  IPv4 local host address.
    serv.sin_addr.s_addr=htonl(INADDR_ANY);
   
    //将通讯设备和服务器的地址和端口号绑定
    int b=bind(s_fd,(SA *)&serv,\
            sizeof(serv));
    if(b==-1){
        perror("bind");
        return -1;
    }
    //将s_fd设置为被动连接
    listen(s_fd,backlog);
    return s_fd;
}
/*
 *
 * */
int r_accept(int fd,SA *addr,socklen_t *addrlen){
    int c_fd;
    c_fd=accept(fd,addr,addrlen);
    if(c_fd==-1){
        perror("accept");
        return -1;
    }
    return c_fd;
}

doit.c

/*本文件主要应用于处理客户端的业务
 * */
#include "doit.h"
#include <string.h>
#include <strings.h>
int do_client(int fd){
    char buf[128];
    int i;
    bzero(buf,128);
    //获取客户端的请求信息
    int r=read(fd,buf,128);
    if(r==-1){
        perror("read");
        return -1;
    }
    //对客户端的数据进行处理
    for(i=0;i<r;i++)
        buf[i]=toupper(buf[i]);
    
    //将处理后的 信息发送给客户端
    write(fd,buf,r);
    if(strcmp(buf,"QUIT")==0)
        return -2;
    return r;
}

r_net.h

1 #ifndef R_NET_H_
  2 #define R_NET_H_
  3 #include<sys/types.h>
  4 #include<sys/socket.h>
  5 #include<string.h>
  6 #include<unistd.h>
  7 #include<arpa/inet.h>
  8 #include<netinet/in.h>
  9
 10 typedef struct sockaddr SA;
 11 typedef struct  sockaddr_in SA_4;
 12
 13 //函数的声音
 14 int r_listen(unsigned short,int);
 15 #endif

doit.h

#ifndef DO_IT_H_
#define DO_IT_H_
#include <unistd.h>
#include <ctype.h>
#include <stdio.h>
int do_client(int);
#endif

gcc servers.c r_neet.c doit.c -o server

总结

一,基于TCP的网络编程
二,使用多进程实现服务器的开发
多进程和子进程的各自的任务

在服务器的指定的目录下,存在一个文件。实现客户端从服务器端下载文件。实现客户端和服务端

Responses