整体思路
首先,通过构建两个结构体数组分别用于存储登录信息(struct server_fd)和存储注册信息(struct server_msg),以实现注册后账户信息被保存在服务器中,不会随着账号离线而注销,二者均有flag成员用于作为判断其自身是否被有数据进行存储的标志位。但由于该版本是通过创建新线程的操作进行处理后续连接的客户端信息,故若想将二者的地址传输给新的线程,仅通过pthread_creat()函数自带的第四个参数void*类型的形参是无法将二者的数据全部传输过去的,此时可以使用构建一个新的结构体(struct server_address)专门用于保存需要传递的数据所在地址,此时仅通过传递server_address的地址即可将所需数据全部传输仅进子线程中。不过此时还需要注意一点,就是由于是通过检测存储登录信息的结构体的flag成员判断其是否可以使用该结构体数组的某一下标所代表的结构体数组,此时需要将其使用的下标告知相关子线程,此时可以一样使用server_address结构体进行传输,但此时又有可能会发生新的一种情况,那便是新的子线程有可能还未获取到数据,此时数据源已经由于进入下一轮循环而变更,导致其线程接收数据有误,此时可以使用加上一个标志位作为“锁”,来进行略微限制主线程的执行速率,确保子线程接收到数据后再进行后续处理。
其次,在每个客户端连接服务器后,服务器可以创建一个新的子线程专门用于处理该客户端的请求。该子线程中需就为通过检测客户端所发信息进行不同处理即可。此时又会牵扯到服务器如何知晓客户端所发信息,解决方式就是可以构建结构体(struct msg)进行处理相关信息,在该结构体中只需要定义好命令位、ID位、信息位即可。通过使用recv()函数和send()函数进行收发处理,将收发信息均保存在该类型结构体中,通过对接收信息的命令位进行判断服务器和客户端做出不同的处理操作。
最后,就是一些细节处理,例如客户端部分也可以使用创建新线程进行处理接收操作,此时只需要设立好相关标志位以告知主线程需继续进行何种操作即可,此时可能会出现的问题同上,就是可能会出现子线程数据还未更改完毕,主线程可能会由于未收到相关标志位改变而进行新的一轮等待处理,解决方案一般有两种,一为通过构建一个标志位来更改recv()是主线程中的执行还是子线程中的执行,即为主线程和子线程设立执行顺序线,例如在登录完成之前都使用主线程进行接收数据,当登录成功后,将标志位清零,让其使用子线程进行recv()接收数据。二为同上述类似,不过也是通过构建标志位来实现一种类似“锁”的机制控制主线程和子线程的顺序即可。
整体思路中未包含创建服务器和连接服务器客户端操作,大体思路可参考如下图示进行搭建。
示例代码服务器端
//server
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define OLD struct sockaddr
#define NEW struct sockaddr_in
#define IP "192.168.3.89"
#define DUAN 10000
typedef struct server_fd{
int flag;//存储登录信息结构体是否被使用的标志位
int fd;//客户端套接字
char name[20];//当前登录人员姓名
}XX;
typedef struct server_msg{
int flag;//存储注册信息结构体是否被使用的标志位
char name[10];//注册人员姓名
char passwd[20];//注册人员密码
}YY;
typedef struct server_address{
XX (*fd)[40];//用于保存登录信息结构体数组
YY (*msg)[40];//用于保存注册信息结构体数组
int num;//用于告知子线程后续使用的结构体数组下标
int flag_pth;//新建子线程前主线程与上一个子线程之间速度限制,确保子线程收到num数据
}ZZ;
typedef struct msg{
int cmd;//命令
char name[20];//待发送的姓名
char msg[64];//待发送的消息
}MSG;
/*cmd数值具体含义:
* 1:注册
* 2:登录
* 3:注册成功
* 4:注册失败
* 5:登录成功
* 6:登录失败
* 7:主界面退出
* 8:公聊
* 9:私聊
* 10:查看当前在线人员
* 11:服务器回复请求发送消息
* 12:服务器开始发送当前在线人员信息
* 13:服务器发送结束当前在线人员信息
*/
//void cle(void *p);//线程终止清理函数
void *pthread_func(void *p);//线程处理函数
void msg_clean(MSG *infor);//接收信息结构体清理函数
int log_func(XX (*data)[40],YY (*msg)[40],MSG infor,int num,int *flag);//登录函数
int register_func(XX data,YY (*msg)[40],MSG infor,int num);//注册函数
void over(XX (*data)[40],int num);//退出函数
int all(MSG *infor,XX (*data)[40],int num);//公聊函数
int personal(MSG *infor,XX (*data)[40],int num);//私聊函数
int view(MSG *infor,XX (*data)[40],int num);//查看在线人员函数
int main()
{
int x=1,sockid,num1=0;
NEW my_fu,my_ke;
socklen_t kh_size=sizeof(struct sockaddr_in);
XX (*data)[40]=calloc(40,sizeof(XX));
YY (*msg)[40]=calloc(40,sizeof(YY));
ZZ *addr=calloc(1,sizeof(ZZ));
addr->fd=data;
addr->msg=msg;
addr->flag_pth=1;
pid_t pid_fo;
pthread_t pthid[40];
char *client_p=NULL;
my_fu.sin_family=AF_INET;
my_fu.sin_addr.s_addr=inet_addr(IP);
my_fu.sin_port=htons(DUAN);
sockid=socket(AF_INET,SOCK_STREAM,0);
if(sockid==-1)
{
perror("error in socket!\n");
return -1;
}
setsockopt(sockid,SOL_SOCKET,SO_REUSEADDR,&x,sizeof(x));
if(bind(sockid,(OLD *)&my_fu,sizeof(my_fu))==0)
{
printf("bind is over!\n");
}
else
{
perror("error in bind!\n");
return -1;
}
listen(sockid,40);
while(1)
{
data[num1]->fd=accept(sockid,(OLD*)&my_ke,&kh_size);
if(data[num1]->fd!=-1)
{
pthread_create(&pthid[num1],NULL,pthread_func,addr);
}
else
{
printf("error in pthread_creat!\n");
}
while(addr->flag_pth);
addr->flag_pth=1;//可以理解为一把锁
for(;num1<40;num1++)
{
if(data[num1]->flag==0)
{
break;
}
}
printf("data_flag:%d\n",num1);
addr->num=num1;
}
return 0;
}
void *pthread_func(void *p)
{
int success_flag=0;//登录是否成功标志位,0为未成功
MSG infor={0};//接收信息所用结构体
ssize_t recv_size;//是否成功接收标志
ZZ *temp=p;
XX(*data)[40]=temp->fd;
YY(*msg)[40]=temp->msg;//将两个结构体地址传进来
int num=temp->num;//保存当前所用数组编号,后续client_fd基于此寻找
data[num]->flag=1;
temp->flag_pth=0;
//pthread_cleanup_push(cle,temp);
while(1)
{
msg_clean(&infor);
recv_size=recv(data[num]->fd,&infor,sizeof(MSG),0);
if(recv_size>0)
{
printf("CMD:%d\n",infor.cmd);
if(infor.cmd==2)//登录
{
log_func(data,msg,infor,num,&success_flag);
}
else if(infor.cmd==1)//注册
{
register_func(*data[num],msg,infor,num);
}
else if(infor.cmd==8)//公聊
{
infor.cmd=14;
send(data[num]->fd,&infor,sizeof(MSG),0);
}
else if(infor.cmd==9)//私聊
{
infor.cmd=16;
send(data[num]->fd,&infor,sizeof(MSG),0);
}
else if(infor.cmd==10)//查看在线人员
{
if(success_flag==1)
{
view(&infor,data,num);
}
}
else if(infor.cmd==15)//公聊信息
{
if(success_flag==1)
{
all(&infor,data,num);
}
}
else if(infor.cmd==17)//私聊信息
{
if(success_flag==1)
{
personal(&infor,data,num);
}
}
}
else if(recv_size==0)
{
over(data,num);
pthread_exit(NULL);
break;
}
}
//pthread_cleanup_pop(1);
}
// void cle(void *p)
// {
// ZZ *temp=p;
// XX (*data)[40]=temp->fd;
// int num=temp->num;
// over(data,num);
// }
void msg_clean(MSG *infor)
{
infor->cmd=0;
bzero(infor->name,sizeof(infor->name));
bzero(infor->msg,sizeof(infor->msg));
}
int log_func(XX (*data)[40],YY (*msg)[40],MSG infor,int num,int *flag)
{
MSG temp={0};
for(int i=0;i<40;i++)
{
if(strcmp(msg[i]->name,infor.name)==0&&strcmp(data[i]->name,infor.name)!=0)//登录时比较是否有账户相匹配
{
strcpy(data[num]->name,infor.name);
temp.cmd=5;
*flag=1;
send(data[num]->fd,&temp,sizeof(MSG),0);
return 0;
}
}
temp.cmd=6;
send(data[num]->fd,&temp,sizeof(MSG),0);
return 0;
}
int register_func(XX data,YY (*msg)[40],MSG infor,int num)
{
MSG temp={0};
for(int i=0;i<40;i++)
{
if(strcmp(msg[i]->name,infor.name)==0)//注册时比较是否有账户相匹配
{
temp.cmd=4;
send(data.fd,&temp,sizeof(MSG),0);
return 0;
}
}
int m=0;
for(;m<40;m++)
{
if(msg[m]->flag==0)
break;
}
strcpy(msg[m]->name,infor.name);
strcpy(msg[m]->passwd,infor.msg);
msg[m]->flag=1;
temp.cmd=3;
send(data.fd,&temp,sizeof(MSG),0);
return 0;
}
void over(XX (*data)[40],int num)
{
MSG temp={0};
sprintf(temp.msg,"%s is over",data[num]->name);
temp.cmd=11;
for(int i=0;i<40;i++)
{
if((data[i]->flag==1)&&(i!=num))
{
send(data[i]->fd,&temp,sizeof(MSG),0);
}
}
printf("xiaxiann\n");
data[num]->flag=0;
bzero(data[num]->name,sizeof(data[num]->name));
}
int all(MSG *infor,XX (*data)[40],int num)
{
infor->cmd=11;
for(int i=0;i<40;i++)
{
if((data[i]->flag==1)&&(i!=num))
{
send(data[i]->fd,infor,sizeof(MSG),0);
}
}
}
int personal(MSG *infor,XX (*data)[40],int num)
{
infor->cmd=11;
for(int i=0;i<40;i++)
{
if(strcmp(data[i]->name,infor->name)==0)
{
send(data[i]->fd,infor,sizeof(MSG),0);
return 0;
}
}
bzero(infor->msg,sizeof(infor->msg));
strcpy(infor->msg,"To Forgive!");//查无此人
send(data[num]->fd,infor,sizeof(MSG),0);
return 0;
}
int view(MSG *infor,XX (*data)[40],int num)
{
msg_clean(infor);
int flag=0;
for(int i=0;i<40;i++)
{
if(data[i]->flag==1)
{
infor->cmd=12;
bzero(infor->msg,sizeof(infor->msg));
strcpy(infor->msg,data[i]->name);
printf("cmd:%d mag:%s\n",infor->cmd,infor->msg);
send(data[num]->fd,infor,sizeof(MSG),0);
flag=1;
}
}
if(flag==0)
{
infor->cmd=13;
printf("cmd:%d \n",infor->cmd);
send(data[num]->fd,infor,sizeof(MSG),0);
}
}
客户端代码示例
//client
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define OLD struct sockaddr
#define NEW struct sockaddr_in
#define IP "192.168.3.89"
#define DUAN 10000
typedef struct msg{
int cmd;
char name[20];
char msg[64];
}MSG;
typedef struct flag{
int sockid;//服务器的套接字
int log_flag;//登录是否成功标志位
int registe_flag;//注册是否成功标志位
int over1;//可以向服务器发送公聊消息
int over2;//可以向服务器发送私聊消息
int suo;//以此作为锁来限制主进程和子进程执行顺序
pthread_t pthid;
MSG *info;//仅用作传递mag地址
}FLAG;
/*cmd数值具体含义:
* 1:注册
* 2:登录
* 3:注册成功
* 4:注册失败
* 5:登录成功
* 6:登录失败
* 7:主界面退出
* 8:公聊
* 9:私聊
* 10:查看当前在线人员
* 11:服务器回复请求发送消息
* 12:服务器开始发送当前在线人员信息
* 13:服务器发送结束当前在线人员信息
* 14:服务器告知客户端可以发送公聊信息
* 15:客户端向服务器发送公聊信息
* 16:服务器告知客户端可以发送私聊信息
* 17:客户端向服务器发送私聊信息
*/
void msg_clean(MSG *infor);
int log_func(MSG *infor,int sockid);
int register_func(MSG *infor,int sockid);
void *pthread_func(void *p);
int main()
{
MSG *infor=calloc(1,sizeof(MSG));
FLAG *temp=calloc(1,sizeof(FLAG));
temp->log_flag=0;
temp->suo=1;
temp->registe_flag=0;
temp->over1=0;
temp->over2=0;
temp->info=infor;
int choose;
NEW my_fu;
my_fu.sin_family=AF_INET;
my_fu.sin_addr.s_addr=inet_addr(IP);
my_fu.sin_port=htons(DUAN);
temp->sockid=socket(AF_INET,SOCK_STREAM,0);
if(temp->sockid==-1)
{
perror("error in socket!\n");
return -1;
}
connect(temp->sockid,(OLD *)&my_fu,sizeof(my_fu));
temp->pthid=pthread_create(&temp->pthid,NULL,pthread_func,temp);
while(1)
{
msg_clean(infor);
if(temp->log_flag==0)
{
printf("*************************\n");
printf("1:登录QQ\n");
printf("2:注册QQ\n");
printf("3:下线\n");
printf("*************************\n");
scanf("%d",&choose);
if(choose==1)
{
while(temp->log_flag==0)
{
log_func(infor,temp->sockid);
while(temp->suo);
temp->suo=1;
if(temp->log_flag==0)
{
char m;
printf("请问是否继续登录,否:'n'或'N'\n");
bzero(&m,sizeof(m));
getchar();
scanf("%c",&m);
if(m=='n'||m=='N')
break;
}
}
}
else if(choose==2)
{
while(temp->registe_flag==0)
{
//system("clear");
register_func(infor,temp->sockid);
while(temp->suo);
temp->suo=1;
if(temp->registe_flag==0)
{
char m;
printf("请问是否继续注册,否:'n'或'N'\n");
bzero(&m,sizeof(m));
getchar();
scanf("%c",&m);
if(m=='n'||m=='N')
break;
}
}
}
else if(choose==3)
{
break;
}
//system("clear");
}
else if(temp->log_flag==1)
{
printf("*************************\n");
printf("1:公聊\n");
printf("2:私聊\n");
printf("3:查看当前在线人员\n");
printf("4:退出\n");
printf("*************************\n");
scanf("%d",&choose);
if(choose==1)
{
infor->cmd=8;
send(temp->sockid,infor,sizeof(MSG),0);
while(temp->suo);
temp->suo=1;
if(temp->over1==1)
{
bzero(infor->msg,sizeof(infor->msg));
printf("请输入待发送的信息\n");
scanf("%s",infor->msg);
infor->cmd=15;
send(temp->sockid,infor,sizeof(MSG),0);
temp->over1=0;
}
}
else if(choose==2)
{
infor->cmd=9;
send(temp->sockid,infor,sizeof(MSG),0);
while(temp->suo);
temp->suo=1;
if(temp->over2==1)
{
bzero(infor->msg,sizeof(infor->msg));
bzero(infor->name,sizeof(infor->name));
printf("请输入待发送的对象ID\n");
scanf("%s",infor->name);
printf("请输入待发送的信息\n");
scanf("%s",infor->msg);
infor->cmd=17;
send(temp->sockid,infor,sizeof(MSG),0);
temp->over2=0;
}
}
else if(choose==3)
{
infor->cmd=10;
send(temp->sockid,infor,sizeof(MSG),0);
}
else if(choose==4)
{
break;
}
}
}
return 0;
}
void msg_clean(MSG *infor)
{
infor->cmd=0;
bzero(infor->name,sizeof(infor->name));
bzero(infor->msg,sizeof(infor->msg));
}
int log_func(MSG *infor,int sockid)
{
infor->cmd=2;
printf("请输入待登录的账户名\n");
scanf("%s",infor->name);
printf("请输入该账户的密码\n");
scanf("%s",infor->msg);
send(sockid,infor,sizeof(MSG),0);
}
int register_func(MSG *infor,int sockid)
{
infor->cmd=1;
printf("请输入待注册的账户名\n");
scanf("%s",infor->name);
printf("请输入该账户的密码\n");
scanf("%s",infor->msg);
send(sockid,infor,sizeof(MSG),0);
}
void *pthread_func(void *p)
{
FLAG *temp=p;
MSG infor={0};
ssize_t recv_size;//是否成功接收标志
while(1)
{
msg_clean(&infor);
recv_size=recv(temp->sockid,&infor,sizeof(MSG),0);
if(recv_size>0)
{
printf("%d\n",infor.cmd);
if(infor.cmd==3)
{
printf("注册成功\n");
temp->registe_flag=1;
}
else if(infor.cmd==4)
{
printf("注册失败\n");
temp->registe_flag=0;;
}
else if(infor.cmd==5)
{
printf("登录成功\n");
temp->log_flag=1;
}
else if(infor.cmd==6)
{
printf("登录失败\n");
temp->log_flag=0;
}
else if(infor.cmd==11)
{
printf("%s\n",infor.msg);
}
else if(infor.cmd==12)
{
printf("%s\n",infor.msg);
}
else if(infor.cmd==13)
{
printf("以上为当前在线的所有人员\n");
}
else if(infor.cmd==14)
{
temp->over1=1;
}
else if(infor.cmd==16)
{
temp->over2=1;
}
temp->suo=0;
}
}
}
整体如上,若有不足之处,还望各位大佬指出。