客户端流程:
客户端程序:
#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; }
服务器流程:
服务器端程序:
#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; }