linux C++聊天室项目(1)

实现内容:

1.聊天室消息群发(一个人发全都可以收到,利用epoll模型实现)

2.把服务器挂载到阿里云服务器上(利用XShell远程控制+XFtp传文件+Visual Studio linux编辑工具)

对于linux服务器来说,网络编程需要的以下几步骤:

  1. 创建epoll实例:epoll_create1
  2. 创建服务器套接字:socket
  3. 绑定本机IP和端口:bind
  4. 监听客户端:listen
  5. 循环等待epoll消息:epoll_wait
  6. (1)监听套接字消息:新加一个客户端套接字到epoll里
  7. (2)客户端发消息来:如果是第一个消息就是客户端的名称,用map记录,否则就是普通消息群发给其他所有用户

对于Windows客户端来说,网络编程需要的以下几步骤:

  1. 创建客户端套接字:socket
  2. 绑定本机IP和端口:bind
  3. 连接服务端:connect
  4. 添加两个线程,一个是发消息,一个是收消息

套接字socket:

int socket(int af, int type, int protocol);
af:一个地址家族,通常为AF_INET(IPv4)
type:套接字类型,SOCK_STREAM表示创建面向流连接的套接字。为SOCK_DGRAM,表示创建 面向无连接的数据包套接字。为SOCK_RAW,表示创建原始套接字
protocol:套接字所用协议,不指定可以设置为0 ,会通过前两个参数自动推断协议类型

返回值就是一个socket类型,可以通过这个socket利用send,recv,read,write等函数进行客户端和服务端的通信,我更愿意把这个socket理解成一个标识,标识和哪个主机的端口在通信,然后通过这个socket来进行收发消息。

epoll使用:

网络连接,服务器也是通过文件描述符来管理这些连接上来的客户端。

有三种方式select,poll,epoll。这里不研究原理和它们的特点,只说用法

创建epoll实例: int epoll_create1() 返回epoll实例的标识

管理epoll实例: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event )

控制某个epoll监控的文件描述符上的事件:注册,修改,删除

参数释义:

epfd:为epoll的句柄

op:表示动作,用3个宏来表示

··· EPOLL_CTL_ADD(注册新的 fd 到epfd)

··· EPOLL_CTL_DEL(从 epfd 中删除一个 fd)

··· EPOLL_CTL_MOD(修改已经注册的 fd 监听事件)

fd:监听的套接字

event:告诉内核需要监听的事件

epoll_event:监听的事件

struct epoll_event {
    uint32_t events;  // epoll 事件类型,包括可读,可写等
    epoll_data_t data; // 用户数据,可以是一个指针或文件描述符等
};

events:

  • EPOLLIN表示对应的文件描述符上有数据可读
  • EPOLLOUT表示对应的文件描述符上可以写入数据
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;

这里表示服务端代码文件描述符上有数据可读,并指定监听套接字

Linux服务端代码

#include <iostream>
#include <string>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <map>

const int MAX_CONN = 20;

int main()
{
    //创建epoll实例
	int epid = epoll_create1(0);
	if (epid < 0)
	{
		perror("epoll create error!\n");
		return -1;
	}
    //创建socket套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
		perror("socket create error!\n");
		return -1;
	}
    //指定服务端连接地址族,地址和端口
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(5555);
	
    //绑定套接字
	if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
	{
		perror("socket create error!\n");
		return -1;
	}
    //监听客户端
	if (listen(sockfd, 1024) < 0)
	{
		perror("listen create error!\n");
		return -1;
	}
	printf("server is listening\n");
	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.fd = sockfd;
    
    //将其加入epoll实例
	if (epoll_ctl(epid, EPOLL_CTL_ADD, sockfd, &ev) < 0)
	{
		printf("server epoll_ctl error!\n");
		return -1;
	}

	std::map<int, std::string> client_info;//存放用户信息,key是套接字整型,value是用户名称

	while (true)
	{
		struct epoll_event evs[MAX_CONN];//存放有消息的套接字
		int n = epoll_wait(epid, evs, MAX_CONN, -1);

		if (n < 0)
		{
			printf("epoll_wait error!\n");
			break;
		}
		for (int i = 0; i < n; i++)
		{
			int fd = evs[i].data.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);

				struct epoll_event ev_client;
				ev_client.events = EPOLLIN;
				ev_client.data.fd = client_sockfd;
                
                //新加的和客户端连接的套接字加入epoll实例
				if (epoll_ctl(epid, EPOLL_CTL_ADD, client_sockfd, &ev_client) < 0)
				{
					printf("client epoll_ctl error!\n");
					return -1;
				}
				printf("%d is connected\n",client_sockfd);
				client_info[client_sockfd] = "";
			}
			else//如果是客户端套接字
			{
				char buffer[1024];
				int n = read(fd, buffer, 1024);

				if (n < 0) {
					printf("read error!\n");
					break;
				}
				else if (n == 0)//客户端关闭
				{
					close(fd);
					epoll_ctl(epid, EPOLL_CTL_DEL, fd, 0);
					client_info.erase(fd);
				}
				else
				{
					std::string msg(buffer, n);
					printf("%d sends a message!\n",fd);
					if (client_info[fd] == "")
					{
						client_info[fd] = msg;
						std::cout<<fd<<" name is "<<msg<<'\n';
						write(fd, "hello newuser", 13);
					}
					else
					{
						for (auto& iter : client_info)
						{
							if (iter.first != fd)
							{
								write(iter.first, ('[' + client_info[fd] + ']' + ": " + msg).c_str(), msg.size() + client_info[fd].size() + 4);
							}
						}
					}
				}
			}
		}
	}


	close(sockfd);
	close(epid);
	return 0;

}

Windows客户端代码

#include <WinSock2.h>
#include <WS2tcpip.h>
#include <Windows.h>
#include <stdio.h>


#pragma comment(lib,"Ws2_32.lib")
#define BUF_SIZE 1024

char sendmsg[BUF_SIZE];
char recvmsg[BUF_SIZE];

unsigned Send_Msg(void* args)
{
    SOCKET sock = (SOCKET)args;
    while (true)
    {
        scanf("%s", sendmsg);
        if (!strcmp(sendmsg, "quit"))
        {
            closesocket(sock);
            exit(0);
        }
        
        send(sock, sendmsg, strlen(sendmsg), 0);
    }

    return 0;
}

unsigned Recv_Msg(void* args)
{
    SOCKET sock = (SOCKET)args;
    while (true)
    {
        int len = recv(sock, recvmsg, sizeof(recvmsg)-1, 0);
        if (len == -1)
        {
            return 0;
        }
        recvmsg[len] = '\0';
        printf("%s\n", recvmsg);
    }
    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;
    }

    /* The WinSock DLL is acceptable. Proceed. */

    //创建套接字
    SOCKET cSock;
    cSock = socket(AF_INET, SOCK_STREAM, 0);
    if (cSock < 0)
    {
        perror("socket create error !\n");
        return -1;
    }
    
    //指定地址族,端口和连接IP地址(云服务器用公网IP,本地的就用本地局域网IP)
    SOCKADDR_IN serv_adr;
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_port = htons(5555);
    inet_pton(AF_INET, "59.110.162.233", &serv_adr.sin_addr);

    printf("client is connecting the server...\n");
    if (connect(cSock, (SOCKADDR*)&serv_adr, sizeof(serv_adr)) == SOCKET_ERROR)
    {
        printf("connect error !\n%d", GetLastError());
        return -1;
    }
    else
    {
        printf("服务器连接成功!\n");
        printf("请输入您的用户名:\n");
    }

    HANDLE hsend = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Send_Msg, (void *)cSock,0,NULL);//发送线程
    HANDLE hrecv = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Recv_Msg, (void*)cSock,0,NULL);
//接收线程
    WaitForSingleObject(hsend, INFINITE);
    WaitForSingleObject(hrecv, INFINITE);

    closesocket(cSock);

    WSACleanup();
	return 0;
}

挂载的阿里云服务器

 千里之行始于足下,C++网络编程从最简单的聊天室开始做起吧!

猜你喜欢

转载自blog.csdn.net/qq_30798083/article/details/131013401