基于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;

}

你可能感兴趣的:(UDP)