该程序是一个线程安全(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.
由结果可见,该实例实现了线程安全。