LinuxC语言のUDP简易聊天室 sokcet
Posted MangataTS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LinuxC语言のUDP简易聊天室 sokcet相关的知识,希望对你有一定的参考价值。
设计思路
考虑到只是一个简易版本的UDP聊天服务,所以很多不完善的地方
服务器
服务器我是开了一个父子进程,分别负责的
- 接受客户端的消息&&发送某一个客户端的信息
- 服务器的命令终端(只不过没有实现,只写了广播功能)
然后我封装了一个数据结构,每当客户端发一个包过来,这个包由三部分组成
- 消息类型(login、cheat、end)
- 发送者的姓名
- 发送者的消息构成
然后我们分三种情况来解析这三种消息就行,具体实现就是一个简单的链表结构,详情可以看代码。
CODE
/*
*
* 缺点:
* 1.以奇怪的方式下线的用户不能清理
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#define false 0
#define true 1
#define N 1024
struct MSG{//消息封装
char type;
char name[20];
char text[N];
};
struct Node {//用户节点信息
struct sockaddr_in addr;
struct Node * next;
};
struct Node * init() {//用户节点初始化
struct Node * p = (struct Node *)malloc(sizeof(struct Node));
memset(p,0,sizeof(struct Node));
p->next = NULL;
return p;
}
const char *ip = "127.0.0.1";
const int port = 8000;
struct Node *head;
void login(int sockfd,struct MSG msg,struct sockaddr_in clientaddr) {//登陆消息
struct Node *p = head;
strcpy(msg.text,msg.name);
msg.text[strlen(msg.text) - 1] = '\\0';
strcat(msg.text," 已经上线欢迎来聊^_^~");
//puts(msg.text);
int cnt = 0;
while(p->next) {//发给已经上线的用户
if(sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&p->next->addr,sizeof(p->next->addr)) < 0){
perror("online error");
}
p = p->next;
cnt++;
}
struct Node *k = (struct Node *)malloc(sizeof(struct Node));
k->addr = clientaddr;
k->next = NULL;
p->next = k;
printf("当前总用户数:%d \\n上线用户的port: %u \\n",cnt,ntohl(clientaddr.sin_port));
}
void chat(int sockfd,struct MSG msg,struct sockaddr_in clientaddr) {//聊天消息处理函数
struct Node *p = head;
char str[N]= {0};
//puts(msg.name);
//puts(msg.text);
sprintf(str,"%s : %s",msg.name,msg.text);
strcpy(msg.text,str);
puts(msg.text);//输出到服务器终端
while(p->next) {
if (memcmp(&clientaddr, &p->next->addr, sizeof(clientaddr)) != 0) {
if (sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *) &p->next->addr, sizeof(p->next->addr)) < 0) {
perror("chat sendto error");
}
}
p = p->next;
}
}
void quit(int sockfd,struct MSG msg,struct sockaddr_in clientaddr) {//退出函数
struct Node *p = head;
struct Node *q = NULL;
sprintf(msg.text,"%s用户已下线",msg.name);
puts(msg.text);
while(p->next) {//因为头号节点是服务器
if(memcmp(&clientaddr,&p->next->addr,sizeof(clientaddr)) == 0) {
q = p->next;
p->next = q->next;
free(q);
q=NULL;//free后将指针赋为NULL
}
else {
sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr*)&p->next->addr,sizeof(p->next->addr));
}
p = p->next;
}
}
int socketfd;
void my_end(int sign_no) {//这个是注册一个ctr+c退出时候的一个信号处理
close(socketfd);
exit(1);
}
int main() {
signal(SIGINT,my_end);
head = init();
struct sockaddr_in serveraddr,clientaddr;
bzero(&serveraddr,sizeof(serveraddr));
bzero(&clientaddr,sizeof(clientaddr));
struct MSG msg;
socklen_t addrlen = sizeof(struct sockaddr);
char buf[N] = "";
if((socketfd = socket(AF_INET, SOCK_DGRAM, 0))< 0) {
perror("socket error");
}
printf("socketfd = %d\\n",socketfd);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(port);
printf("serveraddr.sin_port = %d\\n", htonl(port));
if(bind(socketfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) { // bind绑定
perror("bind error");
}
printf("serveraddr.sin_port = %u\\n", serveraddr.sin_port);
printf("Accepting connections ...\\n");
pid_t pid = fork();
if(pid < 0) {
perror("fork error");
}
if(pid == 0) {//服务器的命令终端(只不过没有实现,只写了广播功能)
memset(&msg,0,sizeof(msg));
strncpy(msg.name,"server",6);
msg.type = 'C';
while(1) {
fgets(buf,N,stdin);
strcpy(msg.texxt,buf);
msg.text[strlen(msg.text)-1] = '\\0';
puts(msg.text);
sendto(socketfd,&msg,sizeof(msg),0,(struct sockaddr*)&serveraddr,addrlen);
}
}
else {//接受客户端的消息&&发送某一个客户端的信息
while(1) {
// puts("father");
if(recvfrom(socketfd,&msg,sizeof(msg),0,(struct sockaddr *)&clientaddr,&addrlen) <= 0){
perror("father recvfrom error");
}
if(msg.type == 'L') {//login
puts("YES");
login(socketfd,msg,clientaddr);
}
else if(msg.type == 'C') {//chat
chat(socketfd,msg,clientaddr);
}
else if(msg.type == 'Q') {//quit
quit(socketfd,msg,clientaddr);
}
}
}
close(socketfd);//正常退出的close处理
return 0;
}
客户端
客户端这边的话就稍微简单一点了,因为只有接受和发送这两个事件
-
发送事件
- 登陆发送一次
- 退出发送一次
- 聊天一直发送
-
接收事件
其实我们只用接受服务器给我们发送的消息即可
总体逻辑非常的简单,详情请看代码
CODE
//
// Created by Mangata on 2021/10/27.
//
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#define N 1024
struct MSG{
char type;
char name[20];
char text[N];
}msg;
int port = 8000;
char *ip = "127.0.0.1";
//int socketfd;
//struct sockaddr_in serveraddr;
/*void my_fun(int signal_) {
msg.type = 'Q';
if(sendto(socketfd,&msg,sizeof msg,0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) {
perror("Q - sendto error");
exit(1);
}
printf("You Quit !");
kill(getppid(),SIGKILL);
exit(1);
}
*/
int main() {
struct MSG msg;
int socketfd;
struct sockaddr_in serveraddr;
bzero(&serveraddr,sizeof(serveraddr));
socklen_t addrlen;
//signal(SIGINT, my_fun);
if((socketfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket failed");
printf("socket failed");
exit(1);
}
serveraddr.sin_family = AF_INET;
// serveraddr.sin_addr.s_addr = inet_addr(ip);
serveraddr.sin_port = htons(port);
inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr);
printf("输入用户名:");
fgets(msg.name,sizeof(msg.name),stdin);
msg.name[strlen(msg.name)-1]='\\0';
msg.text[0]='\\0';
msg.type= 'L';
printf("name = %s\\t type = %c\\n",msg.name,msg.type);
if(sendto(socketfd,&msg,sizeof(msg),0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) {//发送登陆信号
perror("fail to sendto");
printf("fail to sendto");
exit(1);
}
pid_t pid=fork();
if(pid == 0) {//子进程输入数据
while(1) {
fgets(msg.text,N,stdin);
msg.text[strlen(msg.text)-1]='\\0';
printf("send msg = %s\\n",msg.text);
if(!strncmp(msg.text,"quit",4)) {
msg.type = 'Q';
if(sendto(socketfd,&msg,sizeof msg,0,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0) {
perror("Q - sendto error");
exit(1);
}
printf("You Quit !");
kill(getppid(),SIGKILL);
exit(1);
}
else {
msg.type = 'C';
if(sendto(socketfd,&msg,sizeof msg,0,(struct sockaddr*)&serveraddr,sizeof(serveraddr)) < 0) {
printf("C - sendto error");
exit(1);
}
}
}
}
else {
while(1)
{
recvfrom(socketfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, &addrlen);
puts(msg.text);
}
}
close(socketfd);
return 0;
}
关于BUG
由于写的时间比较短,有一些发现的bug,但是还不清楚原因
- 当所有的用户退出后,服务器会段错误然后宕机
- 用户推出后会有一个用户收不到退出消息(这个原因是删除退出用户的链表的问题,只不过不想修了)
- 服务器断开的时候,客户端不会有反馈,emmm这个其实只是我没写,逻辑还是蛮简单的,可以给用户发送一个Q类型的消息,然后让用户也自动退出即可
其他倒是没有什么东西了,服务器和用户端的端口是设死了的,所以想要更改的直接改源码就行
效果图
服务器端
客户端口
over
以上是关于LinuxC语言のUDP简易聊天室 sokcet的主要内容,如果未能解决你的问题,请参考以下文章