基于多线程TCP服务器的聊天小程序
记录网络程序设计课程设计,小组作业,本人完成的是1、2、4功能,有很多不足,谢谢!
实现功能
1、群聊
2、私聊
3、发送文件
4、成员的管理与维护
实现思路
本程序基于客户端/服务器模型进行实现,客户端加入聊天小程序,服务器进行成员的动态维护;客户端发送消息给服务器,服务器负责向群内成员进行转发。
数据结构设计:服务器使用结构体数组储存客户的用户名,状态和套接字描述符
多线程设计:服务器使用多线程接收新的客户端连接,并执行相应的功能,同时服务器启动一个线程进行成员的管理与维护。客户端每个功能对应一个接收信息的线程。
传输设计:客户端连接服务器,服务器储存用户名、状态和对应套接字描述符。服务器通过TCP套接字描述符可以向任何人转发消息,实现群聊。客户端通过筛选特定信息实现私聊。文件传输同理。
成员管理设计:服务器储存了用户名、状态和对应套接字描述符,通过遍历数组查询状态得到在线人数,记录在数组中的位置,可以使用shutdown函数关闭对应套接字实现下线。
具体实现
服务器
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>
#define BUF_SIZE 20
struct{//储存客户端信息,最多连接20
char name[20];
int state;
int client;
}identity[20];
//线程函数,实现服务器消息转发
void *ThreadFunc(void *arg)
{
int number;//记录本线程接待的客户的位置
int ret=-1;
int iClient=*(int *)arg;
char name[20];
ret=recv(iClient,name,20,0);//接收客户端姓名
if(ret<=0)
{
printf("recv error!----\r\n");
close(iClient);
pthread_exit("thread exit");
}else{
for(int i=0;i<20;i++)//遍历,状态为0则储存消息
{
if(identity[i].state==0)
{
strcpy(identity[i].name,name);
identity[i].state=1;
number=i;
identity[i].client=iClient;
printf("Welcome %s to join the server\r\n",identity[i].name);
break;
}
if(i==19)//人数满了
{
printf("The number of people is full\r\n");
close(iClient);
pthread_exit("thread exit");
}
}
}
//接收客户端选择的功能,使用whlie switch
char a[1];
char b;
char buf[BUF_SIZE]={0};//接收客户端消息使用
char bufname[50]={0};//转发消息使用
int flag=-1;
while(1){
ret=recv(iClient,a,1,0);
if(ret<=0)//出错则需要初始化数组信息,并结束
{
printf("User %s goes offline!----\r\n",identity[number].name);
memset(identity[number].name,0,20);
identity[number].state=0;
identity[number].client=-1;
close(iClient);
pthread_exit("thread exit");
}
b=a[0];
switch(b){
case '1'://群聊全部转发
printf("%s Joins Group Chat\r\n",identity[number].name);
while(1)
{
//recv
memset(buf,0,20);
memset(bufname,0,50);
ret=recv(iClient,buf,BUF_SIZE,0);
if(ret<=0)
{
printf("User %s goes offline!----\r\n",identity[number].name);
memset(identity[number].name,0,20);
identity[number].state=0;
identity[number].client=-1;
close(iClient);
pthread_exit("thread exit");
}
if(buf[0]=='$'){//客户端退出功能,服务器对应
printf("%s quit group chat\r\n",identity[number].name);
break;}
printf("Message from %s(%d):%s\r\n",identity[number].name,number,buf);
//遍历状态为1,构造消息格式全部发送消息
for(int i=0;i<20;i++)
{
if(identity[i].state==1){
sprintf(bufname,"%s%c%s",identity[number].name,':',buf);
send(identity[i].client,bufname,50,0);}
}
}
break;
case '2'://私聊单独转发
printf("%s Joins Private Chat\r\n",identity[number].name);
memset(name,0,20);
ret=recv(iClient,name,20,0);//接收私聊的对象
if(ret<=0)
{
printf("User %s goes offline!----\r\n",identity[number].name);
memset(identity[number].name,0,20);
identity[number].state=0;
identity[number].client=-1;
close(iClient);
pthread_exit("thread exit");
}
printf("%s\r\n",name);
for(int i=0;i<20;i++)//看私聊的对象是否在线
{
if(strncmp(name,identity[i].name,15)==0&&identity[i].state==1)
{send(iClient,"1",1,0);
flag=i;
break;}
if(i==19)
send(iClient,"0",1,0);
}
if(flag!=-1){//在线则进行消息转发
while(1)
{
//recv
memset(buf,0,20);
memset(bufname,0,50);
ret=recv(iClient,buf,BUF_SIZE,0);
if(ret<=0)
{
printf("User %s goes offline!----\r\n",identity[number].name);
memset(identity[number].name,0,20);
identity[number].state=0;
identity[number].client=-1;
close(iClient);
pthread_exit("thread exit");
}
if(buf[0]=='$'){
printf("%s quit private chat\r\n",identity[number].name);
break;}
printf("Message from %s(%d):%s\r\n",identity[number].name,number,buf);
//send
sprintf(bufname,"%s%s%s",identity[number].name,"(Private):",buf);
send(identity[flag].client,bufname,50,0);
}
}
break;
case '3'://转发文件
printf("%s Joins send files\r\n",identity[number].name);
int j=0;
char buf1[BUF_SIZE]={0};
while(1){
ret=recv(iClient,buf,BUF_SIZE,0);//USER NAME
if(buf[0]=='$'){
printf("%s quit send files\r\n",identity[number].name);
break;}
if(ret<=0)
{
printf("User %s goes offline!----\r\n",identity[number].name);
memset(identity[number].name,0,20);
identity[number].state=0;
identity[number].client=-1;
close(iClient);
pthread_exit("thread exit");
}
if(-1==ret){
printf("user can not recv\r\n");
}
for(int i=0;i<20;i++){
if(identity[i].state!=1||strncmp(identity[i].name,buf,15!=0)){
buf1[0]='1';
}
else{
buf1[0]='0';
j=i;
break;}
}
send(iClient,buf1,BUF_SIZE,0);
if(buf1[0]=='0'){
memset(buf,0,BUF_SIZE);
send(identity[j].client,"0",BUF_SIZE,0);
recv(iClient,buf,BUF_SIZE,0);//文件名
if(buf[0]=='$'){
printf("%s quit send files\r\n",identity[number].name);
break;}
send(identity[j].client,buf,BUF_SIZE,0);
recv(iClient,buf,BUF_SIZE,0);//文件大小
int size = atoi(buf);
send(identity[j].client,buf,BUF_SIZE,0);
while(size>0){
ret=recv(iClient,buf,BUF_SIZE,0);
send(identity[j].client,buf,ret,0);
size-=BUF_SIZE;
}
printf("send file to %s success\r\n",identity[j].name);
}
else{
printf("user can not find!\r\n");
send(identity[j].client,"1",BUF_SIZE,0);
exit(1);
}
}
break;
case '4'://未实现的功能,转发文件夹,思路:可以将文件夹中文件逐个转发
printf("send \r\n");
break;
default:printf("input error");
break;
}
}
}
//线程管理成员
void *ThreadFunc1(void *arg){
int a;
int b;
while(1){
scanf("%d",&a);
if(a==1){//遍历在线人数
printf("Number of people online:\r\n");
for(int i=0;i<20;i++)
{
if(identity[i].state==1)
printf("%s(%d)--",identity[i].name,i);
}
}
if(a==2){//踢人
printf("Please enter the user number you want to offline:\r\n");
scanf("%d",&b);
memset(identity[b].name,0,20);
identity[b].state=0;
shutdown(identity[b].client,SHUT_RDWR);//先踢掉,再修改套接字
identity[b].client=-1;
}
}
}
int main()
{
for(int i=0;i<20;i++){//数组初始化
identity[i].state=0;
identity[i].client=-1;}
//1 socket
int iServer=socket(AF_INET,SOCK_STREAM,0);
if(-1==iServer)
{
printf("create socket error!-------\r\n");
return -1;
}
printf("1:create socket ok!----------iServer=%d\r\n",iServer);
int opt=SO_REUSEADDR;
setsockopt(iServer,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
//2 bind
struct sockaddr_in stServer;
memset(&stServer,0,sizeof(struct sockaddr_in));
stServer.sin_family=AF_INET;
stServer.sin_port=htons(8888);
stServer.sin_addr.s_addr=inet_addr("127.0.0.1");
int ret=bind(iServer,(struct sockaddr *)&stServer,sizeof(struct sockaddr));
if(-1==ret)
{
printf("bind error!----------\r\n");
return -1;
}
printf("2:bind ok!---------------\r\n");
//3 listen
ret=listen(iServer,5);
if(-1==ret)
{
printf("listen error!---------\r\n");
return -1;
}
printf("3:listen ok!--------------\r\n");
//4 accept
struct sockaddr_in stClient;
socklen_t len=sizeof(struct sockaddr_in);
char buf[BUF_SIZE];
pthread_t tid=-1;
if(0!=pthread_create(&tid,NULL,ThreadFunc1,NULL))//管理线程,需要持续开启
{
printf("create thread error!----------\r\n");
}
while(1)
{
memset(buf,0,BUF_SIZE);
memset(&stClient,0,sizeof(struct sockaddr_in));
int iClient=accept(iServer,(struct sockaddr *)&stClient,&len);
if(-1==iClient)
{
continue;
}
printf("4:accept ok!--------------\r\n");
//pthread
pthread_t tID=-1;
if(0!=pthread_create(&tID,NULL,ThreadFunc,&iClient))
{
printf("create thread error!----------\r\n");
close(iClient);
continue;
}
}
return 0;
}
客户端
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 20
struct STR{//私聊传参使用
int client;
char pname[20];
}str;
struct{//登录使用(本地登录)
char admin[50];
char passwd[50];
int flag;
}admin[100];
void *ThreadFunc(void *arg)//多线程接收服务器数据(群聊天)
{
int iClient=*(int *)arg;
char bufname[50]={0};
int ret;
while(1)
{
ret=recv(iClient,bufname,50,0);
if(ret<=0)
{
printf("recv error!-------\r\n");
exit(1);
}
printf("-------%s\r\n",bufname);
memset(bufname,0,50);
}
}
void *ThreadFunc2(void *arg)//多线程接收服务器数据(私聊天)
{
struct STR info;
info.client=((struct STR *)arg)->client;
strcpy(info.pname, ((struct STR *)arg)->pname);
char bufname[50]={0};
char bianbie[30];
int ret;
while(1)
{
ret=recv(info.client,bufname,50,0);
if(-1==ret)
{
printf("recv error!-------\r\n");
}
sprintf(bianbie,"%s%s",info.pname,"(Private):");
if(strncmp(bianbie,bufname,10)==0)//如果是 姓名(Private)格式则打印
printf("-------%s\r\n",bufname);
memset(bufname,0,50);
}
}
void *ThreadFunc1(void *arg)//接收文件
{
int iClient=*(int *)arg;
char buf[BUF_SIZE]={0};
int ret;
ret=recv(iClient,buf,BUF_SIZE,0);
if(ret<=0)
{
printf("recv error!-------\r\n");
exit(1);
}
if(buf[0]=='0'){
while(1)
{
ret=recv(iClient,buf,BUF_SIZE,0);
if(-1==ret){
printf("file name recv error\r\n");
}
int dest=open(buf,O_CREAT|O_WRONLY|O_TRUNC,0666);
if(-1==dest)
{
printf("open dest file error!\r\n");
return -1;
}
memset(buf,0,BUF_SIZE);
ret=recv(iClient,buf,BUF_SIZE,0);
if(-1==ret){
printf("file size recv error\r\n");
}
int size = atoi(buf);
while(size>0){
ret=recv(iClient,buf,BUF_SIZE,0);
write(dest,buf,ret);
size-=BUF_SIZE;
}
printf("transmission success\r\n");
}
}
}
int main()
{
admin[0].flag=1;
strcpy(admin[0].admin,"admin");
strcpy(admin[0].passwd,"passwd");
char user[50];
char password[50];
printf("Welcome to authentication system!\r\n");
printf("please input your admin:\r\n");
scanf("%s",user);
printf("please input your password:\r\n");
scanf("%s",password);
if(strncmp(user,admin[0].admin,10)!=0||strncmp(password,admin[0].passwd,10)!=0){
printf("your admin or password error\r\n");
exit(1);
}
else{
printf("welcome!\r\n");
}
char a[1];//用来给服务器发送选择的结果
char b;//记录选择的结果
pthread_t tID1,tID2,tID3,tID4;
char name[20];
struct stat sta;
printf("Please enter your user name:\r\n");
scanf("%s",name);
//1 socket
int iClient=socket(AF_INET,SOCK_STREAM,0);
if(-1==iClient)
{
printf("create socket error!-----\r\n");
return -1;
}
printf("1:create socket ok!---------------\r\n");
//2 connect
struct sockaddr_in stServer;
memset(&stServer,0,sizeof(struct sockaddr_in));
stServer.sin_family=AF_INET;
stServer.sin_port=htons(8888);
stServer.sin_addr.s_addr=inet_addr("127.0.0.1");
int ret=connect(iClient,(struct sockaddr *)&stServer,sizeof(struct sockaddr_in));
if(-1==ret)
{
printf("connect error!---------\r\n");
return -1;
}
printf("2:connect server ok!----------\r\n");
//3 io-->send/recv
ret=send(iClient,name,20,0);
char buf[BUF_SIZE]={0};
if(0!=pthread_create(&tID3,NULL,ThreadFunc1,&iClient))
{
printf("create thread error!----------\r\n");
close(iClient);
}
while(1){
printf("Please enter your choice: 1 Group chat, 2 Private chat, 3 Send files,4 Send Folder \r\n");
setbuf(stdin, NULL);
b=getchar();
getchar();
a[0]=b;
ret=send(iClient,a,1,0);//发送选择的结果,send要字符数组
switch(b){
case '1':
printf("Welcome to join the chat group\r\n");
if(0!=pthread_create(&tID1,NULL,ThreadFunc,&iClient))//创建线程接收数据
{
printf("create thread error!----------\r\n");
close(iClient);
}
while(1){//循环发送数据
setbuf(stdin, NULL);//fgets函数前清理缓冲
fgets(buf,BUF_SIZE,stdin);
ret=send(iClient,buf,BUF_SIZE,0);
if(-1==ret)
{
printf("send error!-----\r\n");
}
if(buf[0]=='$'){
ret = pthread_cancel(tID1);
if (ret != 0) {
printf("Failed to terminate thread\n");
return 0;
}
pthread_join(tID1,NULL);
break;}
memset(buf,0,BUF_SIZE);
}
break;
case '2':
printf("Welcome to join the Private chat\r\n");
printf("please choice your peer user name:\r\n");
setbuf(stdin, NULL);
scanf("%s",name);
ret=send(iClient,name,20,0);
memset(buf,0,20);
ret=recv(iClient,buf,1,0);
if(-1==ret)
{
printf("recv error!-------\r\n");
}
if(buf[0]=='1'){
printf("%s Users online,please enter information:\r\n",name);
str.client=iClient;
strcpy(str.pname, name);
if(0!=pthread_create(&tID2,NULL,ThreadFunc2,(void *)&str))//创建线程接收数据
{
printf("create thread error!----------\r\n");
close(iClient);
}
while(1){//循环发送数据
setbuf(stdin, NULL);//fgets函数前清理缓冲
fgets(buf,BUF_SIZE,stdin);
ret=send(iClient,buf,BUF_SIZE,0);
if(-1==ret)
{
printf("send error!-----\r\n");
}
if(buf[0]=='$'){
ret = pthread_cancel(tID2);
if (ret != 0) {
printf("Failed to terminate thread\n");
return 0;
}
pthread_join(tID2,NULL);
break;}
memset(buf,0,BUF_SIZE);
}
}
if(buf[0]=='0')
printf("User is not online!");
break;
case '3':
printf("please input you want to send name of user\r\n");
scanf("%s",buf);
ret=send(iClient,buf,BUF_SIZE,0);
if(-1==ret){
printf("user name send error\r\n");
}
if(buf[0]=='$'){
ret = pthread_cancel(tID3);
if (ret != 0) {
printf("Failed to terminate thread\n");
return 0;
}
pthread_join(tID3,NULL);
break;}
memset(buf,0,BUF_SIZE);
recv(iClient,buf,BUF_SIZE,0);
if(buf[0]=='1'){
printf("user can not found!\r\n");
exit(1);
}
else{ printf("user exit!\r\n");}
while(1){
printf("input you want to send the name of file\r\n");
scanf("%s",buf);
ret=send(iClient,buf,BUF_SIZE,0);
if(-1==ret){
printf("file name send error\r\n");
}
if(buf[0]=='$'){
ret = pthread_cancel(tID3);
if (ret != 0) {
printf("Failed to terminate thread\n");
return 0;
}
pthread_join(tID3,NULL);
break;}
int src=open(buf,O_RDONLY,0666);
if(-1==src)
{
printf("open src file error!\r\n");
return -1;
}
printf("open src file ok\r\n",src);
stat(buf,&sta);
int size = sta.st_size;
snprintf(buf,BUF_SIZE,"%ld",sta.st_size);
ret=send(iClient,buf,BUF_SIZE,0);
if(-1==ret){
printf("file size send error\r\n");
}
while(size>0){
ret=read(src,buf,BUF_SIZE);
send(iClient,buf,ret,0);
size-=BUF_SIZE;
}
printf("transmission success!\r\n");
}
break;
case '4':break;
default:printf("Please enter the correct number:\r\n");
break;
}
}
close(iClient);
return 0;
}
我对一些线程函数了解还不够,bug有点多,而且只是记录一次作业,应该到此为止了
文件资源我已上传,包含源码、可执行文件和说明
说明如下(需要的可以去下载):
编译1.c 2.c 生成两个可执行文件server client,或者直接执行1和2(可执行文件 Linux下)
gcc 1.c -o server -lpthread
gcc 2.c -o client -lpthread
./server 启动服务器
./client 启动客户端
客户端输入账号 admin 密码passwd,证明身份,然后输入用户名,选择功能1到3,发送文件夹功能未实现
功能1 群聊 客户端输入消息,其他进入群聊的客户端可以接收到消息
功能2 私聊 客户端输入消息,只有私聊的对象才能接收到消息(对方在群聊模式下也可见)
功能3 发送文件 bug有点多,只能在一开始,一方选择3,另一方不进行选择的情况下能正常传输。
客户端输入$退出当前模式
服务器输入1打印在线人数
输入2可以让指定客户端下线,下线输入的为数组位置,即括号里的数字(0),有点小bug
11.c 和22.c 只完成了私聊和群聊,是前期完成的,比较稳定无bug,可以在此基础上修改完成