首页 > 其他分享 >TCP/UDP网络聊天室

TCP/UDP网络聊天室

时间:2024-08-17 09:25:00浏览次数:13  
标签:UDP int 聊天室 TCP server sockfd sizeof include servermsg

        本博客仅对网络聊天室项目进行分享,仅供学习讨论使用,欢迎大家讨论。

UDP网络聊天室

项目要求

        利用UDP协议,实现一套聊天室软件。服务器端记录客户端的地址,客户端发送消息后,服务器群发给各个客户端软件,服务器也可以自己发送通知给所有客户端。

        要实现服务器广播消息,可以定义一个新的消息类型,此处我采用新的线程来实现:

 // 创建服务器消息发送线程
    pthread_t send_thread;
    pthread_create(&send_thread, NULL, server_send_message, NULL);

        线程函数

// 服务器消息发送线程函数
void* server_send_message(void* arg) {
    while (1) {
        struct msgserver servermsg;
        servermsg.type = 's';  // 定义一个新的类型's'表示服务器消息
        strcpy(servermsg.name, "Server");

        // 从标准输入读取服务器要发送的消息
        fgets(servermsg.text, sizeof(servermsg.text), stdin);
        servermsg.text[strcspn(servermsg.text, "\n")] = '\0';  // 去除换行符

        link_p temp = head->next;
        while (temp != NULL) {
            sendto(sockfd, &servermsg, sizeof(servermsg), 0, (struct sockaddr*)&temp->client_addr, sizeof(temp->client_addr));
            temp = temp->next;
        }
    }
    return NULL;
}

        注意:

        主线程的阻塞:在 recvfrom 函数调用时,主线程会阻塞,等待接收来自客户端的消息。这个阻塞会一直持续到有新的客户端消息到达。

        发送线程:服务器的发送线程 (server_send_message) 会持续运行,并等待用户从标准输入(键盘)输入消息,然后将消息发送给所有客户端。

       这两个线程之间的执行并不会相互影响,因为它们分别在等待各自的输入:主线程等待客户端数据,发送线程函数等待服务器输入。

        这种设计本质上是为了使服务器能够同时处理来自客户端的消息和服务器自己发送的消息。

服务器代码

        此处使用多进程来实现,也可以使用IO多路复用,可以类比于下面的tcp网络聊天室

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
// 结构体或共用体可以放在头文件内
// 定义服务器接收客户端的信息的结构体
struct msgserver
{
    int type;        // 消息类型执行不同操作
    char name[32];   // 用户姓名;
    char text[1024]; // 消息正文

} servermsg;
char buf[1024];
// 创建新的有头单项链表结构体
typedef struct link_list
{
    struct sockaddr_in client_addr; // 存储客户端信息
    struct msgserver servermsg;     // 存储客户端信息
    struct link_list *next;

} link, *link_p;
int main(int argc, char const *argv[])
{
    // 0、使用提示
    if (argc != 2)
    {
        printf("usage:%s<port>", argv[0]);
        return -1;
    }
    // 1、创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.\n");
        return -1;
    }
    printf("socket sucess.\n");
    // 2、绑定ip和端口号
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[1]));
    server.sin_addr.s_addr = INADDR_ANY;
    socklen_t addrlen = sizeof(server);
    if (bind(sockfd, (struct sockaddr *)&server, addrlen) < 0)
    {
        perror("bind err.\n");
        return -1;
    }
    printf("bind sucess.\n");
    printf("wait accept client request.\n");
    // 3、接受客户端数据并存储
    //(1)定义发送端的结构体,不用指定,所以这里不用写其他的,接收任意端口和ip的客户端,协议不同都发不过来的!所以不用写全
    //(2)创建有头单项链表
    // 开辟堆区空间
    link_p head = (link_p)malloc(sizeof(link));
    // 容错判断
    if (head == NULL) // 每次开辟堆区空间都要有容错判断
    {
        perror("CreateEplink error");
        return -1;
        ;
    }
    // 初始化头节点,只有指针域
    head->next = NULL;
    // 客户端ip和端口号获取
    struct sockaddr_in client;
    socklen_t clientlen = sizeof(client);
    //(2)判断消息类型,执行不同操作
    while (1)
    {
        // 读取对应客户端的信息
        recvfrom(sockfd, &servermsg, sizeof(servermsg), 0, (struct sockaddr *)&client, &clientlen);
        if (servermsg.type == 'l') // 登录消息处理
        {
            // 添加新节点存储该客户端信息
            link_p newclient = (link_p)malloc(sizeof(link));
            if (newclient == NULL)
            {
                perror("CreateEplink error");
                return -1;
            }
            // 固定登录语句
            sprintf(servermsg.text, "--------%s login--------", servermsg.name);
            // 存储对应客户端的内容
            // 初始化新节点
            newclient->client_addr.sin_addr = client.sin_addr; // 保存客户端地址,每一个客户端的ip都是相同的,同样的ip不需要保存,应对不同ip,所有保存
            newclient->client_addr.sin_port = client.sin_port; // 保存客户端的端口号,或相同结构体可以直接赋值,相当于给每个变量分别赋值
            newclient->servermsg = servermsg;                  // 保存客户端消息
            newclient->next = head->next;
            head->next = newclient;
            // 服务器显示客户端登录的ip地址
            printf("ip:%s port:%d join chat room\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
            // 向所有客户端发送登录消息
            link_p temp = head->next;
            while (temp != NULL)
            {
                if (strcmp(temp->servermsg.name, servermsg.name) != 0)
                {
                    sendto(sockfd, &servermsg, sizeof(servermsg), 0, (struct sockaddr *)&temp->client_addr, clientlen);
                }
                temp = temp->next;
            }
        }

        if (servermsg.type == 'q') // 退出消息处理
        {
            sprintf(servermsg.text, "--------%s quit--------", servermsg.name);
            printf("ip:%s port:%d quit chat room\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
            // 广播退出消息给所有客户端
            link_p temp = head->next;
            while (temp != NULL)
            {
                if (strcmp(temp->servermsg.name, servermsg.name) != 0)
                {
                    sendto(sockfd, &servermsg, sizeof(servermsg), 0, (struct sockaddr *)&temp->client_addr, clientlen);
                }
                temp = temp->next;
            }
            // 从链表中删除该客户端
            link_p q = head;
            link_p pdel = head->next;
            while (pdel != NULL)
            {
                if (strcmp(pdel->servermsg.name, servermsg.name) == 0)
                {
                    q->next = pdel->next;
                    free(pdel);
                    break;
                }
                else
                {
                    q = pdel;
                    pdel = pdel->next;
                }
            }
        }

        if (servermsg.type == 'c') // 普通聊天消息处理
        {
            // 广播消息给除了自己的所有客户端
            link_p temp = head->next;
            while (temp != NULL)
            {
                if (strcmp(temp->servermsg.name, servermsg.name) != 0)
                {
                    sendto(sockfd, &servermsg, sizeof(servermsg), 0, (struct sockaddr *)&temp->client_addr, clientlen);
                }
                temp = temp->next;
            }
        }
    }

    return 0;
}

客户端代码

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

// 定义发送信息的结构体
struct client
{
    int type;        // 消息类型执行不同操作
    char name[32];   // 用户姓名;
    char text[1024]; // 消息正文
} clientmsg;
// 定义接收服务器信息的结构体
struct msgserver
{
    int type;        // 消息类型执行不同操作
    char name[32];   // 用户姓名;
    char text[1024]; // 消息正文
} servermsg;
int sockfd;
int pid;
struct sockaddr_in server;
socklen_t server_len = sizeof(server);
// 按下ctrl c退出客户端
void handler(int sig)
{
    clientmsg.type = 'q';
    sendto(sockfd, &clientmsg, sizeof(clientmsg), 0, (struct sockaddr *)&server, server_len);
    close(sockfd);
    //waitpid(pid, NULL, 0);
    exit(0);
}
int main(int argc, char const *argv[])
{
    // 处理ctrl c退出
    signal(SIGINT, handler);
    if (argc != 3)
    {
        printf("usage:%s<post><ip>", argv[0]);
        return -1;
    }
    // 1、创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }
    printf("socket success.\n");
    // 2、指定服务器ip地址和端口号,请求链接

    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[1]));
    server.sin_addr.s_addr = inet_addr(argv[2]);

    // 3、登录
    printf("please enter your username:");
    fgets(clientmsg.name, sizeof(clientmsg.name), stdin);
    for (int i = 0; i < sizeof(clientmsg.text); i++)
    {
        if (clientmsg.name[i] == '\n')
            clientmsg.name[i] = '\0';
    }
    clientmsg.type = 'l';
    sendto(sockfd, &clientmsg, sizeof(clientmsg), 0, (struct sockaddr *)&server, server_len);
    // 4、创建子进程
    pid = fork();
    if (pid < 0)
    {
        perror("fork err.");
        return -1;
    }
    if (pid == 0) // 子进程接收
    {
        while (1)
        {
            int recv_len = recvfrom(sockfd, &servermsg, sizeof(servermsg), 0, (struct sockaddr *)&server, &server_len);
            if (recv_len > 0)
            {
                if (servermsg.type == 'l' )
                    printf("%s\n", servermsg.text);
                else if (servermsg.type == 'q')
                {
                    printf("%s\n", servermsg.text);
                    close(sockfd);
                    exit(0);  // 子进程退出,不打印消息
                }
                else
                    printf("%s: %s\n", servermsg.name, servermsg.text);
            }
            else if (recv_len == 0)
            {
                printf("server exit");
                close(sockfd);
                _exit(0);
            }
            else
            {
                perror("recv err.");
                close(sockfd);
                return -1;
            }
        }
    }
    else // 父进程
    {
        while (1)
        {
            fgets(clientmsg.text, sizeof(clientmsg.text), stdin);
            // 去掉末尾换行符
            for (int i = 0; i < sizeof(clientmsg.text); i++)
            {
                if (clientmsg.text[i] == '\n')
                    clientmsg.text[i] = '\0';
            }
            clientmsg.type = 'c';
            if (strcmp(clientmsg.text, "quit") == 0)
            {
                clientmsg.type = 'q';
                sendto(sockfd, &clientmsg, sizeof(clientmsg), 0, (struct sockaddr *)&server, server_len);
                break;
            }
            else
                sendto(sockfd, &clientmsg, sizeof(clientmsg), 0, (struct sockaddr *)&server, server_len);
        }
    }
    close(sockfd);
    return 0;
}

TCP网络聊天室

        项目要求大抵和UDP网络聊天室类似,不过这两个是在学习函数接口时顺手创建,有很多东西没有考虑到,如有需要可以自行完善。

select

服务器代码

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
    // 客户端编号
    int client_map[1024];
    int client_count;
    // 1、创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }
    printf("socket success\n");
    // 2、绑定ip和端口号
    struct sockaddr_in ipvf;
    ipvf.sin_family = AF_INET;
    ipvf.sin_port = htons(atoi(argv[1]));
    ipvf.sin_addr.s_addr = INADDR_ANY;
    socklen_t socklen = sizeof(ipvf);
    if (bind(sockfd, (struct sockaddr *)&ipvf, socklen) < 0)
    {
        perror("bind err.");
        return -1;
    }
    printf("bind success\n");
    // 3、启动监听
    if (listen(sockfd, 6) < 0)
    {
        perror("listen err");
        return -1;
    }
    printf("listen success\n");

    // 4、等待接收客户端请求
#define N 1024
    char buf[N] = "";
    char text[N]="";
    struct sockaddr_in client;
    socklen_t clientlen = sizeof(client);
    int acceptfd = 0;
    //(1)创建表(原表、监听表),并清零
    fd_set readfds, tempfds;
    FD_ZERO(&readfds);
    //(2)向表中添加需要监听的文件描述符
    FD_SET(sockfd, &readfds); // 其实就是置一
    FD_SET(0, &readfds);
    //(3)定义文件描述符的最大值
    int max = sockfd;
    // (4)循环监听
    while (1)
    {
        // a、将原表单赋值给监听表
        tempfds = readfds;
        // b、监听监听表
        int ret = select(max + 1, &tempfds, NULL, NULL, NULL);
        if (ret < 0)
        {
            perror("select err");
            return -1;
        }
        // c、判断表中描述符是否被操作
        if (FD_ISSET(sockfd, &tempfds)) // 监听sockfd,判断是否有客户端链接
        {
            int acceptfd = accept(sockfd, (struct sockaddr *)&client, &clientlen);
            if (acceptfd < 0)
            {
                perror("accept err.");
                return -1;
            }
            printf("accept success\n");
            printf("acceptfd:%d\n", acceptfd);
            printf("ip:%s,port:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
            FD_SET(acceptfd, &readfds); // 当客户端连接后,把新创建的acceptfd文件描述符保存到监听表中
            if (max < acceptfd)         // 更改文件描述符最大值,让其监听到,因为从0开始下标
                max = acceptfd;
            // 保存客户端的姓名
            client_map[acceptfd] = ++client_count;
        }
        else if (FD_ISSET(0, &tempfds))
        {
            fgets(buf, sizeof(buf), stdin);
            for (int i = 4; i <= max; i++) // 服务器遍历发送给所有客户端
            {
                if (FD_ISSET(i, &readfds)) // 判断文件描述符是否在原表内为1
                {
                    send(i, buf, sizeof(buf), 0);
                }
            }
            memset(buf, 0, sizeof(buf));
        }
        for (int i = 4; i <= max; i++) // 遍历所有客户端是否有消息发送过来
        {

            if (FD_ISSET(i, &readfds))//客户端是否存在原表内,即是否在线
            {
                if (FD_ISSET(i, &tempfds))//某一个客户端有消息
                {
                    int ret = recv(i, buf, sizeof(buf), 0);

                    if (ret > 0)
                    {
                        printf("client %d:%s", client_map[i], buf);
                        sprintf(text, "client %d:%s", client_map[i], buf);
                        int len=sizeof(text);
                        for (int j = 4; j <= max; j++) // 除了自己和不在的客户端都发送信息
                        {
                            if (j!=i&&FD_ISSET(j,&readfds))
                            {
                                send(j, text, len, 0);
                            }
                        }
                        memset(buf, 0, sizeof(buf));
                    }
                    else if (ret == 0)
                    {
                        printf("client%d exit\n", client_map[i]);
                        close(i);
                        FD_CLR(i, &readfds);             // 从原表中删除退出的客户端
                        while (!FD_ISSET(max, &readfds)) // 减少文件描述符至最大的,保证遍历最大个数最少
                        {
                            max--;
                        }
                    }
                    else
                    {
                        perror("recv err");
                        break;
                    }
                }
            }
        }
    }
    close(sockfd);
    for(int i=4;i<=max;i++)//关闭所有文件描述符
        close(i);
    return 0;
}

客户端代码

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
    // 1、创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }
    printf("socket success\n");
    // 2、申请连接
    struct sockaddr_in server;
    server.sin_family=AF_INET;//
    server.sin_port = htons(atoi(argv[1]));
    server.sin_addr.s_addr = inet_addr(argv[2]);
    socklen_t slen = sizeof(server);
    if (connect(sockfd, (struct sockaddr *)&server, slen) < 0)
    {
        perror("connect err");
        return -1;
    }
    printf("connect success\n");
#define N 1024
    char buf[N] = "";
    char text[N] = "";
    //(1)创建表(原表、监听表),并清零
    fd_set readfds, tempfds;
    FD_ZERO(&readfds);
    //(2)向表中添加需要监听的文件描述符
    FD_SET(sockfd, &readfds); // 其实就是置一
    FD_SET(0, &readfds);
    //(3)定义文件描述符的最大值
    int max = sockfd;
    // (4)循环监听
    while (1)
    {
        // a、将原表单赋值给监听表
        tempfds = readfds;
        // b、监听监听表
        int ret = select(max + 1, &tempfds, NULL, NULL, NULL);
        if (ret < 0)
        {
            perror("select err");
            return -1;
        }
        // c、判断表中描述符是否被操作
        if (FD_ISSET(sockfd, &tempfds)) // 监听sockfd,判断是服务器是否发送消息
        {
            int ret = recv(sockfd, buf, sizeof(buf), 0);
            if (ret < 0)
            {
                perror("recv err");
                break;
            }
            else if (ret > 0)
            {
                printf("%s",buf);
                memset(buf, 0, sizeof(buf));
            }
            else
            {
               printf("server exit\n");
               return -1;
            }
        }
        else if (FD_ISSET(0, &tempfds))//监听客户端是否发送消息
        {
            fgets(buf, sizeof(buf), stdin);
            
           
            send(sockfd, buf, sizeof(buf), 0);
           
            memset(buf, 0, sizeof(buf));
        }
    }
    close(sockfd);
    return 0;
}

poll

服务器代码

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

#include <netinet/ip.h> /* superset of previous */
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("usage:%s<port>", argv[0]);
        return -1;
    }
    // 1、创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }
    printf("socket success\n");
    // 2、绑定ip和端口号
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[1]));
    server.sin_addr.s_addr = INADDR_ANY;
    socklen_t socklen = sizeof(server);
    if (bind(sockfd, (struct sockaddr *)&server, socklen) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("bind sucess\n");
    // 3、启动监听
    if (listen(sockfd, 6) < 0)
    {
        perror("listen err");
        return -1;
    }
    printf("listen success\n");
    // 3、等待连接
    struct sockaddr_in client;
    socklen_t clientlen = sizeof(client);
    int acceptfd = 0;
    // 4、创建poll表
    struct pollfd fds[100];
    // 5、填表
    int last = -1; //定义遍历的最大值
    fds[++last].fd = sockfd;
    fds[last].events = POLLIN;
    fds[++last].fd = 0;
    fds[last].events = POLLIN;
// 6、循环监听
#define N 1024
    char buf[N];
    char text[N];
    int num[N];
    int num_count = 0;
    while (1)
    {
        poll(fds, last + 1, -1);
        for (int i = 0; i <= last; i++)
        {
            if (fds[i].revents == POLLIN)
            {
                if (fds[i].fd == sockfd)
                {
                    acceptfd = accept(sockfd, (struct sockaddr *)&client, &clientlen);
                    if (acceptfd < 0)
                    {
                        perror("accept err");
                        return -1;
                    }
                    printf("accept success\n");
                    printf("acceptfd:%d\n", acceptfd);
                    fds[++last].fd = acceptfd;
                    fds[last].events = POLLIN;
                    fds[last].revents = 0; // 将 revents 字段初始化为 0。
                    //revents 是 poll 返回时填充的字段,表示实际发生的事件。
                    //初始化为 0 是为了确保在 poll 调用之前,revents 不包含任何之前的事件,避免误判或干扰。
                    // 打印客户端ip和端口
                    num[last] = ++num_count;
                    printf("ip:%s port:%d join chat room\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
                }
                else if (fds[i].fd == 0)
                {
                    fgets(buf, sizeof(buf), stdin); // 这个地方要写stdin,要不然可能无法正确访问
                    for (int i = 0; i < sizeof(buf); i++)
                    {
                        if (buf[i] == '\n')
                            buf[i] = '\0';
                    }
                    sprintf(text, "server:%s", buf);
                    for (int j = 2; j <= last; j++)
                    {
                        // if (fds[j].revents == POLLIN) //这一步不需要,因为是保证了所有的客户端都在线,
                        //{
                        send(fds[j].fd, text, sizeof(text), 0);
                        //}
                    }
                    memset(buf, 0, sizeof(buf));
                    memset(text, 0, sizeof(text));
                }
                else // 接受并发送给所有客户端,除了不管还能有啥方法呢---遍历表中的fd去判断是否相等???怎么实现呢?
                {
                    int ret = recv(fds[i].fd, buf, sizeof(buf), 0);
                    if (ret > 0)
                    {
                        printf("client:%d:%s\n", num[i], buf);
                        sprintf(text, "client %d:%s", num[i], buf);
                        for (int j = 2; j <= last; j++) // 从数组下标2开始遍历,重新遍历一次更换的文件描述符
                        {
                            if (j != i)
                            {
                                send(fds[j].fd, text, sizeof(text), 0);
                            }
                        }
                        memset(buf, 0, sizeof(buf));
                        memset(text, 0, sizeof(text));
                    }
                    else if (ret == 0)
                    {
                        printf("client%d exit\n", num[i]);
                        close(fds[i].fd);
                        num[i] = num[last];// 将最后一个num数组的元素复制到当前元素,更新标号,否则会一直遍历num[3]=1,因为最后一个删除了到不了4
                        fds[i--] = fds[last--];//将最后一个结构体赋值给删除的结构体,同时循环次数-1,i-1,重新问一次当前文件描述符有没有动作,保证正确性
                    }
                    else
                    {
                        perror("recv err");
                        return -1;
                    }
                }
            }
        }
    }
    for (int i = 0; i <= last; i++)
    {
        if (fds[i].revents == POLLIN)
        {
            close(fds[i].fd);
        }
    }
    return 0;
}

客户端代码

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

#include <netinet/ip.h> /* superset of previous */
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
    if (argc != 3)
    {
        printf("usage:%s<port><ip>", argv[0]);
        return -1;
    }
    // 1、创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err.");
        return -1;
    }
    printf("socket success\n");
    // 2、申请连接
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[1]));
    server.sin_addr.s_addr = inet_addr(argv[2]);
    socklen_t socklen = sizeof(server);
    if (connect(sockfd, (struct sockaddr *)&server, socklen))
    {
        perror("connect err");
        return -1;
    }
    printf("connect success\n");
    // 4、创建poll表
    struct pollfd fds[100];
    // 5、填表
    int last = -1;
    fds[++last].fd = sockfd;
    fds[last].events = POLLIN;
    fds[++last].fd = 0;
    fds[last].events = POLLIN;
// 6、循环监听
#define N 1024
    char buf[N];
    while (1)
    {
        poll(fds, last + 1, -1);
        for (int i = 0; i <= last; i++)
        {
            if (fds[i].revents == POLLIN)
            {
                if (fds[i].fd == sockfd)
                {
                    int ret = recv(fds[i].fd, buf, sizeof(buf), 0);
                    if (ret > 0)
                    {
                        printf("%s\n", buf);
                        memset(buf, 0, sizeof(buf));
                    }
                    else if (ret == 0)
                    {
                        close(fds[i].fd);
                        fds[i--] = fds[last--];
                    }
                    else
                    {
                        perror("recv err");
                        return -1;
                    }
                }
                else if (fds[i].fd == 0)
                {
                    fgets(buf, sizeof(buf), stdin); // 这个地方要写stdin,要不然可能无法正确访问
                    for (int i = 0; i < sizeof(buf); i++)
                    {
                        if (buf[i] == '\n')
                            buf[i] = '\0';
                    }
                    send(sockfd, buf, sizeof(buf), 0);
                    memset(buf, 0, sizeof(buf));
                }
            }
        }
    }
    for (int i = 0; i <= last; i++)
    {
        if (fds[i].revents == POLLIN)
        {
            close(fds[i].fd);
        }
    }
    return 0;
}

标签:UDP,int,聊天室,TCP,server,sockfd,sizeof,include,servermsg
From: https://blog.csdn.net/m0_74749947/article/details/141193792

相关文章

  • 【网络】UDP回显服务器和客户端的构造,以及连接流程
    回显服务器(EchoServer)最简单的客户端服务器程序,不涉及到业务流程,只是对与API的用法做演示客户端发送什么样的请求,服务器就返回什么样的响应,没有任何业务逻辑,没有进行任何计算或者处理0.构造方法网络编程必须要使用网卡,就需要用到Socket对象创建一个DatagramS......
  • C# Tcp Server端实现,使用TcpListener
    usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading;usingSystem.Net.Sockets;usingSystem.Net;usingF.Studio.Common.Cfg;usingSystem.Collections.Concurrent;namespaceKomaxCSTcpServer{pub......
  • 坐牢第二十七天(聊天室)
    基于UDP的网络聊天室一.项目需求:1.如果有用户登录,其他用户可以收到这个人的登录信息2.如果有人发送信息,其他用户可以收到这个人的群聊信息3.如果有人下线,其他用户可以收到这个人的下线信息4.服务器可以发送系统信息二.代码 udp.h#ifndefUDP_H#defineUDP_H#includ......
  • 第六章 网络互连与互联网(五):TCP 和 UDP 协议
    五、TCP和UDP协议在TCP/IP协议簇中有两个传输协议,即传输控制协议(TCP)和用户数据报协议(UDP)。TCP是面向连接的,而UDP是无连接的。1、TCP服务(1)TCP协议提供面向连接的、可靠的传输服务,适用于各种可靠的或不可靠的网络。(2)TCP用户送来的是字节流形式的数据,这些数据缓存......
  • Python 通过UDP传输超过64k的信息
    在UDP中,单个数据包的最大尺寸通常受到网络层的限制,这通常被称为最大传输单元(MTU)。在以太网环境中,标准的MTU大小通常为1500字节。尽管有些网络环境可能支持更大的数据包,但是UDP数据包的理论最大限制是65535字节(64KB),这是由于UDP头部的16位长度字段决定的。然而,如果你需要发送超过这......
  • udp和arp之间的交互作用
    udp和arp之间的交互作用arp-a验证arp告诉缓存是空的sock-u-i-nl-w8192svr4discard1.在第一个arp应答返回以前,总共产生了6个arp请求。2.在接收到第一个arp应答时(第7行),只发送最后一个数据报片(第9行)3.在大多数的现实中,在等待一个arp应答时,只将最后一个报文发送给特定目标......
  • udp介绍
    1.udp介绍udp是一个简单的面向数据报的运输层协议,进程的每个输出操作都正好产生一个udp数据报,并组装成一份待发送的ip数据包,这与面向流字符的协议不同,如tcp,应用程序产生的全体数据与真正发送的单个ip数据报可能没有什么联系。udp不提供可靠性:它把应用程序传给ip层的数据发送出......
  • tcp介绍.3
    1.tcp的成块数据流我们看到tftp使用了停止等待协议,数据发送方在发送下一个数据块之前需要等待接收对已发送数据的确认,本章我们介绍tcp所使用的被称为滑动窗口协议的另一种形式的流量控制方法,该协议允许发送方在停止并等待确认前可以连续发送多个分组,由于发送方不必每发一个分组就......
  • tcp介绍.2
    2msl等待状态time_wait状态也称为2msl等待状态,每个具体tcp实现必须选择一个报文段最大生存的时间msl(maximumsegmentlifetime).它是任何报文段被丢弃前在网络内的最长时间。msl为2分钟,然而,实现中的常用值是30秒,1分钟,或2分钟。对一个具体实现所给定的msl值,处理的原则是:当tcp执......
  • tcp介绍.1
    1.tcp介绍tcp的服务tcp提供一种面向连接的、可靠的字节流服务。面向连接意味着两个使用tcp的应用(通常一个客户和一个服务器)在彼此交换数据之前必须先建立一个tcp链接。在一个tcp链接中,仅有两方进行彼此通信。tcp的服务tcp提供一种面向连接的、可靠的字节流服务。面向连接......