项目需求:
- 如果有用户登录,其他用户可以收到这个人的登录信息
- 如果有人发送信息,其他用户可以收到这个人的群聊信息
- 如果有人下线,其他用户可以收到这个人的下线信息
- 服务器可以发送系统信息
写项目的方法:
- 画流程图
- 根据流程图写框架
- 将每个功能实现(一个一个实现,不要一起写)
服务器代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
//链表结构体
typedef struct node{
union{
int len;
struct sockaddr_in node_cin;
};
struct node *next;
}linklist;
//函数声明
void* send_msg(void*arg);
linklist *list_create();
int list_empty(linklist *l);
linklist *create_node(struct sockaddr_in cin);
int list_insert(linklist *l,struct sockaddr_in cin);
int list_delete(linklist *l,int pos);
int list_search_value(linklist *l,struct sockaddr_in cin);
int list_delete_head(linklist *l);
void list_free(linklist *l);
//数据包结构体协议
typedef struct data{
char type; //操作码,用于确定数据包的类型
char name[20]; //数据包名字,用于确定是客户端的姓名
char text[520]; //文本信息
}DataPack;
//创建链表变量
linklist *l;
//宏定义
#define ERR_MSG(msg) do{\
fprintf(stderr,"line:%d\n",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.8.129"
#define PORT 6666
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd=socket(AF_INET, SOCK_DGRAM,0);
if(sfd < 0){
ERR_MSG("socket");
return -1;
}
//允许端口快速重用
int reuse = 1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) < 0){
ERR_MSG("setsockopt");
return -1;
}
//创建真实信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(IP);
//绑定服务器
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) < 0){
ERR_MSG("bind");
return -1;
}
printf("*******************温馨提示*******************\n");
printf("网络聊天室服务器已打开,各位用户可以登录聊天\n");
printf("如果需要关闭服务器,请输入'system quit'\n");
printf("**********************************************\n");
//定义变量
DataPack datapack;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
int newfd = 0;
ssize_t res = 0;
//创建链表
l=list_create();
if(NULL == l){
return -1;
}
//创建线程
pthread_t tid;
if(pthread_create(&tid,NULL,&send_msg,&sfd) != 0){
fprintf(stderr,"line:%d pthread_create failed\n",__LINE__);
return -1;
}
while(1){
bzero(datapack.name,sizeof(datapack.name));
bzero(datapack.text,sizeof(datapack.text));
//接收数据包
res = recvfrom(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&cin,&addrlen);
if(res < 0){
ERR_MSG("recvfrom");
return -1;
}
//判断数据包类型
if('L' == datapack.type){
printf("[%s:%d]:%s已上线\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),datapack.name);
//如果是登录,则加入链表中
list_insert(l,cin);
linklist* q=l->next;
for(int i=0; i<l->len ;i++){
if((q->node_cin.sin_port != cin.sin_port) && (q->node_cin.sin_addr.s_addr == cin.sin_addr.s_addr)){
if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&(q->node_cin),sizeof(q->node_cin))<0){
ERR_MSG("sendto");
break;
}
}
q=q->next;
}
}else if('C' == datapack.type){
linklist* q=l->next;
printf("[%s:%d]:%s:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),datapack.name,datapack.text);
//如果是消息
for(int i=0; i<l->len ;i++){
if((q->node_cin.sin_port != cin.sin_port) && (q->node_cin.sin_addr.s_addr == cin.sin_addr.s_addr)){
if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&(q->node_cin),sizeof(q->node_cin))<0){
ERR_MSG("sendto");
break;
}
}
q=q->next;
}
}else if('Q' == datapack.type){
//如果是下线,则遍历链表,找到下线的客户端,剔除下线的客户端
if(strcmp(datapack.text,"system quit")==0){
printf("聊天室服务器关闭\n");
break;
}
}else if('q'==datapack.type){
printf("%s已下线\n",datapack.name);
linklist* q = l->next;
for(int i=0; i<l->len ;i++){
if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&(q->node_cin),sizeof(q->node_cin))<0){
ERR_MSG("sendto");
break;
}
q=q->next;
}
int pos=list_search_value(l,cin);
list_delete(l,pos);
}
}
list_free(l);
close(sfd);
return 0;
}
void* send_msg(void* arg){
//分离线程
pthread_detach(pthread_self());
//定义结构体变量
DataPack datapack;
//定义套接字管道符
int sfd = *(int*)arg;
while(1){
//修改数据包操作码
datapack.type = 'S';
//清空datapack.text
bzero(datapack.text,sizeof(datapack.text));
//从终端输入,将内容放入datapack.text中
fgets(datapack.text,sizeof(datapack.text),stdin);
datapack.text[strlen(datapack.text)-1] = '\0';
linklist* q = l->next;
socklen_t node_addrlen = sizeof(q->node_cin);
if(strcmp(datapack.text,"system quit") == 0){
datapack.type = 'Q';
for(int i=0; i<l->len ;i++){
if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&(q->node_cin),node_addrlen)<0){
ERR_MSG("sendto");
break;
}
q=q->next;
}
break;
}
//发送系统消息
for(int i=0; i<l->len ;i++){
if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&(q->node_cin),node_addrlen)<0){
ERR_MSG("sendto");
break;
}
q=q->next;
}
}
}
//创建链表
linklist *list_create(){
//申请一个结点空间
linklist *l = (linklist *)malloc(sizeof(linklist));
if(NULL == l){
printf("创建失败\n");
return NULL;
}
//创建成功进行初始化
l->len=0;
l->next=NULL;
return l;
}
//链表判空
int list_empty(linklist *l){
return l->next==NULL;
}
//申请结点封装数据
linklist *create_node(struct sockaddr_in cin){
//在堆区申请一个结点的空间
linklist *p=(linklist*)malloc(sizeof(linklist));
if(NULL==p){
printf("空间申请失败\n");
return NULL;
}
//将数据封装到结点的数据域中
p->node_cin = cin;
p->next=NULL;
return p;
}
//插入链表
int list_insert(linklist *l,struct sockaddr_in cin){
if(NULL==l){
printf("插入链表失败,所给链表不合法\n");
return 0;
}
//申请结点封装数据
linklist *p=create_node(cin);
//头插逻辑
p->next=l->next;
l->next=p;
//表的变化
l->len++;
return 1;
}
//按值查找
int list_search_value(linklist *l,struct sockaddr_in cin){
if(list_empty(l) || NULL == l){
printf("查找失败\n");
return -1;
}
linklist *q=l->next;
for(int i=1; i<=l->len;i++){
if((q->node_cin.sin_port == cin.sin_port) && (q->node_cin.sin_addr.s_addr == cin.sin_addr.s_addr)){
return i;
}
q=q->next;
}
return 0;
}
//删除结点
int list_delete(linklist *l,int pos){
if(NULL == l || list_empty(l) || pos<1 ||pos>l->len){
printf("删除失败\n");
return 0;
}
linklist *q=l;
for(int i=0;i<pos-1;i++){
q=q->next;
}
linklist *p=q->next;
q->next=p->next;
free(p);
l->len--;
return 1;
}
//头删
int list_delete_head(linklist *l){
if(NULL==l || list_empty(l)){
printf("删除失败\n");
return 0;
}
linklist *q=l->next;
l->next=q->next;
free(q);
l->len--;
printf("删除成功\n");
return 1;
}
//释放链表
void list_free(linklist *l){
if(NULL!=l){
//循环进行头删
while(l->next != NULL){
list_delete_head(l);
}
free(l);
l=NULL;
}
}
客户端代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
//函数声明
void* send_msg(void*arg);
//数据包结构体协议
typedef struct data{
char type; //操作码,用于确定数据包的类型
char name[20]; //数据包名字,用于确定是客户端的姓名
char text[520]; //文本信息
}DataPack;
//传参结构体
struct pass_value{
struct sockaddr_in sin;
int sfd;
DataPack datapack;
};
//宏定义
#define ERR_MSG(msg) do{\
fprintf(stderr,"line:%d\n",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.8.129"
#define PORT 6666
int main(int argc, const char *argv[])
{
//传参结构体
struct pass_value pas_val;
//创建报式套接字
pas_val.sfd=socket(AF_INET, SOCK_DGRAM,0);
if(pas_val.sfd < 0){
ERR_MSG("socket");
return -1;
}
//创建真实地址信息结构体
pas_val.sin.sin_family = AF_INET;
pas_val.sin.sin_port = htons(PORT);
pas_val.sin.sin_addr.s_addr = inet_addr(IP);
//发送客户端名字
bzero(pas_val.datapack.text,sizeof(pas_val.datapack.text));
pas_val.datapack.type = 'L';
printf("请输入登录名字:");
scanf("%s",pas_val.datapack.name);
while(getchar()!=10);
if(sendto(pas_val.sfd,&pas_val.datapack,sizeof(pas_val.datapack),0,(struct sockaddr*)&(pas_val.sin),sizeof(pas_val.sin)) < 0){
ERR_MSG("sendto");
return -1;
}
printf("登录成功\n");
//创建线程
pthread_t tid;
if(pthread_create(&tid,NULL,&send_msg,&pas_val) != 0){
fprintf(stderr,"line:%d pthread_create failed\n",__LINE__);
return -1;
}
//定义接收数据包结构体变量
DataPack datapack;
ssize_t res = 0;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while(1){
bzero(datapack.name,sizeof(datapack.name));
bzero(datapack.text,sizeof(datapack.text));
//接收数据包
res = recvfrom(pas_val.sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&cin,&addrlen);
if(res < 0){
ERR_MSG("recvfrom");
return -1;
}
if('L' == datapack.type){
printf("%s已上线\n",datapack.name);
}else if('S' == datapack.type){
printf("---system---:%s\n",datapack.text);
}else if('C' == datapack.type){
printf("%s:%s\n",datapack.name,datapack.text);
}else if('Q' == datapack.type){
if(sendto(pas_val.sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&cin,sizeof(cin)) < 0){
ERR_MSG("sendto");
return -1;
}
printf("聊天室服务器关闭,用户强制下线\n");
pthread_cancel(tid);
break;
}else if('q'==datapack.type){
if(strcmp(datapack.name,pas_val.datapack.name)==0){
printf("退出聊天室成功\n");
break;
}else{
printf("%s已下线\n",datapack.name);
}
}
}
pthread_join(tid,NULL);
close(pas_val.sfd);
return 0;
}
void* send_msg(void* arg){
//定义传参变量
DataPack datapack =(*(struct pass_value*)arg).datapack;
int sfd = (*(struct pass_value*)arg).sfd;
struct sockaddr_in sin = (*(struct pass_value*)arg).sin;
while(1){
bzero(datapack.text,sizeof(datapack.text));
fgets(datapack.text,sizeof(datapack.text),stdin);
datapack.text[strlen(datapack.text)-1] = '\0';
//发送下线消息
if(strcmp(datapack.text,"quit") == 0){
datapack.type = 'q';
if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&sin,sizeof(sin))<0){
ERR_MSG("sendto");
break;
}
break;
}else{
//发送群聊消息
datapack.type = 'C';
if(sendto(sfd,&datapack,sizeof(datapack),0,(struct sockaddr*)&sin,sizeof(sin))<0){
ERR_MSG("sendto");
break;
}
}
}
pthread_exit(NULL);
}
运行结果:
登录界面:
聊天界面: