1、基于UDP聊天室
服务器
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__",__LINE__);\
perror(msg);\
}while(0)
#define IP "127.0.0.1"
#define PORT 6666
//创建链表
Linklistptr list_create();
Linklistptr node_buy(datatype e);
int list_insert_head(Linklistptr L,datatype e);
int chat_login(Linklistptr L,int sfd,msg_t msg,datatype e);
int chat_enter(Linklistptr l,int sfd,msg_t msg,datatype e);
int chat_exit(Linklistptr L,int sfd,msg_t msg,datatype e);
typedef struct sockaddr_in datatype;//类型重命名
//创建信息结构体
typedef struct msg
{
char type;//操作码 'L'登录 'C'群聊 'Q'退出
char name[20];
char text[128];
}msg_t;
//创建链表保存地址信息
typedef struct Node
{
union
{
datatype res_addr;//数据域
int len;//头结点数据域
};
struct Node *next;//指针域
}Node, *Linklistptr;
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success sfd=%d\n",sfd);
//填充接收方的地址信息结构体,给bind函数使用
datatype 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("bind success");
//创建接收地址信息结构体
datatype cin;
socklen_t addrlen = sizeof(cin);
pthread_t tid;
msg_t msg;
//创建一个链表
Linklistptr L = list_create();
if(NULL == L)
{
return -1;
}
while(1)
{
//主线程负责接收并处理
if(recvfrom(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&cin, &addrlen) < 0)
{
ERR_MSG("recvfrom");
return -1;
}
printf("[%s:%d] : %s\n", msg.name, ntohs(cin.sin_port), msg.text);
switch(msg.type)
{
case 'L'://登录
chat_login(L,sfd,msg,e);
break;
case 'C'://群聊
chat_enter(L,sfd,msg,e);
break;
case 'Q'://退出
chat_exit(L,sfd,msg,e);
break;
default :
printf("输入错误\n");
break;
}
info.sfd=sfd;
info.sin=sin;
//分支线程只负责发送系统信息
if(pthread_create(&tid, NULL, task,(void*)&info) != 0)
{
fprintf(stderr, "pthread_create failed__%d__\n",__LINE__);
return -1;
}
pthread_detach(tid);
}
//关闭文件描述符
if(close(sfd) < 0)
{
ERR_MSG("close");
return -1;
}
return 0;
}
void *task(void *arg)
{
int sfd = ((struct Climsg*)arg)->sfd;
datatype sin = ((struct Climsg*)arg)->sin;
msg_t msg;
msg.type = 'C';
while(1)
{
//从终端获取消息文本
fgets(msg.text, sizeof(msg.text), stdin);
msg.text[strlen(msg.text)-1] = '\0';
//将信息包名定为服务器
strcpy(msg.name,"servce");
if(sendto(sfd , &msg, sizeof(msg), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
}
}
close(sfd);
pthread_exit(NULL);
}
//创建链表
Linklistptr list_create()
{
//从堆区申请一个头结点类型
Linklistptr L = (Linklistptr)malloc(sizeof(Node));
if(NULL == L)
{
printf("创建失败\n");
return NULL;
}
//创建成功后,对节点进行初始化工作
L->len = 0;
L->next = NULL;
return L;
}
//申请节点封装地址信息
Linklistptr node_buy(datatype e)
{
//在堆区申请节点
Linklistptr p = (Linklistptr)malloc(sizeof(Node));
if(NULL == p)
{
printf("申请失败]n");
return NULL;
}
//节点申请成功,封装数据
p->res_addr = e;
p->next = NULL;
return p;
}
//头插
int list_insert_head(Linklistptr L,datatype e)
{
//判断逻辑
if(NULL == L)
{
printf("所给链表不合法\n");
return -1;
}
//调用节点封装数据
Linklistptr p = node_buy(e);
if(NULL == p)
{
return -1;
}
//头插
p->next = L->next;
L->next = p;
//表长变化
L->len++;
return 0;
}
int chat_login(Linklistptr L,int sfd,msg_t msg,datatype e)
{
Linklistptr p=L;//定义一个遍历指针
while(p->next!=NULL)
{
p=p->next;
if(sendto(sfd,&msg,sizeof(msg_t),0,(struct sockaddr *)&(p->res_addr),sizeof(p->res_addr))<0)
{
ERR_MSG("sendto");
return -1;
}
}
//头插,将自己的地址存入链表中
list_insert_head(L,e);
return 0;
}
//群聊
int chat_enter(Linklistptr l,int sfd,msg_t msg,datatype e)
{
//定义一个遍历指针
Linklistptr p=L;
while(p->next!=NULL)
{
p=p->next;
//判断链表客户端信息
if(memcmp(&(p->res_addr),&e,sizeof(e))==0)
{
if(sendto(sfd,&msg,sizeof(msg_t),0,(struct sockaddr*)&(p->res_addr),sizeof(p->res_addr))<0)
{
ERR_MSG("sendto");
return -1;
}
}
}
return 0;
}
//退出
int chat_exit(Linklistptr L,int sfd,msg_t msg,datatype e)
{
Linklistptr p=L;
while(p->next!=NULL)
{
p=p->next;
//判断链表客户端信息
if(memcmp(&(p->res_addr),&e,sizeof(e))==0)
{
if(sendto(sfd,&msg,sizeof(msg_t),0,(struct sockaddr*)&(p->res_addr),sizeof(p->res_addr))<0)
{
ERR_MSG("sendto");
return -1;
}
}
else//此时当前节点的下一个节点保存的就是要退出的成员的信息
{
Linklistptr q=p->next;
if(sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&(q->resin), sizeof(q->resin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
p->next=q->next;
free(q);
q=NULL;
}
}
return 0;
}
客户端
#include <myhead.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__",__LINE__);\
perror(msg);\
}while(0)
#define IP "127.0.0.1"
#define PORT 4399
typedef struct sockaddr_in datatype;//类型重命名
//创建信息结构体
typedef struct msg
{
char type;//操作码 'L'登录 'C'群聊 'Q'退出
char name[20];
char text[128];
}msg_t;
struct Climsg
{
int cfd;
datatype cin;
msg_t msg;
};
void *task(void *arg);
int main(int argc, const char *argv[])
{
//创建报式套接字
int cfd = socket(AF_INET, SOCK_DGRAM, 0);
if(cfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success cfd=%d\n",cfd);
//填充接收方的地址信息结构体,给sendto函数使用
datatype sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(IP);
//创建接收地址信息结构体
datatype cin;
socklen_t c_addrlen = sizeof(cin);
pthread_t tid;
msg_t msg;
memset(&msg, 0, sizeof(msg_t));
struct Climsg info;
printf("请输入登录名:");
fgets(msg.name, sizeof(msg.name), stdin);
msg.name[strlen(msg.name)-1] = '\0';
msg_t msg1=msg;
msg.type = 'L';
strcpy(msg.text,"已进入群聊");
//发送登录请求包
if(sendto(cfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("登陆成功\n");
info.cfd = cfd;
info.cin = sin;
info.msg = msg;
while(1)
{
memset(&msg, 0, sizeof(msg));
if(recvfrom(cfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&cin, &c_addrlen) < 0)
{
ERR_MSG("recvfrom");
return -1;
}
if(strcmp(msg.name, msg1.name) == 0)
{
break;
}
printf("[%s] : %s\n", msg.name, msg.text);
if(pthread_create(&tid, NULL, task, (void *)&info) != 0)
{
ERR_MSG("pthread_create");
return -1;
}
//将分支线程设置为分离态
pthread_detach(tid);
}
//关闭套接字描述符
close(cfd);
return 0;
}
//线程体函数
void *task(void *arg)
{
int cfd = ((struct Climsg*)arg)->cfd;
datatype sin = ((struct Climsg*)arg)->cin;
msg_t msg = ((struct Climsg*)arg)->msg;
while(1)
{
//清空文本内容
bzero(msg.text, sizeof(msg.text));
//从终端获取数据
fgets(msg.text, sizeof(msg.text), stdin);
msg.text[strlen(msg.text)-1] = '\0';
msg.type = 'C';
if(strcmp(msg.text,"quit") == 0)
{
msg.type = 'Q';
strcpy(msg.text, "已下线");
}
if(sendto(cfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
}
if(strcmp(msg.text, "已下线") == 0)
{
break;
}
}
close(cfd);
pthread_exit(NULL);
}
2、思维导图