Linux网络编程5——使用UDP协议实现群聊
引言
本文实现的功能类似于我之前所写的一篇博文(Linux之select系统调用_2),区别在于进程之间的通信方式有所不同。之前的文章中,我所使用的是管道,而本文我将会使用socket接口。
需求
客户端发送消息给服务器,服务器收到消息后,会转发该消息给所有客户端。
思路
1. server端维护一个链表,用于存放客户端的联系方式。结构如下:
typedef struct sockaddr_in SA ; typedef struct client_tag
{
SA ct_addr;
struct client_tag* ct_next;
}CNODE, *pcNODE;
2. 服务器创建一个socket端口,用于接收客户端发送的消息。消息类别分为:通知上线,通知下线,以及聊天信息。因为消息类别不同,我们使用结构体将客户端发送的消息进行如下封装:
#define TYPE_ON 1
#define TYPE_OFF 2
#define TYPE_CHAT 3
#define SIZE 1024
typedef struct msg_tag
{
int msg_type;
int msg_len; /* 实际消息长度 */
char msg_buf[SIZE];
}MSG, *pMSG;
注意,服务器所创建的socket端口需要绑定自己的联系方式,以便其他客户端可以发消息(sendto函数)给服务器。
3. 服务器使用select轮询函数监听自己的socket端口。当返回值为0(轮询时间内没有客户端发消息)或者-1(收到信号,出错)时,继续轮询;当返回值为1时,说明有客户端发送消息。我们可以从recvfrom函数的传出参数中获取客户端的联系方式,此时根据收到的MSG类型,进行处理。如果MSG类型为上线,则将该客户端的联系方式加入链表;如果MSG类型为下线,则将其从链表中删除;如果MSG类型为聊天信息,则服务器将其转发给所有客户端。
代码
server端
/*************************************************************************
> File Name: my_server.c
> Author: KrisChou
> Mail:zhoujx0219@163.com
> Created Time: Fri 29 Aug 2014 04:21:51 PM CST
************************************************************************/ #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define TYPE_ON 1
#define TYPE_OFF 2
#define TYPE_CHAT 3
#define SIZE 1024
typedef struct sockaddr_in SA ;
typedef struct msg_tag
{
int msg_type ;
int msg_len;
char msg_buf[SIZE] ;
}MSG, *pMSG;
typedef struct client_tag
{
SA ct_addr ;
struct client_tag* ct_next ;
}CNODE, *pCNODE; void msg_broadcast(int sockfd, char* msg, pCNODE phead)
{
int n ;
while(phead)
{
n = sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)&phead -> ct_addr, sizeof(SA) ); printf("%d: %s : %d \n", n, inet_ntoa(phead -> ct_addr.sin_addr), ntohs(phead -> ct_addr.sin_port));
phead = phead -> ct_next ;
}
} void list_insert(pCNODE * phead, pCNODE p)
{
p ->ct_next = *phead ;
*phead = p ;
} void list_delete(pCNODE* phead, SA* p)
{
pCNODE pCur, pPre ;
pPre = NULL ;
pCur = *phead ;
while(pCur)
{
if(pCur -> ct_addr.sin_port == p ->sin_port && pCur ->ct_addr.sin_addr.s_addr ==p ->sin_addr.s_addr )
{
break ;
}else
{
pPre = pCur ;
pCur = pCur -> ct_next ;
}
}
if(pPre == NULL)
{
*phead = pCur -> ct_next ;
free(pCur);
pCur = NULL ;
}else
{
pPre -> ct_next = pCur -> ct_next ;
free(pCur);
pCur = NULL ;
}
} int main(int argc, char* argv[])// EXE CONF
{
if(argc != 2)
{
printf("USAGE: EXE CONF ! \n");
exit(1);
}
pCNODE my_list = NULL ;
/* 创建服务器socket端口 */
int fd_server ;
if((fd_server = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("socket");
exit(1);
}
/* 从配置文件中读取服务器联系方式,以便绑定到socket端口 */
FILE* fp_conf ;
fp_conf = fopen(argv[1], "r");
if(fp_conf == NULL)
{
perror("fopen");
exit(1);
}
char server_ip[32]="";
int server_port ;
fscanf(fp_conf,"%s%d",server_ip, &server_port);
fclose(fp_conf);
/* 绑定服务器socket端口的联系方式 */
SA server_addr ;
memset(&server_addr, 0, sizeof(SA));
server_addr.sin_family = AF_INET ;
server_addr.sin_port = htons(server_port);
server_addr.sin_addr.s_addr = inet_addr(server_ip);
if(-1 ==bind(fd_server, (struct sockaddr*)&server_addr, sizeof(SA)))
{
perror("bind");
close(fd_server);
exit(1);
}
/* 设置select参数:监听集合以及轮询时间 */
fd_set readset, readyset ;
FD_ZERO(&readset);
FD_ZERO(&readyset);
FD_SET(fd_server, &readset);
struct timeval tm ; /* 进入轮询 */
int select_ret ;
while(1)
{
readyset = readset ;
tm.tv_sec = 0 ;
tm.tv_usec = 1000 ;
select_ret = select(fd_server + 1, &readyset, NULL, NULL, &tm);
if(select_ret == 0)
{
continue ;
}else if(select_ret == -1)
{
continue ;
}else if(select_ret == 1)
{
pCNODE pNew = (pCNODE)calloc(1, sizeof(CNODE));
int len = sizeof(SA);
char info[1024];
MSG my_msg ;
memset(&my_msg, 0, sizeof(MSG));
recvfrom(fd_server,&my_msg,sizeof(my_msg), 0, (struct sockaddr*)&(pNew ->ct_addr), &len);
if(my_msg.msg_type == TYPE_ON)//on
{
list_insert(&my_list, pNew );
printf("%s:%d on! \n",inet_ntoa(pNew ->ct_addr.sin_addr), ntohs(pNew ->ct_addr.sin_port)); }else if(my_msg.msg_type == TYPE_OFF)// off
{
list_delete(&my_list, &(pNew -> ct_addr) );
printf("%s:%d off! \n",inet_ntoa(pNew ->ct_addr.sin_addr), ntohs(pNew ->ct_addr.sin_port));
//kris add: 当客户端通知下线时,发送一条空消息给客户端,这样可以使对方孙子的recvfrom返回值为0
//从而可以退出循环,退出进程
sendto(fd_server,"",0,0,(struct sockaddr*)&(pNew->ct_addr),sizeof(SA));
}else //send
{
printf("chat msg ! \n");
memset(info, 0, 1024);
sprintf(info,"\tfrom %s:%5d:\n%s\n",inet_ntoa(pNew ->ct_addr.sin_addr), ntohs(pNew ->ct_addr.sin_port),my_msg.msg_buf);
puts(info);
msg_broadcast(fd_server, info, my_list);
}
}
} return 0 ;
}
client端
/*************************************************************************
> File Name: my_client.c
> Author: KrisChou
> Mail:zhoujx0219@163.com
> Created Time: Fri 29 Aug 2014 05:20:20 PM CST
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
typedef struct sockaddr* pSA ;
typedef struct sockaddr_in SA ;
#define TYPE_ON 1
#define TYPE_OFF 2
#define TYPE_CHAT 3
#define SIZE 1024 /* 将消息封装成结构体 */
typedef struct msg_tag
{
int msg_type ;
int msg_len;
char msg_buf[SIZE] ;
}MSG, *pMSG; int main(int argc, char* argv[])
{
if(argc != 2)
{
printf("USAGE: EXE CONF ! \n");
exit(1);
}
/* 从配置文件中读取服务器的联系方式:IP及端口号 */
FILE* fp_conf ;
char server_ip[32]="";
int server_port ;
fp_conf = fopen(argv[1], "r");
if(fp_conf == NULL)
{
perror("fopen");
exit(1);
}
fscanf(fp_conf,"%s%d",server_ip, &server_port);
fclose(fp_conf); /* 创建客户端socket */
int fd_client ;
if((fd_client = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
perror("socket");
exit(1);
}
/* 存入服务器联系方式 */
SA server_addr ;
memset(&server_addr, 0, sizeof(SA));
server_addr.sin_family = AF_INET ;
server_addr.sin_port = htons(server_port);
server_addr.sin_addr.s_addr = inet_addr(server_ip);
/* 通知服务器上线 */
//MSG my_msg = {TYPE_ON,2,"on"} ;
MSG my_msg = {TYPE_ON,0,""} ;
sendto(fd_client, &my_msg, 8 + my_msg.msg_len , 0, (pSA)&server_addr, sizeof(SA));
/* 孙子进程用于接收服务器转发的消息,并显示在屏幕上 */
/* 当儿子进程fork出孙子后,立马会退出,从而被父进程(主程序)wait掉。
* 从而孙子成为孤儿进程,当其退出时资源会被init所回收。
* 实际上fork出孙子有两点原因,如下:
* 一是不愿意父进程wait子进程,因为wait是阻塞函数。
* 二是如果不wait,在子进程先退的情况下父进程不能回收其资源,从而先退的子进程会成为僵尸进程。
* 现在让儿子fork出孙子后立马滚蛋,这样孙子就直接变成孤儿进程了,由init收养,并在退出时由init回收资源。
* 注意,父进程先滚蛋其实是无碍的,大不了init做儿子(孤儿进程)的爹,并由init来回收资源。但通常父进程会模拟服务器,不会退。*/
if(fork() == 0)
{
if(fork() == 0)
{ char msg_buf[1024];
// recvfrom是阻塞函数,孙子进程退出程序后,会被init回收。此处
while(memset(msg_buf, 0, 1024), recvfrom(fd_client, msg_buf, 1024, 0, NULL, NULL) > 0)
{
write(1,msg_buf, strlen(msg_buf)); }
printf("child exit ! \n");
close(fd_client);
exit(0);
/* 用于说明爷爷退了之后,孙子不会被一起带走。
sleep(5);
while(1)
{
printf("hahahahaha\n");
}
*/
}
close(fd_client);
exit(0);
}
wait(NULL);
/* 从键盘输入消息,发送给服务器,按ctrl+D退出循环 */
while(memset(&my_msg, 0, sizeof(MSG)), fgets(my_msg.msg_buf, SIZE, stdin) != NULL)
{
my_msg.msg_type = TYPE_CHAT ;
my_msg.msg_len = strlen(my_msg.msg_buf);
sendto(fd_client, &my_msg, 8 + my_msg.msg_len , 0, (pSA)&server_addr, sizeof(SA));
}
/* 向服务器发送离线消息 */
my_msg.msg_type = TYPE_OFF ;
my_msg.msg_len = 0 ;
sendto(fd_client, &my_msg, 8 + my_msg.msg_len , 0, (pSA)&server_addr, sizeof(SA)); close(fd_client); return 0 ;
/*注意:只要主进程滚蛋了,马上会显示出shell界面。但是此时,fork出来的进程可能并没有结束。
*fork出来的进程先滚蛋,是不会显示shell界面的。只有主进程滚蛋,才会显示出shell界面。 */
}
配置文件
192.168.0.180
8888
注意
程序运行时,先启动服务器端,不然客户端没法连接。
Linux网络编程5——使用UDP协议实现群聊的更多相关文章
- Linux 网络编程五(UDP协议)
UDP和TCP的对比 --UDP处理的细节比TCP少. --UDP不能保证消息被传送到目的地. --UDP不能保证数据包的传递顺序. --TCP处理UDP不处理的细节. --TCP是面向连接的协议 - ...
- Linux网络编程10——使用UDP实现五子棋对战
思路 1. 通信 为了同步双方的棋盘,每当一方在棋盘上落子之后,都需要发送给对方一个msg消息,让对方知道落子位置.msg结构体如下: /* 用于发给对方的信息 */ typedef struct t ...
- 网络编程4之UDP协议
一.定义 UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种[无 ...
- python 网络编程 tcp和udp 协议
1. 网络通信协议 osi七层,tcp\ip五层 tcp\ip五层 arp协议:通过IP地址找到mac地址 2.tcp和udp的区别 tcp协议:面向连接,消息可靠,相对udp来讲,传输速度慢,消息是 ...
- 网络编程之基于UDP协议的套接字编程、基于socketserver实现并发的socket
目录 基于UDP协议的套接字编程 UDP套接字简单示例 服务端 客户端 基于socketserver实现并发的socket 基于TCP协议 server类 request类 继承关系 服务端 客户端1 ...
- python 之 网络编程(基于UDP协议的套接字通信)
8.5 基于UDP协议的套接字通信 UDP协议:数据报协议 特点:无连接,一发对应一收,先启动哪一端都不会报错 优点:发送效率高,但有效传输的数据量最多为500bytes 缺点:不可靠:发送数据,无需 ...
- Linux网络编程四、UDP,广播和组播
一.UDP UDP:是一个支持无连接的传输协议,全称是用户数据包协议(User Datagram Protocol).UDP协议无需像TCP一样要建立连接后才能发送封装的IP数据报,也是因此UDP相较 ...
- UNIX/Linux网络编程基础:应用层协议简介
目录 1.HTTP协议 2.FTP协议 3.TELNET协议 4.NFS协议 1.HTTP协议 应用层协议HTTP协议是Web的核心.HTTP协议在Web的客户程序和服务器程序中得以实现,运行在不同系 ...
- Linux网络编程:基于UDP的程序开发回顾篇
基于无连接的UDP程序设计 同样,在开发基于UDP的应用程序时,其主要流程如下: 对于面向无连接的UDP应用程序在开发过程中服务端和客户端的操作流程基本差不多.对比面向连接的TCP程序,服务端少了 ...
随机推荐
- JavaScript高级程序设计之表单基础
A FORM <form id='form' action='http://a-response-url' method="post"> <!--maxlengt ...
- python 生成二维码
#coding:utf8 try: import qrcode except ImportError: qrcode = None class MakeQr: def onUseQrcode(self ...
- 从零开始学ios开发(三):第一个有交互的app
感谢大家的关注,也给我一份动力,让我继续前进.有了自己的家庭有了孩子,过着上有老下有小的生活,能够挤出点时间学习真的很难,每天弄好孩子睡觉已经是晚上10点左右了,然后再弄自己的事情,一转眼很快就到12 ...
- @property @synthesize的含义以及误区。
@property的作用是定义属性,声明getter,setter方法.(注意:属性不是变量) @synthesize的作用是实现属性的,如getter,setter方法. 在声明属性的情况下如果重写 ...
- dubbox使用
1.命令行下 git clone https://github.com/dangdangdotcom/dubbox 2.mvn install -Dmaven.test.skip=true 跳过测试编 ...
- C Primer Plus学习笔记
1.汇编语言是特地的Cpu设计所采用的一组内部指令的助记符,不同的Cpu类型使用不同的Cpu C给予你更多的自由,也让你承担更多的风险 自由的代价是永远的警惕 2.目标代码文件.可执行文件和库 3.可 ...
- UIDatePicker swift
// // ViewController.swift // UILabelTest // // Created by mac on 15/6/23. // Copyright (c) 2015年 fa ...
- 用vs2013编译lua源码方法
1.下载lua源码:lua-5.2.3.tar.gz,解压 2.用vs2013建立一个win32工程: 1)下载后解压到一个目录下,这里假设解压到 F:\lua-5.2.3 注意下载的版本,如果是 ...
- 如何在Report Builder中使用fnd_profile.value
在EBS的Report开发中,需要根据客户化的一个Profile来控制用户可以访问的数据,可是在开发的过程中发现一直取不到该Profile的值,后来百度才找到了原因. 解决方法: 1.添加用户参数p_ ...
- 不会JS中的OOP,你也太菜了吧!(第一篇)
一.你必须知道的 1) 字面量 2) 原型 3) 原型链 4) 构造函数 5) 稳妥对象(没有公共属性,而且其方法也不引用this的对象.稳妥对象适合用在安全的环境中和防止数据被其它程序改变的时候) ...