实现内容:
1.聊天室消息群发(一个人发全都可以收到,利用epoll模型实现)
2.把服务器挂载到阿里云服务器上(利用XShell远程控制+XFtp传文件+Visual Studio linux编辑工具)
对于linux服务器来说,网络编程需要的以下几步骤:
- 创建epoll实例:epoll_create1
- 创建服务器套接字:socket
- 绑定本机IP和端口:bind
- 监听客户端:listen
- 循环等待epoll消息:epoll_wait
- (1)监听套接字消息:新加一个客户端套接字到epoll里
- (2)客户端发消息来:如果是第一个消息就是客户端的名称,用map记录,否则就是普通消息群发给其他所有用户
对于Windows客户端来说,网络编程需要的以下几步骤:
- 创建客户端套接字:socket
- 绑定本机IP和端口:bind
- 连接服务端:connect
- 添加两个线程,一个是发消息,一个是收消息
套接字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++网络编程从最简单的聊天室开始做起吧!