zoukankan      html  css  js  c++  java
  • 基于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;
    }
  • 相关阅读:
    mybatis实战教程(mybatis in action),mybatis入门到精通
    jquery 设置select的默认值
    一些最佳做法,即将推出的产品列表
    My97DatePicker日历控件日报、每周和每月的选择
    Android在第三方应用程序系统应用尽早开始,杀死自己主动的第三方应用程序,以重新启动
    Scrapy研究和探索(五岁以下儿童)——爬行自己主动多页(抢别人博客所有文章)
    Arcgis sde 10.1您不能创建在安装后的空间库,提示User has privileges required to create database objects.
    cocos2d-x 网络请求
    HDU 3729 I&#39;m Telling the Truth(二部图最大匹配+结果输出)
    解决opengl计算顶点的法线问题
  • 原文地址:https://www.cnblogs.com/pengdonglin137/p/2960304.html
Copyright © 2011-2022 走看看