9.11网编项目——UDP网络聊天

发布于:2025-09-12 ⋅ 阅读:(19) ⋅ 点赞:(0)

服务器端

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <25061head.h>
#define SER_IP "192.168.144.128"
#define SER_PORT 8888

// 链表节点结构定义
typedef struct Node
{
    char usrName[30];           // 用户名
    struct sockaddr_in cin;     // 用户的地址信息
    struct Node *next;          // 指针域
}*linklist;

// 消息结构定义
struct msgTyp
{
    char type;
    char usrName[30];
    char msgText[50];
};

int main(int argc, const char *argv[])
{
    // 前期配置
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd == -1)
    {
        ERR_MSG("socket error");
        return -1;
    }

    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);
    sin.sin_addr.s_addr = inet_addr(SER_IP);

    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
    {
        ERR_MSG("bind error");
        close(sfd);
        return -1;
    }
    printf("绑定成功\n");

    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);

    struct msgTyp recv_msg;  // 接收的信息
    // 创建头节点并初始化
    linklist head = (linklist)malloc(sizeof(struct Node));
    if(head == NULL)
    {
        ERR_MSG("malloc error");
        close(sfd);
        return -1;
    }
    head->next = NULL;  // 初始化头节点

	pid_t pid=fork();
	if(pid>0)
	{
    // 核心操作
    while(1)
    {
        // 清空接收缓冲区
        memset(&recv_msg, 0, sizeof(recv_msg));
        // 接收消息
        ssize_t recv_len = recvfrom(sfd, &recv_msg, sizeof(recv_msg), 0, 
                                  (struct sockaddr*)&cin, &addrlen);
        if(recv_len == -1)
        {
            ERR_MSG("recvfrom error");
            continue;
        }
		
        switch(recv_msg.type)
        {
            case 'L':  // login登录
                printf("%s登录成功\n", recv_msg.usrName);
                // 创建新用户的节点并初始化
                linklist temp = (linklist)malloc(sizeof(struct Node));
                if(temp == NULL)
                {
                    ERR_MSG("malloc error");
                    break;
                } 
				//存储新用户结点的相关信息
                strcpy(temp->usrName, recv_msg.usrName);
                temp->cin = cin;
				//头插
                temp->next = head->next;
                head->next = temp;
                
                // 广播登录消息
                linklist s = head->next;
                char sbuf[128] = "";
                snprintf(sbuf, sizeof(sbuf)-1, "%s已上号!", recv_msg.usrName);
				strcpy(recv_msg.msgText,sbuf);//放入recv.msg中,发给其他人
				//recv_msg.msgText[sizeof(recv_msg.msgText)-1] = '\0';
				while(s != NULL)
				{
					// 跳过发送者自己(用IP和端口区分,后面的广播都是这种方法排除自己)
					if (s->cin.sin_port == cin.sin_port && s->cin.sin_addr.s_addr == cin.sin_addr.s_addr)
					{
						s = s->next;
						continue;
					}
					sendto(sfd, &recv_msg, sizeof(recv_msg), 0, 
							(struct sockaddr*)&(s->cin), sizeof(s->cin));
					s = s->next;
				}
				break;

            case 'C':  // chat聊天
				if(strcmp(recv_msg.usrName,"系统")!=0)
				{
                printf("%s:chat成功\n", recv_msg.usrName);
				}
                char cbuf[128] = "";
                snprintf(cbuf, sizeof(cbuf)-1, "%s说:%s", recv_msg.usrName, recv_msg.msgText);
                strcpy(recv_msg.msgText, cbuf);
                //recv_msg.msgText[sizeof(recv_msg.msgText)-1] = '\0';
                
                s = head->next;//从第一个用户开始
				while(s != NULL)
				{
					// 跳过发送者自己
					if (s->cin.sin_port == cin.sin_port && s->cin.sin_addr.s_addr == cin.sin_addr.s_addr)
					{
						s = s->next;
						continue;
					}
					sendto(sfd, &recv_msg, sizeof(recv_msg), 0, 
							(struct sockaddr*)&(s->cin), sizeof(s->cin));
					s = s->next;
				}
				break;

            case 'Q':  // quit退出
                printf("%s退出聊天室\n", recv_msg.usrName);
                char qbuf[128] = "";
                snprintf(qbuf, sizeof(qbuf)-1, "%s退出聊天室", recv_msg.usrName);
                strcpy(recv_msg.msgText, qbuf);
                //recv_msg.msgText[sizeof(recv_msg.msgText)-1] = '\0';
                
                // 先广播退出消息
                s = head->next;
                while(s != NULL)
				{
					// 跳过发送者自己
					if (s->cin.sin_port == cin.sin_port && s->cin.sin_addr.s_addr == cin.sin_addr.s_addr)
					{
						s = s->next;
						continue;
					}
					sendto(sfd, &recv_msg, sizeof(recv_msg), 0, 
							(struct sockaddr*)&(s->cin), sizeof(s->cin));
					s = s->next;
				}           
                // 然后从链表中删除该用户节点
                linklist prev = head;
                linklist curr = head->next;
                int found = 0;//判断找没找到结点

                while(curr != NULL)
                {
                    if(strcmp(curr->usrName, recv_msg.usrName) == 0)
                    {
						//头删
                        prev->next = curr->next;
                        free(curr);
						curr=NULL;
                        found = 1;
                        printf("已从链表中移除用户: %s\n", recv_msg.usrName);
                        break;
                    }
                    prev = curr;
                    curr = curr->next;
                }
                if(found==0)
                {
                    printf("警告: 未在链表中找到用户 %s\n", recv_msg.usrName);
                }
                break;
            default:
                printf("发送格式错误\n");
        }
	}
	}
	//服务器广播系统信息
	else if(pid==0)
	{
			
			struct msgTyp sys_msg;
			sys_msg.type='C';
			strcpy(sys_msg.usrName, "系统");

	while(1)
	{
		bzero(sys_msg.msgText,50);
		fgets(sys_msg.msgText,50, stdin);
		sys_msg.msgText[strlen(sys_msg.msgText)-1] = 0;
        //核心操作,因为是进程的原因,链表在子进程用不了,不能循环广播
        //那我直接向主进程发消息,让主进程广播
		sendto(sfd, &sys_msg, sizeof(sys_msg), 0, (struct sockaddr*)&sin, sizeof(sin));
	}
	printf("系统消息发送成功");
	}
    close(sfd);
    return 0;
}

客户端

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <25061head.h>
#define SER_IP "192.168.144.128"
#define SER_PORT 8888

struct msgTyp
{
    char type;
    char usrName[30];
    char msgText[50];
};

int cfd;                  // 客户端socket
struct sockaddr_in sin;   // 服务器的相关配置
char usrName[30] = "";   // 用户名
//虽然是线程,但有多个阻塞函数,所以用多线程
//该线程主要功能是接收消息
void *recv_msg_thread(void *arg)
{
    struct msgTyp recv_msg;
    socklen_t addrlen = sizeof(sin);
    while(1)
    {
        // 清空接收缓冲区
        memset(&recv_msg, 0, sizeof(recv_msg));
        // 接收服务器发送的消息
        ssize_t recv_len = recvfrom(cfd, &recv_msg,sizeof(recv_msg),0,(struct sockaddr*)&sin, &addrlen);
        // 打印接收的消息
        printf("%s\n", recv_msg.msgText);
        fflush(stdout);  // 刷新输出缓冲区
    }
    
    // 接收线程退出时关闭socket
    close(cfd);
    pthread_exit(0);
    return NULL;
}

int main(int argc, const char *argv[])
{
    // 获取用户名
    printf("请输入你的用户名: ");
    fgets(usrName, sizeof(usrName)-1, stdin);
    // 去除换行符
    usrName[strcspn(usrName, "\n")] = '\0';
    
    // 创建socket
    cfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(cfd == -1)
    {
        ERR_MSG("socket error");
        return -1;
    }    
    // 初始化服务器相关配置
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SER_PORT);
    sin.sin_addr.s_addr = inet_addr(SER_IP);
    
    // 配置登录消息
    struct msgTyp send_msg;
    send_msg.type = 'L';
    strcpy(send_msg.usrName,usrName);
    //strcpy(send_msg.msgText, "");
    if(sendto(cfd, &send_msg, sizeof(send_msg), 0,(struct sockaddr*)&sin, sizeof(sin)) == -1)
    {
        ERR_MSG("sendto error");
        close(cfd);
        return -1;
    }
    
    printf("登录成功!输入'quit'退出聊天室...\n");
    
    // 创建接收消息的线程
    pthread_t recv_tid;
    if(pthread_create(&recv_tid, NULL, recv_msg_thread, NULL) != 0)
    {
        ERR_MSG("pthread_create error");
        close(cfd);
        return -1;
    }
    // 分离线程,系统自动回收
    pthread_detach(recv_tid);
    
    // 主线程用于发送消息
    while(1)
    {
        char input[50] = "";
        fgets(input, sizeof(input)-1, stdin);
        // 去除换行符
        input[strcspn(input, "\n")] = '\0';
        
        //输入quit,消息类型则设置从Q类型
        if(strcmp(input, "quit") == 0)
        {
            send_msg.type = 'Q';
            strcpy(send_msg.usrName, usrName); 
            sendto(cfd, &send_msg, sizeof(send_msg),0,(struct sockaddr*)&sin, sizeof(sin));
            printf("已退出聊天室\n");
            close(cfd);
            return 0;
        }
        
        // 发送聊天消息
        send_msg.type = 'C';
        strcpy(send_msg.usrName, usrName);
        strcpy(send_msg.msgText, input);
        
        if(sendto(cfd, &send_msg, sizeof(send_msg),0,(struct sockaddr*)&sin, sizeof(sin)) == -1)
        {
            ERR_MSG("sendto error");
            close(cfd);
            return -1;
        }
    }
    
    //关闭socket
    close(cfd);
    return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到