之前在学习关于网络tcp和多线程的编程,学了知识以后不用一下总绝对心虚,于是就编写了一个基于tcp和多线程的多人聊天室。
具体的实现过程:
服务器端:绑定socket对象->设置监听数->等待连接->有客户端连接就新建一个线程,这个线程中,一旦就收到这个客户发送的消息,就广播的向其他客户端发送同样的消息。
客户端:向客户端连接->新建线程用来接收服务器端发送的消息,同时主进程用来发送消息
话不多说,直接上代码
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/socket.h> 4 #include <pthread.h> 5 #include <arpa/inet.h> 6 #include <netinet/in.h> 7 #include <string.h> 8 #include <unistd.h> 9 #include <stdlib.h> 10 11 typedef struct sockaddr *sockaddrp; 12 13 //存储客户端地址的结构体数组 14 struct sockaddr_in src_addr[50]; 15 socklen_t src_len = sizeof(src_addr[0]); 16 17 18 19 //连接后记录confd数组 20 int confd[50] = {}; 21 22 23 //设置连接人数 24 int count = 0; 25 26 27 void *broadcast(void *indexp) 28 { 29 int index = *(int *)indexp; 30 char buf_rcv[255] = {}; 31 char buf_snd[255] = {}; 32 //第一次读取用户姓名 33 char name[20] = {}; 34 int ret = recv(confd[index],name,sizeof(name),0); 35 if(0 > ret) 36 { 37 perror("recv"); 38 close(confd[index]); 39 return; 40 } 41 42 while(1) 43 { 44 bzero(buf_rcv,sizeof(buf_rcv)); 45 recv(confd[index],buf_rcv,sizeof(buf_rcv),0); 46 47 //判断是否退出 48 if(0 == strcmp("quit",buf_rcv)) 49 { 50 sprintf(buf_snd,"%s已经退出悟空聊天室",name); 51 for(int i = 0;i <= count;i++) 52 { 53 if(i == index || 0 == confd[i]) 54 { 55 continue; 56 } 57 58 send(confd[i],buf_snd,strlen(buf_snd),0); 59 } 60 confd[index] = -1; 61 pthread_exit(0); 62 63 } 64 65 66 sprintf(buf_snd,"%s:%s",name,buf_rcv); 67 printf("%s ",buf_snd); 68 for(int i = 0;i <= count;i++) 69 { 70 if(i == index || 0 == confd[i]) 71 { 72 continue; 73 } 74 75 send(confd[i],buf_snd,sizeof(buf_snd),0); 76 } 77 78 } 79 80 } 81 82 83 84 85 86 int main(int argc,char **argv) 87 { 88 printf("悟空聊天室服务器端开始运行 "); 89 90 91 //创建通信对象 92 int sockfd = socket(AF_INET,SOCK_STREAM,0); 93 if(0 > sockfd) 94 { 95 perror("socket"); 96 return -1; 97 } 98 99 //准备地址 100 struct sockaddr_in addr = {AF_INET}; 101 addr.sin_port = htons(atoi(argv[1])); 102 addr.sin_addr.s_addr = inet_addr(argv[2]); 103 104 socklen_t addr_len = sizeof(addr); 105 106 107 108 //绑定 109 int ret = bind(sockfd,(sockaddrp)&addr,addr_len); 110 if(0 > ret) 111 { 112 perror("bind"); 113 return -1; 114 } 115 116 117 //设置最大排队数 118 listen(sockfd,50); 119 120 int index = 0; 121 122 123 while(count <= 50) 124 { 125 confd[count] = accept(sockfd,(sockaddrp)&src_addr[count],&src_len); 126 ++count; 127 //保存此次客户端地址所在下标方便后续传入 128 index = count-1; 129 130 pthread_t tid; 131 int ret = pthread_create(&tid,NULL,broadcast,&index); 132 if(0 > ret) 133 { 134 perror("pthread_create"); 135 return -1; 136 } 137 138 139 } 140 141 142 }
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/socket.h> 4 #include <arpa/inet.h> 5 #include <netinet/in.h> 6 #include <pthread.h> 7 #include <string.h> 8 9 10 11 typedef struct sockaddr *sockaddrp; 12 int sockfd; 13 14 void *recv_other(void *arg) 15 { 16 char buf[255]= {}; 17 while(1) 18 { 19 int ret = recv(sockfd,buf,sizeof(buf),0); 20 if(0 > ret) 21 { 22 perror("recv"); 23 return; 24 } 25 printf("%s ",buf); 26 } 27 } 28 29 30 31 32 int main(int argc,char **argv) 33 { 34 if(3 != argc) 35 { 36 perror("参数错误"); 37 return -1; 38 } 39 40 //建立socket对象 41 sockfd = socket(AF_INET,SOCK_STREAM,0); 42 if(0 > sockfd) 43 { 44 perror("socket"); 45 return -1; 46 } 47 48 //准备连接地址 49 struct sockaddr_in addr = {AF_INET}; 50 addr.sin_port = htons(atoi(argv[1])); 51 addr.sin_addr.s_addr = inet_addr(argv[2]); 52 53 socklen_t addr_len = sizeof(addr); 54 55 56 //连接 57 int ret = connect(sockfd,(sockaddrp)&addr,addr_len); 58 if(0 > ret) 59 { 60 perror("connect"); 61 return -1; 62 } 63 64 //发送名字 65 char buf[255] = {}; 66 char name[255] = {}; 67 printf("请输入您的昵称:"); 68 scanf("%s",name); 69 ret = send(sockfd,name,strlen(name),0); 70 if(0 > ret) 71 { 72 perror("connect"); 73 return -1; 74 } 75 76 //创建接收子线程 77 pthread_t tid; 78 ret = pthread_create(&tid,NULL,recv_other,NULL); 79 80 if(0 > ret) 81 { 82 perror("pthread_create"); 83 return -1; 84 } 85 //循环发送 86 while(1) 87 { 88 //printf("%s:",name); 89 scanf("%s",buf); 90 int ret = send(sockfd,buf,strlen(buf),0); 91 if(0 > ret) 92 { 93 perror("send"); 94 return -1; 95 } 96 97 //输入quit退出 98 if(0 == strcmp("quit",buf)) 99 { 100 printf("%s,您已经退出了悟空聊天室 ",name); 101 return 0; 102 } 103 104 } 105 106 }
将两份代码分别编译生成相应可执行文件,例如在Linux下server,client,然后先执行./server 端口号 ip ,再执行./client 端口号 ip就可以运行这个聊天室了。
总结:关于网络编程,tcp是一种连接方式的通信方式,两边一旦建立连接,就可以通过send和recv函数发送消息,比较的可靠,缺点是速度比较慢(相对于udp来说)。另外关于多线程编程方面,线程其实是一个进程的实体,是一个进程的组成部分,多个线程共享除了栈区以外的大部分区域,因此进程间的通信比较方便,这种方便带来的代价是,当多个进程同时去操作同一量时,容易造成不可预知的错误,因此就引入了互斥量(锁)的概念,互斥量的使用就保证了进程间通信的同步。