zoukankan      html  css  js  c++  java
  • linux socket 聊天室

    linux socket 聊天室,本来这并不是我自己要做的,只是为了帮别人完成作业。刚好最近这段时间的课是关于socket编程,何不拿来练练手?

    基于socket的聊天室早在开学初就有做过类似的,只不过当时用的java来实现,而且因为没有正式学过socket,代码只是搬用别人的,并没有深入理解。

    单用户-服务的对话还是很好实现的,即使是多用户-服务,只要不是连续服务,服务端还是可以通过轮询的方式服务多个用户。问题就在于,常用socket I/O函数大都是阻塞的,这就意味着,单个线程只能服务于一个用户。于是自然而然的想到用多线程,然而多线程并不是最佳的解决方案,毕竟如果频繁的创建和销毁线程会造成一定的浪费。而利用select的多路复用,就能更好的解决。

    客户端实现

    客户端是很好实现的,只有2个I/O要通信,server socket和stdin,并且是fd值固定的,maxfdp直接取socket+1就行了。

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #include <stdbool.h>
     5 #include <unistd.h>
     6 #include <sys/socket.h>
     7 #include <arpa/inet.h>
     8 
     9 #define BUF_SIZE 256 // 缓冲区长度
    10 #define STDIN 0 // stdinfd
    11 #define STDOUT 1 // stdoutfd
    12 #define INVALID -1
    13 
    14 /** 负责 socket 的初始化,连接到服务器 */
    15 int socket_setup(const char *serv_ip, int serv_port)
    16 {
    17     int rtn, sockfd = socket(AF_INET, SOCK_STREAM, 0);
    18     struct sockaddr_in sockaddr;
    19 
    20     bzero(&sockaddr, sizeof(sockaddr));
    21     sockaddr.sin_family = AF_INET;
    22     sockaddr.sin_port = htons(serv_port);
    23     inet_pton(AF_INET, serv_ip, &sockaddr.sin_addr);
    24 
    25     rtn = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
    26 
    27     if (rtn == INVALID)
    28     {
    29         puts("Connection failure");
    30         exit(1);
    31     }
    32     else
    33     {
    34         puts("Connection successful");
    35         return sockfd;
    36     }
    37 }
    38 
    39 int main(int argc, const char *argv[])
    40 {
    41     int i, read_size, sockfd = socket_setup(argv[1], atoi(argv[2]));
    42     char buffer[BUF_SIZE];
    43     fd_set fdset;
    44 
    45     while (true)
    46     {
    47         FD_ZERO(&fdset);
    48         FD_SET(STDIN, &fdset);
    49         FD_SET(sockfd, &fdset);
    50         select(sockfd + 1, &fdset, NULL, NULL, NULL);
    51 
    52         /** socket -> 标准输出 */
    53         if (FD_ISSET(sockfd, &fdset))
    54         {
    55             read_size = read(sockfd, buffer, BUF_SIZE);
    56             write(STDOUT, buffer, read_size);
    57 
    58             if (read_size == 0)
    59             {
    60                 puts("Server close");
    61                 exit(1);
    62             }
    63         }
    64 
    65         /** 标准输入 -> socket */
    66         if (FD_ISSET(STDIN, &fdset))
    67         {
    68             read_size = read(STDIN, buffer, BUF_SIZE);
    69             write(sockfd, buffer, read_size);
    70         }
    71     }
    72 
    73     return 0;
    74 }

    服务端实现

    这个聊天室的实现难点是服务端,要能支持多用户聊天。

    我的实现方法是,用一个结构体数组clients保存已连接的客户信息,使用遍历数组的方式广播消息,一般来说都能想到这点,重点是利用select实现多路复用。

    因为客户socket是动态的,必须小心处理客户的连接/断开,当有用户连接时保存其socket,断开连接后将socket值为无效。

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <stdbool.h>
      5 #include <unistd.h>
      6 #include <time.h>
      7 #include <sys/socket.h>
      8 #include <arpa/inet.h>
      9 
     10 #define TIME_SIZE 16 // 表示时间的字符串长度
     11 #define IP_SIZE 16 // IP 字符串长度
     12 #define BUF_SIZE 256 // 缓冲区大小
     13 #define CLIENT_SIZE 8 // 允许的客户端数量
     14 #define BACKLOG CLIENT_SIZE // listen 队列长度,等于允许的客户端数量
     15 #define INVALID -1
     16 
     17 /** 保存客户端连接信息结构体 */
     18 struct CLIENT {
     19     int clientfd;
     20     struct sockaddr_in sockaddr;
     21     char ip[IP_SIZE];
     22     int port;
     23 } clients[CLIENT_SIZE];
     24 
     25 /** 初始化客户端列表 */
     26 void init_clients(void)
     27 {
     28     int i;
     29 
     30     for (i = 0; i< CLIENT_SIZE; i++)
     31         clients[i].clientfd = INVALID;
     32 }
     33 
     34 /** 向每一个已连接的用户广播消息 */
     35 void broadcast(char *msg)
     36 {
     37     int i;
     38 
     39     for (i = 0; i< CLIENT_SIZE; i++)
     40         if (clients[i].clientfd != INVALID)
     41             write(clients[i].clientfd, msg, strlen(msg));
     42 }
     43 
     44 /** 格式化要发送的消息 */
     45 void strfmsg(int i, char *buffer, const char *msg)
     46 {
     47     char curtime[TIME_SIZE];
     48     time_t curtime_t;
     49     struct tm *timeinfo;
     50 
     51     time(&curtime_t);
     52     timeinfo = localtime(&curtime_t);
     53     strftime(curtime, TIME_SIZE, "%X", timeinfo);
     54 
     55     sprintf(
     56         buffer,
     57         "<%s %s:%d> %s",
     58         curtime,
     59         clients[i].ip,
     60         clients[i].port,
     61         msg);
     62 }
     63 
     64 /** 新连接处理 */
     65 void accept_connect(int listenfd)
     66 {
     67     int connectfd, i;
     68     char buffer[BUF_SIZE];
     69     struct sockaddr_in clientaddr;
     70     socklen_t connectlen = sizeof(struct sockaddr_in);
     71 
     72     connectfd = accept(
     73         listenfd,
     74         (struct sockaddr *)&clientaddr,
     75         &connectlen);
     76 
     77     /** 记录连接者信息 */
     78     for (i = 0; i < CLIENT_SIZE; i++)
     79     {
     80         if (clients[i].clientfd == INVALID)
     81         {
     82             clients[i].clientfd = connectfd;
     83             memcpy(&clients[i].sockaddr, &clientaddr, connectlen);
     84             clients[i].port = ntohs(clients[i].sockaddr.sin_port);
     85             inet_ntop(
     86                 AF_INET,
     87                 &clients[i].sockaddr.sin_addr,
     88                 clients[i].ip,
     89                 IP_SIZE);
     90 
     91             strfmsg(i, buffer, "login\n");
     92             printf("%s", buffer);
     93             broadcast(buffer);
     94 
     95             break;
     96         }
     97     }
     98 
     99     /** 连接数超出 */
    100     if (i == CLIENT_SIZE)
    101     {
    102         strcpy(buffer, "Out of Number\n");
    103         write(connectfd, buffer, strlen(buffer));
    104         close(connectfd);
    105     }
    106 }
    107 
    108 /** 客户端消息处理 */
    109 void chat(fd_set fdset)
    110 {
    111     int sockfd, read_size, i;
    112     char read_buf[BUF_SIZE], send_buf[BUF_SIZE];
    113 
    114     for (i = 0; i < CLIENT_SIZE; i++)
    115     {
    116         sockfd = clients[i].clientfd;
    117 
    118         if (sockfd != INVALID && FD_ISSET(sockfd, &fdset))
    119         {
    120             read_size = read(sockfd, read_buf, BUF_SIZE - 1);
    121 
    122             if (read_size == 0)
    123             {
    124                 /** 失去连接 */
    125                 close(sockfd);
    126                 clients[i].clientfd = INVALID;
    127 
    128                 strfmsg(i, send_buf, "logout\n");
    129                 printf("%s", send_buf);
    130                 broadcast(send_buf);
    131 
    132                 continue;
    133             }
    134             else
    135             {
    136                 read_buf[read_size] = '\0';
    137                 strfmsg(i, send_buf, read_buf);
    138                 printf("%s", send_buf);
    139                 broadcast(send_buf);
    140             }
    141         }
    142     }
    143 }
    144 
    145 /** 负责 socket 初始化,绑定监听端口 */
    146 int socket_setup(int port)
    147 {
    148     int rtn, listenfd = socket(AF_INET, SOCK_STREAM, 0);
    149     struct sockaddr_in sockaddr;
    150 
    151     bzero(&sockaddr, sizeof(sockaddr));
    152     sockaddr.sin_family = AF_INET;
    153     sockaddr.sin_port = htons(port);
    154     sockaddr.sin_addr.s_addr= htonl(INADDR_ANY);
    155 
    156     rtn = bind(listenfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
    157 
    158     if (rtn == INVALID)
    159     {
    160         puts("Bind failure");
    161         exit(1);
    162     }
    163 
    164     if (listen(listenfd, BACKLOG) == INVALID)
    165     {
    166         puts("Listen failure");
    167         exit(1);
    168     }
    169 
    170     puts("Service startup");
    171     return listenfd;
    172 }
    173 
    174 int main(int argc, const char *argv[])
    175 {
    176     int maxfdp, i, listenfd = socket_setup(atoi(argv[1]));
    177     fd_set fdset;
    178 
    179     init_clients();
    180 
    181     while (true)
    182     {
    183         FD_ZERO(&fdset);
    184         FD_SET(listenfd, &fdset);
    185         maxfdp = listenfd;
    186 
    187         /** 将可用的客户端 socket 加入 fdset,并计算 maxfdp */
    188         for (i = 0; i < CLIENT_SIZE; i++)
    189         {
    190             if (clients[i].clientfd != INVALID)
    191             {
    192                 FD_SET(clients[i].clientfd, &fdset);
    193 
    194                 if (clients[i].clientfd > maxfdp)
    195                     maxfdp = clients[i].clientfd;
    196             }
    197         }
    198 
    199         select(maxfdp + 1, &fdset, NULL, NULL, NULL);
    200 
    201         if (FD_ISSET(listenfd, &fdset))
    202             accept_connect(listenfd);
    203 
    204         chat(fdset);
    205     }
    206 
    207     return 0;
    208 }
  • 相关阅读:
    EasyFlash 的初始化配置
    不能靠眼睛之 KEIL 中失效代码灰暗特性
    C 头文件、宏、编译问题
    C++ 中 const、volatile、mutable的用法
    【转】C++ const 关键字总结
    你想要的成都全攻略,好耍不重样——成都胖娃呕心巨作
    【转】RO段、RW段和ZI段 --Image$$??$$Limit 含义(zz)
    深有体会内存对系统性能的重要性
    毕业论文编写笔记
    (二)基于商品属性的相似商品推荐算法——Flink SQL实时计算实现商品的隐式评分
  • 原文地址:https://www.cnblogs.com/7c00/p/2775510.html
Copyright © 2011-2022 走看看