linux下基于TCP协议的多线程聊天室的搭建

        文章是博主在学习unix网络编程一段时间之后,算是做的一个小的总结吧。希望能够给刚入门unix网络编程的同学学习和参考,当然博主也是学生一枚,更希望有大神批评指点。。。

      博主首先先介绍一下多线程的概念:
      线程是基于进程来说的,一个进程可以有多个线程,多个线程共享进程的资源。举一个例子,比如我们启动了qq程序,可以说是启动了一个进程,而你打开的多个聊天窗口就是基于qq这个进程的多个线程。多线程和多进程都能够使得代码并行,但是由于多进程开销比较大(fork子进程会复制父进程除了代码区之外的所有区域),而多线程会共享进程的资源,所以多线程在实际应用中很广泛。

     下面来介绍几个线程的基本函数:
       1、intpthread_create(pthread_t *tid,const pthread_attr_t*attr,void*(*func)(void*),void*arg),我想大家可以看得出这就是线程的创建函数。这个函数包含在头文件#include中,共有四个指针形参,所以线程的创建函数又叫做四针函数,其中第一个参数表示线程ID,第二个是线程属性,很多情况下给0就可以(好像又听说linux下只支持0,但不确定),第三个参数是一个函数指针,用于执行自己的线程函数,传入的就是自定义线程函数的函数指针。第四个参数的作用是我们可以传入一个我们需要的参数给线程函数,后面的程序中大家就会明白。

2、pthread_join(pthread_ttid,void**status),这个函数的作用是让一个线程等待另一个线程的结束。当我们不希望得到线程函数的返回值时,第二个参数给0.例如我们在线程A中写了pthread_join(B,0),就表示A线程要等到b线程的结束。类似于进程中的waitpid()函数。函数的第二个参数用于带出线程函数的返回值,我认为大家可以理解一下为什么是二级指针。当然,第一个参数表示线程ID。

     3、pthread_self(),没有形参,函数的作用是取得自己的线程ID。

      4、pthread_detach(pthread_ttid),函数的作用是讲一个线程设置为分离线程。分离线程是什么意思呢?由于线程在回收资源上是不确定的,就是说没有固定的说法说他什么时候回收。当一个线程是分离线程的时候,只要他结束,所有的资源都会释放。还有一种线程是pthread_join()过得线程,也会在执行这个函数之后释放所 有资源,所以我们最好是把线程设置成这两种类型。形参表示线程ID。

      5、pthread_exit(void*status),用于退出线程。

    值得注意的是,这一块的函数的返回值基本都是返回错误码,并不是以前的错误返回-1什么的,错误码通过strerror()函数可以解析出错误的信息。

线程这块还有一些知识,比如线程同步,线程尺这些,由于没用到,就不细说了,下面我们先搭建聊天室的服务器端。
/*基于TCP协议的多线程聊天室服务器端*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <pthread.h>
#include <arpa/inet.h>
char *IP = "192.168.1.100"; //服务器IP号
short PORT = 5555;          //通信端口号
int fd;
/*客户端信息的结构*/
typedef struct client1{
   char name[20];
   int sock;
} Client;
Client client[100];         //能同时容纳100个客户上线
int size = 0;               //存放当前在线客户端数目

void fa(int signo){
   printf("服务器出现异常,正在退出...\n");
   sleep(3);
   close(fd);
   printf("服务器已关闭\n");
   exit(0);
}
/*线程函数*/
void* task(void* p);
/*数据发送函数*/
void sendmsgtoALL(char* msg);
void init();
void start();
void closeServe();

int main(){
   signal(SIGINT,fa);
   init();
   start();
   closeServe();
}
void init(){
   printf("服务器正在初始化...\n");
   sleep(3);
   /*创建socket描述符*/
   fd = socket(AF_INET,SOCK_STREAM,0);
   if (fd==-1) perror("socket"),exit(-1);
   /*准备通信地址*/
   struct sockaddr_in addr;
   addr.sin_family = AF_INET;
   addr.sin_port = htons(PORT);
   addr.sin_addr.s_addr = inet_addr(IP);
   /*绑定*/
   int res = bind(fd,(struct sockaddr*)&addr,sizeof(addr));
   if(res==-1) perror("bind"),exit(-1);
   /*监听*/
   listen(fd,100);
   printf("服务器初始化完毕!\n");
}
void start(){
   printf("服务器正在启动...\n");
   sleep(3);
   printf("服务器启动成功!\n");
   printf("等待客户端的连接....\n");
   while(1){
      /*创建连接上来的客户端通信地址*/
      struct sockaddr_in from;
      socklen_t len = sizeof(from);
      int sockfd = accept(fd,(struct sockaddr*)&from,&len);
      if(sockfd == -1) perror("accept"),exit(-1);
      /*为连接上来的客户端开辟新的线程*/
      client[size].sock = sockfd;
      pthread_t p_id;
      pthread_create(&p_id,0,task,(void*)&(client[size].sock));
   }
}
void closeServe(){
   printf("服务器正在关闭...\n");
   sleep(3);
   close(fd);
   printf("服务器关闭成功!\n");
   exit(0);
}
void* task(void* p){
   int socktemp = *((int*)p);
   char name[20] = {};      //用于接收用户名
   char buf[100] = {};
   int i,temp;
   if(read(client[size].sock,name,sizeof(name))>0)
      strcpy(client[size].name,name);
   temp = size;
   size++;
   /*首次进入聊天室打印如下提示*/
   char remind[100] = {};
   sprintf(remind,"欢迎%s进入本聊天室..",client[size-1].name);
   sendmsgtoALL(remind);
   while(1){
      if (read(socktemp,buf,sizeof(buf))<=0){
         char remind1[100]={};
         sprintf(remind1,"%s退出聊天室",client[temp].name);
         sendmsgtoALL(remind1);
         for(i=0;i<size;i++)
            if(client[i].sock == socktemp){
               client[i].sock = 0;
         }
         return;//结束线程
      }
      /*把消息发送给除了他自己的其他所有在线的客户端*/
      char msg[100] = {};
      sprintf(msg,"%s:%s",client[temp].name,buf);
      sendmsgtoALL(msg);
      memset(buf,0,strlen(buf));
   }
}
//用来分发消息的函数
void sendmsgtoALL(char * msg){
	int i=0;
	for(;i<size;i++){
		if(client[i].sock!=0){
			printf("sendto****%d\n",client[i].sock);
			send(client[i].sock,msg,strlen(msg),0);//
			}
		}
	}
//
//

/*基于TCP协议的多线程聊天室客户端口*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <string.h>
int fd;
char name[50];
char *IP = "192.168.1.100";
short PORT = 5555;
void init();
void start();
void* task(void* p);
void fa(int signo){
   printf("正在退出...");
   sleep(1);
   close(fd);
   exit(0);
}
int main(){
   signal(SIGINT,fa);        //将信号2注册为自定义函数,用于退出
   printf("正在启动...\n");
   sleep(3);
   printf("启动成功,请输入用户名:");
   scanf("%s",name);
   init();
   write(fd,name,strlen(name));
   start();
}
void init(){
   /*步骤和服务器端差不多*/
   fd = socket(AF_INET,SOCK_STREAM,0);
   if (fd==-1) perror("socket"),exit(-1);
   /*创建通信地址*/
   struct sockaddr_in addr;
   addr.sin_family = AF_INET;
   addr.sin_port = htons(PORT);
   addr.sin_addr.s_addr = inet_addr(IP);
   /*连接服务器*/
   int res = connect(fd,(struct sockaddr*)&addr,sizeof(addr));
   if(res == -1) perror("connect"),exit(-1);
   printf("连接服务器成功!\n");
}
void start(){
   /*客户端除了要给服务器发送数据外,还要接收服务器的数据
   而这两个步骤是并行的,所以我们在主线程中写,开辟的线程中读*/
   pthread_t pid;
   pthread_create(&pid,0,task,0);
   char msg[100] = {};
   while(1){
      scanf("%s",msg);
      write(fd,msg,strlen(msg));
      /*必须把缓冲区清空*/
      memset(msg,0,strlen(msg));
   }
}
/*线程函数,用于读服务器发送过来的数据*/
void* task(void* p){
   while(1){
      char buf[100] = {};
      if(read(fd,buf,sizeof(buf))<=0)
         return;
      printf("%s\n",buf);
      memset(buf,0,sizeof(buf));
   }
}
聊天室搭建完毕,值得注意的是我们的运行平台是linux而不是windows。

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值