【C/C++多人聊天室】基于epoll模型的IO多路复用

聊天室程序文档说明

1. 介绍

本文档是关于一个简单的聊天室程序的说明文档。该聊天室程序由服务端和客户端两部分组成,服务端代码运行在Linux环境下,客户端代码运行在Windows环境下。

1.2 效果演示

支持多个客户端进行连接,项目上传到云端可以支持不同主机进行聊天
客户端

2. 服务端代码说明

以下是服务端代码的详细说明:

#include <iostream>
#include <string>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstdlib>
#include <cstdio>
#include <errno.h>
#include <map>

#define MAX_CONN 1024 //最大连接数

//保存客户端的信息
struct Client
{
    int sockfd;
    std::string name; //名字
};

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

    //创建监听的socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket error\n");
        return -1;
    }

    //绑定本地ip和端口
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
    {
        perror("bind error\n");
        return -1;
    }

    //监听
    if (listen(sockfd, 5) < 0)
    {
        perror("listen error\n");
        return -1;
    }

    //创建epoll句柄
    int epollfd = epoll_create(MAX_CONN);
    if (epollfd < 0)
    {
        perror("epoll_create error\n");
        return -1;
    }

    //将监听的socket加入到epoll中
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev) < 0)
    {
        perror("epoll_ctl error\n");
        return -1;
    }

    //保存客户端的信息
    std::map<int, Client> client_map;

    //创建epoll事件数组
    struct epoll_event events[MAX_CONN];

    while (1)
    {
        //等待事件发生
        int nfds = epoll_wait(epollfd, events, MAX_CONN, -1);
        if (nfds < 0)
        {
            perror("epoll_wait error\n");
            break;
        }

        //处理事件
        for (int i = 0; i < nfds; ++i)
        {
            int fd = events[i].data.fd;

            //有新的连接请求
            if (fd == sockfd)
            {
                struct sockaddr_in client_addr;
                socklen_t client_len = sizeof(client_addr);
                int connfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
                if (connfd < 0)
                {
                    perror("accept error\n");
                    continue;
                }

                //将新的连接加入到epoll中
                ev.events = EPOLLIN;
                ev.data.fd = connfd;
                if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev) < 0)
                {
                    perror("epoll_ctl error\n");
                    continue;
                }

                //保存客户端信息
                Client client;
                client.sockfd = connfd;
                client.name = "Unknown";
                client_map[connfd] = client;

                //发送欢迎消息
                std::string welcome_msg = "Welcome to the chat room!\n";
                send(connfd, welcome_msg.c_str(), welcome_msg.length(), 0);
            }
            else //有数据可读
            {
                char buf[1024];
                int len = recv(fd, buf, sizeof(buf) - 1, 0);
                if (len <= 0)
                {
                    //客户端断开连接
                    close(fd);
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
                    client_map.erase(fd);
                    continue;
                }

                buf[len] = '\0';

                //处理客户端发送的消息
                std::string msg = buf;
                if (msg.find("NAME:") == 0)
                {
                    //更新客户端的名字
                    std::string name = msg.substr(5);
                    client_map[fd].name = name;
                }
                else
                {
                    //转发消息给其他客户端
                    std::string send_msg = client_map[fd].name + ": " + msg;
                    for (auto it = client_map.begin(); it != client_map.end(); ++it)
                    {
                        if (it->first != fd)
                        {
                            send(it->first, send_msg.c_str(), send_msg.length(), 0);
                        }
                    }
                }
            }
        }
    }

    //关闭socket
    close(sockfd);

    return 0;
}
2.1 功能说明

该服务端代码实现了一个简单的聊天室服务器,具有以下功能:

一、监听客户端连接请求,并接受连接。
首先创建了一个`TCP/IP`套接字 sockfd,然后通过`bind() `方法将服务器地址和端口绑定到套接字上。接下来,使用`listen()`方法开始监听连接请求,参数`MAX_CONN`表示最多同时接受 `1024 `个连接请求
#define MAX_CONN 1024 //最大连接数
 //创建监听的socket    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if ( sockfd < 0)
    {
        perror("socket error\n");
        return -1;
    }
    //绑定本地ip和端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;//IPv4
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(8888);
    int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind error\n");
        return -1;
    }
    //监听客户端
    ret = listen(sockfd, MAX_CONN);
    if (ret < 0)
    {
        printf("listen error\n");
        return -1;
    }
二、将新连接加入到epoll事件监听中

首先创建了一个epoll对象 epfd。然后,将服务器套接字 sockfd添加到epfd事件监听中。
创建一个struct epoll_event类型的变量ev,用于设置要监听的事件。使用epoll_ctl()函数进行注册。 EPOLLIN表示监听可读事件。
创建了一个Map对象clients接收客户端信息和名称。
然后循环监听:
在循环中,创建一个struct epoll_event类型的数组evs,用于存储触发的事件.然后,调用epoll_wait()函数来等待事件的发生。该函数会阻塞当前线程,直到有事件触发或超时。
当epoll_wait()函数返回时,它会返回触发事件的数量n。如果返回值小于0,表示出现了错误,此时会打印错误信息并返回-1。
接下来,通过一个循环遍历触发的事件。循环的次数为n,即触发事件的数量

//保存客户端的信息
struct Client
{
    int sockfd; 
    std::string name;//名字
    /* data */
};
//创建epoll实例
    int epfd = epoll_create1(0);
    if ( epfd < 0)
    {
        perror("epoll create error\n");
        return -1;
    }

    //将监听的socket加入epoll
    struct epoll_event ev;
    ev.events = EPOLLIN;//读
    ev.data.fd = sockfd;
    //epoll_ctl 添加待检测任务
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
    if (ret < 0)
    {
        printf("eppoll_ctl error\n");
        return -1;
    }
    //保存客户端信息
    std::map<int, Client> clients;
    //循环去监听
    while (1)
    {
        struct epoll_event evs[MAX_CONN];
        //阻塞 n 返回监听的数量
        int n = epoll_wait(epfd, evs, MAX_CONN, -1);
        //处理
        if(n < 0)
        {
            printf("epoll_wait error\n");
            return -1;
        }
三、接收客户端并判断当前触发的事件是否是新的客户端连接

if (fd == sockfd)如果是监听的fd收到消息 那么则表示有客户端连接
首先通过accept()函数接受新的客户端连接,并将返回的客户端socket描述符保存在client_sockfd变量中。

然后,创建一个新的struct epoll_event结构体 ev_client,并设置其events字段为EPOLLIN,表示监听可读事件。

接下来,将客户端socket描述符 client_sockfd 添加到epoll事件监听中,使用epoll_ctl()函数,参数EPOLL_CTL_ADD表示添加新的监听.

&ev_client表示要添加的事件结构体。如果添加成功,epoll_ctl()函数将返回0,否则返回-1。
接受新的客户端连接,并将新的客户端socket描述符添加到epoll事件监听中。并保存客户端的信息

for (size_t i = 0; i < n; i++)
        {
            int fd = evs[i].data.fd;
            //如果是监听的fd收到消息 那么则表示有客户端连接
            if (fd == sockfd)
            {
                struct sockaddr_in  client_addr;
                socklen_t client_addr_len = sizeof(client_addr);
                int client_sockfd =  accept(sockfd,(struct sockaddr*)&client_addr, &client_addr_len);
                if (client_sockfd < 0)
                {
                    printf("accept error\n");
                    continue;
                }
                //将客户端的socket加入epoll
                struct epoll_event ev_client;
                ev_client.events = EPOLLIN;//检测客户端有没有消息
                ev_client.data.fd = client_sockfd;
                
                ret = epoll_ctl(epfd, EPOLL_CTL_ADD, client_sockfd, &ev_client);
                if(ret < 0)
                {
                    printf("epoll_ctl error\n");
                    break;;
                }
                //将客户端加入客户端列表
                char ip[16] = "";
                printf("ip:%s  正在连接......\n", inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,16));

                //保存客户端信息
                Client client;
                client.sockfd = client_sockfd;
                client.name = "";

                clients[client_sockfd] = client;
            }
四、将客户端发送的消息转发给其他客户端。

fd不等于sockfd时,表示当前触发的事件是已连接的客户端发送的消息。在这种情况下,需要接收该客户端发送的消息,并将消息转发给其他客户端。

使用recv()函数从当前客户端的socket描述符fd接收消息,并将消息存储在buffer中。

检查接收到的消息长度len,如果len小于等于0,表示该客户端已经断开连接,进行相应的处理,关闭该客户端的连接。

如果接收到的消息长度len大于0,表示接收到了有效的消息。接下来,通过一个嵌套的循环遍历所有的触发事件,即所有的客户端。

对于每个其他客户端,通过判断其文件描述符other_fd不等于sockfd且不等于当前触发事件的文件描述符fd来确定该客户端是其他客户端,需要将消息转发给它们。使用send()函数将消息buf发送给其他客户端,实现消息的转发。

else//如果是客户端有消息
            {   //存储消息
                char buffer[MAX_CONN];
                int ret = recv(fd, buffer, MAX_CONN, 0);
                if (ret < 0)
                {
                    break;
                }
                else if (ret == 0)//客户端断开连接
                {
                    close(fd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                    clients.erase(fd);
                    printf("客户端断开连接\n");
                    continue;
                }
                else
                {
                    std::string msg(buffer,ret);
                    //如果客户端name为空说明该消息是客户端的用户名
                    if(clients[fd].name == "")
                    {
                        clients[fd].name = msg;
                    }
                    else //否则是聊天消息
                    {
                        std::string name  = clients[fd].name;
                        //将消息发送给其他客户端
                        for (std::map<int, Client>::iterator it = clients.begin(); it != clients.end(); ++it)
                        {
                            
                            if (it->first != fd)
                            {
                                write(it->first, ('[' + name + "] : " + msg + " \n").c_str(), msg.size() + name.size() + 5);
                            }
                            
                        }
                        
                    }
                    
                }
                
            }
五、关闭

最后关闭epoll实例和socket描述符,释放相关资源并终止服务器的运行

//关闭epoll实例
    close(epfd);
//关闭socket描述符
    close(sockfd);
2.2 代码说明

以下是服务端代码的详细说明:

  • MAX_CONN:定义了最大连接数。
  • struct Client:保存客户端的信息,包括socket文件描述符和名字。
  • main函数:程序入口函数。
    • 创建监听的socket。
    • 绑定本地ip和端口。
    • 监听连接请求。
    • 创建epoll实例。
    • 将监听的socket加入到epoll中。
    • 创建epoll事件数组。
    • 进入事件循环,等待事件发生。
    • 处理事件:
      • 如果是新的连接请求,接受连接,将新的连接加入到epoll中,并保存客户端信息,发送欢迎消息。
      • 如果有数据可读,接收数据,根据消息类型进行相应的处理,转发消息给其他客户端。
    • 关闭socket。

3. 客户端代码说明

该客户端代码实现了一个简单的聊天室客户端,具有以下功能:
windows下的库函数一般都是大写,大致都是一样的。

3.1 功能说明
一、 创建socket环境。

创建socket之前需要先初始化socket环境
具体可以看一下这里:地址

Winsock是Windows下的网络编程接口,它是由Unix下的BSD Socket发展而来,是一个与网络协议无关的编程接口。
Winsock在常见的Windows平台上有两个主要的版本,即Winsock1和Winsock2。编写与Winsock1兼容的程序你需要引用头文件WINSOCK.H,如果编写使用Winsock2的程序,则需要引用WINSOCK2.H此外还有一个MSWSOCK.H头文件,它是专门用来支持在Windows平台上高性能网络程序扩展功能的。使用WINSOCK.H头文件时,同时需要库文件WSOCK32.LIB,使用WINSOCK2.H时,则需要WS2_32.LIB,如果使用MSWSOCK.H中的扩展API,则需要MSWSOCK.LIB。正确引用了头文件,并链接了对应的库文件,你就构建起编写WINSOCK网络程序的环境了

创建socket套接字并连接到服务器

// 创建 socket 环境
    SOCKET hSock;
    hSock = socket(AF_INET, SOCK_STREAM, 0);
    if (hSock == INVALID_SOCKET) {
        // 处理创建 socket 失败的情况
        printf("Failed to create socket. Error code: %d\n", WSAGetLastError());
        return -1;
    }
二、连接到服务器。

首先,声明了一个变量servAdr,用于存储服务器的地址信息。通过memset函数将servAdr的内存清零,然后设置地址族为AF_INET(IPv4),端口号为8888。inet_pton函数将字符串形式的IP地址转换为二进制形式,并存储在servAdr.sin_addr中。

接下来,调用connect函数来连接到服务器。connect函数的第一个参数是之前创建的套接字hSock,第二个参数是指向服务器地址的指针,第三个参数是服务器地址的长度。

然后,通过判断result是否等于SOCKET_ERROR来检查连接是否成功。如果连接失败,会打印错误信息并关闭套接字,然后返回-1。如果连接成功,则输出欢迎信息。

    // 设置服务器地址
    SOCKADDR_IN servAdr;
    memset(&servAdr, 0, sizeof(servAdr));
    servAdr.sin_family = AF_INET;
    servAdr.sin_port = htons(8888);
    inet_pton(AF_INET, "192.168.126.132", &servAdr.sin_addr);
    // 连接到服务器
    int result = connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
    if (result == SOCKET_ERROR) {
        // 处理连接失败的情况
        printf("Failed to connect to server. Error code: %d\n", WSAGetLastError());
        closesocket(hSock);
        return -1;
    }
    else
    {
        printf("欢迎来到聊天室,请输入你的聊天用户名:");

    }
三、创建两个线程

创建两个线程,一个用于发送消息,一个用于接收消息。
RecvMsg函数用于接收消息。首先,将传入的参数arg解引用为SOCKET类型的变量sock。然后,进入一个无限循环,不断接收消息。使用recv函数接收消息,将接收到的消息存储在msg数组中,最多接收sizeof(msg) - 1,-1是因为会有一个字节。如果接收失败(返回-1),则返回0。接收成功后,在消息末尾添加’\0’作为字符串的结束符,并打印消息。

SendMsg函数用于发送消息。首先,将传入的参数arg解引用为SOCKET类型的变量sock。然后,进入一个无限循环,不断发送消息。使用scanf函数从标准输入中读取消息,并存储在szMsg数组中。如果读取到的消息是"QUIT"或"quit",则关闭套接字并退出程序。否则,使用send函数发送消息。

使用CreateThread函数创建两个线程,分别用于循环发送消息和循环接收消息。第一个参数为NULL表示使用默认的安全属性,第二个参数为0表示使用默认的堆栈大小,第三个参数为SendMsgRecvMsg函数的指针,第四个参数为指向套接字hSock的指针,第五个参数为0表示线程立即开始,最后一个参数为NULL表示不返回线程标识符。

HANDLEWindows操作系统中的一个数据类型,用于表示对象的句柄(handle)。句柄是一个特殊的标识符,用于引用操作系统内核对象,如线程、进程、文件、互斥体等

使用WaitForSingleObject函数来等待线程的结束。第一个参数是线程的句柄,第二个参数是等待时间(INFINITE表示无限等待,直到线程结束)。
分别等待hSendHand和hRecvHand两个线程的结束。当线程结束后,WaitForSingleObject函数会返回。通过调用这两个函数,主线程会等待发送消息和接收消息的线程都结束后再继续执行后面的代码。

#define BUF_SIZE 1024 
char szMsg[BUF_SIZE];
unsigned int RecvMsg(void* arg)
{
    SOCKET sock = *((SOCKET*)arg);
    char msg[BUF_SIZE];
    while (1)
    {
        int len = recv(sock, msg, sizeof(msg) - 1, 0);
        if (len == -1)
        {
            return 0;
        }
        msg[len] = '\0';
        printf("%s\n", msg);
    }
    return 0;
}
unsigned int SendMsg(void* arg)
{
    SOCKET sock = *((SOCKET*)arg);
    while (1)
    {
        scanf("%s", szMsg);
        if (!strcmp(szMsg, "QUIT\n")|| !strcmp(szMsg, "quit\n"))
        {
            closesocket(sock);
            exit(0);
        }
        send(sock, szMsg, strlen(szMsg), 0);
    }
    return 0;
}
 //循环发消息
    HANDLE  hSendHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SendMsg, (void*)&hSock, 0, NULL);
    
    //循环收消息
    HANDLE hRecvHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RecvMsg, (void*)&hSock, 0, NULL);
    
    //等待线程结束

    WaitForSingleObject(hSendHand, INFINITE);
    WaitForSingleObject(hRecvHand, INFINITE);
3.2 代码说明

以下是客户端代码的详细说明:

  • 头文件:包含了一些头文件、宏定义、全局变量
  • 定义了两个函数RecvMsg和SendMsg,分别用于接收和发送消息。
  • 首先进行了WinSock的初始化。然后,创建了一个套接字hSock,
  • 使用connect函数连接到服务器。
  • 使用CreateThread函数创建两个线程,分别用于循环发送消息和循环接收消息
  • WaitForSingleObject函数等待这两个线程的结束。最后,关闭套接字,清理WinSock环境

4. 客户端以及服务端源代码

4.1 服务端
#include <iostream>
#include <string>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstdlib>
#include <cstdio>
#include <errno.h>
#include <map>
#define MAX_CONN 1024 //最大连接数
//保存客户端的信息
struct Client
{
    int sockfd; 
    std::string name;//名字
    /* data */
};
int main(int argc, char *argv[])
{
  
    //创建监听的socket    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if ( sockfd < 0)
    {
        perror("socket error\n");
        return -1;
    }
    //绑定本地ip和端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;//IPv4
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(8888);
    int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind error\n");
        return -1;
    }
    //监听客户端
    ret = listen(sockfd, MAX_CONN);
    if (ret < 0)
    {
        printf("listen error\n");
        return -1;
    }
    //创建epoll实例
    int epfd = epoll_create1(0);
    if ( epfd < 0)
    {
        perror("epoll create error\n");
        return -1;
    }

    //将监听的socket加入epoll
    struct epoll_event ev;
    ev.events = EPOLLIN;//读
    ev.data.fd = sockfd;
    //epoll_ctl 添加待检测任务
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
    if (ret < 0)
    {
        printf("eppoll_ctl error\n");
        return -1;
    }
    //保存客户端信息
    std::map<int, Client> clients;
    //循环去监听
    while (1)
    {
        struct epoll_event evs[MAX_CONN];
        //阻塞 n 返回监听的数量
        int n = epoll_wait(epfd, evs, MAX_CONN, -1);
        //处理
        if(n < 0)
        {
            printf("epoll_wait error\n");
            return -1;
        }
        //检测多少条消息 处理客户端
        for (size_t i = 0; i < n; i++)
        {
            int fd = evs[i].data.fd;
            //如果是监听的fd收到消息 那么则表示有客户端连接
            if (fd == sockfd)
            {
                struct sockaddr_in  client_addr;
                socklen_t client_addr_len = sizeof(client_addr);
                int client_sockfd =  accept(sockfd,(struct sockaddr*)&client_addr, &client_addr_len);
                if (client_sockfd < 0)
                {
                    printf("accept error\n");
                    continue;
                }
                //将客户端的socket加入epoll
                struct epoll_event ev_client;
                ev_client.events = EPOLLIN;//检测客户端有没有消息
                ev_client.data.fd = client_sockfd;
                
                ret = epoll_ctl(epfd, EPOLL_CTL_ADD, client_sockfd, &ev_client);
                if(ret < 0)
                {
                    printf("epoll_ctl error\n");
                    break;;
                }
                //将客户端加入客户端列表
                char ip[16] = "";
                printf("ip:%s  正在连接......\n", inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,16));

                //保存客户端信息
                Client client;
                client.sockfd = client_sockfd;
                client.name = "";

                clients[client_sockfd] = client;
            }
            else//如果是客户端有消息
            {   //存储消息
                char buffer[MAX_CONN];
                int ret = recv(fd, buffer, MAX_CONN, 0);
                if (ret < 0)
                {
                    break;
                }
                else if (ret == 0)//客户端断开连接
                {
                    close(fd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                    clients.erase(fd);
                    printf("客户端断开连接\n");
                    continue;
                }
                else
                {
                    std::string msg(buffer,ret);
                    //如果客户端name为空说明该消息是客户端的用户名
                    if(clients[fd].name == "")
                    {
                        clients[fd].name = msg;
                    }
                    else //否则是聊天消息
                    {
                        std::string name  = clients[fd].name;
                        //将消息发送给其他客户端
                        for (std::map<int, Client>::iterator it = clients.begin(); it != clients.end(); ++it)
                        {
                            
                            if (it->first != fd)
                            {
                                write(it->first, ('[' + name + "] : " + msg + " \n").c_str(), msg.size() + name.size() + 5);
                            }
                            
                        }
                        
                    }
                    
                }
                
            }
        }
        
    }
    //关闭epoll实例
    close(epfd);
    close(sockfd);
}
4.2客户端
#include <WinSock2.h>
#include<WS2tcpip.h>
#include <Windows.h>

#include <string.h>
#include<stdio.h>
#define _CRT_SECURE_NO_WARNINGS
// link with Ws2_32.lib
#pragma comment(lib,"Ws2_32.lib")
#define BUF_SIZE 1024 
char szMsg[BUF_SIZE];
unsigned int RecvMsg(void* arg)
{
    SOCKET sock = *((SOCKET*)arg);
    char msg[BUF_SIZE];
    while (1)
    {
        int len = recv(sock, msg, sizeof(msg) - 1, 0);
        if (len == -1)
        {
            return 0;
        }
        msg[len] = '\0';
        printf("%s\n", msg);
    }
    return 0;
}
unsigned int SendMsg(void* arg)
{
    SOCKET sock = *((SOCKET*)arg);
    while (1)
    {
        scanf("%s", szMsg);
        if (!strcmp(szMsg, "QUIT\n")|| !strcmp(szMsg, "quit\n"))
        {
            closesocket(sock);
            exit(0);
        }
        send(sock, szMsg, strlen(szMsg), 0);
    }
    return 0;
}
int main()
{
    
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        return -1;
    }

    /* Confirm that the WinSock DLL supports 2.2.*/
    /* Note that if the DLL supports versions greater    */
    /* than 2.2 in addition to 2.2, it will still return */
    /* 2.2 in wVersion since that is the version we      */
    /* requested.                                        */

    if (LOBYTE(wsaData.wVersion) != 2 ||
        HIBYTE(wsaData.wVersion) != 2) {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        WSACleanup();
        return -1;
    }
    // 创建 socket 环境
    SOCKET hSock;
    hSock = socket(AF_INET, SOCK_STREAM, 0);
    if (hSock == INVALID_SOCKET) {
        // 处理创建 socket 失败的情况
        printf("Failed to create socket. Error code: %d\n", WSAGetLastError());
        return -1;
    }

    // 设置服务器地址
    SOCKADDR_IN servAdr;
    memset(&servAdr, 0, sizeof(servAdr));
    servAdr.sin_family = AF_INET;
    servAdr.sin_port = htons(8888);
    inet_pton(AF_INET, "192.168.126.132", &servAdr.sin_addr);

    // 连接到服务器
    int result = connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
    if (result == SOCKET_ERROR) {
        // 处理连接失败的情况
        printf("Failed to connect to server. Error code: %d\n", WSAGetLastError());
        closesocket(hSock);
        return -1;
    }
    else
    {
        printf("欢迎来到聊天室,请输入你的聊天用户名:");

    }
    //循环发消息
    HANDLE  hSendHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SendMsg, (void*)&hSock, 0, NULL);
    
    //循环收消息
    HANDLE hRecvHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RecvMsg, (void*)&hSock, 0, NULL);
    
    //等待线程结束

    WaitForSingleObject(hSendHand, INFINITE);
    WaitForSingleObject(hRecvHand, INFINITE);
    
    closesocket(hSock);

    WSACleanup();

    return 0;
    /* The WinSock DLL is acceptable. Proceed. */
}

5 总结

通过编写这个客户端和服务端的程序,我学到了网络通信的基本原理和流程,掌握了Winsock库的使用方法。同时也学会了多线程编程和错误处理。这对于我今后进行网络编程和开发网络应用程序非常有帮助。同时,也提高了我的编程能力和解决问题的能力。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
聊天室项目中,IO多路复用是一种常用的技术,用于同时处理多个客户端的输入输出。通过使用IO多路复用,可以避免为每个客户端创建一个线程或进程来处理输入输出,从而提高系统的性能和可扩展性。 在使用IO多路复用时,可以使用select、poll或epoll等系统调用来实现。这些调用可以监视多个文件描述符(包括套接字)的状态,并在有事件发生时通知应用程序。通过将多个文件描述符传递给这些系统调用,应用程序可以同时监听多个客户端的输入输出。 在使用select时,需要使用fd_set数据结构来管理文件描述符的状态。fd_set实际上是一个数组的宏定义,每个数组元素与一个文件描述符建立联系。当调用select时,内核会根据文件描述符的状态修改fd_set的内容,从而通知应用程序哪些文件描述符可读或可写。 在使用poll时,需要使用struct pollfd结构体来管理文件描述符的状态。pollfd结构体包含了文件描述符的信息以及感兴趣的事件类型。调用poll时,内核会修改pollfd结构体的revents字段,以通知应用程序哪些文件描述符可读或可写。 在使用epoll时,需要使用epoll_create、epoll_ctl和epoll_wait等函数来管理文件描述符的状态。epoll使用一个事件驱动的模型,通过注册事件和等待事件的方式来实现IO多路复用epoll可以高效地处理大量的并发连接。 综上所述,IO多路复用是在聊天室项目中常用的技术,可以同时处理多个客户端的输入输出,提高系统的性能和可扩展性。可以使用select、poll或epoll等系统调用来实现IO多路复用,并根据具体的需求选择合适的方法。 #### 引用[.reference_title] - *1* *2* *3* [IO多路复用技术总结](https://blog.csdn.net/qigeminghao/article/details/122004757)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我中意你呀丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值