Linux聊天室(IO多路复用、多线程)
1、功能描述
建立一个服务器,有很多个客户端加入服务器,就可以实现群聊功能
基于tcp的select服务器与客户端
2、知识点
- 服务器:建立套接字、设置ip和port、绑定ip和port、监听、连接、收发消息。
- 客户端:建立套接字、配置服务器ip和port、连接服务器、创建线程收发消息。
- 服务器需要很多连接加入,所以用到IO多路复用。
3、功能实现
1、chat_room.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <sys/select.h>
// 聊天室和人数
static char rooms[20] = {0};
static int pnum=0;
int server_init(char *ip, int port)
{
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sfd){
perror("socket");
return -1;
}
//设置ip port
struct sockaddr_in sddr, cddr;
memset(&sddr, 0, sizeof(sddr));
bzero(&cddr, sizeof(struct sockaddr_in));
sddr.sin_family = AF_INET;
sddr.sin_port = htons(8008);
//设置服务器为自己主机的任意ip == INADDR_ANY
sddr.sin_addr.s_addr = inet_addr("0.0.0.0");
/*
*bind:将ip,port 和 套接字绑定
*参数:sockfd (struct sockaddr *) saddr大小
*返回值 0成功 -1失败
* */
if(-1 == bind(sfd,(void *)&sddr,sizeof(sddr)))
{
perror("bind");
return -1;
}
//监听
/*
*listen:监听套接字有无连接进来
*sockfd: 目标套接字的文件描述符
*10: 监听队列的长度
*return : 0 success -1 failed
* */
if(-1 == listen(sfd, 10))
{
perror("listen");
return -1;
}
puts("listen---------");
return sfd;
}
int server_wait_client_connect(int sfd)
{
struct sockaddr_in sddr, cddr;
socklen_t len = sizeof(cddr);
int nfd = accept(sfd,(void *)&cddr,&len);
if(-1 == nfd){
perror("accept");
return -1;
}
//解析客户端ip 和 port 需要用到inet_ntoa() ntohs()转换
printf("IP:%s PORT:%d connected.\n",inet_ntoa(cddr.sin_addr),
ntohs(cddr.sin_port));
return nfd;
}
int main(int argc, char *argv[])
{
int listenfd = server_init("0.0.0.0", 8008);
if(-1 == listenfd)
return -1;
//构建集合 操作集合 备份集合
fd_set readfds, backfds;
FD_ZERO(&backfds);
//把关注的文件描述符添加到备份集合
FD_SET(listenfd, &backfds);
//记录文件描述符的个数
int nfds = listenfd + 1;
int ret = -1;
char buf[100]={0};
while(1){
//设置操作的集合
readfds = backfds;
//个数 读取 写 时间
//select会阻塞循环检测关注的IO集合
ret = select(nfds,&readfds,NULL,NULL,NULL);
if(ret == -1)
{
perror("select");
return -1;
}
//遍历文件描述符是否还在集合中
for(int fd=0;fd<nfds;fd++){
if(!FD_ISSET(fd, &readfds))
continue;//不再集合中结束本次循环
//判断是否是listenfd准备就绪
if(fd == listenfd){
int confd = server_wait_client_connect(fd);
//将通信套接字confd加入备份集合
FD_SET(confd, &backfds);
//集合内的文件描述符+1
nfds = (confd>=nfds?nfds+1:nfds);
//聊天室人数+1
pnum++;
//加入通信套接字到聊天室
rooms[pnum-1] = confd;
printf("聊天室人数:%d\n", pnum);
}
else{
//printf("加入的fd:%d\n", fd);
//处理通信套接字
ret = read(fd, buf, 100);
if(ret <= 0)
{
//将对应通信套接字从集合删除
FD_CLR(fd, &backfds);
//聊天室人数-1
//printf("触发-1\n");
int j=0;
for(int i=0;i<pnum;i++){
if(rooms[i] == fd){
pnum--;
continue;
}
rooms[j++] = rooms[i];
}
printf("聊天室人数:%d\n", pnum);
close(fd);
continue;
}
//把消息转发给聊天室的所有人
for(int i=0;i<pnum;i++){
write(rooms[i], buf, 100);
}
}
}
}
close(listenfd);
return 0;
}
2、client.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
void *send_msg(void *arg);
void *recv_msg(void *arg);
int main(int argc, char *argv[])
{
if(argc != 3){
printf("%s ip port.\n", argv[0]);
return -1;
}
//参数:ip协议 TCP 0
//应用层到底层的通道
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sfd){
perror("socket");
return -1;
}
//设置ip port
struct sockaddr_in sddr, cddr;
memset(&sddr, 0, sizeof(sddr));
bzero(&cddr, sizeof(struct sockaddr_in));
sddr.sin_family = AF_INET;
sddr.sin_port = htons(atoi(argv[2]));
//设置服务器为自己主机的任意ip == INADDR_ANY
sddr.sin_addr.s_addr = inet_addr(argv[1]);
int confd = connect(sfd,(void *)&sddr,sizeof(sddr));
if(-1 == confd)
{
perror("connect");
return -1;
}
printf("connect success.\n");
//创建两个子线程
pthread_t sid, rid;
//发送消息线程
sid = pthread_create(&sid,NULL, send_msg, &sfd);
if(sid == -1)
{
perror("pthread_create1");
return -1;
}
//接收消息线程
rid = pthread_create(&rid,NULL, recv_msg, &sfd);
if(rid == -1)
{
perror("pthread_create1");
return -1;
}
char buf[100] = {0};
while(1){
sleep(1);
}
return 0;
}
void *send_msg(void *arg){
int fd = *((int *)arg);
char buf[100] = {0};
while(1){
fgets(buf, 99, stdin);
write(fd, buf, 100);
if(strncmp(buf,"quit",4)==0)
exit(0);
}
}
void *recv_msg(void *arg){
int fd = *((int *)arg);
char buf[100] = {0};
while(1){
int n = read(fd, buf, 100);
if(n==0) exit(0);
printf("Recv:%s", buf);
}
}
4、编译
gcc chat_room.c -o chat
gcc client.c -o cli -lpthread
服务器:
./chat
客户端:
./cli 127.0.0.1 8008
./cli 127.0.0.1 8008
./cli 127.0.0.1 8008