該程序是一個線程安全(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.
由結果可見,該實例實現了線程安全。