• 【C】——网络编程-聊天室


    功能介绍:

      此demo是基于TCP套接字编程,目的是实现一个聊天室效果。类似于QQ群效果,如果上线可以通知其他好友,下线也会通知其他好友。

    需要用的技术:

      一、socket编程。

        1> socket 网络编程常识:既要考虑客户端 又要考虑服务器端。

        2>TCP 一对多开发步骤:

        服务端:

          ①:创建socket,使用socket()    

    #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当做文件描述符来使用即可,就像服务端的第六步。

      二、多线程技术;

    编程思想:

      一、首先需要两个执行程序,一个模拟客户端,一个模拟服务端

      二、客户端首先使用socket connect 等函数连接上服务器,然后创建多线程用来接收服务器发来的消息,主线程用来发送消息到服务器。

      三、服务器端:

        ①:首先创建一个全局变量数组用来保存客户端连接上来的信息,因为不止一个客户端连接上。

        ②:创建 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("聊天室客户端开始启动
    ");
    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("客户端启动失败
    ");
    27         exit(-1);
    28     }
    29     printf("客户端启动成功
    ");
    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
    ",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("聊天室服务器开始启动..
    ");
     24     sockfd = socket(AF_INET,SOCK_STREAM,0);
     25     if(sockfd == -1){
     26         perror("创建socket失败");
     27         printf("服务器启动失败
    ");
     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("服务器启动失败
    ");
     38         exit(-1);
     39     }
     40     printf("成功绑定
    ");
     41     //设置监听
     42     if(listen(sockfd,10)==-1){
     43         perror("设置监听失败");
     44         printf("服务器启动失败
    ");
     45         exit(-1);
     46     }
     47     printf("设置监听成功
    ");
     48     printf("初始化服务器成功
    ");
     49     //等待客户端链接,放到另一个函数中
     50 }
     51 //线程函数,用来接受客户端的消息,并把消息发给所有客户端
     52 //分发消息函数
     53 void sendMsgToAll(char* msg){
     54     int i = 0;
     55     for(;i<size;i++){
     56         printf("sendto%d
    ",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
    ",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
    ",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
    ",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("服务器开始服务
    ");
    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("客户端链接出错
    ");
    111             continue;//继续循环,处理连接
    112         }
    113         //如果客户端成功连接上
    114         printf("fd=%d
    ",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("服务器已经关闭
    ");
    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
    ",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函数接收数据的时候碰到 
    就结束了,下面的gets 就会把缓存区的 
     给接收到, 如果下面用 scanf 接收数据的话,此时就不需要用 getchar了, 因为 scanf 在第一个字符碰到 
    的时候会直接把 
     给忽略掉
    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);
     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
    ");
     46     t = head->next;
     47     while(t){
     48         printf("t->num:%d
    ",t->num);
     49         t = t->next;
     50     }
     51 }
     52 
     53 void* message(void* p){
     54     printf("this is message
    ");
     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
    ",cin.name,cin.buf);
     77             t = head->next; 
     78             while(t){
     79                 printf("t->num:%d,num:%d
    ",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("即将退出线程
    ");
     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连接上来
    ",inet_ntoa(from.sin_addr));
    139         //创建线程
    140         pthread_create(&(tmp->pthid),0,message,(void*)(i-1));
    141     }
    142 
    143 }
  • 相关阅读:
    第四章 方法(4.2 方法的嵌套调用)
    C#利用for循环打印图形练习题
    第三章 C#程序结构 (3.3 循环结构)
    第六章 数组和索引器 (6.6 索引器)
    第五章 类与对象 5.2 猫类(案例二)
    第五章 类与对象 5.1 时间类(案例一)
    第三章 C#程序结构[3.2 选择结构的应用(Windows窗体应用程序)(四)]
    第三章 C#程序结构(3.1 顺序与选择结构)
    第二章 C#语法基础 (2.2 C#语言的运算符和表达式)
    第二章 C#语法基础(2.1C#语言的数据类型二)
  • 原文地址:https://www.cnblogs.com/ngnetboy/p/3529989.html
Copyright © 2020-2023  润新知