代码思路
服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
//链表节点结构体:
typedef struct node
{
struct sockaddr_in addr; //data memcmp
struct node *next;
} link_t;
//类型
enum type_t
{
login,
chat,
quit,
};
//消息对应的结构体(同一个协议)
typedef struct msg_t
{
int type; //'L' C Q enum un{login,chat,quit};
char name[32]; //用户名
char text[128]; //消息正文
} MSG_t;
link_t *link_creat();
void client_login(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr);
void client_quit(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr);
void client_chat(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr);
int main(int argc, char const *argv[])
{
// 创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd);
// 绑定
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = INADDR_ANY;
socklen_t len = sizeof(caddr);
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err");
return -1;
}
printf("bind ok\n");
// 创建链表
link_t *p = link_creat();
printf("creat ok\n");
MSG_t msg;
// 可扩展功能,服务器可以给所有用户发送“公告”
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
while (1)
{
fgets(msg.text, sizeof(msg.text), stdin);
if (msg.text[strlen(msg.text) - 1] == '\n')
msg.text[strlen(msg.text) - 1] = '\0';
msg.type = chat;
strcpy(msg.name, "server");
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&saddr, sizeof(saddr));
}
}
else
{
while (1)
{
if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&caddr, &len) < 0)
{
perror("recvfrom err");
return -1;
}
printf("type:%d\n", msg.type);
switch (msg.type)
{
case login: // 上线函数
client_login(sockfd, p, msg, caddr);
break;
case chat: // 聊天函数
client_chat(sockfd, p, msg, caddr);
break;
case quit: // 下线函数
client_quit(sockfd, p, msg, caddr);
break;
default:
break;
}
}
}
close(sockfd);
return 0;
}
// 创建链表
link_t *link_creat()
{
// 给头结点开辟空间
link_t *p = (link_t *)malloc(sizeof(link_t));
if (p == NULL)
{
perror("malloc err");
return NULL;
}
// 初始化头结点
p->next = NULL;
return p;
}
// 客户端上线功能:将用户上线消息发送给其他用户,并将用户信息添加到链表中
void client_login(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr)
{
printf("%s login....\n", msg.name);
// 先遍历链表
// 再插入新的节点
while (p->next != NULL)
{
p = p->next;
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
}
link_t *pnew = (link_t *)malloc(sizeof(link_t));
pnew->addr = caddr;
pnew->next = NULL;
p->next = pnew;
return;
}
// 聊天功能:转发消息到在链表中除了自己的所有用户
void client_chat(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr)
{
printf("%s says %s\n", msg.name, msg.text);
while (p->next != NULL)
{
p = p->next;
// strcmp:对比前后两个字符串中的内容是否一致
// memcmp:对比前后两个对应的地址(任意类型)的内容是否一致
if (memcmp(&(p->addr), &caddr, sizeof(caddr)))
{
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
}
}
return;
}
// 客户端退出功能:将用户下线消息发送给其他用户,将下线用户从链表中删除
void client_quit(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr)
{
printf("%s quit.......\n", msg.name);
link_t *pdel = NULL;
while (p->next != NULL)
{
if (memcmp(&(p->next->addr), &caddr, sizeof(caddr)))
{
// 不是自己
p = p->next;
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
}
else
{
// 是自己,删除节点
pdel = p->next;
p->next = pdel->next;
free(pdel);
pdel = NULL;
}
}
return;
}
客户端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
//链表节点结构体:
typedef struct node
{
struct sockaddr_in addr; //data memcmp
struct node *next;
} link_t;
//类型
enum type_t
{
login,
chat,
quit,
};
//消息对应的结构体(同一个协议)
typedef struct msg_t
{
int type; //'L' C Q enum un{login,chat,quit};
char name[32]; //用户名
char text[128]; //消息正文
} MSG_t;
int main(int argc, char const *argv[])
{
int ret;
// 创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd);
// 绑定
struct sockaddr_in saddr, caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(caddr);
MSG_t msg;
// 给服务器发送上线消息
printf("请输入用户名:");
fgets(msg.name, sizeof(msg.name), stdin);
if (msg.name[strlen(msg.name) - 1] == '\n')
msg.name[strlen(msg.name) - 1] = '\0';
msg.type = login;
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&saddr, sizeof(saddr));
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) // 循环接受服务器消息
{
while (1)
{
ret = recvfrom(sockfd, &msg, sizeof(MSG_t), 0, NULL, NULL);
if (ret < 0)
{
perror("recv from err");
return -1;
}
if (msg.type == login)
printf("%s login.......\n", msg.name);
else if (msg.type == chat)
printf("%s says %s\n", msg.name, msg.text);
else if (msg.type == quit)
printf("%s quit.......\n", msg.name);
}
}
else // 循环发送消息
{
while (1)
{
fgets(msg.text, sizeof(msg.text), stdin);
if (msg.text[strlen(msg.text) - 1] == '\n')
msg.text[strlen(msg.text) - 1] = '\0';
if (!strcmp(msg.text, "quit"))
{
msg.type = quit;
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&saddr, sizeof(saddr));
break;
}
else
{
msg.type = chat;
sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&saddr, sizeof(saddr));
}
}
kill(pid, SIGINT);
wait(NULL);
}
close(sockfd);
return 0;
}