【c++项目】从零开始写一个局域网聊天室03:将多次收发数据修改为2次

本文最后更新于:2022年7月9日 下午

【c++项目】从零开始写一个局域网聊天室03:将多次收发数据修改为2次

详细的过程我都以注释的形式写在代码里,这里就不啰嗦了。其实不仅仅是局域网,如果能够获取公网ip,也可以建立通信。

server端

server.cpp

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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
/*
将多次收发数据修改为2次
2022-6-22
Liansixin
*/

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#include "iostream"
#include "stdio.h"
using namespace std;
/*使用报文的方式进行传输*/
//数据头
enum CMD
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGINOUT,
CMD_LOGINOUT_RESULT,
CMD_ERROR
};
struct DataHeader
{
short dataLength;//数据长度
short cmd;//命令
};
//DataPackage
//包体
struct Login : public DataHeader
{
//DataHeader header;
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char PassWord[32];
};
struct LoginResult : public DataHeader
{
LoginResult()
{
dataLength = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
}
char result[32] = "登陆成功!!";
};
struct LoginOut : public DataHeader
{
LoginOut()
{
dataLength = sizeof(LoginOut);
cmd = CMD_LOGINOUT;
}
char userName[32];
};
struct LoginOutResult : public DataHeader
{
LoginOutResult()
{
dataLength = sizeof(LoginOutResult);
cmd = CMD_LOGINOUT_RESULT;
}
char result[32] = "注销成功!!";
};

int main()
{
/*启动Windows socket 2.x环境*/
//版本号
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
//socket网络编程启动函数
WSAStartup(ver, &dat);
//---------------------------
//--用Socket API建立简易TCP服务端
//1、建立一个socket 套接字 (windows) linux上指的是指针
/*socket(
_In_ int af,(表示什么类型的套接字)
_In_ int type,(数据流)
_In_ int protocol
);*/
//IPV4的网络套接字 AF_INET
//IPV6的网络套接字 AF_INET6
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
char msgBuf[] = "Hello, I'm Server.\n";
//2、bind 绑定用于接收客户端链接的网络端口
/*
bind(
_In_ SOCKET s,
_In_reads_bytes_(namelen) const struct sockaddr FAR * name,
_In_ int namelen
);
*/
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;//ipv4
_sin.sin_port = htons(4567);//端口号 由于主机和网络的数据类型不同 因此需要进行转换 使用 host to net unsigned short
_sin.sin_addr.S_un.S_addr = INADDR_ANY;//inet_addr("127.0.0.1");//服务器的ip地址 INADDR_ANY本机所有的Ip地址都可以访问 一般这样
//有可能绑定失败
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
{
printf("错误,绑定网络端口失败...\n");
}
else
{
printf("绑定端口成功...\n");
}

//3、listen 监听网络端口
/*
listen(
_In_ SOCKET s,
_In_ int backlog
);*/
if (SOCKET_ERROR == listen(_sock, 5))
{
printf("错误,监听网络端口失败...\n");
}
else
{
printf("监听网络端口成功...\n");
}

//4、accept 等待接收客户端链接
/*
accept(
_In_ SOCKET s,
_Out_writes_bytes_opt_(*addrlen) struct sockaddr FAR * addr,
_Inout_opt_ int FAR * addrlen
);*/
sockaddr_in clientAddr = {};//客户端地址
int nAddrLen = sizeof(sockaddr_in);//地址长度
SOCKET _cSocket = INVALID_SOCKET;

_cSocket = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen);
if (INVALID_SOCKET == _cSocket)
{
printf("错误,接收到无效客户端SOCKET...\n");
}
printf("新客户端加入: socket = %d IP = %s \n", _cSocket, inet_ntoa(clientAddr.sin_addr));//inet_ntoa转换为可读地址
//一直不停的收发数据
//char _recvBuf[128] = {};//接收缓冲区
while (true)
{
//使用缓冲区来接受数据
char szRecv[1024] = {};
//5 接收客户端请求数据
int nLen = recv(_cSocket, (char*)&szRecv, sizeof(DataHeader), 0);
//拆包 和 分包
/*拆包和分包的作用主要是用在服务端接受数据时一次接受数据过长 和 过短的情况*/
DataHeader* header = (DataHeader*)szRecv;
if (nLen <= 0)
{
printf("客户端已经退出, 任务结束。\n");
break;
}
//printf("收到命令: %d 数据长度:%d\n", header.cmd, header.dataLength);
/*判断所收到的数据*/ //多客户端进行收发数据的情况下使用
//if (nLen > sizeof(DataHeader))
//{
//}
switch (header->cmd)
{
case CMD_LOGIN:
{
recv(_cSocket, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
Login* login = (Login*)szRecv;
printf("收到命令:CMD_LOGIN 数据长度:%d userName = %s passWord = %s\n", login->dataLength, login->userName, login->PassWord);
//忽略判断用户名密码是否正确的过程
LoginResult ret;
//send(_cSocket, (char*)&header, sizeof(DataHeader), 0);
send(_cSocket, (char*)&ret, sizeof(LoginResult), 0);
}
break;
case CMD_LOGINOUT:
{
recv(_cSocket, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
LoginOut *loginout = (LoginOut*)szRecv;
printf("收到命令:CMD_LOGINOUT 数据长度:%d userName = %s\n", loginout->dataLength, loginout->userName);
//忽略判断用户名密码是否正确的过程
LoginOutResult ret;
//send(_cSocket, (char*)&header, sizeof(DataHeader), 0);
send(_cSocket, (char*)&ret, sizeof(LoginOutResult), 0);
}
break;
default:
{
DataHeader header = { 0, CMD_ERROR };
send(_cSocket, (char*)&header, sizeof(DataHeader), 0);
}
break;
}

}
//5、send 向客户端发送一条数据
/*send(
_In_ SOCKET s,
_In_reads_bytes_(len) const char FAR * buf,
_In_ int len,
_In_ int flags
);*/
//char msgBuf[] = "Hello, I'm Server.";
//+1表示将结尾符一并发送过去
//send(_cSocket, msgBuf, strlen(msgBuf) + 1, 0);

//6、关闭套接字closesocket
closesocket(_sock);

//---------------------------
WSACleanup();
printf("已退出,任务结束\n");
getchar();
return 0;
}

client端

client.cpp

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
/*
将多次收发数据修改为2次
2022-6-22
Liansixin
*/

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#include <stdio.h>
#include "iostream"
using namespace std;
enum CMD
{
CMD_LOGIN,
CMD_LOGIN_RESULT,
CMD_LOGINOUT,
CMD_LOGINOUT_RESULT,
CMD_ERROR
};
struct DataHeader
{
short dataLength;//数据长度
short cmd;//命令
};
//DataPackage
//包体
struct Login : public DataHeader
{
//DataHeader header;
Login()
{
dataLength = sizeof(Login);
cmd = CMD_LOGIN;
}
char userName[32];
char PassWord[32];
};
struct LoginResult : public DataHeader
{
LoginResult()
{
dataLength = sizeof(LoginResult);
cmd = CMD_LOGIN_RESULT;
}
char result[32] = "登陆成功!!";
};
struct LoginOut : public DataHeader
{
LoginOut()
{
dataLength = sizeof(LoginOut);
cmd = CMD_LOGINOUT;
}
char userName[32];
};
struct LoginOutResult : public DataHeader
{
LoginOutResult()
{
dataLength = sizeof(LoginOutResult);
cmd = CMD_LOGINOUT_RESULT;
}
char result[32] = "注销成功!!";
};

int main()
{
/*启动Windows socket 2.x环境*/
//版本号
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
//socket网络编程启动函数
WSAStartup(ver, &dat);
//---------------------------
//用Socket API建立简易TCP客户端
//1、建立一个socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == _sock)
{
printf("错误,建立Socket失败...\n");
}
else
{
printf("建立socket成功...\n");
}

//2、链接服务器 connect
sockaddr_in _sin = {};//将结构体初始化
_sin.sin_family = AF_INET;//ipv4
_sin.sin_port = htons(4567);//将网络转换为成无符号类型
_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in)); //使用sizeof(sockaddr_in)类型更安全
if (SOCKET_ERROR == ret)
{
printf("错误,链接服务器失败...\n");
}
else
{
printf("链接服务器成功...\n");
}
while (true)
{
//3 输入请求命令
char cmdBuf[128] = {};
scanf("%s", cmdBuf);

//4 处理请求命令
if (0 == strcmp(cmdBuf, "exit"))
{
printf("收到exit命令,任务结束\n");
break;
}
else if (0 == strcmp(cmdBuf, "login"))
{
// 当创建Login这种类型时就已经实例化了cmd等信息
Login login;
strcpy(login.userName, "lsx");
strcpy(login.PassWord, "lsxyyds");
//DataHeader dh = {sizeof(login), CMD_LOGIN};
//login.userName = "lsx";
//login.PassWord = "lsxyyds";
//5 向服务器发送请求命令
//send(_sock, (const char*)&dh, sizeof(dh), 0);
send(_sock, (const char*)&login, sizeof(login), 0);
//接收服务器返回的数据
//DataHeader retHeader = {};
LoginResult loginRet;
//recv(_sock, (char*)&retHeader, sizeof(retHeader), 0);
recv(_sock, (char*)&loginRet, sizeof(loginRet), 0);
printf("LoginResult: %s\n", loginRet.result);

}
else if (0 == strcmp(cmdBuf, "logout"))
{
LoginOut logout;
strcpy(logout.userName, "lsx");
//DataHeader dh = {sizeof(logout), CMD_LOGINOUT };
//5 向服务器发送请求命令
//send(_sock, (const char*)&dh, sizeof(dh), 0);
send(_sock, (const char*)&logout, sizeof(logout), 0);
//接收服务器返回的数据
//DataHeader retHeader = {};
LoginOutResult loginoutRet;
//recv(_sock, (char*)&retHeader, sizeof(retHeader), 0);
recv(_sock, (char*)&loginoutRet, sizeof(loginoutRet), 0);
printf("LogoutResult: %s\n", loginoutRet.result);
}
else
{
printf("不支持的命令,请重新输入。\n");
}
}

//7 关闭套节字closesocket
closesocket(_sock);
//---------------------------
WSACleanup();
printf("已退出.\n");
getchar();
return 0;
}

成果展示

upload successful

server.exe体验:点我下载

client.exe体验:点我下载


【c++项目】从零开始写一个局域网聊天室03:将多次收发数据修改为2次
https://jinbilianshao.github.io/2022/06/22/【c-项目】从零开始写一个局域网聊天室03:/
作者
连思鑫
发布于
2022年6月22日
许可协议