1. 程式人生 > >基於Linux c 用socket和執行緒 實現的簡易聊天室之伺服器

基於Linux c 用socket和執行緒 實現的簡易聊天室之伺服器

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sqlite3.h>


#define PORTNUMBER 9994
#define MAXNUM 10
#define OK 1
#define ERROR 0




typedef  int Elementtype; 
typedef  int Status; 


//與客戶端一致的結構體
typedef struct client
{
Elementtype id;
Elementtype flag;
Elementtype case_num;//伺服器進行功能選擇 
Elementtype case_numnext;//登入成功聊天功能選擇
char name[100];
    char password[150];
//char content_c[1024];//接收客戶端傳送訊息

}Client_message;


//定義一個伺服器的結構體
typedef struct server
{   
    Elementtype i_s; 
    Elementtype num_s[10];

Elementtype case_snum;//伺服器傳送進行的功能  0註冊成功,1登入失敗,2普通使用者登入,3管理員登入
Elementtype id__s;    //4私聊時查詢ID有人,5私聊查詢ID沒人,6查詢線上人數
 char name_s[100];
    char password_s[150];
//char content_s[1024];//伺服器傳送的訊息快取區

}Server_message;
//連結串列結構體
typedef struct node
{
int id_l;
int fd_l;
struct node *next;
}linkedlist;




Client_message msg_accept;//定義一個全域性的結構體名字,接收客戶端的結構體
Server_message msg_send;//定義一個全域性的結構體名字,傳送伺服器的結構體




linkedlist *list;
//連結串列函式
linkedlist *createlinkedlist();
int getcount(linkedlist *list);
//void insert1(linkedlist *list, int id, int fd);
linkedlist *insert2(linkedlist *list, int id, int fd);
int queryfd(linkedlist *list, int id);
int queryid(linkedlist *list, int fd);
void printf_list(linkedlist *list);
linkedlist *dellinkedlist(linkedlist *list, int fd);


void *func(void * arg);


int newfd;//定義一個全域性整形的套接字


int main(int argc, char *argv[])
{
    system("clear");

//message msg_accept;
int ret;

printf("伺服器正在初始化......\n");
sleep(2);

int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == socket_fd)
{
perror("建立通訊失敗");
exit(1);
}

struct sockaddr_in server_addr;

memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORTNUMBER);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

printf("初始化伺服器成功......\n");
printf("正在啟動伺服器......\n");
    sleep(2);

if (-1 == bind(socket_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)))
{
perror("繫結地址失敗");
exit(2);
}

if(-1 == listen(socket_fd, MAXNUM))//最大監聽使用者人數為10
{
perror("監聽失敗");
exit(3);
}

printf("伺服器啟動成功......\n");


sqlite3 *db;
ret = sqlite3_open("./server.db", &db);//開啟資料庫
if(ret != SQLITE_OK)
{
printf("開啟資料庫失敗\n");
}

printf("開啟資料庫成功......\n");
printf("正在等待客戶端的連線......\n");

ret = sqlite3_close(db);
if(ret != SQLITE_OK)
{
   printf("關閉資料庫失敗\n");
}

//建立一個單鏈表
list = createlinkedlist();
pthread_t pid;

while(1)
{
struct sockaddr_in client_addr;
int length = sizeof(struct sockaddr);

newfd = accept(socket_fd, (struct sockaddr *)(&client_addr), &length);
if (-1 == newfd)
{
perror("接收失敗");
exit(1);
}
printf("客服端%d連上了伺服器......\n",newfd);

pthread_create(&pid,NULL,(void*)func,(void*)newfd);

    }

//close(newfd);
return 0;
}




void *func(void * arg)
{
    int newfd = (int)arg;//強轉為套接字型別
    char sendbuff[1024] = {0};//伺服器傳送資訊
    char buff[1024] = {0};//把套接字先快取到的區域
int ret;
static int num = 1000;//用作ID

// message *msg_accept = (message *)arg ;//把arg強轉為結構體

while(1)
{

   ret = read(newfd, buff,1024);
if(-1 == ret)
{
            perror("讀取錯誤");
            exit(1);
}  

memset(&msg_accept,0,sizeof(msg_accept));//清空結構體
   memcpy(&msg_accept,buff,sizeof(msg_accept));//把收到的資訊轉換為結構體

//printf("msg_accept.name = %s\t,msg_accept.password = %s\n",msg_accept.name,msg_accept.password);
//printf("msg_accept.case_num = %d\n",msg_accept.case_num);
switch(msg_accept.case_num)
{
case 1 : //客戶請求註冊
       {

printf("---------------------------------------------\n");
printf("客戶端需要註冊的名字為:  %s    密碼 :%s\n",msg_accept.name,msg_accept.password);
printf("---------------------------------------------\n");      
 
sqlite3 *db;
char *errmsg;
ret = sqlite3_open("server.db",&db);
if(ret != SQLITE_OK)
{
perror("開啟資料庫失敗");
exit(2);
}

char sql_insert[1024] = {0};

sprintf(sql_insert,"insert into server(id ,name,password) values(%d,'%s','%s');",num,msg_accept.name,msg_accept.password);
printf("----------------------以下資訊已進入資料庫-----------------------\n");
printf("%s\n",sql_insert);
printf("-----------------------------------------------------------------\n");
ret = sqlite3_exec(db,sql_insert,NULL,NULL,&errmsg);

if(ret != SQLITE_OK)
{
perror("插入資料庫失敗");
exit(3);
}
ret = sqlite3_close(db);
if(ret != SQLITE_OK)
{
perror("關閉資料庫失敗");
exit(4);
       }

           msg_send.case_snum = 0;
msg_send.id__s = num;

num++;

memset(sendbuff,0,1024);
memcpy(sendbuff,&msg_send,sizeof(msg_send));

ret = write(newfd ,sendbuff, 1024);
                if(-1 == ret)
                {
                    perror("寫入失敗");
                    exit(2);
                } 
break;

   }
           
            case 2: //客戶請求登入
            {   
 
    printf("---------------------------------------------\n");
                 printf("有客戶需要登入, 登入ID:%d    密碼:%s  \n",msg_accept.id,msg_accept.password);
printf("---------------------------------------------\n");
 
sqlite3 *db;
char *errmsg;
int  row;
int  column;
char**result;
 
ret = sqlite3_open("server.db",&db);
if(ret != SQLITE_OK)
{
perror("開啟資料庫失敗");
exit(2);
}
 
char sql_client[1024] = {0};

sprintf(sql_client,"select id,password from server where id = %d and password = '%s';",msg_accept.id,msg_accept.password);

ret = sqlite3_get_table(db, sql_client, &result, &row, &column, &errmsg);

if(ret != SQLITE_OK)
{
perror("查詢資料庫失敗");
exit(3);
}
int i;
                 int j;
                 printf("-------查詢結果如下--------\n");
            for(i = 1;i <= row;i++)
            {
                     for(j = 0;j < column;j++)
            {
            printf("%s|",result[i * column + j]);

            }
            printf("\n");
}
printf("---------------------------\n");
ret = sqlite3_close(db);
if(ret != SQLITE_OK)
{
perror("關閉資料庫失敗");
exit(4);
}
 
if(column == 0)
{
printf("你登入的資訊有誤,請重新登入.\n");
 
msg_send.case_snum = 1;
memset(sendbuff,0,1024);
    memcpy(sendbuff,&msg_send,sizeof(msg_send));


    ret = write(newfd ,sendbuff, 1024);
                     if(-1 == ret)
                     {
                         perror("寫入失敗");
                         exit(2);
                     }
 break;
}
 
else//登入成功
{    
printf("恭喜你登入成功.\n");
 
if(msg_accept.id == 1000)//管理員登入
{
memset(sendbuff,0,1024);
 
                         printf("管理員上線.\n");  
    msg_send.case_snum  = 3;//管理登入成功後傳送到客戶端的指令
        memcpy(sendbuff,&msg_send,sizeof(msg_send));
 
        ret = write(newfd ,sendbuff, 1024);
                         if(-1 == ret)
                         {
                             perror("寫入失敗");
                             exit(2);
                         }
    }
 
else //普通使用者登入成功
{
 memset(sendbuff,0,1024);
 
                          printf("普通使用者上線.\n");
 msg_send.case_snum  = 2;//普通使用者登入成功後傳送到客戶端的指令
         memcpy(sendbuff,&msg_send,sizeof(msg_send));
 
         ret = write(newfd ,sendbuff, 1024);
                          if(-1 == ret)
                          {
                              perror("寫入失敗");
                              exit(2);
                          }
 
}
//插入資訊到連結串列
list = insert2(list, msg_accept.id, newfd);
printf("現在線上人物資訊 :\n");
printf_list(list);
printf("線上人數為:%d\n", getcount(list));

while(1)
{
                          memset(buff,0,1024);
                          ret = read(newfd,buff,1024);
 if(-1 == ret)
 {
 perror("讀取失敗");
 exit(1);
 }
 
                          memcpy(&msg_accept,buff,sizeof(msg_accept));
                          
                          
                          switch(msg_accept.case_numnext)
                          { 
                              case 1://檢視當前人數
                              { 
     /*
     char str[1024] = {0};
 sprintf(str,"當前線上人數為: %d\n",count);      
                                 
                                  ret = write(newfd,str,1024);
 
 if(-1 == ret)
 {
 perror("寫入人數失敗");
 exit(2);
 }
                                   */
                                  int count = getcount(list);//用連結串列檢視當前的人數
  
 memset(sendbuff ,0 ,1024);
 msg_send.id__s = count;//用作id_s臨時記錄下線上的人數 
 printf("count = %d\n",msg_send.id__s);
 
 msg_send.case_snum = 6;//查詢線上人數
 //memcpy(sendbuff,&msg_send,sizeof(msg_send));
        
                 ret = write(newfd ,sendbuff, 1024);
                                  if(-1 == ret)
                                  {
                                      perror("寫入失敗");
                                      exit(2);
                                  }
 
 sleep(1);


                                  linkedlist *h = list;
                                  int temp = 0;
 
 while(h != NULL)
                                  {
 msg_send.num_s[temp] = h -> id_l;
                                      
 temp++;

                             h = h -> next;

 }
 
  msg_send.i_s = temp; 
  memcpy(sendbuff,&msg_send,sizeof(msg_send));
        
                  ret = write(newfd ,sendbuff, 1024);
                                   if(-1 == ret)
                                   {
                                       perror("寫入失敗");
                                       exit(2);
                                   }
                       
             break; 
 } 
                             
 case 2: //傳送私聊
 {
 
 int send_newfd = queryfd(list, msg_accept.id);//查詢私聊人物的FLAG
 //是否有此人
// printf("查到的客戶端是: %d\n", send_newfd);
 int id_client = queryid(list,newfd);//查詢傳送訊息人物的ID 
 //printf("id_client = %d\n",id_client);//列印他的ID
 
 if(send_newfd != 0)//有此人
 {
 
 printf("客戶端%d  私聊客戶端%d  訊息為 : %s\n",newfd,send_newfd,msg_accept.name);
 
 memset(sendbuff ,0 ,1024);
 
                                      msg_send.id__s = send_newfd;//用作id_s臨時記錄下客戶端的FLAG 
 msg_send.case_snum = 4;//查詢有這個ID
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
                 
                     ret = write(newfd ,sendbuff, 1024);
                                      if(-1 == ret)
                                      {
                                          perror("寫入失敗");
                                          exit(2);
                                      }
     
 memset(sendbuff,0,1024);
 
 strcpy(msg_send.name_s,msg_accept.name);
 msg_send.case_snum = 7;//用於傳送私聊訊息的指令
 msg_send.id__s = id_client;//用於記錄傳送人的ID
 
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
 
 ret = write(send_newfd,sendbuff,1024);//傳送給要私聊的人
 if(-1 == ret)
                                      {
                                          perror("寫入失敗");
                                          exit(2);
                                      }
 
 memset(sendbuff,0,1024);
 
 msg_send.case_snum = 8;//用於發聵訊息傳送成功了指令
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
 
     ret = write(newfd,sendbuff,1024);//傳送給發起私聊的人
 
 if(-1 == ret)
                                      {
                                          perror("寫入失敗");
                                          exit(2);
                                      }
 
 
                                  } 
 else
 {
 printf("沒有這個ID\n");
 
  memset(sendbuff ,0 ,1024);
 
 msg_send.case_snum = 5;//沒有這個ID
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
                
                     ret = write(newfd ,sendbuff, 1024);
                                      if(-1 == ret)
                                      {
                                          perror("寫入失敗");
                                          exit(2);
                                      }
 
 }

 break;
 }
                              
 case 3: //傳送群聊
 {  
     int id_client = queryid(list,newfd);//查詢傳送群聊訊息人物的ID
 
 printf("客戶端%d發起了群聊,內容是:%s\n",newfd,msg_accept.name);
 
 int i; 
 linkedlist *h = list;
 //把線上人數都遍歷出來
 for(i = 0; i < getcount(list); i++)
 {
     if(h -> fd_l != newfd)
 {    
         memset(sendbuff, 0, 1024);
 
     strcpy(msg_send.name_s,msg_accept.name);//複製群聊的訊息到伺服器的結構體裡面
         msg_send.case_snum = 9;//用於傳送群聊訊息的指令
         msg_send.id__s = id_client;//用於記錄傳送人的ID
 
         memcpy(sendbuff,&msg_send,sizeof(msg_send));
 
 ret = write(h -> fd_l, sendbuff, 1024);
 if(-1 == ret)
 {
 perror("write");
 exit(EXIT_FAILURE);
 }


 }
 h = h -> next;
 }

                         memset(sendbuff,0,1024);
 
 msg_send.case_snum = 10;//用於發聵訊息傳送成功了指令
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
 
 ret = write(newfd,sendbuff,1024);//傳送給發起群聊的人
 
 if(-1 == ret)
                                  {
                                      perror("寫入失敗");
                                      exit(2);
                                  }
 
 break;
 }
 
 case 4: //客戶請求傳送檔案
                              {

                     int ret;
 
 int accept_newfd = queryfd(list, msg_accept.id);//查詢檔案要傳送到人物的FLAG

 int id_send = queryid(list,newfd);//查詢傳送檔案的ID 
 
 printf("客戶端%d請求傳送檔案給客戶端%d,檔名是: %s\n",newfd,accept_newfd,msg_accept.name);
 
 memset(sendbuff,0,1024);
 //先把名字發給要接受檔案的人
 
 msg_send.case_snum = 18;
 msg_send.id__s = id_send;
 strcpy(msg_send.name_s,msg_accept.name);
 
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
 
 ret = write(accept_newfd,sendbuff,1024);
 if(-1 == ret)
                                  {
                                       perror("寫入失敗\n");
                                       exit(1);   
 }
  
 //把內容發給要接受檔案的人
while(1)
{

char buffer_file[1024];
memset(buffer_file, 0, 1024);//清空快取

if(recv(newfd, buffer_file, 1024, 0) < 0)
{
perror("接收失敗");
exit(1);

}

printf("內容是 :\n");
printf("%s\n",buffer_file);

if(send(accept_newfd, buffer_file, 1024, 0) < 0)
{
perror("傳送失敗");
exit(1);

}

if(0 == strncmp(buffer_file, "END", 3))
{

break;

}


}
 //把傳送成功的訊息發聵給傳送者
                                  memset(sendbuff,1024,0);
                                  msg_send.case_snum = 19;
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
                                  
 ret = write(newfd,sendbuff,1024);
 if(-1 == ret)
                                  {
                                       perror("寫入失敗\n");
                                       exit(1);   
 }
                                   
 
 break;   
 }
 
 case 5: //客戶請求下線
 {
 printf("客戶端%d請求下線\n",newfd);
 
 list = dellinkedlist(list, newfd);
 printf_list(list);
 
 memset(sendbuff,0,1024);
 
 msg_send.case_snum = 11;//用於發聵訊息傳送成功了指令
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
 
 ret = write(newfd,sendbuff,1024);//傳送給發起請求下線的人
 
 if(-1 == ret)
                                  {
                                      perror("寫入失敗");
                                      exit(2);
                                  }
 //break;
         goto out;
 }
 case 6://禁言
 {
 printf("管理員請求禁言,禁言的ID是%d\n",msg_accept.id);
 
 memset(sendbuff, 0, 1024);
 int jinyan_newfd = queryfd(list,msg_accept.id);//查詢被禁言人的客戶端
 
 
 msg_send.case_snum = 12;
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
  
 ret = write(jinyan_newfd,sendbuff,1024);//傳送給被禁言的人
 
 if(-1 == ret)
                                  {
                                      perror("寫入失敗");
                                      exit(2);
                                  }
 
 //禁言成功反饋給管理員
 msg_send.case_snum = 13;
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
  
 ret = write(newfd,sendbuff,1024);//傳送給被禁言的人
 
 if(-1 == ret)
                                  {
                                      perror("寫入失敗");
                                      exit(2);
                                  }
 
 

 break;
 }
 case 7://解禁
 {
 printf("管理員請求解禁,解禁的ID是%d\n",msg_accept.id);
 
 memset(sendbuff, 0, 1024);
 int jiejin_newfd = queryfd(list,msg_accept.id);//查詢被禁言人的客戶端
 
 
 msg_send.case_snum = 14;
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
  
 ret = write(jiejin_newfd,sendbuff,1024);//傳送給被禁言的人
 
 if(-1 == ret)
                                  {
                                      perror("寫入失敗");
                                      exit(2);
                                  }
                                  
                                   
  //解禁成功反饋給管理員
 msg_send.case_snum = 15;
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
  
 ret = write(newfd,sendbuff,1024);//傳送給管理員
 
 if(-1 == ret)
                                  {
                                      perror("寫入失敗");
                                      exit(2);
                                  }
 
 
 
 break;
 }
 case 8://踢人下線
 {
 printf("管理員請求踢人,被踢人的ID是%d\n",msg_accept.id);
 
 memset(sendbuff, 0, 1024);
 int tiren_newfd = queryfd(list,msg_accept.id);//查詢被踢人的客戶端
 
 
 msg_send.case_snum = 16;
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
  
 ret = write(tiren_newfd,sendbuff,1024);//傳送給被踢的人
 
 if(-1 == ret)
                                  {
                                      perror("寫入失敗");
                                      exit(2);
                                  }
                                  
 
                                 
  //踢人成功反饋給管理員
 msg_send.case_snum = 17;
 memset(sendbuff, 0, 1024);
 memcpy(sendbuff,&msg_send,sizeof(msg_send));
  
 ret = write(newfd,sendbuff,1024);//傳送給管理員
 
 if(-1 == ret)
                                  {
                                      perror("寫入失敗");
                                      exit(2);
                                  }
 
 break;
 }
 } 


}  
 
 
break;
}
 
    break;  
}
             
            case 3:
            {
                 printf("客戶端%d請求退出\n",newfd);
goto outroom;  
}
     
        }//switch
    
out:
    
printf("-----------\n");

    }//while(1) 
 
    outroom:
    
close(newfd);


}


linkedlist *createlinkedlist()
{
linkedlist *h;
h = NULL;

return h;
}


linkedlist *dellinkedlist(linkedlist *list, int fd)
{
linkedlist *p = list -> next;
linkedlist *h = list;
if(list -> fd_l == fd)
{
free(h);
return list -> next;

}
else
{
while(p -> fd_l != fd)
{
p = p -> next;
h = h -> next;
}
h -> next = p -> next;
free(p);
return list;
}

}


linkedlist *insert2(linkedlist *list, int id, int fd)
{
linkedlist *head, *p;
head = list;
p = (linkedlist *)malloc(sizeof(linkedlist));
if(head == NULL)
{

head = p;
p -> next = NULL;
}
else
{
p -> next = head;
head = p;
}


p -> id_l = id;
p -> fd_l = fd;

return head;

}


int getcount(linkedlist *list)
{
linkedlist *h = list;
int count = 0;
while(h != NULL)
{
count++;
h = h -> next;
}
return count;
}


int queryid(linkedlist *list, int fd)
{
linkedlist *p, *h;
h = list;
while(h != NULL)
{
if(fd == h -> fd_l)
{
return h -> id_l;
}

h = h -> next;
}
}




int queryfd(linkedlist *list, int id)
{
linkedlist *h = list;
while(h != NULL)
{


if(id == h -> id_l)
{
return h -> fd_l;
}
h = h -> next;
}

return 0;
}


void printf_list(linkedlist *list)
{
linkedlist *h = list;
while(h != NULL)
{
printf("線上人物的ID為 :%d  FLAG :  %d\n", h ->id_l, h -> fd_l);
        h = h -> next;
}


printf("\n");
}