项目:基于UDP的网络聊天室
需求
项目需求
1.如果有用户登录,其他用户可以收到这个人的登录信息
2.如果有人发送信息,其他用户可以收到这个人的群聊信息
3.如果有人下线,其他用户可以收到这个人的下线信息
4.服务器可以发送系统信息
写项目的方法
1.画流程图
2.根据流程图写框架
3.将每个功能实现
流程图
客户端流程图
服务器流程图
代码示例
服务器端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <linux/input.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__",__LINE__);\
perror(msg);\
}while(0);
int sfd;
//定义单链表存储客户端IP和端口
typedef struct Node
{
union
{
int len; //头结点的数据域,记录个数
struct sockaddr_in sin; //普通节点的数据域
};
struct Node* next;
}linkList;
//定义数据包的结构体
typedef struct
{
char type; //L代表登录 Q代表退出 C代表通信
char name[20]; //姓名
char text[128]; //群聊消息
}Data;
Data msgdata;
//创建单链表
linkList* list_create()
{
linkList* L = (linkList*)malloc(sizeof(linkList));
if(NULL==L)
{
printf("创建失败!\n");
return NULL;
}
//初始化
L->len=0;
L->next=NULL;
//printf("创建成功!\n");
return L;
}
//服务器接收
void* SerRcv(void* arg)
{
linkList* L = *(linkList**)arg;
while(1)
{
//申请节点
linkList* p = (linkList*)malloc(sizeof(linkList));
if(NULL == p)
{
printf("节点申请失败\n");
return NULL;
}
p->next = NULL;
//长度
socklen_t addrlen = sizeof(p->sin);
//接收
bzero(msgdata.text,sizeof(msgdata.text));
bzero(msgdata.name,sizeof(msgdata.name));
ssize_t res = recvfrom(sfd,&msgdata,sizeof(msgdata),0,(struct sockaddr*)&(p->sin),&addrlen);
if(res < 0)
{
ERR_MSG("recvfrom");
break;
}
if(msgdata.type=='L')
{
//将存储地址信息的节点插入到单链表中
//定义遍历节点
linkList* q = L;
while(q->next!=NULL)
q=q->next;
q->next = p;
L->len++;
//printf("长度:%d\n",L->len);
printf("%s [%s:%d]:login\n",msgdata.name,inet_ntoa((p->sin).sin_addr),ntohs((p->sin).sin_port));
//发送上线通知
linkList* s = L;
while(s->next!=NULL)
{
s=s->next;
if(ntohs((s->sin).sin_port)!=ntohs((p->sin).sin_port))
{
//服务器将收到的信息转发给所有人
if(sendto(sfd,&msgdata,sizeof(msgdata),0,(struct sockaddr*)&(s->sin),sizeof(s->sin))<0)
{
ERR_MSG("sendto");
break;
}
}
}
}
//printf("%c %s %s\n",msgdata.type,msgdata.name,msgdata.text);
if(msgdata.type=='C')
{
linkList* s = L;
if(strcasecmp(msgdata.text,"quit")==0)
{
msgdata.type='Q';
printf("%s [%s:%d]:quit\n",msgdata.name,inet_ntoa((p->sin).sin_addr),ntohs((p->sin).sin_port));
while(s->next!=NULL)
{
s=s->next;
if(ntohs((s->sin).sin_port)!=ntohs((p->sin).sin_port))
{
//服务器将收到的信息转发给所有人
if(sendto(sfd,&msgdata,sizeof(msgdata),0,(struct sockaddr*)&(s->sin),sizeof(s->sin))<0)
{
ERR_MSG("sendto");
break;
}
}
//printf("sendto success\n");
}
continue;
}
printf("%s [%s:%d]:chat\n",msgdata.name,inet_ntoa((p->sin).sin_addr),ntohs((p->sin).sin_port));
while(s->next!=NULL)
{
s=s->next;
if(ntohs((s->sin).sin_port)!=ntohs((p->sin).sin_port))
{
//服务器将收到的信息转发给所有人
if(sendto(sfd,&msgdata,sizeof(msgdata),0,(struct sockaddr*)&(s->sin),sizeof(s->sin))<0)
{
ERR_MSG("sendto");
break;
}
}
}
}
}
pthread_exit(NULL);
}
//服务器发送系统信息
void* SerSnd(void* arg)
{
linkList* L = *(linkList**)arg;
while(1)
{
bzero(msgdata.text, sizeof(msgdata.text));
//printf("请输入>>>");
fgets(msgdata.text, sizeof(msgdata.text), stdin);
msgdata.text[strlen(msgdata.text)-1] = 0;
strcpy(msgdata.name,"System");
msgdata.type = 'S';
linkList* s = L;
while(s->next!=NULL)
{
s=s->next;
//服务器将收到的信息转发给所有人
if(sendto(sfd,&msgdata,sizeof(msgdata),0,(struct sockaddr*)&(s->sin),sizeof(s->sin))<0)
{
ERR_MSG("sendto");
break;
}
}
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
if(argc < 3)
{
fprintf(stderr,"请输入IP port\n");
return -1;
}
//将获取到的端口号字符串转换成整形
int port = atoi(argv[2]);
if(port < 1024 || port > 49151)
{
fprintf(stderr,"port %d input error!! 1024~49151\n",port);
return -1;
}
//创建报式套接字
sfd = socket(AF_INET,SOCK_DGRAM,0);
if(sfd<0)
{
ERR_MSG("socket");
return -1;
}
//填充服务器的IP地址以及端口号
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(argv[1]);
//绑定服务器的地址信息结构体
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
//创建单链表
//创建头节点
linkList* L=list_create();
if(NULL == L)
{
return -1;
}
//创建线程
pthread_t tid1,tid2;
if(pthread_create(&tid1,NULL,SerRcv,(void*)&L)!=0)
{
ERR_MSG("pthread_create");
return -1;
}
if(pthread_create(&tid2,NULL,SerSnd,(void*)&L)!=0)
{
ERR_MSG("pthread_create");
return -1;
}
//主线程阻塞
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//关闭套接字
close(sfd);
return 0;
}
客户端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
//打印错误新的宏函数
#define ERR_MSG(msg) do{\
fprintf(stderr, " __%d__ ", __LINE__);\
perror(msg);\
}while(0)
int sfd;
typedef struct
{
char type; //L代表login Q代表quit C代表Chat
char name[20]; //姓名
char text[128]; //消息
}Data;
Data msgdata;
char name[20];
//客户端发送
void* CliSnd(void* arg)
{
struct sockaddr_in sin = *(struct sockaddr_in*)arg;
while(1)
{
bzero(msgdata.text, sizeof(msgdata.text));
//printf("请输入>>>");
fgets(msgdata.text, sizeof(msgdata.text), stdin);
msgdata.text[strlen(msgdata.text)-1] = 0;
msgdata.type = 'C';
strcpy(msgdata.name,name);
//将数据包发送给服务器,所以地址信息结构体需要填服务器的信息
if(sendto(sfd, &msgdata, sizeof(msgdata), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
break;
}
//printf("sendto success\n");
if(strcasecmp(msgdata.text,"quit")==0)
exit(0);
}
pthread_exit(NULL);
}
//客户端接收
void* CliRcv(void* arg)
{
while(1)
{
//接收
bzero(msgdata.text, sizeof(msgdata.text));
//接收服务器发送过来的数据包
if(recvfrom(sfd, &msgdata, sizeof(msgdata), 0,NULL,NULL) < 0)
{
ERR_MSG("recvfrom");
break;
}
//printf("test:%c\n",msgdata.type);
if(msgdata.type=='L')
printf("-----%s已上线-----\n",msgdata.name);
else if(msgdata.type=='Q')
printf("-----%s已下线-----\n",msgdata.name);
else if(msgdata.type=='C')
printf("%s:%s\n", msgdata.name,msgdata.text);
else
printf("%s:%s\n", msgdata.name,msgdata.text);
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
if(argc < 3)
{
fprintf(stderr, "请输入IP port\n");
return -1;
}
//将获取到的端口号字符串,转换成整形
int port = atoi(argv[2]);
if(port < 1024 || port > 49151)
{
fprintf(stderr, "port %d input error!! 1024~49151\n", port);
return -1;
}
//填充服务器的IP地址以及端口号 -->因为客户端要主动发送数据包给服务器
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(argv[1]);
//创建报式套接字
sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//输入姓名登录
printf("请输入姓名登录>>>");
fgets(msgdata.name, sizeof(msgdata.name), stdin);
msgdata.name[strlen(msgdata.name)-1] = 0;
msgdata.type = 'L';
strcpy(name,msgdata.name);
if(sendto(sfd, &msgdata, sizeof(msgdata), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
exit(0);
}
printf("-------登录成功-------\n");
//创建线程
pthread_t tid1,tid2;
if(pthread_create(&tid1,NULL,CliSnd,(void*)&sin)!=0)
{
ERR_MSG("pthread_create");
return -1;
}
if(pthread_create(&tid2,NULL,CliRcv,NULL)!=0)
{
ERR_MSG("pthread_create");
return -1;
}
//主线程阻塞
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//关闭套接字
close(sfd);
return 0;
}