【C】——網絡編程-聊天室

功能介紹:編程

  此demo是基於TCP套接字編程,目的是實現一個聊天室效果。相似於QQ羣效果,若是上線能夠通知其餘好友,下線也會通知其餘好友。數組

須要用的技術:緩存

  1、socket編程。服務器

    1> socket 網絡編程常識:既要考慮客戶端 又要考慮服務器端。網絡

    2>TCP 一對多開發步驟:多線程

    服務端:socket

      ①:建立socket,使用socket()    ide

#include <sys/socket.h>
  int socket(int family, int type, int protocol);

        family: 指明協議族函數

                                  AF_INET: IPv4協議;測試

                                 AF_INET6: IPv6協議;

                                 AF_LOCAL:  Unix域協議;

                                 AF_ROUTE: 路由套接字;

                                 AF_KEY: 密鑰套接字;

                       type:指明套接字類型

                                 SOCK_STREAM   字節流套接字(TCP)

                                 SOCK_DGRAM    數據報套接字(UDP)

                                 SOCK_SEQPACKET  有序分組套接字

                                 SOCK_RAW        原始套接字

                      protocol:設爲某個協議類型常值  通常設爲 0;

      ② 準備通訊地址,sockaddr_in:        

struct sockaddr{
    sa_family_t    sa_family;
    char               sa_data[];
    ....
    ....
};

struct in_addr{
    in_addr_t    s_addr;
};

struct sockaddr_in{
    sa_family_t    sin_family;     //協議族 要和socket中的family相同
    in_port_t        sin_port;       //網絡端口
    struct in_addr  sin_addr;     //網絡地址
};

                         須要注意的是在對sockaddr_in 賦值的時候須要用到兩個函數:

#include <arpa/inet.h>
  uint16_t htons(uint16_t hostint16);   //轉換成網絡字節序表示的16位整型數

  in_addr_t inet_addr(const char *cp);   //把字符串轉換成32位二進制網絡字節序的IPV4地址

      ③:綁定 套接字描述符 和 通訊地址 使用函數 bind();

#include <sys/socket.h>
    int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

                      須要注意的是爲使不一樣格式地址可以被傳入到套接字函數,地址被強制轉換成通用的地址結構 sockadd表示。

      ④:監聽客戶端, 使用函數 listen();

#include <sys/socket.h>
  int listen(int sockfd, int backlog);

      ⑤:等待客戶端的鏈接,使用函數 accept(). 此函數在客戶端鏈接上來後,將返回一個新的socket描述符,這個心得描述符用於和客戶端的交互。

#include <sys/socket.h>
  int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

        使用此函數須要注意的是,若是想從客戶端那裏獲得一些信息須要從新聲明一個 struct socketaddr_in 類型的參數,而後傳給此函數的第二個和第三個參數便可。

      ⑥:把accept 返回的socket描述符當作文件描述符來操做便可;

#include <unistd.h>

  ssize_t read(int filedes, void *buf, size_t nbytes);
  ssize_t write(int filedes, const void *buf, size_t nbytes);

  ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
  ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);

        recv 的返回值: >0表示接受到消息的字節數; =0表示對方已經斷開鏈接; <0 表示出錯;

      ⑦:關閉描述符;

    客戶端:

      ①:建立socket,使用socket()

      ②: 準備通訊地址,sockaddr_in:     

      ③:創建鏈接 使用函數 connect()

#include <sys/socket.h>
  int connect(int sockfd, const struct sockaddr *addr, socklen_t len);

      ④:把socketfd當作文件描述符來使用便可,就像服務端的第六步。

  2、多線程技術;

編程思想:

  1、首先須要兩個執行程序,一個模擬客戶端,一個模擬服務端

  2、客戶端首先使用socket connect 等函數鏈接上服務器,而後建立多線程用來接收服務器發來的消息,主線程用來發送消息到服務器。

  3、服務器端:

    ①:首先建立一個全局變量數組用來保存客戶端鏈接上來的信息,由於不止一個客戶端鏈接上。

    ②:建立 socket 套接字,而後監聽客戶端發來的消息,當有客戶端鏈接上來的時候建立多線程爲此客戶端服務。

                  須要注意的是對全局變量——保存全部客戶端套接字的數組。當客戶端斷開鏈接的時候須要清理數組中的信息。

client.c:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <sys/socket.h>
 4 #include <netinet/in.h>
 5 #include <arpa/inet.h>
 6 #include <stdlib.h>
 7 #include <unistd.h>
 8 #include <string.h>
 9 #include <signal.h>
10 //準備工做
11 int sockfd;//
12 char* IP = "127.0.0.1";//本機IP,回送地址
13 short PORT = 10222;
14 typedef struct sockaddr SA;//類型轉換
15 char name[20];//客戶端暱稱
16 //啓動客戶端,鏈接服務器
17 void init(){
18     printf("聊天室客戶端開始啓動\n");
19     sockfd = socket(AF_INET,SOCK_STREAM,0);
20     struct sockaddr_in addr;
21     addr.sin_family = AF_INET;
22     addr.sin_port = htons(PORT);
23     addr.sin_addr.s_addr = inet_addr(IP);
24     if(connect(sockfd,(SA*)&addr,sizeof(addr))==-1){
25         perror("沒法鏈接到服務器");
26         printf("客戶端啓動失敗\n");
27         exit(-1);
28     }
29     printf("客戶端啓動成功\n");
30 }
31 //開始通訊
32 void* recv_thread(void* p){//收消息
33     while(1){
34         char buf[100] = {0};
35         if(recv(sockfd,buf,sizeof(buf),0)<=0){
36             return;
37         }
38         printf("%s\n",buf);
39     }
40 }
41 void start(){
42     //發送消息
43     //發消息以前,啓動一個線程,用來接受服務器發送過來的消息
44     pthread_t pid;
45     pthread_create(&pid,0,recv_thread,0);
46     while(1){
47         char buf[100] = {0};
48         scanf("%s",buf);//接受用戶輸入
49         char msg[100] = {0};
50         sprintf(msg,"%s 說:%s",name,buf);
51         send(sockfd,msg,strlen(msg),0);//發給服務器
52     }
53 }
54 void sig_close(){
55     //關閉客戶端的描述符
56     close(sockfd);
57     exit(0);
58 }
59 int main(){
60     signal(SIGINT,sig_close);//關閉CTRL+C
61     printf("請輸入您的暱稱:");
62     scanf("%s",name);
63     init();//鏈接服務器
64     send(sockfd,name,strlen(name),0);//將暱稱發給服務器
65     start();//開始通訊
66     return 0;
67 }
View Code

server.c:

  1 #include <stdio.h>
  2 #include <pthread.h>
  3 #include <sys/socket.h>
  4 #include <netinet/in.h>
  5 #include <arpa/inet.h>
  6 #include <stdlib.h>
  7 #include <unistd.h>
  8 #include <string.h>
  9 #include <signal.h>
 10 //準備工做
 11 int sockfd;//
 12 char* IP = "127.0.0.1";//本機IP,回送地址
 13 short PORT = 10222;
 14 typedef struct sockaddr SA;//類型轉換
 15 struct client{//
 16     char name[20];//存儲客戶暱稱
 17     int fds;//客戶端socket描述符
 18 };
 19 struct client c[100] = {0};//最多記錄100個連接到服務器的客戶端
 20 int size = 0;//記錄客戶端的個數,數組的索引
 21 //初始化服務器的網絡,建立socket
 22 void init(){
 23     printf("聊天室服務器開始啓動..\n");
 24     sockfd = socket(AF_INET,SOCK_STREAM,0);
 25     if(sockfd == -1){
 26         perror("建立socket失敗");
 27         printf("服務器啓動失敗\n");
 28         exit(-1);
 29     }
 30     //準備網絡通訊地址
 31     struct sockaddr_in addr;
 32     addr.sin_family = AF_INET;
 33     addr.sin_port = htons(PORT);
 34     addr.sin_addr.s_addr = inet_addr(IP);
 35     if(bind(sockfd,(SA*)&addr,sizeof(addr))==-1){
 36         perror("綁定失敗");
 37         printf("服務器啓動失敗\n");
 38         exit(-1);
 39     }
 40     printf("成功綁定\n");
 41     //設置監聽
 42     if(listen(sockfd,10)==-1){
 43         perror("設置監聽失敗");
 44         printf("服務器啓動失敗\n");
 45         exit(-1);
 46     }
 47     printf("設置監聽成功\n");
 48     printf("初始化服務器成功\n");
 49     //等待客戶端連接,放到另外一個函數中
 50 }
 51 //線程函數,用來接受客戶端的消息,並把消息發給全部客戶端
 52 //分發消息函數
 53 void sendMsgToAll(char* msg){
 54     int i = 0;
 55     for(;i<size;i++){
 56         printf("sendto%d\n",c[i].fds);
 57         send(c[i].fds,msg,strlen(msg),0);
 58     }
 59 }
 60 void* service_thread(void* p){
 61     int fd = *(int*)p;//拿到標記客戶端的sockfd
 62     printf("pthread=%d\n",fd);//輸出測試
 63     //記錄客戶端的sockfd
 64     c[size].fds = fd;
 65     char name[20] = {0};
 66     if(recv(fd,name,sizeof(name),0)>0){
 67         strcpy(c[size].name,name);//拿到暱稱
 68     }
 69     size++;
 70     char tishi[100] = {0};
 71     //羣發通知消息
 72     sprintf(tishi,"熱烈歡迎 %s 登陸聊天室..",name);
 73     //發給全部人
 74     sendMsgToAll(tishi);
 75     while(1){
 76         char buf[100] = {0};
 77         if(recv(fd,buf,sizeof(buf),0)==0){
 78             //返回0,表示TCP另外一端斷開連接
 79             //有客戶端退出
 80             printf("fd=%dquit\n",fd);//測試
 81             int i;
 82             char name[20] = {0};
 83             for(i=0;i<size;i++){
 84                 if(c[i].fds == fd){
 85                     strcpy(name,c[i].name);
 86                     c[i].fds = c[size-1].fds;
 87                     strcpy(c[i].name,c[size-1].name);//用最後一個有效的數組元素,覆蓋當前沒用的這個保存退出的客戶端的信息的數組元素
 88                 }
 89             }
 90             size--;
 91             printf("quit->fd=%dquit\n",fd);
 92             char msg[100] = {0};
 93             sprintf(msg,"歡送 %s 離開聊天室,再見!",name);
 94             //羣發退出通知
 95             sendMsgToAll(msg);
 96             close(fd);
 97             return;//客戶端退出了,結束服務線程
 98         }
 99         sendMsgToAll(buf);//接受成功,廣播聊天信息
100     }
101 }
102 //等待客戶端的鏈接,啓動服務器的服務
103 void service(){
104     printf("服務器開始服務\n");
105     while(1){
106         struct sockaddr_in fromaddr;
107         socklen_t len = sizeof(fromaddr);
108         int fd = accept(sockfd,(SA*)&fromaddr,&len);
109         if(fd == -1){
110             printf("客戶端連接出錯\n");
111             continue;//繼續循環,處理鏈接
112         }
113         //若是客戶端成功鏈接上
114         printf("fd=%d\n",fd);//測試
115         //啓動線程
116         pthread_t pid;
117         pthread_create(&pid,0,service_thread,&fd);
118     }
119 }
120 void sig_close(){
121 //關閉服務器的socket
122     close(sockfd);
123     printf("服務器已經關閉\n");
124     exit(0);
125 }
126 int main(){
127     signal(SIGINT,sig_close);//退出CTRL+C
128     init();
129     service();
130     return 0;
131 }
View Code

  以上的代碼是老師寫的,老師用的是數組來保存全部的客戶端的信息。可是本身在作這個程序的時候想法有點誤差,我用的是動態分配的鏈表技術來保存鏈接上來得客戶端的socket 套接字,動態分配,動態刪除。代碼以下:

client.c:

 1 #include <stdio.h>
 2 #include <sys/socket.h>
 3 #include <netinet/in.h>
 4 #include <unistd.h>
 5 #include <arpa/inet.h>
 6 #include <stdlib.h>
 7 #include <string.h>
 8 
 9 struct info{
10     char name[10];
11     char buf[100];
12 };
13 
14 //利用多線程接收發來的信息,能夠在任什麼時候候接收信息,包括髮信息的時候
15 void* getmssage(void* p){
16     int* sockfd = (int*)p;    
17     struct info from;
18     while(recv(*sockfd,&from,sizeof(from),0) > 0){
19         printf("%s說:%s\n",from.name,from.buf);
20         memset(from.name,0,sizeof(from.name));
21         memset(from.buf,0,sizeof(from.buf));
22     }
23 }
24 
25 int main(){
26     int sockfd = socket(AF_INET,SOCK_STREAM,0);
27     if(sockfd == -1) perror("socket"),exit(-1);
28     struct sockaddr_in addr;
29     addr.sin_family = AF_INET;
30     addr.sin_port = htons(2222);
31     addr.sin_addr.s_addr = inet_addr("172.30.13.70");
32 
33     int res = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
34     if(res == -1) perror("connect"),exit(-1);
35 
36     struct info in;
37     printf("請輸入用戶名:");
38     scanf("%s",in.name);
39     getchar(); //此時必需要用getchar 由於第 43 行用的是gets接收數據, 38行的scanf函數接收數據的時候碰到 \n就結束了,下面的gets 就會把緩存區的 \n 給接收到, 若是下面用 scanf 接收數據的話,此時就不須要用 getchar了, 由於 scanf 在第一個字符碰到 \n的時候會直接把 \n 給忽略掉
40     pthread_t pid;
41     pthread_create(pid,0,getmssage,(void*)&sockfd);
42     while(1){
43         //scanf("%s",in.buf);
44         gets(in.buf);
45         if(!strcmp(in.buf,"bye")){
46             strcpy(in.buf,"退出登錄");
47             send(sockfd,&in,sizeof(in),0);
48             exit(0);
49         }
50         if(send(sockfd,&in,sizeof(in),0) == -1) perror("send"),exit(-1);
51         memset(in.buf,0,strlen(in.buf));
52     }
53 }
View Code

server.c:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <sys/socket.h>
  4 #include <netinet/in.h>
  5 #include <arpa/inet.h>
  6 #include <string.h>
  7 #include <sys/types.h>
  8 /*
  9 int accfd[10];
 10 static int i = 0;
 11 int pthid[10];
 12 */
 13 struct info{
 14     int accfd;
 15     int pthid;
 16     int num;
 17     struct info* next;
 18 };
 19 
 20 struct clinfo{
 21     char name[10];
 22     char buf[100];
 23 };
 24 
 25 static int i = 0;
 26 struct info* head;
 27 
 28 void* del(int n){
 29     struct info *p;
 30     struct info *t;
 31 
 32     t = head;
 33     p = head->next;
 34     printf("del n:%d\n",n);
 35     while(p){
 36         if(p->num == n){
 37             t->next = p->next;
 38             free(p);
 39             break;
 40         }
 41         p = p->next;
 42         t = t->next;
 43     }
 44 
 45     printf("after free\n");
 46     t = head->next;
 47     while(t){
 48         printf("t->num:%d\n",t->num);
 49         t = t->next;
 50     }
 51 }
 52 
 53 void* message(void* p){
 54     printf("this is message\n");
 55     int num = (int)p;
 56     struct info* t = head;
 57     t = t->next;
 58 
 59     int accfd;
 60     int pthid;
 61 
 62     while(t){
 63         if(t->num == num){
 64             accfd = t->accfd;
 65             pthid = t->pthid;
 66             break;
 67         }
 68         t = t->next;
 69     }
 70 
 71     struct clinfo cin;
 72     memset(cin.buf,0,sizeof(cin.buf));
 73     memset(cin.name,0,sizeof(cin.name));
 74     while(1){
 75         while(recv(accfd,&cin,sizeof(cin),0) > 0){
 76             printf("%s say: %s\n",cin.name,cin.buf);
 77             t = head->next; 
 78             while(t){
 79                 printf("t->num:%d,num:%d\n",t->num,num);
 80                 if(t->num != num){
 81                     send(t->accfd,&cin,sizeof(cin),0);
 82                 }
 83                 t = t->next;
 84             }
 85             if(!strcmp(cin.buf,"退出登錄")){
 86                 del(num);
 87                 printf("即將退出線程\n");
 88                 close(accfd);
 89                 sleep(1);
 90                 pthread_exit(0);
 91             }
 92             memset(cin.buf,0,sizeof(cin.buf));
 93             memset(cin.name,0,sizeof(cin.name));
 94         }
 95     }
 96 }
 97 
 98 struct info* init(){
 99     struct info* in;
100     in = (struct info*)malloc(sizeof(struct info));
101     in->next = NULL;
102     return in;
103 }
104 
105 int main(){
106     int sockfd = socket(AF_INET,SOCK_STREAM,0);
107     if(sockfd == -1) perror("socket"),exit(-1);
108     struct sockaddr_in addr;
109     addr.sin_family = AF_INET;
110     addr.sin_port = htons(2222);
111     addr.sin_addr.s_addr = inet_addr("172.30.13.70");
112 
113     //解決地址被佔用問題
114     int reuse = 10;
115     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
116     //綁定
117     int res = bind(sockfd,(struct sockaddr*)&addr,sizeof(addr));
118     if(res == -1) perror("bind"),exit(-1);
119     //監聽
120     listen(sockfd,100);
121     struct sockaddr_in from;
122     socklen_t len = sizeof(from);
123 
124     head = init();
125     while(1){
126         //獲得新的描述符
127         int accfd = accept(sockfd,(struct sockaddr*)&from,&len);
128         struct info* p = head;
129         struct info* tmp = init();
130         //讓指針指向鏈表的尾部
131         while(p->next){
132             p = p->next;
133         }
134         p->next = tmp;
135         
136         tmp->num = i++;
137         tmp->accfd = accfd;
138         printf("%s鏈接上來\n",inet_ntoa(from.sin_addr));
139         //建立線程
140         pthread_create(&(tmp->pthid),0,message,(void*)(i-1));
141     }
142 
143 }
相關文章
相關標籤/搜索