单播:前几篇博客介绍的数据包发送方式只有一个接受方,称为单播
广播:如果同时发给局域网中的所有主机,称为广播
组播:如果只发给局域网内的部分主机,称为组播
同一个sockfd 只能处理一种接收方式,如果即想发单播又想发广播,还想发组播,需要三个sockfd。只有用户数据报(使用UDP协议)套接字才能广播。
2.广播地址以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址(具体以ifconfig 命令查看到的 broadcast 后面的为准)发到该地址的数据包被所有的主机接收。
注:255.255.255.255在所有网段中都代表广播地址。
广播能发给局域网所有主机的原理:
因为广播的数据包比较特殊,他的目的mac地址全是f(ff:ff:ff:ff:ff:ff) 这个数据包会发给交换机,交换机是工作在链路层的,交换机看到这样目的mac全是f的数据包,就会将该数据包发给局域网内的所有主机。到达主机后,进行拆包,看到目的mac是广播的mac,则允许通过。到达网络层一看IP地址是广播的IP地址,则可以通过。到达传输层,只要端口号匹配,则数据就能到达应用层。
广播的应用:ARP请求,通过ip地址获取对方的mac地址,使用的就是广播。
3.广播的流程发送者:
创建套接字 socket( )
设置为允许发送广播权限 setsockopt( )
填充广播信息结构体 sockaddr_in
发送数据 sendto( )
接收者:
创建套接字 socket( )
填充广播信息结构体 sockaddr_in
将套接字与广播信息结构体绑定 bind( )
接收数据 recvfrom( )
getsockopt函数说明:
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
功能:设置允许发送广播
#include
#include
参数:
@sockfd: socket函数返回的套接字
@level:SOL_SOCKET 套接字级别
@optname:SO_BROADCAST 允许发送广播
@optval:int on = 1
@optlen:sizeof(on)
返回值:成功返回0,失败返回-1,置位错误码
示例:
int on = 1;
if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on))){
ERRLOG("setsockopt error");
}
//接收端 #include#include #include #include #include #include #include #include #include #include #include #include #define N 128 #define ERRLOG(errmsg) do{ perror(errmsg); printf("%s--%s(%d)n", __FILE__, __func__, __LINE__); exit(-1); }while(0) int main(int argc, const char *argv[]){ if(3 != argc){ printf("Usage : %s n", argv[0]); exit(-1); } //1.创建用户数据报式套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(-1 == sockfd){ ERRLOG("socket error"); } //创建服务器广播信息结构体 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); //端口号随便定 ip地址需要是 广播的ip地址 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(atoi(argv[2])); server_addr.sin_addr.s_addr = inet_addr(argv[1]); socklen_t addrlen = sizeof(server_addr); //3.将套接字和广播信息结构体进行绑定 if(-1 == bind(sockfd, (struct sockaddr *)&server_addr, addrlen)){ ERRLOG("bind error"); } //定义一个结构体,保存客户端的信息 struct sockaddr_in client_addr; memset(&server_addr, 0, sizeof(client_addr));//清空 socklen_t clientaddrlen = sizeof(client_addr); char buff[N] = {0}; while(1){ //接收数据,如果想要给对方回应,就必须保存对方的网络信息结构体 //如果不回应,后两个参数写 NULL 也行 if(-1 == recvfrom(sockfd, buff, N, 0, (struct sockaddr *)&client_addr, &clientaddrlen)){ ERRLOG("recvfrom error"); } printf("%s(%d):%sn", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buff); #if 0 //组装应答信息 strcat(buff, "--sever"); if(-1 == sendto(sockfd, buff, N, 0, (struct sockaddr *)&client_addr, clientaddrlen)){ ERRLOG("sendto error"); } #endif memset(buff, 0, N); } //关闭套接字 close(sockfd); return 0; }
//发送端 #include二、组播(Multicast) 1.概念#include #include #include #include #include #include #include #include #include #include #include #define N 128 #define ERRLOG(errmsg) do{ perror(errmsg); printf("%s--%s(%d)n", __FILE__, __func__, __LINE__); exit(-1); }while(0) int main(int argc, const char *argv[]){ if(3 != argc){ printf("Usage : %s n", argv[0]); exit(-1); } //1.创建用户数据报套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(-1 == sockfd){ ERRLOG("socket error"); } //设置套接字允许发送广播 setsockopt int on = 1; if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on))){ ERRLOG("setsockopt error"); } //填充服务器广播信息结构体 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(atoi(argv[2])); //广播的ip地址 server_addr.sin_addr.s_addr = inet_addr(argv[1]); socklen_t addrlen = sizeof(server_addr); char buff[N] = {0}; while(1){ printf("input your msg:"); fgets(buff, N, stdin); buff[strlen(buff)-1] = ' ';//清除 n if(0 == strcmp(buff, "quit")){ break; } if(-1 == sendto(sockfd, buff, N, 0, (struct sockaddr *)&server_addr, addrlen)){ ERRLOG("sendto error"); } #if 0 //因为客户端已经知道服务器的网络信息了 所以后两个参数可以传NULL if(-1 == recvfrom(sockfd, buff, N, 0, NULL, NULL)){ ERRLOG("recvfrom error"); } printf("recv:[%s]n", buff); #endif memset(buff, 0, N); } //关闭套接字 close(sockfd); return 0; }
单播方式只能发给一个接收方。广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。
使用SOCK_DGRAM多播方式既可以发给多个主机,又能避免像广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
2.组播的地址D类地址(组播地址)
不分网络地址和主机地址,第1字节的前4位固定为1110(224.0.0.1 – 239.255.255.255)
3.组播的流程发送者:
创建套接字 socket( )
填充组播信息结构体 sockaddr_in
发送数据 sendto( )
接收者:
创建套接字 scoket( )
填充组播信息结构体 sockaddr_in
将套接字与组播信息结构体绑定 bind( )
设置为加入多播组 setsockopt( )
接收数据 recvfrom( )
setsockopt函数说明:
int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t optlen);
功能:设置套接字加入多播组
#include
#include
参数:
@sockfd: socket函数返回的套接字
@level:IPPROTO_IP IP级别
@optname:IP_ADD_MEMBERSHIP 选项名 设置加入多播组
@optval:
struct ip_mreqn {
struct in_addr imr_multiaddr;
struct in_addr imr_address;
int imr_ifindex;
};
//或者使用不带n的结构体
struct ip_mreq {
struct in_addr imr_multiaddr;
struct in_addr imr_address;
};
@optlen:optval的大小
返回值:成功返回0,失败返回-1,置位错误码
//接收端 #include#include #include #include #include #include #include #include #include #include #include #include #define N 128 #define ERRLOG(errmsg) do{ perror(errmsg); printf("%s--%s(%d)n", __FILE__, __func__, __LINE__); exit(-1); }while(0) int main(int argc, const char *argv[]){ if(3 != argc){ printf("Usage : %s n", argv[0]); exit(-1); } //1.创建用户数据报式套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(-1 == sockfd){ ERRLOG("socket error"); } //创建服务器网络信息结构体 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(atoi(argv[2])); //多播组的ip地址 224.0.0.1 – 239.255.255.255 server_addr.sin_addr.s_addr = inet_addr("224.0.0.1"); socklen_t addrlen = sizeof(server_addr); //3.将套接字和广播信息结构体进行绑定 if(-1 == bind(sockfd, (struct sockaddr *)&server_addr, addrlen)){ ERRLOG("bind error"); } //设置加入多播组 struct ip_mreq my_mreq; memset(&my_mreq, 0, sizeof(my_mreq)); my_mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1"); my_mreq.imr_interface.s_addr = inet_addr(argv[1]); if(-1 == setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &my_mreq, sizeof(my_mreq))){ ERRLOG("setsockopt error"); } //定义一个结构体,保存客户端的信息 struct sockaddr_in client_addr; memset(&server_addr, 0, sizeof(client_addr));//清空 socklen_t clientaddrlen = sizeof(client_addr); char buff[N] = {0}; while(1){ //接收数据,如果想要给对方回应,就必须保存对方的网络信息结构体 //如果不回应,后两个参数写 NULL 也行 if(-1 == recvfrom(sockfd, buff, N, 0, (struct sockaddr *)&client_addr, &clientaddrlen)){ ERRLOG("recvfrom error"); } printf("%s(%d):%sn", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buff); #if 0 //组装应答信息 strcat(buff, "--sever"); if(-1 == sendto(sockfd, buff, N, 0, (struct sockaddr *)&client_addr, clientaddrlen)){ ERRLOG("sendto error"); } #endif memset(buff, 0, N); } //关闭套接字 close(sockfd); return 0; }
//发送端 #include#include #include #include #include #include #include #include #include #include #include #include #define N 128 #define ERRLOG(errmsg) do{ perror(errmsg); printf("%s--%s(%d)n", __FILE__, __func__, __LINE__); exit(-1); }while(0) int main(int argc, const char *argv[]){ if(3 != argc){ printf("Usage : %s n", argv[0]); exit(-1); } //1.创建用户数据报套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(-1 == sockfd){ ERRLOG("socket error"); } //填充服务器组播信息结构体 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(atoi(argv[2])); //多播组的ip地址 224.0.0.1 – 239.255.255.255 //这个范围内的每个ip地址 都表示一个多播组 server_addr.sin_addr.s_addr = inet_addr(argv[1]); socklen_t addrlen = sizeof(server_addr); char buff[N] = {0}; while(1){ printf("input your msg:"); fgets(buff, N, stdin); buff[strlen(buff)-1] = ' ';//清除 n if(0 == strcmp(buff, "quit")){ break; } if(-1 == sendto(sockfd, buff, N, 0, (struct sockaddr *)&server_addr, addrlen)){ ERRLOG("sendto error"); } memset(buff, 0, N); } //关闭套接字 close(sockfd); return 0; }
注:如果接收不到组播消息:
先执行一下命令: sudo ifconfig 网卡名 allmulti
网卡名可以使用 ifconfig命令查出来
三、本地通信 1.概念socket最早起也是用于本地通信的,后来有了TCP/IP协议族的的加入,才可以实现不同主机的进程间通信。本地通信和其他进程间通信方式相比使用方便、效率更高,多用于前后台进程通信。
创建套接字时使用本地协议AF_UNIX(或AF_LOCAL)。分为流式套接字和用户数据报套接字 ----区别还是有无连接。
2.本地通信使用的结构体struct sockaddr_un {
sa_family_t sun_family;
char sun_path[108];
};
服务器:
创建套接字 socket( )
填充服务器本地信息结构体 sockaddr_un
将套接字与服务器本地信息结构体绑定 bind( )
将套接字设置为被动监听状态 listen( )
阻塞等待客户端的连接请求 accept( )
进行通信 recv( )/send( ) 或 read( )/write( )
客户端:
创建套接字 socket( )
填充服务器本地信息结构体 sockaddr_un
发送客户端的连接请求 connect( )
进行通信 send( )/recv( )
代码实现:
//服务器端 #include#include #include #include #include #include #include #define ERRLOG(errmsg) do{ perror(errmsg); printf("%s-%s(%d)n", __FILE__, __func__, __LINE__); exit(-1); }while(0) int main(int argc, char *argv[]){ if(2!=argc){ printf("Usage : %s n", argv[0]); exit(-1); } //1.创建套接字 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if(-1 == sockfd){ ERRLOG("socket error"); } //2.填充服务器本地信息结构体 struct sockaddr_un server_addr; memset(&server_addr, 0, sizeof(server_addr));//清空 server_addr.sun_family = AF_UNIX; //注意:sun_path是一个数组 不能使用 等号赋值 strcpy(server_addr.sun_path, argv[1]); socklen_t addrlen = sizeof(server_addr); //3.将套接字和网络信息结构体进行绑定 if(-1 == bind(sockfd, (struct sockaddr *)&server_addr, addrlen)){ ERRLOG("bind error"); } //4.将服务器的套接字设置成被动监听状态 if(-1 == listen(sockfd, 5)){ ERRLOG("listen error"); } //定义一个结构体,保存客户端的信息 struct sockaddr_un client_addr; memset(&client_addr, 0, sizeof(client_addr));//清空 socklen_t clientaddrlen = sizeof(client_addr); char buff[128] = {0}; int acceptfd = 0; int bytes = 0; while(1){ //5.阻塞等待客户端连接 acceptfd = accept(sockfd, (struct sockaddr *)&client_addr, &clientaddrlen); if(-1 == acceptfd){ ERRLOG("accept error"); } printf("客户端 [%s] 连接到服务器了n", client_addr.sun_path); while(1){ //6.与客户端通信 if(0 > (bytes = recv(acceptfd, buff, 128, 0))){ ERRLOG("recv error"); }else if(bytes == 0){ printf("客户端 [%s] 断开了连接n", client_addr.sun_path); break; }else{ if(0 == strcmp(buff, "quit")){ printf("客户端 [%s] 退出了n", client_addr.sun_path); break; } printf("客户端 [%s] 发来数据:[%s]n", client_addr.sun_path, buff); //组装应答 strcat(buff, "--sever"); if(-1 == send(acceptfd, buff, 128, 0)){ ERRLOG("send error"); } } } //7.关闭套接字 close(acceptfd); } close(sockfd); return 0; }
//客户端 #include4.UDP本地通信流程及代码实现#include #include #include #include #include #include #define ERRLOG(errmsg) do{ perror(errmsg); printf("%s-%s(%d)n", __FILE__, __func__, __LINE__); exit(-1); }while(0) int main(int argc, char *argv[]){ if(2!=argc){ printf("Usage : %s n", argv[0]); exit(-1); } //1.创建套接字 int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if(-1 == sockfd){ ERRLOG("socket error"); } struct sockaddr_un server_addr; memset(&server_addr, 0, sizeof(server_addr));//清空 server_addr.sun_family = AF_UNIX; //注意:sun_path是一个数组 不能使用 等号赋值 strcpy(server_addr.sun_path, argv[1]); socklen_t addrlen = sizeof(server_addr); //定义一个结构体,保存客户端的信息 struct sockaddr_un client_addr; memset(&client_addr, 0, sizeof(client_addr));//清空 client_addr.sun_family = AF_UNIX; strcpy(client_addr.sun_path,"tcp_client_sock_file"); socklen_t clientaddrlen = sizeof(client_addr); if(-1 == bind(sockfd, (struct sockaddr *)&client_addr, clientaddrlen)){ ERRLOG("bind error"); } //3.与服务器建立连接 if(-1 == connect(sockfd, (struct sockaddr *)&server_addr, addrlen)){ ERRLOG("connect error"); } //4.与服务器通信 char buff[128] = {0}; while(1){ fgets(buff, 128, stdin); buff[strlen(buff)-1] = ' ';//清除 n if(-1 == send(sockfd, buff, 128, 0)){ ERRLOG("send error"); } if(0 == strcmp(buff, "quit")){ break; } if(-1 == recv(sockfd, buff, 128, 0)){ ERRLOG("recv error"); } printf("收到回复:[%s]n", buff); } //5.关闭套接字 close(sockfd); return 0; }
服务器:
创建套接字 socket( ) SOCK_DGRAM
填充服务器本地信息结构体 sockaddr_un
将套接字与服务器本地信息结构体绑定 bind( )
进行通信 recvfrom( )/sendto( )
客户端:
创建套接字 socket( )
填充客户端本地信息结构体 sockaddr_un
将套接字与客户端本地信息结构体绑定 bind( ) //要绑定 否则服务器无法给客户端回信
填充服务器本地信息结构体 sockaddr_un
进行通信 sendto( ) / recvfrom( )
代码实现:
//服务器端 #include#include #include #include #include #include #include #define ERRLOG(errmsg) do{ perror(errmsg); printf("%s-%s(%d)n", __FILE__, __func__, __LINE__); exit(-1); }while(0) int main(int argc, char *argv[]){ if(2!=argc){ printf("Usage : %s n", argv[0]); exit(-1); } //1.创建套接字 int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0); if(-1 == sockfd){ ERRLOG("socket error"); } //2.填充服务器本地信息结构体 struct sockaddr_un server_addr; memset(&server_addr, 0, sizeof(server_addr));//清空 server_addr.sun_family = AF_UNIX; //注意:sun_path是一个数组 不能使用 等号赋值 strcpy(server_addr.sun_path, argv[1]); socklen_t addrlen = sizeof(server_addr); //3.将套接字和网络信息结构体进行绑定 if(-1 == bind(sockfd, (struct sockaddr *)&server_addr, addrlen)){ ERRLOG("bind error"); } //定义一个结构体,保存客户端的信息 //给发送者回信 需要用到 struct sockaddr_un client_addr; memset(&client_addr, 0, sizeof(client_addr));//清空 socklen_t clientaddrlen = sizeof(client_addr); char buff[128] = {0}; int bytes = 0; while(1){ //6.与客户端通信 if(0 > (bytes = recvfrom(sockfd, buff, 128, 0, (struct sockaddr *)&client_addr, &clientaddrlen))){ ERRLOG("recvfrom error"); }else{ printf("客户端 [%s] 发来数据:[%s]n", client_addr.sun_path, buff); //组装应答 strcat(buff, "--sever"); if(-1 == sendto(sockfd, buff, 128, 0, (struct sockaddr *)&client_addr, clientaddrlen)){ ERRLOG("sendto error"); } } } close(sockfd); return 0; }
//客户端 #include#include #include #include #include #include #include #define ERRLOG(errmsg) do{ perror(errmsg); printf("%s-%s(%d)n", __FILE__, __func__, __LINE__); exit(-1); }while(0) int main(int argc, char *argv[]){ if(3!=argc){ printf("Usage : %s n", argv[0]); exit(-1); } //1.创建套接字 int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0); if(-1 == sockfd){ ERRLOG("socket error"); } //服务器的本地信息结构体 struct sockaddr_un server_addr; memset(&server_addr, 0, sizeof(server_addr));//清空 server_addr.sun_family = AF_UNIX; //注意:sun_path是一个数组 不能使用 等号赋值 strcpy(server_addr.sun_path, argv[1]); socklen_t addrlen = sizeof(server_addr); //定义一个结构体,保存自己的信息 struct sockaddr_un client_addr; memset(&client_addr, 0, sizeof(client_addr));//清空 client_addr.sun_family = AF_UNIX; strcpy(client_addr.sun_path, argv[2]); socklen_t clientaddrlen = sizeof(client_addr); if(-1 == bind(sockfd, (struct sockaddr *)&client_addr, clientaddrlen)){ ERRLOG("bind error"); } //4.与服务器通信 char buff[128] = {0}; while(1){ fgets(buff, 128, stdin); buff[strlen(buff)-1] = ' ';//清除 n if(-1 == sendto(sockfd, buff, 128, 0, (struct sockaddr *)&server_addr, addrlen)){ ERRLOG("sendto error"); } //无需再保存服务器的 本地信息结构体了 因为server_addr我们没修改过 if(-1 == recvfrom(sockfd, buff, 128, 0, NULL, NULL)){ ERRLOG("recvfrom error"); } printf("收到回复:[%s]n", buff); } //5.关闭套接字 close(sockfd); return 0; }
至此,“网络编程” 专栏的全部知识讲完了,下面的小项目检测一下学习成果,建议先试着自己写,不会的部分再看下面的代码。
注:接下来会更新Python专栏,喜欢的我的博客可以点赞关注,持续更新中。
四、练习:UDP群聊聊天室 1.功能①有新用户登录,其他在线的用户可以收到登录信息
②有用户群聊,其他在线的用户可以收到群聊信息
③有用户退出,其他在线的用户可以收到退出信息
④服务器可以发送系统信息
提示:
①客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程
②服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程
③服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存
④数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据
2.写项目的流程①画流程图
②根据流程图写框架
③一个功能一个功能实现
流程图:
3.功能实现//服务器端 #include#include #include #include #include #include #include #include #include #include #define ERRLOG(errmsg) do{ perror(errmsg); printf("%s-%s(%d)n", __FILE__, __func__, __LINE__); exit(-1); }while(0) typedef struct __MSG{ char code; char name[32]; char text[128]; }msg_t; typedef struct __NODE{ struct sockaddr_in clientaddr; struct __NODE *next; }node_t; void do_login(int sockfd, node_t * phead, msg_t msg, struct sockaddr_in clientaddr); void do_chat(int sockfd, node_t * phead, msg_t msg, struct sockaddr_in clientaddr); void do_quit(int sockfd, node_t * phead, msg_t msg, struct sockaddr_in clientaddr); void create_node(node_t **pnew); int main(int argc, char *argv[]){ //对入参合理性进行检查 if(3 != argc){ printf("Usage : %s n", argv[0]); exit(-1); } //创建数据报式套接字 int sockfd = 0; if(-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0))){ ERRLOG("socket error"); } //填充服务器的网络信息结构体 struct sockaddr_in serveraddr; memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(atoi(argv[2])); serveraddr.sin_addr.s_addr = inet_addr(argv[1]); socklen_t serveraddr_len = sizeof(serveraddr); //绑定套接字与服务器网络信息结构体 if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){ ERRLOG("bind error"); } //创建子进程 pid_t pid = 0; if(-1 == (pid = fork())){ ERRLOG("fork error"); }else if(0 == pid){//子进程逻辑 //创建保存客户端网络信息结构体的链表的头结点 node_t *phead = NULL; create_node(&phead); //定义结构体保存客户端的信息 struct sockaddr_in clientaddr; memset(&clientaddr, 0, sizeof(clientaddr)); socklen_t clientaddr_len = sizeof(clientaddr); msg_t msg; while(1){ memset(&msg, 0, sizeof(msg)); if(-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &clientaddr_len)){ ERRLOG("recvfrom error"); } //根据操作码不同,做不同的操作 switch(msg.code){ case 'L': do_login(sockfd, phead, msg, clientaddr); break; case 'C': do_chat(sockfd, phead, msg, clientaddr); break; case 'Q': do_quit(sockfd, phead, msg, clientaddr); break; } } close(sockfd); }else if(0 < pid){ //父进程逻辑 //发送系统消息:把父进程当做客户端给子进程发送群聊消息 msg_t msg; while(1){ memset(&msg, 0, sizeof(msg)); msg.code = 'C'; strcpy(msg.name, "server"); fgets(msg.text, 128, stdin); msg.text[strlen(msg.text)-1] = ' '; if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len)){ ERRLOG("sendto error"); } } close(sockfd); } return 0; } void create_node(node_t **pnew){ *pnew = (node_t *)malloc(sizeof(node_t)); memset((*pnew), 0, sizeof(node_t)); } //登录操作 void do_login(int sockfd, node_t * phead, msg_t msg, struct sockaddr_in clientaddr){ //将xx加入群聊的消息发给链表的所有节点 node_t *ptemp = phead; while(ptemp->next != NULL){ ptemp = ptemp->next; if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->clientaddr), sizeof(ptemp->clientaddr))){ ERRLOG("sendto error"); } } //将新客户端的网络信息结构体插入链表--头插法 node_t *pnew = NULL; create_node(&pnew); pnew->clientaddr = clientaddr; pnew->next = phead->next; phead->next = pnew; } //群聊操作 void do_chat(int sockfd, node_t * phead, msg_t msg, struct sockaddr_in clientaddr){ node_t *ptemp = phead; while(ptemp->next != NULL){ ptemp = ptemp->next; if(memcmp(&(ptemp->clientaddr), &clientaddr, sizeof(clientaddr))){ //进到这里说明 不是自己 if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->clientaddr), sizeof(ptemp->clientaddr))){ ERRLOG("sendto error"); } } } } //退出操作 void do_quit(int sockfd, node_t * phead, msg_t msg, struct sockaddr_in clientaddr){ //先将要突出的客户端网络信息结构体在链表中删除 node_t *ptemp = phead; node_t *pdel = NULL; while(ptemp->next != NULL){ if(!memcmp(&(ptemp->next->clientaddr), &clientaddr, sizeof(clientaddr))){ //说明是自己 pdel = ptemp->next; ptemp->next = pdel->next; free(pdel); pdel = NULL; }else{ if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(ptemp->next->clientaddr), sizeof(ptemp->next->clientaddr))){ ERRLOG("sendto error"); } ptemp = ptemp->next; } } }
//客户端 #include#include #include #include #include #include #include #include #include #include #include #include #define ERRLOG(errmsg) do{ perror(errmsg); printf("%s-%s(%d)n", __FILE__, __func__, __LINE__); exit(-1); }while(0) typedef struct __MSG{ char code; char name[32]; char text[128]; }msg_t; int main(int argc, char *argv[]){ //对入参合理性进行检查 if(3 != argc){ printf("Usage : %s n", argv[0]); exit(-1); } //创建数据报式套接字 int sockfd = 0; if(-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0))){ ERRLOG("socket error"); } //填充服务器的网络信息结构体 struct sockaddr_in serveraddr; memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(atoi(argv[2])); serveraddr.sin_addr.s_addr = inet_addr(argv[1]); socklen_t serveraddr_len = sizeof(serveraddr); //先发送登录消息 msg_t msg; memset(&msg, 0, sizeof(msg)); msg.code = 'L'; printf("请输入用户名:"); fgets(msg.name, 32, stdin); msg.name[strlen(msg.name)-1] = ' '; strcpy(msg.text, "加入了群聊"); if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len)){ ERRLOG("sendto error"); } //创建子进程 pid_t pid = 0; if(-1 == (pid = fork())){ ERRLOG("fork error"); }else if(0 == pid){//子进程 while(1){ memset(&msg, 0, sizeof(msg)); if(-1 == recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL)){ ERRLOG("recvfrom error"); } printf("%s : %sn", msg.name, msg.text); } }else if(0 < pid){//父进程 while(1){ memset(msg.text, 0, sizeof(msg.text)); fgets(msg.text, 128, stdin); msg.text[strlen(msg.text)-1] = ' '; if(!strcmp(msg.text, "quit")){ msg.code = 'Q'; strcpy(msg.text,"退出群聊"); }else{ msg.code = 'C'; } if(-1 == sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, serveraddr_len)){ ERRLOG("sendto error"); } if(!strcmp(msg.text, "退出群聊")){ break; } } //让子进程退出 kill(pid, SIGKILL); wait(NULL); //关闭套接字 close(sockfd); } return 0; }



