線程安全(MT-safe)的多線程併發服務器程序實例

該程序是一個線程安全(MT-safe)的多線程併發服務器實例。包括服務器程序和客戶端程序。編譯及運行的相關信息如下:
操作系統:CentOS 7
編譯工具:GCC
調試工具:GDB
程序實現的功能如下:
1、服務器等候客戶連接,一旦連接成功則顯示客戶的地址,接着接收該客戶的名字並顯示到屏幕。然後接收來自該客戶的信息(字符串)。每當接收到一個字符串,則對其進行顯示,並將接收到的數據翻轉之後發回客戶端。之後,繼續等待該客戶端的信息直至該客戶關閉連接。
服務器具有同時處理多個客戶的能力。並且可以存儲每個連接客戶所發來的所有數據,當連接終止後,服務器將顯示客戶的名字及相應的數據。
2、客戶端首先與服務器連接。連接成功後,顯示成功連接的信息。接着接收用戶輸入的客戶名字,並將名字發送給服務器程序。然後接收用戶輸入的字符串,再將字符串發送給服務器,並接收服務器程序發回的發送成功信息。之後,繼續等待用戶輸入直至用戶輸入Ctrl+D。當收到用戶輸入Ctrl+D之後,客戶關閉連接並退出。
服務器程序如下:(srv.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

#define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000

void process_cli(int connfd, struct sockaddr_in client);        //定義處理客戶端請求的函數
void savedata_r(char* recvbuf,int len,char* cli_data);          //定義保存客戶數據的函數

/*線程執行的函數*/
void *start_routine(void* arg);
struct ARG {                                                    //定義ARG結構
        int connfd;
        struct sockaddr_in client;
};

static pthread_key_t key;                                       //定義TSD關鍵字變量key
static pthread_once_t once=PTHREAD_ONCE_INIT;                   //定義變量ONCE,初指爲PTHREAD_ONE_INIT
static void destructor(void *ptr)                               //定義解析函數destructor,在線程退出時該解析函數被調用,以釋放爲TSD分配的空間
{
        free(ptr);
}
static void getkey_once(void)                                   //定義函數getkey_once(),以產生TSD關鍵字
{
        pthread_key_create(&key,destructor);
}
typedef struct DATA_THR                                         //定義DATA_THR結構,用於存儲TSD
{
        int index;
}DATA_THR;

main()
{
        int listenfd,connfd;
        pthread_t  tid;                                         //定義存放線程ID的變量
        struct ARG *arg;
        struct sockaddr_in server;
        struct sockaddr_in client;
        int sin_size;

        /*創建TCP套接字*/
        if ((listenfd =socket(AF_INET, SOCK_STREAM, 0)) == -1) {
                /*異常處理*/
                perror("Creating socket failed.");
                exit(1);
        }

        /*設置套接字選項爲SO_REUSEADDR*/
        int opt =SO_REUSEADDR;
        setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

        /*綁定套接字到相應地址*/
        bzero(&server,sizeof(server));
        server.sin_family=AF_INET;
        server.sin_port=htons(PORT);
        server.sin_addr.s_addr= htonl (INADDR_ANY);
        if (bind(listenfd,(struct sockaddr *)&server, sizeof(server)) == -1) {
                perror("Bind()error.");
                exit(1);
        }

        /*監聽網絡連接*/
        if(listen(listenfd,BACKLOG)== -1){
                perror("listen()error\n");
                exit(1);
        }

        /*接受客戶連接*/
        sin_size=sizeof(client);
        while(1)
        {
                if ((connfd =accept(listenfd,(struct sockaddr *)&client,&sin_size))==-1) {
                        perror("accept() error\n");
                        exit(1);
                }

                /*設置新線程參數*/
                arg = (struct ARG *)malloc(sizeof(struct ARG));
                arg->connfd =connfd;
                memcpy((void*)&arg->client, &client, sizeof(client));

                /*一旦連接成功,產生新的線程服務客戶,並執行start_routine函數*/
                if(pthread_create(&tid, NULL, start_routine, (void*)arg)) {
                        perror("Pthread_create() error");
                        exit(1);
                }
        }
        close(listenfd);//關閉監聽套接字
}

void process_cli(int connfd, struct sockaddr_in client)
{
        int num;
        char cli_data[5000];
        char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];

        /*打印連接到的客戶端的IP地址*/
        printf("You got a connection from %s. ",inet_ntoa(client.sin_addr) );
        /*接收客戶端名字並打印*/
        num = recv(connfd,cli_name, MAXDATASIZE,0);
        if (num == 0) {
                close(connfd);
                printf("Client disconnected.\n");
                return;
        }
        cli_name[num - 1] ='\0';
        printf("Client's name is %s.\n",cli_name);

        /*接收客戶發來的字符串並做相應處理*/
        while (num =recv(connfd, recvbuf, MAXDATASIZE,0)) {
                recvbuf[num] ='\0';
                printf("Received client( %s ) message: %s",cli_name, recvbuf);
                /*保存客戶數據*/
                savedata_r(recvbuf,num,cli_data);
                /*翻轉客戶數據*/
                int i;
                for (i = 0; i <num - 1; i++) 
                {
                        sendbuf[i] =recvbuf[num-i-2];
                }
                sendbuf[num -1] = '\0';

                /*將翻轉後的數據發回客戶端*/
                send(connfd,sendbuf,strlen(sendbuf),0);
        }
        /*關閉連接套接字並打印客戶的相關信息*/
        close(connfd);
        printf("Clinet (%s) closed connect.User's data: %s\n",cli_name,cli_data);
}

void *start_routine(void* arg)
{
        /*取出相應參數*/
        struct ARG *info;
        info = (struct ARG*)arg;

        process_cli(info->connfd,info->client);         //處理客戶請求
        /*退出線程*/
        free (arg);
        pthread_exit(NULL);
}

void savedata_r(char* recvbuf,int len,char* cli_data)
{
        DATA_THR* data;

        //線程專用個數據
        pthread_once(&once,getkey_once);                //調用getkey_once函數產生TSD關鍵字

        //若當前程序未分配空間給TSD,則分配空間並與TSD關鍵字綁定
        if((data=(DATA_THR *)pthread_getspecific(key))==NULL)
        {
                data=(DATA_THR *)calloc(1,sizeof(DATA_THR));
                pthread_setspecific(key,data);
                data->index=0;
        }

        /*把接收到的客戶數據按先後次序放入客戶數據緩衝區(cli_data)*/
        int j;
        for(j=0; j<len-1;j++)
        {
                cli_data[data->index++]=recvbuf[j];
        }
        cli_data[data->index]='\0';
}

客戶端程序如下:(cli.c)

#include<stdio.h>
#include<unistd.h>
#include<strings.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#include<stdlib.h>

#define PORT    1234
#define MAXDATASIZE     100

void process(FILE *fp,int socket);
char* getMessage(char* sendline,int len,FILE* fp);

int main(int argc,char *argv[])
{
        int fd;
        struct hostent *he;
        struct sockaddr_in server;

        if(argc!=2)
        {
                printf("Usage :%s  <IP Address>\n",argv[0]);
                exit(1);
        }

        if((he=gethostbyname(argv[1]))==NULL)
        {
                printf("gethostbyname() error.\n");
                exit(1);
        }

        if((fd=socket(AF_INET,SOCK_STREAM,0))==-1)
        {
                perror("socket() error");
                exit(1);
        }

        bzero(&server,sizeof(server));
        server.sin_port=htons(PORT);
        server.sin_family=AF_INET;
        server.sin_addr=*((struct in_addr *)he->h_addr);

        if(connect(fd,(struct sockaddr *)&server,sizeof(struct sockaddr))==-1)
        {
                perror("connect() error");
                exit(1);
        }

        process(stdin,fd);

        close(fd);
}

void process(FILE *fp,int sockfd)
{
        int numbytes;
        char recvline[MAXDATASIZE],sendline[MAXDATASIZE];
        printf("Connected to server.\n");
        printf("Input name :");
        if(fgets(sendline,MAXDATASIZE,fp)==NULL)
        {
                printf("\nExit.\n");
                return;
        }
        send(sockfd,sendline,strlen(sendline),0);

        while((getMessage(sendline,MAXDATASIZE,fp))!=NULL)
        {
                send(sockfd,sendline,strlen(sendline),0);
                if((numbytes=recv(sockfd,recvline,MAXDATASIZE,0))==0)
                {
                        printf("Server terminated.\n");
                        return;
                }
                recvline[numbytes]='\0';
                printf("Server Message :%s\n",recvline);
        }

        printf("\nExit.\n");
}

char *getMessage(char *sendline,int len,FILE *fp)
{
        printf("Input string to server:");
        return(fgets(sendline,MAXDATASIZE,fp));
}

對服務器程序和客戶端程序分別進行編譯,生成名爲srv的服務器程序和名爲cli的客戶端程序:

[root@mylinux 2]# gcc -o srv srv.c -lpthread
[root@mylinux 2]# gcc -o cli cli.c -lpthread

服務器程序運行結果如下:

[root@mylinux 2]# ./srv
You got a connection from 127.0.0.1. Client's name is client1.
Received client( client1 ) message: 1234
You got a connection from 127.0.0.1. Client's name is client2.
Received client( client2 ) message: abc
Received client( client1 ) message: 5678
Clinet (client1) closed connect.User's data: 12345678
Received client( client2 ) message: defg
Clinet (client2) closed connect.User's data: abcdefg

客戶1運行結果如下:

[root@mylinux 2]# ./cli 127.0.0.1
Connected to server.
Input name :client1
Input string to server:1234
Server Message :4321
Input string to server:5678
Server Message :8765
Input string to server:
Exit.

客戶2運行結果如下:

[root@mylinux 2]# ./cli 127.0.0.1
Connected to server.
Input name :client2
Input string to server:abc
Server Message :cba
Input string to server:defg
Server Message :gfed
Input string to server:
Exit.

由結果可見,該實例實現了線程安全。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章