需求分析
- 网络协议:UDP
- 服务器需求:
- 需要一个存放用户数据的容器
- 服务器需要区分用户的操作类型(如:上线,下线,发送消息)
- 因为存在多个阻塞IO,需要多进程,多线程,或IO复用实现
- 客户端需求:
- 客户端发送的消息,需要带有相应的消息类型
- 能够接收服务器转发消息,并区分出消息发送者
消息协议及其核心函数
c
复制代码
//消息结构体
typedef struct msg
{
unsigned short type;
unsigned char name[32];
unsigned char data[128];
} Msg;
//登录事件
int Conn_Event(int server, Msg msg, ClistPtr C, struct sockaddr_in caddr);
//消息事件
int Data_Event(int server, Msg msg, ClistPtr C, struct sockaddr_in caddr);
//退出事件
int Close_Event(int server, Msg msg, ClistPtr C, struct sockaddr_in caddr);
登录事件核心功能实现
c
复制代码
//通过遍历链表的方式将用户上线消息发送给所有其他成员
while(p->next != NULL)
{
p = p->next;
if(sendto(server, &msg, sizeof(msg), 0, (struct sockaddr*)&(p->caddr), sizeof(p->caddr)) < 0)
{
LOG("sendto error");
return -1;
}
}
//当遍历结束后将登录用户信息加入链表
//链表头插
list_insert_head(C, caddr);
数据事件核心功能实现
c
复制代码
//遍历链表将用户发送的消息进行群发
while(C->next != NULL)
{
C = C->next;
if(memcmp(&(C->caddr), &caddr, sizeof(caddr)) != 0)
{
sendto(server, &msg, sizeof(msg), 0, (struct sockaddr*)&(C->caddr), sizeof(C->caddr));
}
}
断连事件核心功能实现
c
复制代码
if(NULL == C->next)
{
printf("最后一位用户%s已下线\n", msg.name);
return 0;
}
while(p->next != NULL)
{
//找到断连用户节点的前一个节点,进行节点删除
if(memcmp(&(p->next->caddr), &caddr, sizeof(caddr)) == 0)
{
list_delete(p);
}
else
{
p = p->next;
if(sendto(server, &msg, sizeof(msg), 0, (struct sockaddr*)&p->caddr, sizeof(p->caddr)) < 0)
{
LOG("sendto error");
return -1;
}
}
}
服务器函数
c
复制代码
#include "udp_sever.h"
#define LOG(s) printf("[%s] {%s:%d} %s \n", __DATE__, __FILE__, __LINE__, s);
//线程参数结构体
typedef struct
{
int server;
Msg msg;
ClistPtr C;
struct sockaddr_in caddr;
} Arg_t;
//线程转发函数
void* forward(void* arg)
{
int server = ((Arg_t*)arg)->server;
Msg msg = ((Arg_t*)arg)->msg;
ClistPtr C = ((Arg_t*)arg)->C;
struct sockaddr_in caddr = ((Arg_t*)arg)->caddr;
Data_Event(server, msg, C, caddr);
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
//创建结构体变量
struct sockaddr_in saddr = {0};
ClistPtr C = list_create();
if(NULL == C)
{
LOG("list_creat error");
return -1;
}
if(argc != 2)
{
printf("please inputs: %s <port>\n", argv[0]);
return -1;
}
int server = 0;
if((server = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
LOG("server socket error");
return -1;
}
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) < 0)
{
LOG("bind error");
return -1;
}
printf("udp server start success\n");
int res = -1;
Msg msg;
struct sockaddr_in caddr = {0};
socklen_t len = sizeof(caddr);
pthread_t tid;
Arg_t arg;
//循环判断接收事件类型
while(1)
{
if((res = recvfrom(server, &msg, sizeof(msg), 0, (struct sockaddr*)&caddr, &len)) < 0)
{
LOG("recvfrom error");
return -1;
}
switch(msg.type)
{
//连接事件
case EVE_CONN:
Conn_Event(server, msg, C, caddr);
break;
//数据事件
case EVE_DATA:
arg.server = server;
arg.msg = msg;
arg.caddr = caddr;
arg.C = C;
if(pthread_create(&tid, NULL, forward, &arg) < 0)
{
LOG("pthread_create error");
return -1;
}
pthread_detach(tid);
break;
//断开事件
case EVE_CLOSE:
Close_Event(server, msg, C, caddr);
break;
default:
LOG("event error");
return -1;
}
}
close(server);
return 0;
}
客户端函数
c
复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "udp_sever.h"
#define LOG(s) printf("[%s] {%s:%d} %s\n", __DATE__, __FILE__, __LINE__, s);
//消息结构体
Msg msg;
//文件描述符变量
int sock = 0;
//远端地址变量
struct sockaddr_in remote = {0};
void handler(int sig);
int main(int argc, char const *argv[])
{
if(argc != 4)
{
printf("please inputs: %s <user name> <host port> <server ip> <server port>\n", argv[0]);
return -1;
}
//本机地址变量
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = htonl(INADDR_ANY);
//创建select监听队列和副本
fd_set reads = {0};
fd_set temps = {0};
//创建socket
sock = socket(AF_INET, SOCK_DGRAM, 0);
if( sock == -1 )
{
LOG("socket error");
return -1;
}
signal(SIGINT, handler);
//指定一个远端地址
remote.sin_family = AF_INET;
remote.sin_addr.s_addr = inet_addr(argv[3]);
remote.sin_port = htons(atoi(argv[4]));
//登录
msg.type = EVE_CONN;
stpcpy(msg.name, argv[1]);
if(sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr*)&remote, sizeof(remote)) < 0)
{
LOG("connect failed");
return -1;
}
printf("connect success\n");
FD_ZERO(&reads);
FD_SET(sock, &reads);
FD_SET(0, &reads);
Msg rmsg;
while(1)
{
msg.type = EVE_DATA;
temps = reads;
//轮询监测收发事件
int num = select(sock+1, &temps, NULL, NULL, NULL);
if(num > 0)
{
if(FD_ISSET(0, &temps))
{
bzero(msg.data, sizeof(msg.data));
fgets(msg.data, sizeof(msg.data), stdin);
msg.data[strlen(msg.data)-1] = 0;
if(sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr*)&remote, sizeof(remote)) < 0)
{
LOG("sendto error");
return -1;
}
}
else
{
int res = -1;
if((res = recvfrom(sock, &(rmsg), sizeof(rmsg), 0, NULL, NULL)) < 0)
{
LOG("recvfrom error");
return -1;
}
else if(0 == res)
{
printf("server close \n");
return -1;
}
printf("[%s]: %s\n", rmsg.name, rmsg.data);
}
}
}
close(sock);
return 0;
}
void handler(int sig)
{
msg.type = EVE_CLOSE;
sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr*)&remote, sizeof(remote));
exit(-1);
}