udp聊天室

创建一个简单的udp聊天室

服务器代码思路:

  1. 初始化

    • 创建UDP套接字。
    • 配置服务器的IP和端口号,并绑定套接字到这个地址。
  2. 数据接收和处理

    • 使用循环接收客户端发来的消息。recvfrom()
    • 解析消息类型(如登录、发送、下线)和内容。
  3. 广播消息

    • 对于聊天消息,将其广播给所有连接的客户端。
    • 对于登录和下线消息,通知其他客户端有新用户上线或某用户下线。
  4. 客户端管理

    • 维护一个客户端列表或字典,用于跟踪在线客户端的地址和状态。
    • 更新客户端列表以反映登录和下线事件。
  5. 清理和关闭

    • 在服务器关闭时,适当清理资源并关闭套接字。

服务器代码:

#include <stdio.h>

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

#include <netinet/in.h>

#include <netinet/ip.h>

#include <arpa/inet.h>

#include <string.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/wait.h>

#include <pthread.h>

#include <pthread.h>

typedef struct linklist

{ //创建存储客户端ip、端口号的链表

struct sockaddr_in addr;

struct linklist *next;

} l_node, *l_pnode;

typedef struct sockt_data

{ //创建收发的信息结构体

char name[64]; //名字

char type[8]; //消息类别

char text[128]; //信息内容

} data_t;

typedef struct Sock

{ //创建存储服务器发送数据的信息结构体

struct sockaddr_in caddr;

data_t cdata; //发送消息结构体

int sockfd; //套接字

} sock_t;

l_pnode create(); //创建链表节点

void login(int sockfd, data_t data, struct sockaddr_in saddr); //登录

void line(int sockfd, data_t data, struct sockaddr_in saddr); //发送和下线

void *func(void *arg); //子线程

l_pnode S; //开辟一个全局变量的链表

int main(int argc, char *argv[])

{

// 1、创建套接字 -- socket

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

// 2、绑定IP地址和端口号 -- bind

struct sockaddr_in saddr, caddr;

caddr.sin_family = AF_INET;

caddr.sin_port = htons(8888); //将主机字节序转换为网络字节序再赋值

caddr.sin_addr.s_addr = htons(INADDR_ANY);

int s_len = sizeof(saddr);

bind(sockfd, (struct sockaddr *)&caddr, s_len);

// 3、发送接送数据

data_t sdata = {0, 0, 0}; //定义接送数据结构体

int d_len = sizeof(sdata);

int c_len = sizeof(caddr); //计算服务器地址结构的大小

memset(&saddr, 0, c_len); //清空接收地址结构

S = (l_pnode)malloc(sizeof(l_node)); //初始化链表

S->next = NULL;

sock_t csock; //创建子线程所需结构体

csock.sockfd = sockfd; //传递套接字

pthread_t thread;

pthread_create(&thread, NULL, func, &csock); //创建子线程

while (1)

{

memset(&sdata, 0, d_len); //清空接送到的数据

recvfrom(sockfd, &sdata, d_len, 0, (struct sockaddr *)&saddr, &c_len); //接送来自客户端的消息

printf("%s:%s\n", sdata.name, sdata.text); //在服务器中显示接送到的消息

if (strcmp(sdata.type, "login") == 0) //判断接受消息内型

login(sockfd, sdata, saddr); //上线操作

else

line(sockfd, sdata, saddr); //聊天、下线操作

}

// 4、关闭套接字

close(sockfd);

return 0;

}

// 子线程

void *func(void *arg) //线程处理函数

{

sock_t csock = *(sock_t *)arg; //获取主函数中传递过来的内容

strcpy(csock.cdata.type, "login"); //设置要发送的结构图

strcpy(csock.cdata.name, "服务器");

while (1)

{

memset(&csock.cdata.text, 0, 128); //清空输入进来的内容

fgets(csock.cdata.text, 64, stdin); //获取输入进来的内容

csock.cdata.text[strlen(csock.cdata.text) - 1] = '\0'; //删除获取进来的回车'\n'

line(csock.sockfd, csock.cdata, csock.caddr); //向所有在线的客户端发送入进来的内容

}

}

// 上线

void login(int sockfd, data_t data, struct sockaddr_in saddr) //登录

{

int d_len = sizeof(data);

int s_len = sizeof(saddr);

l_pnode new = create(); //创建新节点

new->addr = saddr; //传递接送到的客户端IP、端口号

new->next = NULL;

l_pnode N = create();

N = S;

while (N->next) //尾插并发送给其他客户端

{

N = N->next;

sendto(sockfd, &data, d_len, 0, (struct sockaddr *)&N->addr, s_len); //发送给除该客户信息的其他客户

}

N->next = new;

}

// 聊天、下线

void line(int sockfd, data_t data, struct sockaddr_in saddr) //聊天、下线

{

int d_len = sizeof(data);

int s_len = sizeof(saddr);

l_pnode N = create();

N = S;

while (N->next)

{

if ((N->next->addr.sin_addr.s_addr == saddr.sin_addr.s_addr) && (N->next->addr.sin_port == saddr.sin_port)) //判断是否存在该客户端数据

{

if (strcmp(data.type, "line") == 0) //下线,删除用户信息

{

l_pnode Q = N->next;

N->next = Q->next; //连线

free(Q); //释放空间

Q = NULL;

}

if (strcmp(data.type, "send_t") == 0) //聊天,跳过用户

N = N->next;

}

if (N->next != NULL) //判断是否到尾节点

{

N = N->next;

sendto(sockfd, &data, d_len, 0, (struct sockaddr *)&N->addr, s_len); //发送给除该客户信息的其他客户

}

else

break;

}

}

// 创建链表新节点

l_pnode create()

{

l_pnode S = (l_pnode)malloc(sizeof(l_node));

S->next = NULL;

return S;

}

客户端代码思路:

  1. 初始化和创建套接字 :在函数中,创建一个UDP套接字,并配置客户端的IP地址和端口。main

  2. 进程创建 :使用创建一个子进程。子进程负责读取用户输入的用户名和消息,并处理登录、发送和下线操作。fork()

  3. 父进程接收消息:父进程不断接收来自服务器的消息,并在控制台显示。

  4. 登录、发送和下线功能

    • login():向服务器发送登录消息。
    • send_t():发送聊天消息。
    • line():发送下线消息并退出子进程。
  5. 信号处理 :使用处理子进程结束的信号,确保子进程资源得到清理。signal(SIGCHLD, mysignal)

客户端代码:

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
typedef struct sockt_data
{
char name[64];//用户户名
char type[8];//消息类别
char text[128];//消息内容
} data_t;

void mysignal(int arg) //回收子进程
{
wait(NULL);
exit(0);
}

void login(int sockfd, data_t cdata, struct sockaddr_in caddr); //登录
void send_t(int sockfd, data_t data, struct sockaddr_in saddr); //发送信息
void line(int sockfd, data_t cdata, struct sockaddr_in caddr); //下线

int main(int argc, char *argv[])
{
if (argc != 2)
printf("请正确输入: ./client IP号\n");
// 1、创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 2、绑定IP地址和端口号 -- bind
struct sockaddr_in saddr, caddr;
caddr.sin_family = AF_INET;
caddr.sin_port = htons(8888); //将主机字节序转换为网络字节序再赋值
caddr.sin_addr.s_addr = inet_addr(argv[1]);

int s_len = sizeof(saddr);
// 3、发送接送数据
data_t cdata = {0, 0, 0}, sdata = {0, 0, 0}; //定义收发数据结构体

int d_len = sizeof(cdata); //计算数据结构体长度

memset(&saddr, 0, s_len); //清空接收地址结构
memset(&cdata, 0, d_len); //清空接收地址结构
signal(SIGCHLD, mysignal); //捕获子进程释放信号

pid_t pid = fork(); //创建线程
if (0 == pid) //子线程
{
printf("请输入你的用户名--->");
fgets(cdata.name, 64, stdin); //获取输入进来的名字
cdata.name[strlen(cdata.name) - 1] = '\0'; //删除获取进来的回车'\n'
login(sockfd, cdata, caddr); //登录

while (1)
{
fgets(cdata.text, 64, stdin); //获取输入进来的内容
cdata.text[strlen(cdata.text) - 1] = '\0'; //删除获取进来的回车'\n'
if (strcmp(cdata.text, "line!!!") == 0) //判断是否结束聊天
line(sockfd, cdata, caddr); //下线
send_t(sockfd, cdata, caddr); //聊天
}
}

while (1)
{
memset(&sdata, 0, d_len); //清空接送到的数据
recvfrom(sockfd, &sdata, d_len, 0, (struct sockaddr *)&saddr, &s_len);
if (strcmp(sdata.type, "send_t") == 0) //在客户端中显示接送到的消息
printf("%s:%s\n", sdata.name, sdata.text);
else
printf("%s-->%s\n", sdata.name, sdata.text);
}
// 4、关闭套接字
close(sockfd);
return 0;
}
// 登录
void login(int sockfd, data_t cdata, struct sockaddr_in caddr) //登录
{
int d_len = sizeof(cdata);
int s_len = sizeof(caddr);
strcpy(cdata.type, "login");
strcpy(cdata.text, "上线了");
sendto(sockfd, &cdata, d_len, 0, (struct sockaddr *)&caddr, s_len);
}
// 下线
void line(int sockfd, data_t cdata, struct sockaddr_in caddr) //下线
{
int d_len = sizeof(cdata);
int s_len = sizeof(caddr);
strcpy(cdata.type, "line");
strcpy(cdata.text, "下线了");
sendto(sockfd, &cdata, d_len, 0, (struct sockaddr *)&caddr, s_len);
exit(0);
}
// 聊天
void send_t(int sockfd, data_t cdata, struct sockaddr_in caddr) //发送信息
{
int ret = 0;
int d_len = sizeof(cdata);
int s_len = sizeof(caddr);
strcpy(cdata.type, "send_t");
ret = sendto(sockfd, &cdata, d_len, 0, (struct sockaddr *)&caddr, s_len);
}

相关推荐
明月看潮生7 分钟前
青少年编程与数学 02-003 Go语言网络编程 14课题、Go语言Udp编程
青少年编程·golang·网络编程·编程与数学
jjyangyou15 分钟前
物联网核心安全系列——物联网安全需求
物联网·算法·安全·嵌入式·产品经理·硬件·产品设计
海绵波波10713 小时前
Webserver(4.8)UDP、广播、组播
单片机·网络协议·udp
橘色的喵15 小时前
Linux编程:DMA增加UDP 数据传输吞吐量并降低延迟
linux·udp·dma·网络驱动·低延迟·吞吐量·nic
憧憬一下16 小时前
Pinctrl子系统中Pincontroller和client驱动程序的编写
arm开发·嵌入式·c/c++·linux驱动开发
蓝天居士16 小时前
ES8388 —— 带耳机放大器的低功耗立体声音频编解码器(4)
嵌入式·音频·es8388
田三番19 小时前
使用 vscode 简单配置 ESP32 连接 Wi-Fi 每日定时发送 HTTP 和 HTTPS 请求
单片机·物联网·http·https·嵌入式·esp32·sntp
启明智显1 天前
AI笔筒操作说明及应用场景
人工智能·嵌入式硬件·嵌入式·ai大模型·启明智显·esp32-s3
C++忠实粉丝1 天前
计算机网络socket编程(1)_UDP网络编程实现echo server
linux·服务器·网络·c++·网络协议·计算机网络·udp
FreakStudio1 天前
全网最适合入门的面向对象编程教程:58 Python字符串与序列化-序列化Web对象的定义与实现
python·单片机·嵌入式·面向对象·电子diy