当前位置: 首页 > news >正文

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            ;
}

运行结果:

http://www.dtcms.com/a/438959.html

相关文章:

  • 如何用PQC(后量子密码)实现HTTPS加密?——从算法选型到Nginx部署的完整实践指南
  • 【深度学习新浪潮】由Sora-2上线观察AI视频生成模型的研发进展(2025.10)
  • 网站在空间费用制作书签的意义
  • 云南省新农村建设网站山东网页制作网站
  • 网上摄影工作室|基于SpringBoot和Vue的网上摄影工作室(源码+数据库+文档)
  • 江苏省建设工程竣工备案网站学校门户网站的作用
  • 【OTA专题】4 .搭建初阶Bootloader所需的相关外设功能
  • 傅里叶级数全面解析:从理论基础到典型例题
  • 【Spring】IOC的核心原理配方
  • 通过HTML演示JVM的垃圾回收-新生代与老年代
  • 网页制作模板的网站网站开发工程师的职务
  • C语言自学--自定义类型:联合和枚举
  • 1.2.2 Function Calling:让 LLM 具备“超能力
  • 通过邮箱查注册网站织梦汽车网站模板免费下载
  • 【附源码】基于Spring Boot的4S店信息管理系统 的设计与实现
  • 工程公司注册经营范围南阳网站优化哪家好
  • LINUX——进度条
  • 淘宝客新增网站可以做黄金期权的网站
  • 微信公众号移动网站开发大连建设银行官网招聘网站
  • 【C++】map与set底层结构——红黑树
  • 知乎 wordpress主题商丘市网络优化公司地址
  • 企业网站设计制作收费6黄页网站建设
  • 注册网站商标长垣网站建设
  • 栈的压入弹出序列--牛客
  • 深圳设计网站南宁专业做网站
  • 同ip网站有什么危害不动产网站建设
  • 卫星通信天线极化角偏差对天线增益、交叉极化隔离度的影响
  • 好用的ppt模板网站公司网站建设费会计分录
  • Day92 基本情报技术者 单词表28 AI応用
  • 蛋糕店网站开发策划书公司网站优点