项目需求
- 如果有用户登录,其他用户可以收到这个人的登录信息
- 如果有人发消息,其他用户可以收到这个人的群聊信息
- 如果有人下线了,其他用户可以收到这个人的下线信息
- 服务器可以发送系统信息
开发步骤
1.流程图(一个大致的思路和想法)
2.根据流程图写框架(像做填空题一样,先写出题目,后去填充)
3.逐步实现每个功能(会发现流程图中的错误,回过头去补充完善)
聊天室服务器
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#define ERR_LOG(msg) do{\
perror(msg);\
printf("%d %s %s\n",__LINE__,__func__,__FILE__);\
}while(0)
#define USRNLEN 10
#define DATALEN 245
#define BUFFLEN 256
#define DATELEN 30
typedef struct client
{
char uname[USRNLEN];
char usrIP[32];
unsigned short usrPORT;
struct client* next;
}USR;
//全局变量
int sfd;
struct sockaddr_in saddr;
struct sockaddr_in caddr;
socklen_t saddr_len;
socklen_t caddr_len;
USR* usrclints;
char name[USRNLEN];
char serverIP[32];
char serverPORT[16];
char date[DATELEN];
time_t t;
struct tm *tm;
//登录下线消息
char usrname[USRNLEN];
char buff[BUFFLEN];
//转发数据消息
char usrname_tsend[USRNLEN];
char chatdata_tsend[DATALEN];
char buff_tsend[BUFFLEN];
//系统消息
char usrname_tsyssend[USRNLEN];
char chatdata_tsyssend[DATALEN];
char buff_tsyssend[BUFFLEN];
//函数声明
USR* create_usr();
USR* insert_usr(USR* head,char* buf,struct sockaddr_in addr);
USR* delete_usr(USR* head,struct sockaddr_in addr);
void printcli(USR* head);
void transmit(USR* head,char* buf,int size,struct sockaddr_in curaddr);
void systransmit(USR* head,char* buf,int size);
//线程2:发送系统消息
void* handler(void* arg)
{
pthread_detach(pthread_self());
while(1)
{
bzero(usrname_tsyssend,USRNLEN);
bzero(chatdata_tsyssend,DATALEN);
bzero(buff_tsyssend,BUFFLEN);
strcpy(usrname_tsyssend,"SERVER");
fgets(chatdata_tsyssend,DATALEN,stdin);
chatdata_tsyssend[strlen(chatdata_tsyssend)-1] = 0;
buff_tsyssend[0] = 7;
strcat(buff_tsyssend+1,usrname_tsyssend);
strcat(buff_tsyssend+11,chatdata_tsyssend);
systransmit(usrclints,buff_tsyssend,11+strlen(chatdata_tsyssend));
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
if(argc < 3)
{
printf("请输入本地的IP和PORT\n");
return 0;
}
//1、创建数据报式socket套接字
sfd = socket(AF_INET,SOCK_DGRAM,0);
if(sfd < 0)
{
ERR_LOG("socket");
return -1;
}
//2.绑定本地ip和端口号
strcpy(serverPORT,argv[2]);
strcpy(serverIP,argv[1]);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(*(unsigned short*)argv[2]);
//inet_aton(argv[1],&saddr.sin_addr);
inet_pton(AF_INET,argv[1],&saddr.sin_addr.s_addr);
//saddr.sin_addr.s_addr = inet_addr(argv[1]);
if(bind(sfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0)
{
perror("bind");
return -1;
}
//3.创建Client对象
usrclints = create_usr();
//初始化变量
caddr_len = sizeof(caddr);
saddr_len = sizeof(saddr);
//4.创建2个线程
pthread_t tid;
if(pthread_create(tid,NULL,handler,NULL) != 0)
{
ERR_LOG("pthread_create");
return -1;
}
printf("服务器启动成功\n");
//线程1:处理用户登录下线 & 接收转发用户聊天消息
while(1)
{
bzero(buff,BUFFLEN);
if(recvfrom(sfd,buff,BUFFLEN,0,(struct sockaddr*)&caddr,&caddr_len) < 0)
{
ERR_LOG("recvfrom");
break;
}
if(1 == buff[0])//注册登录
{
//插入链表
USR* cli = insert_usr(usrclints,buff,caddr);
//打印客户端登录信息
printf(">>");
time(&t);
tm = localtime(&t);
strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm);
printf("%s\n",date);
printf("增加用户%s:[%s:%d]到链表成功\n",cli->uname,cli->usrIP,cli->usrPORT);
printf("***当前所有在线用户列表***\n");//test
printcli(usrclints);//test
printf("**************************\n\n");//test
//发送ACK
bzero(buff,BUFFLEN);
buff[0] = 4;
if(sendto(sfd,buff,1,0,(struct sockaddr*)&caddr,saddr_len) < 0)
{
ERR_LOG("sendto");
break;
}
//转发用户登录信息给其他客户端
bzero(buff,BUFFLEN);
strcpy(buff+1,cli->uname);
buff[0] = 5;
transmit(usrclints,buff,strlen(buff),caddr);
}
else if(2 == buff[0])//下线
{
//从链表中删除
USR* delcli = delete_usr(usrclints,caddr);
//打印客户端登录信息
printf(">>");
time(&t);
tm = localtime(&t);
strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm);
printf("%s\n",date);
char delcliname[USRNLEN] = "";
strcpy(delcliname,delcli->uname);
printf("用户%s:[%s:%d]从链表删除成功\n",delcliname,delcli->usrIP,delcli->usrPORT);
free(delcli);
printf("***当前所有在线用户列表***\n");//test
printcli(usrclints);//test
printf("**************************\n\n");//test
//发送ACK
bzero(buff,BUFFLEN);
buff[0] = 8;
if(sendto(sfd,buff,1,0,(struct sockaddr*)&caddr,saddr_len) < 0)
{
ERR_LOG("sendto");
break;
}
//转发用户退出信息给其他客户端
bzero(buff,BUFFLEN);
strcpy(buff+1,delcliname);
buff[0] = 6;
transmit(usrclints,buff,strlen(buff),caddr);
}
else if(3 == buff[0])//转发用户聊天消息
{
//printf("%s:%s\n", buff+1,buff+11);//test
buff[0] = 9;
transmit(usrclints,buff,11+strlen(buff+11),caddr);
}
}
//退出
close(sfd);
return 0;
}
//创建Client链表
USR* create_usr()
{
USR* head = (USR*)malloc(sizeof(USR));
//头结点保存Server信息
strcpy(head->uname,"Server");
strcpy(head->usrIP,serverIP);
head->usrPORT = *(unsigned short*)serverPORT;
head->next = NULL;
return head;
}
//增加usr用户
USR* insert_usr(USR* head,char* buf,struct sockaddr_in addr)
{
USR* node = (USR*)malloc(sizeof(USR));
strcpy(node->uname,buf+1);
strcpy(node->usrIP,inet_ntoa(addr.sin_addr));
node->usrPORT = ntohs(addr.sin_port);
//头插
node->next = head->next;
head->next = node;
return node;
}
//删除usr用户
USR* delete_usr(USR* head,struct sockaddr_in addr)
{
//根据端口号进行删除
USR* pre = head;
USR* p = head;
unsigned short port = ntohs(addr.sin_port);
while(p->next != NULL)
{
p = p->next;
if(p->usrPORT == port)
{
pre->next = p->next;
return p;
}
pre = pre->next;
}
}
//测试:打印链表中所有的客户端用户
void printcli(USR* head)
{
USR *p = head;
while(p->next != NULL)
{
p = p->next;
printf("*IP:%s PORT:%d uname:%s\n",p->usrIP,p->usrPORT,p->uname);
}
}
//转发给其他用户
void transmit(USR* head,char* buf,int size,struct sockaddr_in curaddr)
{
unsigned short curport = ntohs(curaddr.sin_port);//当前用户的端口号
struct sockaddr_in otheraddr;
socklen_t otheraddr_len = sizeof(otheraddr);
otheraddr.sin_family = AF_INET;
USR *p = head;
while(p->next != NULL)
{
p = p->next;
if(curport != p->usrPORT)
{
otheraddr.sin_port = htons(p->usrPORT);
otheraddr.sin_addr.s_addr = inet_addr(p->usrIP);
if(sendto(sfd,buf,size,0,(struct sockaddr*)&otheraddr,otheraddr_len) < 0)
{
ERR_LOG("sendto");
break;
}
}
}
}
//系统广播
void systransmit(USR* head,char* buf,int size)
{
struct sockaddr_in alladdr;
socklen_t alladdr_len = sizeof(alladdr);
alladdr.sin_family = AF_INET;
USR *p = head;
while(p->next != NULL)
{
p = p->next;
alladdr.sin_port = htons(p->usrPORT);
alladdr.sin_addr.s_addr = inet_addr(p->usrIP);
if(sendto(sfd,buf,size,0,(struct sockaddr*)&alladdr,alladdr_len) < 0)
{
ERR_LOG("sendto");
break;
}
}
}
聊天室客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#define ERR_LOG(msg) do{\
perror(msg);\
printf("%d %s %s\n",__LINE__,__func__,__FILE__);\
}while(0)
#define USRNLEN 10
#define DATALEN 245
#define BUFFLEN 256
#define DATELEN 30
//全局变量
int sfd;
struct sockaddr_in saddr;
socklen_t saddr_len;
char name[USRNLEN];
char date[DATELEN];
time_t t;
struct tm *tm;
//登录下线消息
char usrname[USRNLEN];
char buff[BUFFLEN];
//接收聊天数据
char chatdata[DATALEN];
//发送数据消息
char usrname_tsend[USRNLEN];
char chatdata_tsend[DATALEN];
char buff_tsend[BUFFLEN];
//线程1:发送聊天数据 & 发送退出消息
void* handler_1(void* arg)
{
pthread_detach(pthread_self());
while(1)
{
bzero(usrname_tsend,USRNLEN);
bzero(chatdata_tsend,DATALEN);
bzero(buff_tsend,BUFFLEN);
strcpy(usrname_tsend,name);
fgets(chatdata_tsend,DATALEN,stdin);
chatdata_tsend[strlen(chatdata_tsend)-1] = 0;
if(strncasecmp(chatdata_tsend,"quit",4) == 0)//退出请求
{
//7.预下线
bzero(usrname,USRNLEN);
bzero(buff,BUFFLEN);
//封装预下线数据报并发送
buff[0] = 2;
strcat(buff+1,usrname);
if(sendto(sfd,buff,1+strlen(usrname),0,(struct sockaddr*)&saddr,saddr_len) < 0)
{
ERR_LOG("sendto");
break;
}
}
else//发送聊天数据
{
buff_tsend[0] = 3;
strcat(buff_tsend+1,usrname_tsend);
strcat(buff_tsend+11,chatdata_tsend);
if(sendto(sfd,buff_tsend,11+strlen(chatdata_tsend),0,(struct sockaddr*)&saddr,saddr_len) < 0)
{
ERR_LOG("sendto");
break;
}
}
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
if(argc < 3)
{
printf("请输入服务端的IP和PORT\n");
return 0;
}
//1、创建数据报式socket套接字
sfd = socket(AF_INET,SOCK_DGRAM,0);
if(sfd < 0)
{
ERR_LOG("socket");
return -1;
}
//2.填充服务器ip和端口号
saddr.sin_family = AF_INET;
saddr.sin_port = htons(*(unsigned short*)argv[2]);
saddr.sin_addr.s_addr = inet_addr(argv[1]);
saddr_len = sizeof(saddr);
//3.注册(发送usrname给server)
printf("请输入登录名--->");
fgets(usrname,USRNLEN,stdin);
usrname[strlen(usrname)-1] = 0;
strcpy(name,usrname);
//封装注册登录数据报并发送
buff[0] = 1;
strcat(buff+1,usrname);
if(sendto(sfd,buff,1+strlen(usrname),0,(struct sockaddr*)&saddr,saddr_len) < 0)
{
ERR_LOG("sendto");
return -1;
}
//4.等待server的ACK
bzero(buff,BUFFLEN);
if(recvfrom(sfd,buff,BUFFLEN,0,NULL,NULL) < 0)
{
ERR_LOG("recvfrom");
return -1;
}
//5.登录(打印登录成功)
if(4 == buff[0])
{
printf("***登录成功***\n");
printf("(提示:输入“quit”退出登录)\n");
printf("进入聊天界面>>>\n");
}
//6.创建2个线程
pthread_t tid;
if(pthread_create(tid,NULL,handler_1,NULL) != 0)
{
ERR_LOG("pthread_create");
return -1;
}
//线程2:接收聊天数据
while(1)
{
bzero(buff,BUFFLEN);
bzero(usrname,USRNLEN);
bzero(chatdata,DATALEN);
if(recvfrom(sfd,buff,BUFFLEN,0,NULL,NULL) < 0)
{
ERR_LOG("recvfrom");
return -1;
}
if(9 == buff[0])//其他用户消息
{
time(&t);
tm = localtime(&t);
strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm);
printf("%s",date);
printf("%s:%s\n",buff+1,buff+11);
}
else if(5 == buff[0])//用户上线
{
time(&t);
tm = localtime(&t);
strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm);
printf("%s",date);
printf(">>用户%s上线了\n",buff+1);
}
else if(6 == buff[0])//用户下线
{
time(&t);
tm = localtime(&t);
strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm);
printf("%s",date);
printf(">>用户%s下线了\n",buff+1);
}
else if(7 == buff[0])//系统消息
{
time(&t);
tm = localtime(&t);
strftime(date,DATELEN,"[%Y-%m-%d %H:%M:%S] ",tm);
printf("%s",date);
printf("%s:%s\n",buff+1,buff+11);
}
else if(8 == buff[0])//下线
{
printf("***退出成功***\n");
close(sfd);
exit(1);
}
}
close(sfd);
return 0;
}
演示
本文链接:https://shengto.top/network_programming/udp_chat.html
转载时须注明出处及本声明