基于UDP的聊天室一例

客户端流程:

 

image

 

 

客户端程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define  N  64
//客户端消息类型,R 注册消息 B 广播消息 U 注销消息  E 服务器关闭消息
#define  R  1   
#define  B  2
#define  U  3
#define  E  4

typedef struct sockaddr SA;

//声明消息体类型
typedef struct
{
	int type;  //消息类型
	char name[16];  //客户端名称
	char text[N];   //消息内容
} MSG;

int main(int argc, char *argv[])
{
	int sockfd;
	pid_t pid;
	MSG buf;
	struct sockaddr_in servaddr;

	if (argc < 3)
	{
		printf("Usage : %s <ip> <port>\n", argv[0]);
		return -1;
	}
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = PF_INET;
	servaddr.sin_port = htons(atoi(argv[2]));
	servaddr.sin_addr.s_addr = inet_addr(argv[1]);


	printf("input your name : ");
	fgets(buf.name, 16, stdin);
	buf.name[strlen(buf.name)-1] = '\0';

	// XXX int socket(int domain, int type, int protocol);
	if ((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)   //创建用户数据报套接字
	{
		perror("fail to socket");
		exit(-1);
	}

	buf.type = R;  //客户端先发送注册消息
	
	//将注册消息发到服务器
	sendto(sockfd, &buf, sizeof(buf), 0, (SA *)&servaddr, sizeof(servaddr));

	if ((pid = fork()) < 0)  //创建一个子进程
	{
		perror("fail to fork");
		exit(-1);
	}
	else if (pid == 0) // receive message,子进程负责接收来自服务器的消息
	{
		while ( 1 )
		{
			recvfrom(sockfd, &buf, sizeof(buf), 0, NULL, NULL);
			if (buf.type == E) break;  //收到的消息类型如果是E,表示服务器关闭
			printf("\n *** [%s] %s", buf.name, buf.text);  //输出来自服务器的消息
		}
		printf("Server is down, exit...\n");
		kill(getppid(), SIGUSR1);  //将父进程结束
		exit(0);  //子进程退出
	}
	else  // send message,父进程负责将用户从键盘输入的数据发送到服务器
	{
		buf.type = B;  //将消息类型设置为B,表示广播消息
		while ( 1 )
		{
			printf("client > ");
			fgets(buf.text, N, stdin);  //接收用户的输入
			if (strcmp(buf.text, "quit\n") == 0)   //看用户是否要退出
			{
				buf.type = U;  //消息类型设置为U,表示用户要下线
				sendto(sockfd, &buf, sizeof(buf), 0, (SA *)&servaddr, sizeof(servaddr));
				break;
			}
			sendto(sockfd, &buf, sizeof(buf), 0, (SA *)&servaddr, sizeof(servaddr));
			usleep(100000); 
		}
		usleep(100000);  //先等待100毫秒,等子进程将服务器发送的该客户端下线的消息输出
		kill(pid, SIGUSR1);  //将子进程杀死
		exit(0);  //父进程退出
	}

	close(sockfd);  //关闭套接字

	return 0;
}

 

 

服务器流程:

 

image

 

服务器端程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define  N  64

//客户端消息类型,R 注册消息 B 广播消息 U 注销消息  E 服务器关闭消息
#define  R  1
#define  B  2
#define  U  3
#define  E  4

typedef struct sockaddr SA;

//声明消息体类型
typedef struct
{
	int type;  //消息类型
	char name[16];  //客户端名称
	char text[N];   //消息内容
} MSG;

//声明用于存放用户数据的链表结点类型
typedef struct _node_
{
	struct sockaddr_in peeraddr;  //用户的地址信息
	struct _node_ *next;   //指向下一个结点
} linknode, *linklist;

void add_user(int sockfd, MSG buf, struct sockaddr_in peeraddr, linklist h)  //添加用户
{
	linklist p;

	p = (linklist)malloc(sizeof(linknode));  // 分配一段内存用于存放用户数据
	p->peeraddr = peeraddr;  //用用户的地址信息初始化该结点
	
	//进行头插入
	p->next = h->next;  
	h->next = p;

	
	p = p->next;//p指向刚插入的用户的下一个结点
	sprintf(buf.text, "%s is online\n", buf.name);  //将 <XXX> is online格式化输出到buf.text中
	strcpy(buf.name, "system");  //消息的发送者设为服务器
	
	/*
	遍历剩下的链表,实现向其他用户发送新用户上线的消息
	*/
	while (p != NULL)
	{
		sendto(sockfd, &buf, sizeof(buf), 0, (SA *)&p->peeraddr, sizeof(peeraddr));
		p = p->next;
	}
	strcpy(buf.text, "welcome to farsight chat room\n"); 
	sendto(sockfd, &buf, sizeof(buf), 0, (SA *)&peeraddr, sizeof(peeraddr)); //向刚上线的用户发送欢迎消息
}

void del_user(int sockfd, MSG buf, struct sockaddr_in peeraddr, linklist h)//用户下线处理
{
	linklist p = h->next;//p指向第一个用户

	sprintf(buf.text, "%s is offline\n", buf.name);
	strcpy(buf.name, "system");
	
	/*
	这里采用的方法不错!遍历一次既删除了要下线的用户,又向其他用户发送了该用户下线的消息
	*/
	while (p != NULL)
	{
	
	 //用memcmp比较两个内存区域的数据是否一致,一致返回0
	 //这里在删除元素是采用的两个指针,一前一后(p在前h在后)
		if (memcmp(&p->peeraddr, &peeraddr, sizeof(peeraddr)) == 0)
		{
			h->next = p->next;  //先将p指向的要删除的结点从链表中空出来
			free(p);  //释放p指向的结点
		}
		else
		{
		     //向若不相等,则向该用户发送用户下线的消息
			sendto(sockfd, &buf, sizeof(buf), 0, (SA *)&p->peeraddr, sizeof(peeraddr));
			h = h->next;
		}
		p = h->next;
	}
	strcpy(buf.text, "see you next time\n");
	//向要下线的用户发送告别消息
	sendto(sockfd, &buf, sizeof(buf), 0, (SA *)&peeraddr, sizeof(peeraddr));
}

void broadcast(int sockfd, MSG buf, linklist h)  //广播
{
	h = h->next;
	while (h != NULL)  //遍历链表中的每个结点,并发送消息
	{
		sendto(sockfd, &buf, sizeof(buf), 0, (SA *)&h->peeraddr, sizeof(h->peeraddr));
		h = h->next;
	}
}

int main(int argc, char *argv[])
{
	int sockfd;
	pid_t pid;
	MSG buf;
	struct sockaddr_in myaddr, peeraddr;
	socklen_t peerlen = sizeof(peeraddr);  

	if (argc < 3)
	{
		printf("Usage : %s <ip> <port>\n", argv[0]);
		return -1;
	}
	bzero(&myaddr, sizeof(myaddr));
	myaddr.sin_family = PF_INET;
	myaddr.sin_port = htons(atoi(argv[2]));
	myaddr.sin_addr.s_addr = inet_addr(argv[1]);

	// XXX int socket(int domain, int type, int protocol);
	if ((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
	{
		perror("fail to socket");
		exit(-1);
	}

	if (bind(sockfd, (SA *)&myaddr, sizeof(myaddr)) < 0)
	{
		perror("fail to bind");
		exit(-1);
	}

	if ((pid = fork()) < 0)
	{
		perror("fail to fork");
		exit(-1);
	}
	else if (pid == 0) // receive message
	{

		linklist h;
		h = (linklist)malloc(sizeof(linknode));  //创建一个链表
		h->next = NULL;  //初始化

		while ( 1 )
		{
			recvfrom(sockfd, &buf, sizeof(buf), 0, (SA *)&peeraddr, &peerlen);
			switch ( buf.type )  //判断消息类型
			{
			case R:
				add_user(sockfd, buf, peeraddr, h);
				break;
			case U:
				del_user(sockfd, buf, peeraddr, h);
				break;
			case E:
			case B:
				broadcast(sockfd, buf, h);
				break;
			}
			if (buf.type == E) exit(0);
		}
	}
	else  // send message
	{
		strcpy(buf.name, "system");
		buf.type = B;
		while ( 1 )
		{
			printf("server > ");
			fgets(buf.text, N, stdin);
             //如果用户输入的是quit,则父进程向myaddr发送服务器下线消息(这里没有采用共享内存以及管道) 
			if (strcmp(buf.text, "quit\n") == 0) 			
              {
				buf.type = E;
				sendto(sockfd, &buf, sizeof(buf), 0, (SA *)&myaddr, sizeof(myaddr));
				break;
			}
			sendto(sockfd, &buf, sizeof(buf), 0, (SA *)&myaddr, sizeof(myaddr));
			usleep(100000);
		}
		usleep(100000);
		//kill(pid, SIGUSR1); //这里不能杀死子进程,子进程还要广播告诉客户端服务器下线的消息
		exit(0);
	}

	close(sockfd);//当子进程close后,内核中的套接字结构体才真正释放

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值