C语言实现简单多线程网络聊天室

  • 所遇到的问题

   1.线程资源的回收

     一、服务器所创建的线程是分离式的,线程运行完毕(客户端挂了,断开连接)自动回收

     二、客户端子线程不是分离式,需要手动回收,但是子线程进入了阻塞,无法自己跳出循环,如果ctrl+c结

          束程序,子线程无法回收,自己采用的解决方法是:退出时置子线程循环开关为-1,再让服务器发送一个信号

          过来打通recv函数的阻塞,使其无法进入下一次循环,然后子线程运行完毕结束,正常回收。不知道正

          确的解决方法应该是怎样的

   2.对线程子函数的传参

       注意传入和传出是的类型装换

最多连三个客户端,服务器端至少要连两个客户端才能有群聊效果

客户端用户:chen   对应密码:123456

               :xia                  :223456

               :fang                :323456

如果你看到了这篇博客,想要试一下效果注意编译时链接动态库gcc  xxx  -pthread

并且注意修改成自己的ip地址

会报两个警告!可以忽略

服务器端

#include  
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define Myport 8848                           //端口号                                       
#define Backlog 100                           //


int sockfd=-1;
int recvret=-1;
char recvbuf[50]={0};
int acceptfd=-1;
int i=0;
int j=0;
char username[12]={0};
int flag=0 ;
int jionret=-1;
 struct User
{
	int opclo;
	char name[20];
	char passwd[6];
	char name1[20];
	int fd;
	pthread_t th;
}user[3]={{-1,"chen","123456","chen123456",-1,-1},
          {-1,"xia","223456","xia223456",-1,-1},
          {-1,"fang","323456","fang323456",-1,-1}};

 ////////////////////////////////////////////////////////////////////////////////////
void sigfunc(int sig)
{
    if(sig==SIGINT)
    {
		int k;
		flag=-1;
		for(k=0;k<3;k++)
		{
			if(user[k].opclo==1)
			{
				if((close(user[k].fd))!=0)
				{
					printf("关闭出错sig\n");
				}
			}
		}
		if(close(sockfd)==0)
		{
        printf("安全退出!\n");
        exit(0);
		}
    }
}
/////////////////////////////////////////////////////////////////////////////////////
void* thfunc(void *arg)
{  
	int f=(int)arg;
	char buff[400]={0};
	while(flag==0)
	{
		size_t size;
		if((size=recv(user[f].fd,buff,sizeof(buff),0))<0)
		{
			perror("recv error");
			break;
		}else if(size==0)
		{
			printf("%s已断开\n",user[f].name);
			break;
		}else{
			    char opensig[]={" "};
			    if((strcmp(buff,"signal_quit"))==0)
				{
					send(user[f].fd,opensig,strlen(opensig),0);
					memset(opensig,0,sizeof(opensig));
					break;
				}
			    for(int i=0;i<3;i++)
				{
					if((user[i].opclo==1)&&(i!=f))
					{
						char sendbuff[512]={0};
						memcpy(sendbuff,user[f].name,sizeof(user[f].name));
						strcat(sendbuff," : ");  strcat(sendbuff,buff);
						if(send(user[i].fd,sendbuff,strlen(sendbuff),0)<0)
						{
							if(errno==ENOTCONN)
							    perror("send error");
							break;
						}
					}
				}
			   }
		memset(buff,0,sizeof(buff));
	}
	if((close(user[f].fd))==0)
	user[f].opclo=-1;
	printf("子线程已退出\n");
	pthread_exit(NULL);
	return (void *)0;
}

///////////////////////////////////////////////////////////////////////////////////////
int main(int argc,char *argv[])
{
   
    signal(SIGINT,sigfunc);
	//1.创建套接字
	//2.绑定端口和ip地址
	//套接字返
    int bindfd=-1;
    int listenfd=-1;           //监听函数返回值
    char savedate[100]={0};
    int a=-1;
	sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(-1==sockfd)
	{
		perror("socket");               //返回值验证
		close(sockfd);
		return -1;
	}
    printf("sockfd=%d\n",sockfd);

	// 设置套接字选项避免地址使用错误  
    int on=1;  
    if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)  
    {  
        perror("setsockopt failed");  
        exit(EXIT_FAILURE);  
    }  
	/***********************************
	/*上面部分为创建套接字*/
    struct sockaddr_in svraddr={0};  //bind函数需要传入的ip端口结构体
	svraddr.sin_family=AF_INET;                  //1     //ip类型    ipv4;
    svraddr.sin_port=htons(Myport);           //2     //端口号 需要转换成机器能识别的端口号 不能直接等于6003
    svraddr.sin_addr.s_addr=INADDR_ANY; //3     //ip
	bindfd=bind(sockfd,(const struct sockaddr *)&svraddr,sizeof(svraddr));
    if(-1==bindfd)
    {
        perror("bind");
		close(sockfd);
        return -1;
    }
    printf("bind success.\n");
    printf("bindfd=%d\n",bindfd);
    //3.第三步:listen 监听端口
    listenfd=listen(sockfd,Backlog);
    if(-1==listenfd)
    {
        perror("listen");
		close(sockfd);
        return -1;
    }
    //4.第四步:accept服务器阻塞等待连接
    struct sockaddr_in Acceptretaddr={0};

	socklen_t lenth={0};
	printf("等待客户端连接\n");
    
	/********************************/
	/*设置线程分离属性*/
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	/********************************/
    //第五步:服务器接收数据
   while(flag==0)
   {   
	   acceptfd=accept(sockfd,(struct sockaddr *)&Acceptretaddr,&lenth);
	    if(acceptfd<0)
	   {
	       perror("accept");
	   }
	   //连上了,检测用户和密码
	   memset(username,0,sizeof(username));
	   recvret=recv(acceptfd,username,sizeof(username),0);
	   int t=0;
	   for(j=0;j<3;j++)
	   {
		   int cm=-1;
		   cm=strcmp(username,user[j].name1);
	   	if(cm==0)
	   	{//用户密码都正确创建子线程进行通信
			char information[]={"1"};
			    send(acceptfd,information,strlen(information),0);
				memset(information,0,sizeof(information));
			    printf("%s进入\n",user[j].name);
			    user[j].opclo=1;
				user[j].fd=acceptfd;
				int err=-1;
				err=pthread_create(&user[j].th,&attr,thfunc,(void*)j);//将用户的信息传给对应的线程
				if(err!=0)
				{
					perror("pthread create error");
			    }
				pthread_attr_destroy(&attr);//销毁分离属性
		}else{t++;}
      }
	  if(t==3)
	  {
		  char error_information[]={"0"};
		  send(acceptfd,error_information,sizeof(error_information),0);
		  close(acceptfd);
	  }
   }
   close(acceptfd);
   close(sockfd);
    return 0;
}

 

客户端

 

/*************************************************************************
	> File Name: client.c
	> Author: 客户端程序
	> Mail: 
	> Created Time: 2018年04月07日 星期六 22时05分05秒
 ************************************************************************/

#include
#include  
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define SVERPNET  "192.168.1.103"                        
                                   //客户端不用定义自己的端口号,
                                   //但是需要声明服务器开放给客户端的端口号。
#define SERVport   8848
int sendret=-1;
int recvret=-1;
int flag = 0;
char recvbuf[100]={0};        //收数据的数组

int setsocketret=-1;
int bindfd=-1;
    int connectfd=-1;
    int sendfd=-1;
    char sendbuf[100]={0};        //发送数据的数组
    struct sockaddr_in servaddr={0};
	pthread_t  th=-1;  //线程标识号
	int sockfd=-1;
	int jionret=-1;
	
//显示登录////////////////////////////////////////////////////////////////////////////////
void Display()
{
	char passwd[6]={0};
	int b=-1;
	 char namebuf[12]={0};   //六位数用户账号
	printf("用户名账号:");
	scanf("%s",namebuf);
	printf("用户密码:");
	scanf("%s",passwd);
	strcat(namebuf,passwd);
	send(sockfd,namebuf,strlen(namebuf),0);
	//等待登录结果
	recvret=recv(sockfd,recvbuf,sizeof(recvbuf),0);
	if((recvret==0)||(strcmp(recvbuf,"0"))==0)
	{
		printf("登录错误 !请重新登录\n");
		close(sockfd);
		exit(-1);
	}
	if((recvret>0)&&(strcmp(recvbuf,"1"))==0)
	{
		printf("成功登录\n");
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////
//终止处理函数
void sig1(int signum)
{
	char quit[]={"signal_quit"};

	if(SIGINT==signum)
	{
		flag=-1;
		send(sockfd,quit,strlen(quit),0);
	    close(sockfd);
		sleep(1);
	    jionret=pthread_join(th,NULL);
			if(jionret!=0)
			{
				printf("子线程没有被回收!");
			}
	    if(jionret==0)
	    {
		 printf("安全退出!\n");
	    }
        exit(0);
	}
}

//////////////////////////////////////////////////////////////////////////////////////////////
//子线程函数
void *func(void *arg)
{
	while(  flag==0  )
	{
		recvret=recv(sockfd,recvbuf,sizeof(recvbuf),0); //阻塞接收,
		if(recvret>0)
		{    
			printf("\t\t%s\n",recvbuf);
			memset(recvbuf,0,sizeof(recvbuf));
		}
		if(recvret==0)
		{
			printf("连接断开\n");
			break;
		}
	}
	printf("子线程已退出\n");
	pthread_exit(NULL);
}

////////////////////////////////////////////////////////////////////////////////////////////

int main(int argc,char *argv[])
{
    
	signal(SIGINT,sig1);
//第一步:创建套接字

 sockfd=socket(AF_INET,SOCK_STREAM,0);
 if(-1==sockfd)
    {
        perror("socket");
        return -1;
    }
//第二步:connect 连接服务器
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(SERVport);
    servaddr.sin_addr.s_addr=inet_addr(SVERPNET);
    connectfd=connect(sockfd,(const struct sockaddr *)&servaddr,sizeof(servaddr));
    if(-1==connectfd)
    {
        perror("connect");
        return -1;
    }


	//显示登录
    Display();
	//子线程读
	pthread_create(&th,NULL,func,NULL);
	//主线程写
	while(1)
	{
		scanf("%s",sendbuf);
		send(sockfd,sendbuf,strlen(sendbuf),0);
		memset(sendbuf,0,sizeof(sendbuf));   
	}
    return 0;
}

C语言实现简单多线程网络聊天室_第1张图片

 

你可能感兴趣的:(Linux应用编程网络编程笔记,C语言)