聊天室的流程图如上所示
实现
服务器端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s--%s--(%d)\n",__FILE__,__func__,__LINE__);\
exit(-1);\
}while(0)
typedef struct MSG
{
char code; // 'L'登录 'C'群聊 'Q'退出(类似于选项标号)
char name[32]; //名字
char text[128]; //文本输入的内容
}msg_t;
//链表使用来保存客户端 网络信息结构体的
//所以数据域应该是 struct sockaddr_in 类型
typedef struct NODE
{
struct sockaddr_in c_addr;
struct NODE *next;
}node_t; //定义有头链表结构体
node_t *create_node();
//声明三个函数
void do_login(int , msg_t , node_t *, struct sockaddr_in);
void do_chat(int , msg_t , node_t *, struct sockaddr_in);
void do_quit(int , msg_t , node_t *, struct sockaddr_in);
int main(int argc, char *argv[])
{
if(3 != argc)
{
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(-1);
}
//创建 UDP 套接字
int sockfd = -1;
if(-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
{
ERRLOG("socket error");
}
//填充服务器网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
//定义保存客户端网络信息的结构体
socklen_t serveraddr_len = sizeof(serveraddr);
struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddr_len = sizeof(clientaddr);
//绑定 套接字和服务器网络信息结构体
if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len))
{
ERRLOG("bind error");
}
//创建父子进程分别处理读写
pid_t pid = 0;
if(-1 == (pid = fork()))
{
ERRLOG("fork error");
}
else if(pid>0) //父进程 负责发送系统消息
{ //可以把父进程当做一个客户端 以群聊的方式发送系统消息
msg_t msg;
msg.code = 'C';
strcpy(msg.name,"server"); //把服务器添加进去
while(1)
{
fgets(msg.text, 128, stdin);
msg.text[strlen(msg.text)-1] = '\0';
if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len))
{
ERRLOG("sendto error");
}
}
}
else if(0 == pid) //子进程
{
//创建保存客户端网络信息结构体的链表
node_t *phead = create_node();
msg_t msg;
memset(&msg, 0, sizeof(msg));
while(1)
{
//子进程 负责接收客户端的消息
if(-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &clientaddr_len))
{
ERRLOG("recvfrom error");
}
//printf("%c %s %s\n", msg.code, msg.name, msg.text);
switch(msg.code)
{
case 'L':
do_login(sockfd, msg, phead, clientaddr); //执行相应的函数
break;
case 'C':
do_chat(sockfd, msg, phead, clientaddr); //执行相应的函数
break;
case 'Q':
do_quit(sockfd, msg, phead, clientaddr); //执行相应的函数
break;
}
}
}
return 0;
}
//登录的函数
void do_login(int sockfd, msg_t msg, node_t *phead, struct sockaddr_in clientaddr)
{
node_t *ptemp = phead; //保存一下当前节点信息,操作完毕可以查找到上一节点
sprintf(msg.text, "%s %s", msg.name, "上线了"); //遍历链表 发送 xx上线
while(ptemp->next != NULL)
{
ptemp = ptemp->next;
if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->c_addr), sizeof(ptemp->c_addr)))
{
ERRLOG("sendto error");
}
}
//将新用户的信息 头插到链表中(有头链表的操作)
node_t *pnew = create_node();
pnew->c_addr = clientaddr;
pnew->next = phead->next;
phead->next = pnew;
}
//群聊的函数
void do_chat(int sockfd, msg_t msg, node_t *phead, struct sockaddr_in clientaddr)
{
node_t *ptemp = phead;
//将消息发给除了自己的所有人
while(ptemp->next != NULL)
{
ptemp = ptemp->next;
if(memcmp(&(ptemp->c_addr), &clientaddr, sizeof(clientaddr)))
{
if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->c_addr), sizeof(ptemp->c_addr)))
{
ERRLOG("sendto error");
}
}
}
}
//退出的函数
void do_quit(int sockfd, msg_t msg, node_t *phead, struct sockaddr_in clientaddr)
{
node_t *ptemp = phead;
//将 XX退出的消息 发给除了他自己的所有人
sprintf(msg.text, "%s %s", msg.name, "下线了");
while(ptemp->next != NULL)
{
//ptemp = ptemp->next;
if(memcmp(&(ptemp->next->c_addr), &clientaddr, sizeof(clientaddr)))
{
if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->next->c_addr), sizeof(ptemp->next->c_addr)))
{
ERRLOG("sendto error");
}
ptemp = ptemp->next;
}
else
{
//将退出的客户端的网络信息结构体在链表中删除
node_t *pdel = ptemp->next;
ptemp->next = pdel->next;
free(pdel);
pdel = NULL;
}
}
}
//创建链表节点的函数
node_t *create_node(){
node_t *p = (node_t *)malloc(sizeof(node_t));
p->next = NULL;
return p;
}
客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <signal.h>
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s--%s--(%d)\n",__FILE__,__func__,__LINE__);\
exit(-1);\
}while(0)
typedef struct MSG{
char code;// 'L'登录 'C'群聊 'Q'退出
char name[32];
char text[128];
}msg_t;
int main(int argc, char *argv[])
{
if(3 != argc)
{
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(-1);
}
//创建 UDP 套接字
int sockfd = -1;
if(-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
{
ERRLOG("socket error");
}
//填充服务器网络信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
//先执行登录操作
msg_t msg;
msg.code = 'L';
printf("请输入用户名:");
fgets(msg.name, 32, stdin);
msg.name[strlen(msg.name)-1] = '\0';
//发送登录消息
if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len))
{
ERRLOG("sendto error");
}
//创建父子进程分别处理读写
pid_t pid = 0;
if(-1 == (pid = fork()))
{
ERRLOG("fork error");
}
//父进程
else if(pid>0)
{
//父进程 负责发送消息
while(1)
{
fgets(msg.text, 128, stdin);
msg.text[strlen(msg.text)-1] = '\0';
if(!strcmp(msg.text, "quit"))
{
msg.code = 'Q';
}
else
{
msg.code = 'C';
}
//发送消息
if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len))
{
ERRLOG("sendto error");
}
//判断是否是quit
if(!strcmp(msg.text, "quit"))
{
kill(pid, SIGINT);
close(sockfd);
exit(0);
}
}
}
//子进程
else if(0 == pid)
{
//接收服务器发来的消息
while(1)
{
if(-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL))
{
ERRLOG("recvfrom error");
}
//输出接到的消息
printf("%s : %s\n", msg.name, msg.text);
}
}
return 0;
}
华清远见学习笔记整理(4)