服务器端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <25061head.h>
#define SER_IP "192.168.144.128"
#define SER_PORT 8888
// 链表节点结构定义
typedef struct Node
{
char usrName[30]; // 用户名
struct sockaddr_in cin; // 用户的地址信息
struct Node *next; // 指针域
}*linklist;
// 消息结构定义
struct msgTyp
{
char type;
char usrName[30];
char msgText[50];
};
int main(int argc, const char *argv[])
{
// 前期配置
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd == -1)
{
ERR_MSG("socket error");
return -1;
}
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
ERR_MSG("bind error");
close(sfd);
return -1;
}
printf("绑定成功\n");
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
struct msgTyp recv_msg; // 接收的信息
// 创建头节点并初始化
linklist head = (linklist)malloc(sizeof(struct Node));
if(head == NULL)
{
ERR_MSG("malloc error");
close(sfd);
return -1;
}
head->next = NULL; // 初始化头节点
pid_t pid=fork();
if(pid>0)
{
// 核心操作
while(1)
{
// 清空接收缓冲区
memset(&recv_msg, 0, sizeof(recv_msg));
// 接收消息
ssize_t recv_len = recvfrom(sfd, &recv_msg, sizeof(recv_msg), 0,
(struct sockaddr*)&cin, &addrlen);
if(recv_len == -1)
{
ERR_MSG("recvfrom error");
continue;
}
switch(recv_msg.type)
{
case 'L': // login登录
printf("%s登录成功\n", recv_msg.usrName);
// 创建新用户的节点并初始化
linklist temp = (linklist)malloc(sizeof(struct Node));
if(temp == NULL)
{
ERR_MSG("malloc error");
break;
}
//存储新用户结点的相关信息
strcpy(temp->usrName, recv_msg.usrName);
temp->cin = cin;
//头插
temp->next = head->next;
head->next = temp;
// 广播登录消息
linklist s = head->next;
char sbuf[128] = "";
snprintf(sbuf, sizeof(sbuf)-1, "%s已上号!", recv_msg.usrName);
strcpy(recv_msg.msgText,sbuf);//放入recv.msg中,发给其他人
//recv_msg.msgText[sizeof(recv_msg.msgText)-1] = '\0';
while(s != NULL)
{
// 跳过发送者自己(用IP和端口区分,后面的广播都是这种方法排除自己)
if (s->cin.sin_port == cin.sin_port && s->cin.sin_addr.s_addr == cin.sin_addr.s_addr)
{
s = s->next;
continue;
}
sendto(sfd, &recv_msg, sizeof(recv_msg), 0,
(struct sockaddr*)&(s->cin), sizeof(s->cin));
s = s->next;
}
break;
case 'C': // chat聊天
if(strcmp(recv_msg.usrName,"系统")!=0)
{
printf("%s:chat成功\n", recv_msg.usrName);
}
char cbuf[128] = "";
snprintf(cbuf, sizeof(cbuf)-1, "%s说:%s", recv_msg.usrName, recv_msg.msgText);
strcpy(recv_msg.msgText, cbuf);
//recv_msg.msgText[sizeof(recv_msg.msgText)-1] = '\0';
s = head->next;//从第一个用户开始
while(s != NULL)
{
// 跳过发送者自己
if (s->cin.sin_port == cin.sin_port && s->cin.sin_addr.s_addr == cin.sin_addr.s_addr)
{
s = s->next;
continue;
}
sendto(sfd, &recv_msg, sizeof(recv_msg), 0,
(struct sockaddr*)&(s->cin), sizeof(s->cin));
s = s->next;
}
break;
case 'Q': // quit退出
printf("%s退出聊天室\n", recv_msg.usrName);
char qbuf[128] = "";
snprintf(qbuf, sizeof(qbuf)-1, "%s退出聊天室", recv_msg.usrName);
strcpy(recv_msg.msgText, qbuf);
//recv_msg.msgText[sizeof(recv_msg.msgText)-1] = '\0';
// 先广播退出消息
s = head->next;
while(s != NULL)
{
// 跳过发送者自己
if (s->cin.sin_port == cin.sin_port && s->cin.sin_addr.s_addr == cin.sin_addr.s_addr)
{
s = s->next;
continue;
}
sendto(sfd, &recv_msg, sizeof(recv_msg), 0,
(struct sockaddr*)&(s->cin), sizeof(s->cin));
s = s->next;
}
// 然后从链表中删除该用户节点
linklist prev = head;
linklist curr = head->next;
int found = 0;//判断找没找到结点
while(curr != NULL)
{
if(strcmp(curr->usrName, recv_msg.usrName) == 0)
{
//头删
prev->next = curr->next;
free(curr);
curr=NULL;
found = 1;
printf("已从链表中移除用户: %s\n", recv_msg.usrName);
break;
}
prev = curr;
curr = curr->next;
}
if(found==0)
{
printf("警告: 未在链表中找到用户 %s\n", recv_msg.usrName);
}
break;
default:
printf("发送格式错误\n");
}
}
}
//服务器广播系统信息
else if(pid==0)
{
struct msgTyp sys_msg;
sys_msg.type='C';
strcpy(sys_msg.usrName, "系统");
while(1)
{
bzero(sys_msg.msgText,50);
fgets(sys_msg.msgText,50, stdin);
sys_msg.msgText[strlen(sys_msg.msgText)-1] = 0;
//核心操作,因为是进程的原因,链表在子进程用不了,不能循环广播
//那我直接向主进程发消息,让主进程广播
sendto(sfd, &sys_msg, sizeof(sys_msg), 0, (struct sockaddr*)&sin, sizeof(sin));
}
printf("系统消息发送成功");
}
close(sfd);
return 0;
}
客户端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <25061head.h>
#define SER_IP "192.168.144.128"
#define SER_PORT 8888
struct msgTyp
{
char type;
char usrName[30];
char msgText[50];
};
int cfd; // 客户端socket
struct sockaddr_in sin; // 服务器的相关配置
char usrName[30] = ""; // 用户名
//虽然是线程,但有多个阻塞函数,所以用多线程
//该线程主要功能是接收消息
void *recv_msg_thread(void *arg)
{
struct msgTyp recv_msg;
socklen_t addrlen = sizeof(sin);
while(1)
{
// 清空接收缓冲区
memset(&recv_msg, 0, sizeof(recv_msg));
// 接收服务器发送的消息
ssize_t recv_len = recvfrom(cfd, &recv_msg,sizeof(recv_msg),0,(struct sockaddr*)&sin, &addrlen);
// 打印接收的消息
printf("%s\n", recv_msg.msgText);
fflush(stdout); // 刷新输出缓冲区
}
// 接收线程退出时关闭socket
close(cfd);
pthread_exit(0);
return NULL;
}
int main(int argc, const char *argv[])
{
// 获取用户名
printf("请输入你的用户名: ");
fgets(usrName, sizeof(usrName)-1, stdin);
// 去除换行符
usrName[strcspn(usrName, "\n")] = '\0';
// 创建socket
cfd = socket(AF_INET, SOCK_DGRAM, 0);
if(cfd == -1)
{
ERR_MSG("socket error");
return -1;
}
// 初始化服务器相关配置
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
// 配置登录消息
struct msgTyp send_msg;
send_msg.type = 'L';
strcpy(send_msg.usrName,usrName);
//strcpy(send_msg.msgText, "");
if(sendto(cfd, &send_msg, sizeof(send_msg), 0,(struct sockaddr*)&sin, sizeof(sin)) == -1)
{
ERR_MSG("sendto error");
close(cfd);
return -1;
}
printf("登录成功!输入'quit'退出聊天室...\n");
// 创建接收消息的线程
pthread_t recv_tid;
if(pthread_create(&recv_tid, NULL, recv_msg_thread, NULL) != 0)
{
ERR_MSG("pthread_create error");
close(cfd);
return -1;
}
// 分离线程,系统自动回收
pthread_detach(recv_tid);
// 主线程用于发送消息
while(1)
{
char input[50] = "";
fgets(input, sizeof(input)-1, stdin);
// 去除换行符
input[strcspn(input, "\n")] = '\0';
//输入quit,消息类型则设置从Q类型
if(strcmp(input, "quit") == 0)
{
send_msg.type = 'Q';
strcpy(send_msg.usrName, usrName);
sendto(cfd, &send_msg, sizeof(send_msg),0,(struct sockaddr*)&sin, sizeof(sin));
printf("已退出聊天室\n");
close(cfd);
return 0;
}
// 发送聊天消息
send_msg.type = 'C';
strcpy(send_msg.usrName, usrName);
strcpy(send_msg.msgText, input);
if(sendto(cfd, &send_msg, sizeof(send_msg),0,(struct sockaddr*)&sin, sizeof(sin)) == -1)
{
ERR_MSG("sendto error");
close(cfd);
return -1;
}
}
//关闭socket
close(cfd);
return 0;
}