更多资料请点击:我的目录
本篇仅用于记录自己所学知识及应用,代码仍可优化,仅供参考,如果发现有错误的地方,尽管留言于我,谢谢。
编写一个TCP吐槽聊天室,接收来自各方TCP客户端的吐槽信息,将信息直接输出到屏幕上,并将信息群发给所有已经连上的客户端。
要求:
1.客户端可以根据需要,发送群发消息,或者指定某个人发送私聊消息。
2.客户端可以屏蔽别人的群发消息。
3.服务端仅展示群发消息,不展示私聊消息。
4.当客户端指定私聊的对端不存在或者已经下线时,服务端给发送者必要的提示。
运行结果:
/*
服务器部分
*/
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int clients = 0; //记录当前的已连接用户数量
int csum = 0;
int now = 0;
struct user // 用户节点
{
int connfd; //套接字
struct sockaddr_in addr; //IP地址
};
struct user users[50]; //存放客户端的信息
struct user senus[50]; //存放需要群发信息的客户端(屏蔽别人的群发消息)
void finish()
{
for(int k=0; k<clients; k++)
{
write(users[k].connfd,"quit",4); //当服务器中断时,向服务器发送quit
close(users[k].connfd);
}
exit(0);
}
int main ()
{
int tcpskck=socket(AF_INET, SOCK_STREAM, 0); //创建套接字
int on = 1;
setsockopt(tcpskck, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //设置端口为断开后可以立即重复使用
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
bzero(&addr, len);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); //自动获取本机IP
addr.sin_port = htons(10086); //端口号PORT
if(bind(tcpskck, (struct sockaddr *)&addr, len) == -1) //绑定地址
{
perror("绑定地址失败");
exit(0);
}
listen (tcpskck,3); //进入监听状态
//准备3个套接字集合
fd_set rset; //专门用来监控读就绪状态
fd_set wset; //专门用来监控写就绪状态
fd_set eset; //专门用来监控异常就绪状态
int maxfd = 0; //坐等各方的连接,并顺便获取对方的地址
char buf[100] = {0};
char *to_ip; //存放客户端发送给指定客户端的ip地址
int to_port; //存放客户端发送给指定客户端的端口号
char to_buf[100] = {0}; //存放客户端发送的指定消息
while(1)
{
signal(SIGINT,finish);
FD_ZERO(&rset); //套接字集合清零
FD_ZERO(&wset);
FD_ZERO(&eset);
//将监听套接字fd统统放进对应的集合中
FD_SET(tcpskck, &rset); //监控fd的读就绪状态
maxfd = tcpskck;
for(int k=0; k<clients; k++) //将所有已连接套接字connfd统统放进对应的集合中
{
FD_SET(users[k].connfd, &rset); //监控connfd的读就绪状态
FD_SET(users[k].connfd, &eset); //监控connfd的异常就绪状态
maxfd = maxfd> users[k].connfd ? maxfd : users[k].connfd;
}
// 多路监控:
// select随即进入睡眠,等待直到三个集合中出现一个或多个套接字就绪
// 其余未就绪的套接字,将会被select从集合中抹除
// select的返回值,是就绪的套接字的个数
select(maxfd+1, &rset, &wset, &eset, NULL); //NULL为无限等待
// 逐个判断套接字,看看哪个存留在集合中
// 存留的套接字就是就绪的,被抹除的套接字就是未就绪的
if(FD_ISSET(tcpskck, &rset)) //有新连接请求
{
bzero(&users[clients], sizeof(struct user));
users[clients].connfd = accept(tcpskck, (struct sockaddr *)&users[clients].addr, &len);
senus[clients] = users[clients]; //同步senus与users
if(users[clients].connfd > 0)
{
printf("【%s:%u】连接成功!\n", inet_ntoa(users[clients].addr.sin_addr),
ntohs(users[clients].addr.sin_port));
printf("当前已连接的客户端数目:【%d】\n", ++clients);
csum = clients; //需要发消息的总数,与clients同步
}
}
// 逐个判断已连接套接字connfd,看看哪个有数据
for(int k=0; k<clients; k++)
{
if(FD_ISSET(users[k].connfd, &rset)) //该客户端发来消息
{
now = users[k].connfd; //获取自己的套接字
bzero(buf, 100);
if(read(users[k].connfd, buf, 100) == 0) //判断客户端是否断开,读取消息
{
printf("【%s:%u】已断开连接,再见!\n", inet_ntoa(users[k].addr.sin_addr),
ntohs(users[k].addr.sin_port));
close(users[k].connfd);
printf("当前已连接的客户端数目:【%d】\n", --clients);
csum = clients; //需要发消息的总数,与clients同步
for(int j = k; j < clients; j++) //客户端断开后,将后面所有客户端的套接字向前移
{
users[j].connfd = users[j+1].connfd;
senus[j].connfd = senus[j+1].connfd;
}
continue;
}
int flag = 0;
{
if(strstr(buf,"-") != NULL) //指定信息格式: IP-PORT-message
{
flag = 1;
bzero(to_buf,100);
to_ip = (strtok(buf,"-")); //获取发送给指定客户端的ip地址
to_port = atoi(strtok(NULL,"-")); //获取发送给指定客户端的端口号
sprintf(to_buf,"%s",(strtok(NULL,"-"))); //获取指定的信息
if(strstr(to_buf,"no") != NULL) //屏蔽群发的消息
{
for(int i = k; i < clients-1; i++)
{
senus[i] = users[i+1];
}
csum--; //需要发消息的总数--
continue;
}
for(int i=0; i<clients; i++)
{
//排除把消息发回给自己
if(strstr((inet_ntoa(users[i].addr.sin_addr)),to_ip)!=NULL && to_port == ntohs(users[i].addr.sin_port))
{
write(users[i].connfd,to_buf,strlen(to_buf));
}
}
}
if(flag == 0)
{
for(int i=0; i<csum; i++)
{
if(senus[i].connfd != now) //排除把消息发回给自己
{
write(senus[i].connfd,buf,strlen(buf));
}
}
printf("【%s:%u】:%s\n", inet_ntoa(users[k].addr.sin_addr),
ntohs(users[k].addr.sin_port), buf);
}
}
}
if(FD_ISSET(users[k].connfd, &eset)) // 该客户端发来紧急数据
{
bzero(buf, 100);
if(recv(users[k].connfd, buf, 100, MSG_OOB) == 0)
{
printf("【%s:%u】已断开连接,再见!\n", inet_ntoa(users[k].addr.sin_addr),
ntohs(users[k].addr.sin_port));
close(users[k].connfd);
printf("当前已连接的客户端数目:【%d】\n", --clients);
csum = clients;
for(int j = k; j < clients; j++) //客户端断开后,将后面所有客户端的套接字向前移
{
users[j].connfd = users[j+1].connfd;
senus[j].connfd = senus[j+1].connfd;
}
continue;
}
printf("【%s:%u】:(紧急数据)%s\n", inet_ntoa(users[k].addr.sin_addr),
ntohs(users[k].addr.sin_port), buf);
}
}
}
return 0;
}
/*
客户端1部分:
*/
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <pthread.h>
time_t now ;
struct tm *tm_now ; //定义当前时间结构体
int tcpsock;
int m = 61, s = 61; //定义分、秒初始值为61
pthread_t id;
void *fun()
{
char recv[100];
while(1)
{
bzero(recv,100);
if(read(tcpsock,recv,100)) //接收服务器发送的消息
{
if(strstr(recv,"quit") != NULL) //判断时候收到“quit”(服务器中断)
{
printf("服务器出错!\n");
close(tcpsock);
exit(0);
}
printf("receive %s\n",recv);
}
}
}
void finish()
{
char fbuf[10] = {0};
sprintf(fbuf,"%d gone!\n",tcpsock); //向其他所有客户端发送退出信息
write(tcpsock,fbuf,strlen(fbuf)); //当客户端中断时,向服务器发送gone
close(tcpsock);
pthread_cancel(id);
exit(0);
}
int main()
{
int ret;
char buf[100];
struct sockaddr_in bindaddr; //定义ipv4地址结构体变量
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.157"); //绑定客户端自己的ip地址
bindaddr.sin_port=htons(10000); //绑定客户端自己的端口号
struct sockaddr_in serveraddr; //定义结构体变量存放对方(服务器)的ip和端口号
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=inet_addr("192.168.1.157"); //服务器的ip地址
serveraddr.sin_port=htons(10086); //服务器的端口号
tcpsock=socket(AF_INET,SOCK_STREAM,0); //创建tcp套接字
if(tcpsock==-1)
{
perror("创建tcp套接字!\n");
return -1;
}
int on=1;
setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //设置端口释放后立即就可以被再次使用
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr)); //绑定ip和端口号
if(ret==-1)
{
perror("绑定失败!\n");
return -1;
}
ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr)); //连接服务器
if(ret==-1)
{
perror("连接服务器!\n");
return -1;
}
signal(SIGINT,finish);
pthread_create(&id,NULL,&fun,NULL);
printf("请输入要发送给服务器的信息!\n"); //发送信息给服务器
while(1)
{
bzero(buf,100);
scanf("%s",buf);
if(strstr(buf,"quit")!=NULL) //当客户端输入quit退出时,调用finish()函数
{
finish(); //向服务器发送quit
}
{
time(&now) ; //获取当前时间
tm_now = localtime(&now);
if(m == tm_now->tm_min && tm_now->tm_sec - s < 1) //如果分相等,秒相差1s
{
printf("你说话太快了,请歇息一下再来!\n");
}
else
{
write(tcpsock,buf,strlen(buf)); //发送消息
time(&now) ; //获取本次消息发送的时间
tm_now = localtime(&now);
m = tm_now->tm_min;
s = tm_now->tm_sec ;
}
}
}
close(tcpsock);
return 0;
}
/*
客户端2部分:
*/
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <pthread.h>
time_t now ;
struct tm *tm_now ; //定义当前时间结构体
int tcpsock;
int m = 61, s = 61; //定义分、秒初始值为61
pthread_t id;
void *fun()
{
char recv[100];
while(1)
{
bzero(recv,100);
if(read(tcpsock,recv,100)) //接收服务器发送的消息
{
if(strstr(recv,"quit") != NULL) //判断时候收到“quit”(服务器中断)
{
printf("服务器出错!\n");
close(tcpsock);
exit(0);
}
printf("receive %s\n",recv);
}
}
}
void finish()
{
char fbuf[10] = {0};
sprintf(fbuf,"%d gone!\n",tcpsock); //向其他所有客户端发送退出信息
write(tcpsock,fbuf,strlen(fbuf)); //当客户端中断时,向服务器发送gone
close(tcpsock);
pthread_cancel(id);
exit(0);
}
int main()
{
int ret;
char buf[100];
struct sockaddr_in bindaddr; //定义ipv4地址结构体变量
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.157"); //绑定客户端自己的ip地址
bindaddr.sin_port=htons(20000); //绑定客户端自己的端口号
struct sockaddr_in serveraddr; //定义结构体变量存放对方(服务器)的ip和端口号
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=inet_addr("192.168.1.157"); //服务器的ip地址
serveraddr.sin_port=htons(10086); //服务器的端口号
tcpsock=socket(AF_INET,SOCK_STREAM,0); //创建tcp套接字
if(tcpsock==-1)
{
perror("创建tcp套接字!\n");
return -1;
}
int on=1;
setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //设置端口释放后立即就可以被再次使用
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr)); //绑定ip和端口号
if(ret==-1)
{
perror("绑定失败!\n");
return -1;
}
ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr)); //连接服务器
if(ret==-1)
{
perror("连接服务器!\n");
return -1;
}
signal(SIGINT,finish);
pthread_create(&id,NULL,&fun,NULL);
printf("请输入要发送给服务器的信息!\n"); //发送信息给服务器
while(1)
{
bzero(buf,100);
scanf("%s",buf);
if(strstr(buf,"quit")!=NULL) //当客户端输入quit退出时,调用finish()函数
{
finish(); //向服务器发送quit
}
{
time(&now) ; //获取当前时间
tm_now = localtime(&now);
if(m == tm_now->tm_min && tm_now->tm_sec - s < 1) //如果分相等,秒相差1s
{
printf("你说话太快了,请歇息一下再来!\n");
}
else
{
write(tcpsock,buf,strlen(buf)); //发送消息
time(&now) ; //获取本次消息发送的时间
tm_now = localtime(&now);
m = tm_now->tm_min;
s = tm_now->tm_sec ;
}
}
}
close(tcpsock);
return 0;
}
/*
客户端3部分:
*/
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <pthread.h>
time_t now ;
struct tm *tm_now ; //定义当前时间结构体
int tcpsock;
int m = 61, s = 61; //定义分、秒初始值为61
pthread_t id;
void *fun()
{
char recv[100];
while(1)
{
bzero(recv,100);
if(read(tcpsock,recv,100)) //接收服务器发送的消息
{
if(strstr(recv,"quit") != NULL) //判断时候收到“quit”(服务器中断)
{
printf("服务器出错!\n");
close(tcpsock);
exit(0);
}
printf("receive %s\n",recv);
}
}
}
void finish()
{
char fbuf[10] = {0};
sprintf(fbuf,"%d gone!\n",tcpsock); //向其他所有客户端发送退出信息
write(tcpsock,fbuf,strlen(fbuf)); //当客户端中断时,向服务器发送gone
close(tcpsock);
pthread_cancel(id);
exit(0);
}
int main()
{
int ret;
char buf[100];
struct sockaddr_in bindaddr; //定义ipv4地址结构体变量
bzero(&bindaddr,sizeof(bindaddr));
bindaddr.sin_family=AF_INET;
bindaddr.sin_addr.s_addr=inet_addr("192.168.1.157"); //绑定客户端自己的ip地址
bindaddr.sin_port=htons(30000); //绑定客户端自己的端口号
struct sockaddr_in serveraddr; //定义结构体变量存放对方(服务器)的ip和端口号
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=inet_addr("192.168.1.157"); //服务器的ip地址
serveraddr.sin_port=htons(10086); //服务器的端口号
tcpsock=socket(AF_INET,SOCK_STREAM,0); //创建tcp套接字
if(tcpsock==-1)
{
perror("创建tcp套接字!\n");
return -1;
}
int on=1;
setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //设置端口释放后立即就可以被再次使用
ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr)); //绑定ip和端口号
if(ret==-1)
{
perror("绑定失败!\n");
return -1;
}
ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr)); //连接服务器
if(ret==-1)
{
perror("连接服务器!\n");
return -1;
}
signal(SIGINT,finish);
pthread_create(&id,NULL,&fun,NULL);
printf("请输入要发送给服务器的信息!\n"); //发送信息给服务器
while(1)
{
bzero(buf,100);
scanf("%s",buf);
if(strstr(buf,"quit")!=NULL) //当客户端输入quit退出时,调用finish()函数
{
finish(); //向服务器发送quit
}
{
time(&now) ; //获取当前时间
tm_now = localtime(&now);
if(m == tm_now->tm_min && tm_now->tm_sec - s < 1) //如果分相等,秒相差1s
{
printf("你说话太快了,请歇息一下再来!\n");
}
else
{
write(tcpsock,buf,strlen(buf)); //发送消息
time(&now) ; //获取本次消息发送的时间
tm_now = localtime(&now);
m = tm_now->tm_min;
s = tm_now->tm_sec ;
}
}
}
close(tcpsock);
return 0;
}