您的位置:首页 > 理论基础 > 计算机网络

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

2015-12-11 16:50 731 查看
        文章是博主在学习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));
}
}

/*基于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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息