线程安全(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.

由结果可见,该实例实现了线程安全。

猜你喜欢

转载自blog.csdn.net/xiaolong361/article/details/52705000