基于UDP的网络聊天室
功能:
有新用户登录,其他在线的用户可以收到登录信息
有用户群聊,其他在线的用户可以收到群聊信息
有用户退出,其他在线的用户可以收到退出信息
服务器可以发送系统信息
提示:
客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程
服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程
服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存
数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据
//UDP网络编程之服务器
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#define N 128
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
exit(1);\
}while(0)
typedef struct{
int code; //操作码
char name[32]; //用户名
char text[32]; //消息
}MSG;
//链表结点结构体
typedef struct node{
struct sockaddr_in addr; //数据域,用于保存每一个在线用户的信息
struct node *next;
}linklist;
linklist *linklistcreate();
void do_login(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr);
void do_chat(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr);
void do_quit(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr);
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in serveraddr, clientaddr;
socklen_t addrlen = sizeof(serveraddr);
char buf[N] = {0};
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充服务器网络信息结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
//第三步:将套接字与服务器网络信息结构体绑定
if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("bind error");
}
//创建子进程,实现一边发送系统信息,一边接收数据并处理
pid_t pid;
if((pid = fork()) == -1)
{
ERRLOG("fork error");
}
else if(pid > 0) //父进程负责发送系统信息
{
//将父进程看做是一个客户端,按照群聊的形式将数据发送给子进程
MSG msg;
msg.code = 2;
strcpy(msg.name, "server");
while(1)
{
fgets(msg.text, 32, stdin);
msg.text[strlen(msg.text) - 1] = '\0';
if(sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("sendto error");
}
}
}
else //子进程负责接收数据并处理
{
MSG msg;
linklist *h = linklistcreate();
while(1)
{
if(recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &addrlen) == -1)
{
ERRLOG("recvfrom error");
}
printf("code:%d, name:%s, text:%s\n", msg.code, msg.name, msg.text);
//根据接收到的数据做出相应的处理
//登录:1 群聊:2 退出:3
switch(msg.code)
{
case 1:
//登录操作
do_login(sockfd, msg, h, clientaddr);
break;
case 2:
//群聊操作
do_chat(sockfd, msg, h, clientaddr);
break;
case 3:
//退出操作
do_quit(sockfd, msg, h, clientaddr);
break;
}
}
}
return 0;
}
linklist *linklistcreate()
{
linklist *h = (linklist *)malloc(sizeof(linklist));
h->next = NULL;
return h;
}
void do_login(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr)
{
char buf[N] = {0};
//组包将新用户登录的信息发送给其他在线的用户
sprintf(buf, "----------------------%s上线了----------------------", msg.name);
linklist *p = h;
//遍历链表,给每一个数据域发送数据
while(p->next != NULL)
{
p = p->next;
if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&p->addr, sizeof(struct sockaddr_in)) == -1)
{
ERRLOG("sendto error");
}
}
//将新登录用户的信息保存在链表中
linklist *temp = (linklist *)malloc(sizeof(linklist));
temp->addr = clientaddr;
temp->next = NULL;
temp->next = h->next;
h->next = temp;
}
void do_chat(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr)
{
//组包将用户的群聊信息发送给其他用户
//组包
char buf[N] = {0};
sprintf(buf, "%s: %s", msg.name, msg.text);
//遍历链表将信息发送给其他用户
linklist *p = h;
while(p->next != NULL)
{
p = p->next;
//发送者不接收自己发送的数据
if(memcmp(&p->addr, &clientaddr, sizeof(clientaddr)) != 0)
{
if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&p->addr, sizeof(struct sockaddr_in)) == -1)
{
ERRLOG("sendto error");
}
}
}
}
void do_quit(int sockfd, MSG msg, linklist *h, struct sockaddr_in clientaddr)
{
char buf[N] = {0};
//组包将用户退出的信息发送给其他在线的用户
sprintf(buf, "----------------------%s下线了----------------------", msg.name);
//遍历链表将信息发送给其他用户
linklist *p = h;
linklist *temp;
while(p->next != NULL)
{
//从链表中删除退出用户的信息
if(memcmp(&p->next->addr, &clientaddr, sizeof(clientaddr)) == 0)
{
temp = p->next;
p->next = temp->next;
free(temp);
temp = NULL;
}
else
{
if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&p->next->addr, sizeof(struct sockaddr_in)) == -1)
{
ERRLOG("sendto error");
}
p = p->next;
}
}
}
//UDP网络编程之客户端
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#define N 128
#define ERRLOG(errmsg) do{\
perror(errmsg);\
printf("%s - %s - %d\n", __FILE__, __func__, __LINE__);\
exit(1);\
}while(0)
typedef struct{
int code; //操作码
char name[32]; //用户名
char text[32]; //消息
}MSG;
int main(int argc, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充服务器网络信息结构体
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
MSG msg;
//登录操作
//设置操作码并输入用户名发送给服务器实现登录操作
msg.code = 1;
//输入用户名实现登录操作
printf("您输入用户名:");
fgets(msg.name, 32, stdin);
msg.name[strlen(msg.name) - 1] = '\0';
if(sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("sendto error");
}
//创建子进程,实现一边发送数据,一边接收数据
pid_t pid;
if((pid = fork()) == -1)
{
ERRLOG("fork error");
}
else if(pid > 0) //父进程负责发送数据
{
while(1)
{
//输入数据,判断是群聊还是退出操作
fgets(msg.text, 32, stdin);
msg.text[strlen(msg.text) - 1] = '\0';
//退出操作
if(strcmp(msg.text, "quit") == 0)
{
//设置操作码
msg.code = 3;
//将退出消息发送给服务器
if(sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("sendto error");
}
//客户端退出
kill(pid, SIGKILL);
exit(0);
}
//群聊操作
else
{
//设置操作码
msg.code = 2;
//将群聊数据发送给服务器
if(sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, addrlen) == -1)
{
ERRLOG("sendto error");
}
}
}
}
else //子进程负责接收数据
{
char buf[N] = {0};
while(1)
{
if(recvfrom(sockfd, buf, N, 0, NULL, NULL) == -1)
{
ERRLOG("recvfrom error");
}
printf("%s\n", buf);
}
}
return 0;
}