多进程多线程服务器(tcp_server)编写

来源:互联网 发布:鹤山网络问政平台 编辑:程序博客网 时间:2024/06/05 09:09

编写客户/服务器

1.编写单进程客户/服务器(Version1)

  • 代码清单:
    tcp_servet
#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<stdlib.h>#include<netinet/in.h>#include<arpa/inet.h>#include<string.h>static void usage(const char* proc){    printf("Usage: %s [local_ip] [local_port]",proc);//提示输入出错,应该输入./tcp_servet ip地址 端口号(运行服务器)}int startup(const char* ip,int port)//创建套接字{    int sock = socket(AF_INET,SOCK_STREAM,0);//socket()创建套接字函数    if(sock<0)    {        perror("socket");//提示创建套接字失败        exit(2);    }//socket的返回值只是一个文件描述符,为了进行网络通信,我们还需将其与服务器 ip地址和端口号进行绑定    struct sockaddr_in local;//结构体struct sockaddr_in是struct sockaddr的具体化    local.sin_family = AF_INET;    local.sin_port = htons(port);    local.sin_addr.s_addr = inet_addr(ip);    if(bind(sock,(struct sockaddr*) &local,sizeof(local))<0){//bind(绑定函数        perror("bind");        exit(3);            }//监听    if(listen(sock,10)<0)//服务器24小时监听,等待连接请求    {        perror("listen");        exit(4);    }    return sock;}//Version 1//./tcp_servet 127.0.0.1 8080  本地环回int main(int argc,char* argv[]){    if(argc!=3){        usage(argv[0]);        return 1;    }    int listen_sock = startup(argv[1],atoi(argv[2]));//create socket    int new_sock;    while(1){        struct sockaddr_in client;        socklen_t len = sizeof(client);        new_sock = accept(listen_sock,(struct sockaddr*)&client ,&len);//accept创建新的socket,用来处理客户段请求,listen_sock只负责监听        if(new_sock<0)        {            perror("accept");            exit(5);                continue;        }        //成功获取到客户端,打印客户端的 ip和端口号,accept的第二个参数是输出型参数,由它可得客户端的ip和端口号        printf("get an new client: %s, %d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));        break;    }//客户端和服务器端的数据传输    char buf[1024];    while(1){        int r = read(new_sock,buf,sizeof(buf)-1);//从网络中读取数据(客户端发送数据)放入buf中;        if(r<0){//读取失败            perror("read");            close(new_sock);            break;        }else if(r==0){//客户端已关闭            close(new_sock);            printf("client qiuit...\n");            break;        }        //读取成功        buf[r] = 0;        printf("clinet: %s\n",buf);        write(new_sock,buf,strlen(buf));//回送数据    }    return 0;}

tcp_client.c客户端

#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/socket.h>#include<arpa/inet.h>#include<netinet/in.h>#include<string.h>void static Usage(const char* proc)//提示打印格式{    printf("Usage: %s [local_ip] [local_port]\n",proc);//运行客户端./tcp_servet 服务器ip 服务器端口号}//./tcp_clinet  servet_ip  servet_portint main(int argc,char* argv[]){    if(argc!=3){        Usage(argv[1]);        exit(1);    }    int sock = socket(AF_INET,SOCK_STREAM,0);//创建套接字    if(sock < 0 ){        perror("socket");        exit(2);    }    //客户端socket不绑定    struct sockaddr_in server;    server.sin_family = AF_INET;    server.sin_port = htons(atoi(argv[2]));//htons将本地端口号转为网络端口号    server.sin_addr.s_addr = inet_addr(argv[1]); //给数据结构成员赋值    //connect()链接服务器    if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0){        perror("connect");        exit(3);    }   //客户端发送数据    char buf[1024];    while(1){        printf("Please Enter$");        fflush(stdout);        ssize_t s = read(0,buf,sizeof(buf)-1);//从标准输入读数据到buf        if(s>0){//读取成功            buf[s-1] = 0;            write(sock,buf,strlen(buf));//将数据写到网络中            s = read(sock,buf,sizeof(buf)-1);//读取回显数据            if(s>0){                buf[s] = 0;                printf("servetr echo#:%s\n",buf);        }    }    return 0;}
  • 注意要点

    • 上述的服务器地址采用127.0.0.1,故该服务器没有通过网络通信,还是一直监听所在主机上的客户链接请求,这是本地环回;

    • 该客户/服务器是一个单进程的服务器,一次只能链接一个客户请求,因为我们在收到一个客户请求后,执行完客户端的请求才接受下一个请求的链接

    • 客户端的套接字可以绑定也可以不绑定,但一般不进行绑定,因为一点绑定服务器的ip,那么若别的客户端想链接,链接不上

  • 涉及函数以及命令

    • 创建socket
      这里写图片描述

    domain表示域,ipv4为AF_INET;
    type表示类型,SOCK_STREAM(面向字节流)
    protocal表示协议,单个通信协议情况下为0

    • bind绑定
      这里写图片描述

      sockfd : socket()的返回值,一个文件描述符
      addr:一个struct sockaddr_in 的结构体
      这里写图片描述
      struct in_addr结构体成员是s_addr,表示ip地址;
      addrlen:结构体大小

    • accept创建新的socket
      这里写图片描述
      addr是一个输出型参数,输出的是客户端的struct sockaddr_in 的内容
      addrlen是一个输入输出型参数
    • ip转换
      这里写图片描述
    • 本地端口号与网络端口号的互换
      这里写图片描述
    • 监听
      这里写图片描述
      backlog : 是操作系统底层连接队列,不能设置过大;
    • connect函数(客户端连接服务器)
      这里写图片描述

2.多进程服务器编写

代码清单:
tcp_servet.c

#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<string.h>//Version2void static usage(const char* proc){    printf("Usage: %s [local_ip] [local_port]\n",proc);}int startup(char* _ip,int _port){    int sock = socket(AF_INET,SOCK_STREAM,0);    if(sock<0){        perror("socket");        exit(2);    }    struct sockaddr_in local;    local.sin_family = AF_INET;    local.sin_port = htons(_port);    local.sin_addr.s_addr = inet_addr(_ip);    if(bind(sock,(struct sockaddr*) &local,sizeof(local))<0){        perror("bind");        exit(3);    }    if(listen(sock,10)<0){        perror("listen");        exit(4);    }    return sock;}//./tcp_servet local_ip local_portint main(int argc,char* argv[]){    if(argc!=3){        usage(argv[0]);        return 1;    }    int listen_sock = startup(argv[1],atoi(argv[2]));    int new_sock;    while(1){        struct sockaddr_in client;        socklen_t len = sizeof(client);        new_sock = accept(listen_sock,(struct sockaddr*) &client,&len);        if(new_sock < 0){            perror("accept");            exit(5);            continue;        }        printf("get an new client:%s,%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));        break;    }    //创建了一个子进程代替我们处理客户端的请求---与上述单进程的不同之处    pid_t id = fork();    if(id<0){        perror("fork");        exit(6);    }else if(id==0){//child        close(listen_sock);//关闭不必要的文件        if(fork > 0){//两次fork            exit(7);        }        char buf[1024];        while(1){            int r= read(new_sock);            if(r<0){                perror("read");                close(new_sock);                break;            }else if(r==0){                close(new_sock);                printf("clinet quit...\n");                break;            }            buf[r] = 0;            printf("clien: %S\n",buf);            write(new_sock,buf,strlen(buf));            close(new_sock);        }    }else{//father        close(new_sock);//父进程继续负责监听客户请求,必须关闭new_sock文件    }}
  • 注意要点

    • 与单进程的唯一不同的是, fork了一个子进程,每次客户端的请求交给子进程完成,父进程只负责监听 ;
    • 两次 fork,为了能够让父进程只负责监听,而不阻塞式的等待子进程的退出,我们采用两次fork,此时存在两个进程,两个进程存在爷孙关系,“孙子”进程此时由于父进程已经退出,所以它是孤儿进程,由 init进程回收,与此刻的父进程没有关系
    pid_t id = fork();if(id<0){    perror("fork");    exit(6);}else if(id==0){//child    close(listen_sock);//关闭不必要的文件    if(fork > 0){//两次fork,第二次创建完子进程后该父进程退出        exit(7);    }
    • 父进程必须关闭new_sock文件,因为子进程拷贝了父进程的文件描述符表,两者表是一样的,若不关闭,每次客户端发送来一个新的连接,就要在父进程的文件描述符表中打开一个文件,这样浪费资源,而且很快会用完;(子进程处理完信息后退出时关闭了文件描述符表,而父进程一直处于运行状态,所以必须主动关闭)

3.多线程服务器的编写

代码:
这里写图片描述

这里写图片描述

原创粉丝点击