实现多个客户端之间可以交换信息的简单聊天程序
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <pthread.h> /* 服务端示例中,需要掌握临界区的构成,访问全局变量 clnt_cnt 和数组 clnt_sock_array 的代码将构 成临界区,添加和删除客户端时,变量 clnt_cnt 和数组 clnt_sock_array 将同时发生变化。 因此下列情形会导致数据不一致,从而引发错误: 1. 线程 A 从数组 clnt_sock_array 中删除套接字信息,同时线程 B 读取 clnt_cnt 变量 2. 线程 A 读取变量 clnt_cnt ,同时线程 B 将套接字信息添加到 clnt_sock_array 数组 */ #define BUF_SIZE 256 #define MAX_CLEN 256 int clnt_cnt = 0; int clnt_sock_array[MAX_CLEN]; pthread_mutex_t mutex; void *handle_clnt(void *arg); void error_handling(char *msg); void send_msg(char *msg, int len); int main(int argc, char *argv[]) { int clnt_sock = 0; int serv_sock = 0; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size = 0; pthread_t pid; if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); exit(1); } //创建互斥锁 pthread_mutex_init(&mutex, NULL); //创建套接字 serv_sock = socket(AF_INET, SOCK_STREAM, 0); if (-1 == serv_sock) { error_handling("fail to socket!"); } //初始化IP地址 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); if (-1 == bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) { error_handling("fail to bind"); } if (-1 == listen(serv_sock, 5)) { error_handling("fail to bind"); } while (1) { clnt_addr_size = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size); //上锁 pthread_mutex_lock(&mutex); //将新客户端存入数组 clnt_sock_array[clnt_cnt++] = clnt_sock; //解锁 pthread_mutex_unlock(&mutex); //创建线程为新客户端服务,并且把clnt_sock作为参数 pthread_create(&pid, NULL, handle_clnt, &clnt_sock); //引导线程销毁,不会阻塞 pthread_detach(pid); //打印客户端的ip地址 printf("Connected client ID:%s\n", inet_ntoa(clnt_addr.sin_addr)); } close(serv_sock); return 0; } void *handle_clnt(void *arg) { int i = 0; int str_len = 0; int clnt_sock = *((int *)arg); char buf_msg[BUF_SIZE]; while ((str_len = read(clnt_sock, buf_msg, sizeof(buf_msg))) != 0) { send_msg(buf_msg, str_len); } //接受到的信息为0,说明当前客户端已经断开连接 pthread_mutex_lock(&mutex); //删除没有连接的客户端 for (i=0; i<clnt_cnt; i++) { if (clnt_sock == clnt_sock_array[i]) { while (i++ < clnt_cnt - 1) { clnt_sock_array[i] = clnt_sock_array[i + 1]; } break; } } clnt_cnt--; pthread_mutex_unlock(&mutex); close(clnt_sock); return NULL; } //向所有客户端全发送信息 void send_msg(char *msg, int len) { int i; pthread_mutex_lock(&mutex); for (i=0; i<clnt_cnt; i++) { write(clnt_sock_array[i], msg, len); } pthread_mutex_unlock(&mutex); } void error_handling(char *msg) { fputs(msg, stderr); fputc('\n', stderr); exit(1); }
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <pthread.h> #define BUF_SIZE 256 #define NAME_SIZE 20 void *recv_msg(void *arg); void *send_msg(void *arg); void error_handling(char *msg); char name[NAME_SIZE] = "[default]"; char msg[BUF_SIZE]; int main(int argc, char *argv[]) { int sock; struct sockaddr_in serv_addr; pthread_t send_thread; pthread_t recv_thread; void *thread_return; if (argc != 4) { printf("Usage : %s <IP> <port> <name>\n", argv[0]); exit(1); } sprintf(name, "[%s]", argv[3]); sock = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sock) { error_handling("fail to socket"); } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(atoi(argv[2])); serv_addr.sin_addr.s_addr = inet_addr(argv[1]); if (-1 == connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) { error_handling("fail to connect"); } //创建发送消息线程 pthread_create(&send_thread, NULL, send_msg, (void *)&sock); //创建接受消息线程 pthread_create(&recv_thread, NULL, recv_msg, (void *)&sock); //pthread_join()的作用可以这样理解:主线程等待子线程的终止 pthread_join(send_thread, &thread_return); pthread_join(recv_thread, &thread_return); close(sock); return 0; } void *send_msg(void *arg) { int sock = *((int *)arg); char name_msg[NAME_SIZE + BUF_SIZE]; while (1) { fgets(msg, BUF_SIZE, stdin); if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n")) { close(sock); exit(0); } sprintf(name_msg, "%s %s", name, msg); write(sock, name_msg, strlen(name_msg)); } return NULL; } void *recv_msg(void *arg) { int sock = *((int *)arg); char name_msg[NAME_SIZE + BUF_SIZE]; int str_len; while (1) { str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE -1); name_msg[str_len] = 0; fputs(name_msg, stdout); } return NULL; } void error_handling(char *msg) { fputs(msg, stderr); fputc('\n', stderr); exit(1); }
gcc chat_server.c -D_REENTRANT -o cserv -lpthread gcc chat_clnt.c -D_REENTRANT -o cclnt -lpthread ./cserv 8180 ./cclnt 127.0.0.1 8180 大郎 ./cclnt 127.0.0.1 8180 小潘