TCP併發伺服器的程式設計實現

學習不畢業發表於2020-09-25

TCP併發伺服器的程式設計實現

1. 基於TCP的伺服器程式設計模型

  1. 建立通訊端點(套接字),返回該端點的檔案描述符 sfd socket(2)
    2 )將sfd和本地的ip地址和埠號繫結 bind(2);
    3 )將sfd設定為被動連線狀態,監聽客戶端的到來,如果有客戶段的到來,將其放入到未決連線佇列中.listen(2)
    while{
    4 從未決連線中取出一個處理,返回一個新的連線描述符 如果未決連線為空,阻塞等待 cfd = accept(2)
    5 從連線描述符中讀取客戶段的請求資料到buf中 read(2)
    6 處理buf中的資料
    7 將處理的結果寫給客戶端 write(2)
    8 關閉本次連線close(cfd)
    }
    2. TCP併發伺服器分三個部分實現:
    1 網路程式設計部分:
    網路程式設計部分封裝為一個原始檔和標頭檔案,分別是t_net.h t_net.c
    t_net.h部分原始碼
#ifndef __T_NET_H__
#define __T_NET_H__
#include <sys/socket.h>
#include <sys/types.h>
typedef struct sockaddr SA; 
typedef struct sockaddr_in SA4;
//socket bind
//這裡將套接字的建立過程和繫結過程封裝為一個函式
int bind_sock(int domain,int type,u_int16_t port);
//呼叫這個函式可以從未連線佇列中取出一個進行處理.不會顯示
//客戶端的ip地址
int n_acpt(int fd);
//對比上一個函式,可以顯示客戶端的ip地址
int h_acpt(int fd);
//將套接字的建立 繫結 監聽封裝到一個函式中
int s_listen(int domain,int type,u_int16_t port,int b); 
#endif //__T_NET_H__

編寫完標頭檔案,可以把標頭檔案移動到/usr/local/include目錄下.這樣include可以使用<>
t_net.c原始碼

#include <t_net.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
int bind_sock(int domain,int type,u_int16_t port){
    //建立一個socket裝置,返回該裝置的檔案描述符
    SA4 serv;
    int sfd = socket(domain,type,0);
    if(sfd == -1){
        printf("%s\n",strerror(errno));
        return -1; 
    }   
    //將sfd繫結繫結本地地址
    serv.sin_family = AF_INET;
    //主機位元組序到網路位元組序
    serv.sin_port = htons(port);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
    int b = bind(sfd,(SA*)&serv,sizeof(serv));
    if(b == -1){
        printf("%s\n",strerror(errno));
        return -1;
    }
    return sfd;
	//不顯示顯示客戶端的IP
	int n_acpt(int fd){
    int cfd = accept(fd,NULL,NULL);
    if(cfd == -1){
        printf("%s\n",strerror(errno));
    }
    return cfd;
}

//顯示客戶端的IP地址
int h_acpt(int fd){
    SA4 cli;
    char ip[32];
    socklen_t len = sizeof(cli);
    int cfd = accept(fd,(SA*)&cli,&len);
    if(cfd == -1){
        printf("%s\n",strerror(errno));
        return -1;
    }
    printf("%s\n",inet_ntop(AF_INET,&cli.sin_addr,ip,32));
    return cfd;
}

編寫完原始檔,也可把原始檔封裝成動態庫
具體步驟如下:
1)生成與檔案無關的可執行檔案

gcc - c -fPIC t_net.c
2)打包動態庫
gcc -shared -o libt_net.so t_net.o
3)將動態庫移動到/lib目錄下.
在編譯的時候加上-lt_net即可
2 伺服器的資料處理

#include <unistd.h>
#include <ctype.h>
#include <string.h>
int t_main(int cfd){
    //讀取客戶端的請求訊息,read阻塞
    char buf[128];
    while(1){
        int r = read(cfd,buf,128);
        for(int i = 0;i < r ;i++)
            buf[i] = toupper(buf[i]);
        write(cfd,buf,r);
        if(strcmp(buf,"BYEBYE") == 0) break;
    }   
    return 0;
}

伺服器的資料處理部分很簡單,就是使用read函式讀取客戶段中的資料.並將服務端中的資料寫回給客戶端
3) 併發部分的實現
當一個客戶端與伺服器建立連線之後,在斷開連線之前.伺服器無法從未決連線佇列中與其他的客戶端建立連線.因此,需要一定手段來實現併發.
有三種方式:多執行緒 多程式 多路複用 epoll
這裡使用的是多程式

父程式的任務
① 負責從未決連線佇列中取出一個進行連線處理,返回一個連線描述符.
②建立子程式,子程式繼承父程式的檔案描述符.
③ 關閉連線描述符.
④負責回收子程式的資源.

子程式負責的任務
① 關閉裝置描述符
② 使用連線描述符處理客戶的業務
③處理完畢,關閉連線描述符
④ exit(0)
相關程式碼

#include <t_net.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>
#include <signal.h>

//子程式終止時給父程式傳送SIGCHLD訊號
void handle(int n){
    //回收子程式的資源
    wait(NULL);
    return ;
}
extern int t_main(int cfd);
int main(void){
    //建立一個socket裝置,返回該裝置的檔案描述符
    signal(SIGCHLD,handle);
    char buffer[128];
    SA4 cli;
    int s_fd = s_listen(AF_INET,SOCK_STREAM,8000,5);
    if(s_fd == -1){
        return -1;
    }
    while(1){
          int cfd = h_acpt(s_fd);
          if(cfd == -1) return -1;
          pid_t pid = fork();
          //父子程式是非同步的
          if(pid == -1){
             printf("%s\n",strerror(errno));
             return -1;
          }
          
if(pid == 0){
            close(s_fd);
            t_main(cfd);
            close(cfd);
            exit(0);
          }
          else{
            close(cfd);
            //阻塞等待
            //waitpid(-1,NULL,WNOHANG);
          }
    }
    close(s_fd);
    return 0;
}

這裡使用一個訊號函式,在子程式終止的時候會發射SIGCHILD訊號,通知父程式回收子程式的資源.
3 TCP客戶端的程式設計模型
1)建立socket裝置socket(2);
2)繫結IP地址和埠號bind(2);
3)和伺服器建立連線
4)迴圈處理資料 write(2) read(2)
5)關閉本次連線 close(2)
相關程式碼:

#include <t_net.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>

int main(int argc,char* argv[]){
    //建立套接字
    char msg[128];
    if(argc < 2){
        printf("引數過少\n");
        return 0;
    }
    int cfd = socket(AF_INET,SOCK_STREAM,0);
    if(cfd == -1){
        printf("%s\n",strerror(errno));
        return 0;
    }
	 //向伺服器發起請求
    SA4 addr;
    memset(&addr,0,sizeof(addr));
    addr.sin_family = AF_INET;
    //埠號
    addr.sin_port = htons(8000);
    char *ipaddress = argv[1];
    struct in_addr ip;
    inet_pton(AF_INET,ipaddress,&ip);
    addr.sin_addr = ip;
    int ret = connect(cfd,(SA*)&addr,sizeof(SA));
    if(ret == 0){
        printf("連線成功...\n");
    }
    else{
        printf("連線失敗...\n");
        return 0;
    }
    while(1){
     gets(msg);
        write(cfd,msg,sizeof(msg));
        char rbuf[128];
        int r = read(cfd,rbuf,sizeof(rbuf));
        printf("message from server:\n");
        printf("%s\n",rbuf);
        if(strcmp(rbuf,"BYEBYE") == 0) break;
    }
    close(cfd);
    return 0;
}

相關文章