Linux应用(7)——多线程服务器设计
8.1 简介
多个客户端与一个服务器进行通信,采用多线程是比较容易实现的;

思路:
1.采用链表存放每个客户端的信息,比如IP,多口号,套接字ID等
2.服务器代码在主函数的while循环中循环accept函数,连接一个客户就把该客户的信息插入到链表,并创建接收发送线程与客户端进行通信
3.在对链表进行操作时需要上锁,防止操作时其他线程对链表进行操作
4.当客户端发送"Quit"时结束该客户端线程
8.2 代码
链表文件 mylinklist.h
#ifndef MYLINKLIST_H_
#define MYLINKLIST_H_
#include <pthread.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<netinet/in.h>
typedef struct sockaddr_in ClientSockData;typedef struct
{int clientsockid ;//客户套接字IDpthread_t recvpthreadid;//服务端接收线程IDpthread_t sendpthreadid;//服务端发送线程IDClientSockData clientaddr ;//本地信息,IP信息,端口号,IP类型
}elemType;typedef struct node
{elemType data; struct node*next ;
}Node;//数据节点typedef struct
{Node*head ;//指向第一个节点// Node*tail ;//指向最后一个节点int count ;//节点数量
}LinkList; //有头链表类型typedef enum
{FALSE=0,TRUE,
}BOOL;/**********************[函数声明]**************************/
LinkList*LinkList_create(void);
BOOL LinkList_destroy(LinkList*list);
BOOL LinkList_inserthead(LinkList*list,elemType *data);
BOOL LinkList_inserttail(LinkList*list,elemType *data);
BOOL LinkList_print(LinkList*list);
BOOL LinkList_delete_bysockid(LinkList* list, int clientsockid);
Node* LinkList_find_bysockid(LinkList* list, int clientsockid);
BOOL sort_bysockid(LinkList* list);
#endif
链表文件 mylinklist.c
#include<stdio.h>
#include<stdlib.h>
#include"mylinklist.h"/*********************************
function:创建一个链表
param :void
return :头节点地址
explain :创建只包含管理信息的头节点
time :2025/10/2
**********************************/
LinkList*LinkList_create(void)
{/*1.为头节点申请空间*/LinkList*list=(LinkList*)malloc(sizeof(LinkList));if(list==NULL){printf("line:%d memory is not enough,create headlistlist false!\n",__LINE__);return NULL;}list->head=NULL;//list->tail=NULL;list->count=0 ;return list ;
}/*********************************
function:销毁一个链表
param :LinkList*h表头
return :0/1
explain :false:0;ture:1
time :2025/10/2
**********************************/
BOOL LinkList_destroy(LinkList*list)
{if(list==NULL){printf("line:%d LinkList is null,destroy false\n",__LINE__);return FALSE;}Node*current=list->head;//current指向首元素节点Node*temp=NULL ;//用于存放地址//释放所有节点while(current!=NULL){temp=current->next;//保存下一个节点free(current) ;//释放当前节点current=temp ;//移动到下一个节点}//释放头节点free(list) ;return TRUE;
}/*********************************
function:利用头插法插入节点
param1 :LinkList*list 表头
param2 :elemType*data 要插入的数据
return :成功/失败
explain :为什么data不用值传递而用指针?当结构体较大时,传递结构体指针比传递结构体值更高效
time :2025/10/2
**********************************/
BOOL LinkList_inserthead(LinkList*list,elemType *data)
{if(list==NULL||data==NULL){printf("line:%d LinkList or data is null,inserthead false\n",__LINE__);return FALSE;}Node*new_node=(Node*)malloc(sizeof(Node));memcpy(&new_node->data,data,sizeof(elemType));//插入的数据为新节点数据if(new_node==NULL){printf("line:%d memory is not enough,create new_node false!\n",__LINE__);return FALSE;}new_node->next = list->head ;//新节点指向原来的第一个节点list->head = new_node ;//头节点指向新节点list->count += 1 ;//数据节点+1return TRUE;
}/*********************************
function:利用尾插法插入节点
param1 :LinkList*list 表头
param2 :elemType*data 要插入的数据
return :成功/失败
explain :
time :2025/10/2
**********************************/
BOOL LinkList_inserttail(LinkList*list,elemType*data)
{if(list==NULL||data==NULL){printf("line:%d LinkList or data is null,inserttail false\n",__LINE__);return FALSE;}Node*new_node = (Node*)malloc(sizeof(Node)) ;/*初始化新节点*/memcpy(&new_node->data,data,sizeof(elemType));//插入的数据为新节点数据new_node->next = NULL;if(new_node==NULL){printf("line:%d memory is not enough,create new_node false!\n",__LINE__);return FALSE;}/*找出最后一个节点*/Node*current=list->head ;//指向首节点if(current==NULL){ /*链表为空,新节点成为第一个节点*/list->head = new_node ;//头节点指向新节点}else{ /*链表不为空,找到最后一个节点*/while(current->next!=NULL){current=current->next;}current->next = new_node ;//最后一个节点指向新节点 }list->count += 1 ;//数据节点+1 return TRUE;
}/*********************************
function:打印数据信息
param1 :LinkList*list 表头
return :成功/失败
explain :
time :2025/10/2
**********************************/
BOOL LinkList_print(LinkList*list)
{if(list==NULL){printf("line:%d LinkList is null,print false\n",__LINE__);return FALSE;}printf("LinkedList has %d nodes:\n",list->count);Node*current=list->head ;//指向首节点int index=0;while(current!=NULL){/*打印节点信息*/char IP[16]={0};//用于存放IPinet_ntop(AF_INET,&(current->data.clientaddr.sin_addr),IP,sizeof(IP));printf("Node[%d]:clientsockid=%d,recvpthreadid=%d,sendpthreadid=%d,IP=%s,Port=%d\n",index++, current->data.clientsockid,current->data.recvpthreadid,current->data.sendpthreadid,IP,ntohs(current->data.clientaddr.sin_port));current=current->next;}return TRUE;
}/*********************************
function:根据客户端套接字ID删除节点
param1 :LinkList*list 表头
param2 :int clientsockid 要删除的套接字ID
return :成功/失败
explain :
time :2025/10/2
**********************************/
BOOL LinkList_delete_bysockid(LinkList* list, int clientsockid)
{if(list==NULL||list->head==NULL){printf("line:%d LinkList is null or no have node\n",__LINE__);return FALSE;}Node*current = list->head ;//指向首节点Node*prev = NULL ;//前驱节点/*情况1,只有一个节点*/if(current->data.clientsockid==clientsockid){list->head = current->next;//头节点指向下一个节点free(current) ;//释放当前节点list->count -= 1 ;//更新节点数量return TRUE ;}/*情况2.删除的是中间节点或者尾节点*/prev = current;current = current->next;while(current!=NULL){if(current->data.clientsockid==clientsockid){prev->next = current->next;// 前驱节点指向当前节点的下一个free(current) ;//释放当前节点list->count -= 1;//更新节点数量return TRUE ;}prev = current ;//移动前驱节点current = current->next;//移动当前节点 }/*情况3.未找到匹配节点*/printf("line:%d clientsockid is not found\n",__LINE__);return FALSE;
}/*********************************
function:根据客户端套接字ID查找节点
param1 :LinkList*list 表头
param2 :int clientsockid 要查找的套接字ID
return :成功/失败
explain :
time :2025/10/2
**********************************/
Node* LinkList_find_bysockid(LinkList* list, int clientsockid)
{if(list==NULL||list->head==NULL){printf("line:%d LinkList is null or no have node\n",__LINE__);return NULL;}Node*current = list->head ;//指向首节点while(current!=NULL){if(current->data.clientsockid==clientsockid){return current;} current=current->next; }printf("line:%d clientsockid is not found\n",__LINE__);return NULL;
}
/*********************************
function:根据套接字ID排序
param1 :LinkList*list 表头
return :成功/失败
explain :创建一个新的链表,从原来的链表中找大小尾插到新链表
time :2025/10/2
**********************************/
BOOL sort_bysockid(LinkList* list)
{if(list==NULL||list->head==NULL||list->head->next==NULL){printf("line:%d sort false\n",__LINE__);return FALSE;}Node*current = list->head;//表示当前节点 Node*min_node = NULL ; //表示最小节点Node*temp = NULL ;while(current!= NULL){/*外层循环*/min_node=current ;temp=current->next;while(temp!=NULL){/*内层循环*/if(temp->data.clientsockid<min_node->data.clientsockid){min_node=temp;}temp=temp->next;}if(current!=min_node){ /*数据交换*/elemType temp_data = current->data ;//最小节点的数据暂存current->data = min_node->data ;//min_node->data = temp_data ;// }/*移动到下一个节点*/current = current->next;}return TRUE;
}
服务器文件 sever.c
/***************【服务器】**********************/
/*要求:
多线程通信
*///./serve IP 端口号#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <pthread.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<dirent.h>
#include<fcntl.h>
#include"mylinklist.h"//全局变量
LinkList* client_list = NULL;
void*pthreadSend_function(void*arg);
void*pthreadRecv_function(void*arg);
pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;//定义互斥锁//函数声明
void handle_client_disconnection(int clientsockid);int main(int argc,char*argv[])
{if(argc!=3){printf("argc error,please input ./server IP port");return -1 ;}/*1.创建套接字,IPv4,TCP*/int SocketID=socket(AF_INET,SOCK_STREAM,0);if(SocketID==-1){printf("socket error\n") ;return -2 ;}/*2.绑定本地信息*/struct sockaddr_in LocalMsg ;//本地信息memset(&LocalMsg,0,sizeof(LocalMsg)) ;LocalMsg.sin_family=AF_INET ;//IPv4LocalMsg.sin_port=htons(atoi(argv[2])) ;if(inet_pton(AF_INET,argv[1], &LocalMsg.sin_addr)!=1){printf("inet_pton error\n");close(SocketID) ;return -3 ;}if(bind(SocketID,(struct sockaddr*)&LocalMsg,sizeof(LocalMsg))==-1){printf("bind error\n") ;close(SocketID) ;return -4 ;}/*3.监听队列,最多允许5个会话*/if(listen(SocketID,5)==-1){printf("listen error\n") ;close(SocketID) ;return -5 ;}printf("Server started on %s:%s\n", argv[1], argv[2]);printf("Waiting for client connection...\n") ;/*4.创建一个链表用于存放各个客户端信息*/client_list=LinkList_create() ;if(client_list==NULL){printf("LinkList_create error\n");close(SocketID) ;return -6 ;}/*4.接收客户端连接*/while(1){//动态分配每个客户端的数据,避免共享elemType*client_data=(elemType*)malloc(sizeof(elemType));if(client_data==NULL){printf("malloc client_data error\n");continue;}memset(client_data,0,sizeof(elemType));//内容清零socklen_t AddrLen=sizeof(client_data->clientaddr);client_data->clientsockid = accept(SocketID,(struct sockaddr *)&(client_data->clientaddr),&AddrLen) ; if(client_data->clientsockid==-1) { /*连接失败*/printf("accept error\n") ;free(client_data) ;continue ;}//检查客户端数量pthread_mutex_lock(&list_mutex);//上锁,防止在读取节点的过程中有新节点插入if(client_list->count>=5){printf("Max client limit reached,rejecting connection\n");close(client_data->clientsockid);free(client_data) ;continue ; }/*5.创建接收线程*/if(pthread_create(&(client_data->recvpthreadid),NULL,pthreadRecv_function,client_data)!=0){printf("pthread_create pthreadRecv_function error\n");close(client_data->clientsockid) ;free(client_data) ;pthread_mutex_unlock(&list_mutex) ;continue ; }if(pthread_create(&(client_data->sendpthreadid),NULL,pthreadSend_function,client_data)!=0){printf("pthread_create pthreadSend_function error\n");close(client_data->clientsockid) ;free(client_data) ;pthread_mutex_unlock(&list_mutex) ;continue ; }/*6.插入节点*/if (LinkList_inserttail(client_list,client_data)==FALSE){printf("LinkList_inserttail error\n") ;close(client_data->clientsockid) ;free(client_data) ;}else{char IP[16]={0};//用于存放IPinet_ntop(AF_INET,&(client_data->clientaddr.sin_addr),IP,sizeof(IP)) ;printf("New client connected: IP=%s, Port=%d, SocketID=%d, Total clients: %d\n",IP, ntohs(client_data->clientaddr.sin_port), client_data->clientsockid, client_list->count);}pthread_mutex_unlock(&list_mutex) ;}close(SocketID) ;LinkList_destroy(client_list) ;return 0 ;
} void*pthreadRecv_function(void*arg)
{elemType *data=(elemType*)arg;//区部变量保存实参ssize_t r_len ;char rbuf[1024] ;//打印客户端信息char IP[16]={0};//用于存放IPinet_ntop(AF_INET,&(data->clientaddr.sin_addr),IP,sizeof(IP)) ;printf("Receive thread started for client: clientsockid=%d, IP=%s, Port=%d\n", data->clientsockid, IP, ntohs(data->clientaddr.sin_port)) ;while(1){/*接收函数*/r_len= recv(data->clientsockid,rbuf,sizeof(rbuf)-1,0);if(r_len<=0){if(r_len==0){printf("Client %d disconnected\n", data->clientsockid) ;}else{printf("recv error from client %d\n", data->clientsockid);}break;}//处理数据rbuf[r_len]='\0' ;if(r_len>0&&rbuf[r_len-1]=='\n'){rbuf[r_len]='\0' ;}printf("Server:recv from %d clientsockid:%s\n",data->clientsockid,rbuf);if(strcmp(rbuf,"Quit")==0){break;}}//避免接收线程结束而将client_data清除了,但发送线程依然使用client_data数据,所以在free数据前先结束发送线程pthread_cancel(data->sendpthreadid) ;pthread_join(data->sendpthreadid, NULL) ;// 客户端断开连接,清理资源handle_client_disconnection(data->clientsockid);free(data) ;pthread_exit(NULL) ;
}void*pthreadSend_function(void*arg)
{elemType *data=(elemType*)arg;//区部变量保存实参while(1){sleep(10);char heartbeat[] = "heartbeat\n" ;if(send(data->clientsockid,heartbeat,strlen(heartbeat),0)<=0){break ;}}pthread_exit(NULL) ;
}void handle_client_disconnection(int clientsockid)
{//对链表操作进行上锁pthread_mutex_lock(&list_mutex) ;//在链表中删除断开连接的客户端信息节点if(LinkList_delete_bysockid(client_list,clientsockid)==TRUE){printf("Client %d removed from list\n", clientsockid);}//关闭套接字close(clientsockid) ;pthread_mutex_unlock(&list_mutex) ;
}
客户端文件 client.c
/*******************【客户端】******************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/stat.h>//全局变量
int SocketID ;//用于接收套接字ID
pthread_t recvpthreadid,sendpthreadid;//客户端ID//函数声明
void*pthreadRecv_function(void*arg);
void*pthreadSend_function(void*arg);int main(int argc,char *argv[])
{if(argc!=3){printf("argc error\n") ;printf("please input ./client IP地址 端口号\n");return -1 ;}/*1.创建套接字、IPv4,TCP*/ SocketID = socket(AF_INET,SOCK_STREAM,0) ;if(SocketID==-1){printf("socket error\n") ;return -2 ;}/*2.连接服务器*/struct sockaddr_in ServeAddr ;memset(&ServeAddr,0,sizeof(ServeAddr)) ;//清空ServeAddrServeAddr.sin_family=AF_INET ;//IP类型ServeAddr.sin_port=htons(atoi(argv[2])) ;//将 argv[2]从字符串变为数字再变为大端模式if(inet_pton(AF_INET,argv[1],&ServeAddr.sin_addr)!=1)//IP地址转换32bit{printf("inet_pton error\n") ;close(SocketID) ;return -3 ;}if(connect(SocketID,(struct sockaddr *)&ServeAddr,sizeof(ServeAddr))==-1){printf("connect error\n") ;close(SocketID) ;return -4 ;}printf("Connect to server %s:%s successfully!\n", argv[1], argv[2]);/*2.创建读写线程*/if(pthread_create(&recvpthreadid,NULL,pthreadRecv_function,NULL)!=0){printf("pthread_create pthreadRecv_function error\n");close(SocketID) ;return -5 ;}if(pthread_create(&sendpthreadid,NULL,pthreadSend_function,NULL)!=0){printf("pthread_create pthreadSend_function error\n");close(SocketID) ;return -6 ;}
pthread_join(recvpthreadid,NULL);
pthread_join(sendpthreadid,NULL);printf("Closing client...\n");close(SocketID) ;return 0 ;
}void*pthreadRecv_function(void*arg)
{char RecvBuff[1024] ;ssize_t ret ;//实际接收的字节数while(1){ret=recv(SocketID,RecvBuff,sizeof(RecvBuff)-1,0) ; if(ret==0){printf("Serve has close\n") ;goto close_function ;}else if(ret==-1){printf("pthreadRecv_function error\n") ;goto close_function ;}RecvBuff[ret]='\0' ;//除去换行符if(ret>0&&RecvBuff[ret-1]=='\n'){RecvBuff[ret-1]='\0' ;}printf("Client:recv from %d clientsockid:%s\n",SocketID,RecvBuff);if(strcmp(RecvBuff,"Quit")==0){goto close_function ;}}
close_function:pthread_cancel(sendpthreadid) ;return NULL ;
}
void*pthreadSend_function(void*arg)
{char SendBuff[1024] ;ssize_t ret ;//实际发送的字节数while(1){if(fgets(SendBuff,sizeof(SendBuff),stdin)==NULL){printf("fgets error\n") ;goto close_function ;}SendBuff[strcspn(SendBuff,"\n")]='\0' ;//去除'\n'if(strlen(SendBuff)==0) //检查是否为空{continue ;}ret=send(SocketID,SendBuff,strlen(SendBuff),0);//发数据if(ret==-1){goto close_function ;}printf("Client send:%s\n",SendBuff) ;if(strcmp(SendBuff,"Quit")==0){goto close_function ;}}
close_function:pthread_cancel(recvpthreadid) ;return NULL ;
}
运行结果: