前言

前几天博客托管平台被墙了,正好碰上阿里云学生计划送了7个月的云服务器,把博客部署完想着正好能用来学学网络编程

代码

服务器端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#include <iostream>
#include <map>
#include <sys/epoll.h> // epoll_create
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <stdio.h> // perror
#include <string>

// 最大连接数
const int MAX_CONN = 1024;

// 客户端信息
struct Client
{
int sockfd;
std::string name;
};

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

// 绑定本地IP和端口
struct sockaddr_in addr; // <netinet/in.h>
addr.sin_family = AF_INET; // IPv4
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定本地所有IP
addr.sin_port = htons(9999);

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

// 监听客户端连接
ret = listen(sockfd, 1024);
if (ret < 0)
{
perror("listen error!\n");
return -1;
}

// 创建epoll实例
int epld = epoll_create(1); // 参数需要大于0
if (epld < 0)
{
perror("epoll create error!\n");
return -1;
}

// 将监听的socket加入epoll
struct epoll_event ev;
ev.events = EPOLLIN; // 读事件
ev.data.fd = sockfd;

ret = epoll_ctl(epld, EPOLL_CTL_ADD, sockfd, &ev);
if (ret < 0)
{
perror("epoll_ctl error!\n");
return -1;
}

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

// 循环监听客户端
while (1)
{
struct epoll_event evs[MAX_CONN];
int n = epoll_wait(epld, evs, MAX_CONN, -1); // 阻塞,返回有操作的客户端的数量
if (n < 0)
{
perror("epoll_wait error!\n");
break;
}

for (int 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)
{
perror("accept error!\n");
continue;
}

// 将客户端socket加入epoll
struct epoll_event ev_client;
ev_client.events = EPOLLIN; // 读事件, 检测客户端消息
ev_client.data.fd = client_sockfd;
ret = epoll_ctl(epld, EPOLL_CTL_ADD, client_sockfd, &ev_client);
if (ret < 0)
{
perror("client_epoll_ctl error!\n");
break;
}
std::cout << client_addr.sin_addr.s_addr << "has entered~" << std::endl;

// 保存该客户端的信息
Client client;
client.sockfd = client_sockfd;
client.name = "";
clients[client_sockfd] = client;
}
else // 监听到客户端消息
{
char buffer[1024];
int n = read(fd, buffer, 1024);
if (n < 0)
{
std::cout << "消息接收错误!" << std::endl;
break;
}
else if (0 == n) // 客户端断开连接
{
// 清除客户端信息
close(fd);
epoll_ctl(epld, EPOLL_CTL_DEL, fd, 0);
clients.erase(fd);
}
else
{
// char数组转化为string
std::string msg(buffer, n);
// 若客户端name为空,则消息是该客户端的用户名
if (clients[fd].name == "")
{
clients[fd].name = msg;
}
// 否则是聊天消息
else
{
std::string name = clients[fd].name;
// 将消息发送给其他所有客户端
for (auto &c : clients)
{
if (c.first != fd)
{
write(c.first, ('[' + name + ']' + ": " + msg).c_str(), msg.size() + name.size() + 4);
}
}
}
}
}
}
}

// 关闭epoll实例
close(epld);
close(sockfd);
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <Windows.h>
#include <stdio.h>
#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment(lib,"Ws2_32.lib") // link with Ws2_32.lib

#define BUF_SIZE 1024
char szMsg[BUF_SIZE];

unsigned 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;
}

unsigned RecvMsg(void* arg)
{
SOCKET sock = *((SOCKET*)arg);

char msg[BUF_SIZE];
while (1)
{
int len = recv(sock, msg, sizeof(msg) - 1, 0); // 字符串结束有一个'\0'所以减一
if (len == -1) // 服务器崩溃
{
return -1;
}
msg[len] = '\0';
printf("%s\n", msg);
}

return 0;
}

int main()
{
// 初始化socket环境
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;
}

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
return -1;
}
// 建立windows socket环境

// 创建socket
SOCKET hSock;
hSock = socket(AF_INET, SOCK_STREAM, 0);

// 绑定端口
SOCKADDR_IN servAdr;
memset(&servAdr, 0, sizeof(SOCKADDR_IN));
servAdr.sin_family = AF_INET;
servAdr.sin_port = htons(9999);
inet_pton(AF_INET, "47.115.204.216", &servAdr.sin_addr);

// 连接服务器
if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
{
printf("connect error: %d\n", GetLastError());
return -1;
}
else
{
printf("欢迎来到聊天室,请输入您的用户名: ");
}

// 循环发消息
HANDLE hSendHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SendMsg, (void *)&hSock, 0, NULL);

// 循环收消息
HANDLE hReceiveHand = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)RecvMsg, (void*)&hSock, 0, NULL);

// 等待两个线程结束
WaitForSingleObject(hSendHandle, INFINITE);
WaitForSingleObject(hReceiveHand, INFINITE);

closesocket(hSock);
WSACleanup();
}

测试

服务器运行服务端程序,多台电脑运行客户端程序即可实现简易的收发消息

注:这里演示用的本机

image-20231015180114762

image-20231015180031194

image-20231015180045513

image-20231015180057934