目录
前言
- 协议选择TCP
- 这里只给出每个步骤的大致流程,具体实现依照自身需求编写。(循环或者并发等等)
服务器端
1. 加载Socket库
WSAStartup()
: 通过进程启动 Winsock DLL 的使用
WORD wVersionRequested;
WSADATA wsaData;
int err;
//版本2.2(lowbyte,highbyte)
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
//找不到 winsock.dll
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
//找不到2.2版本的Winsock.dll
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
else
printf("The Winsock 2.2 dll was found okay\n");
启动Socket库的固定模板,直接使用即可。
2. 创建socket
套接字 socket(协议族,套接字类型。协议号)
- 这里使用IPv4协议族,所以选择
AF_INET
- 套接字类型选择
SOCK_STREAM
,对应的是TCP协议 - 协议号默认为0
SOCKET socklisten = socket(AF_INET, SOCK_STREAM, 0);
if(INVALID_SOCKET == sockclient){
printf("socket err\n");
WSACleanup();
return 1;
}
3. 绑定地址信息bind
bind(套接字, (const sockaddr*)&addr, sizeof(addr))
addr
为sockaddr_in
类型的变量,存储着我们要绑定的地址信息。
sockaddr_in addr;
addr.sin_family = AF_INET; // IPv4
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //ip地址为本机地址
addr.sin_port = htons(8080);
if(SOCKET_ERROR == bind(m_socklisten, (const sockaddr*)&addr, sizeof(addr))){
printf("bind err\n");
closesocket(socklisten);
WSACleanup();
return 1;
}
inet_addr()
:将IPv4地址的字符串转化为IN_ADDR结构的正确地址
htons()
:主机字节顺序转化为网络字节顺序
4. 监听listen
listen(套接字, 挂起连接队列的最大长度)
if(SOCKET_ERROR == listen(socklisten, 100)){
closesocket(socklisten);
WSACleanup();
return 1;
}
5. 接收accept
接收套接字 accept(监听套接字, (sockaddr*)&addrclient, &nSize)
addrclient
:存储接收到的客户端的地址信息
sockaddr_in addrclient;
int nSize = sizeof(addrclient);
SOCKET sockWaiter = accept(m_socklisten, (sockaddr*)&addrclient, &nSize); // accept用来创建接收数据的套接字
if(INVALID_SOCKET == sockWaiter){
printf("与客户端数据传送的套接字建立失败,错误代码为:%d\n", WSAGetLastError());
return 1;
}
printf("client ip:%s\nclient port:%d\n", inet_ntoa(addrclient.sin_addr), addrclient.sin_port);
inet_ntoa()
: 将网络地址转换为IPv4标准点十进制格式
6. 接收数据
接收到的数据大小 recv(接收套接字, buf, sizeof(buf), 0)
考虑到TCP协议是面向字节流的,所以进程在接收缓冲区读取数据时会出现粘包问题
所以,我们采用规定传输格式的方法(接收发送双方共同遵守):
- 先发送包大小:告知服务器要接收的包大小,好让服务器动态分配出相应的空间
- 再发送包数据:服务器按照字节顺序进行接收
//接收包大小
int nPackSize;
int nRecvNum = recv(sockWaiter, (char*)&nPackSize, sizeof(int), 0);
if(nRecvNum <= 0){
closesocket(sockWaiter);
}
//接收包数据(申请空间)
char *pszbuf = new char[nPackSize];
int offset = 0;
while(nPackSize){
int nRecvNum = recv(sockWaiter, pszbuf+offset, nPackSize, 0); // 由于每次接收的数据大小不一定为整个数据包的大小,所以需要设置偏移量
offset += nRecvNum;
nPackSize -= nRecvNum;
}
7. 发送数据
send(接收套接字, buf, sizeof(buf), 0)
char szbuf[1024] = {0};
int nlen = sizeof(szbuf);
printf("Send:");
cin.getline(szbuf, nlen);
// 发送包大小
send(sockWaiter, (char*)&nlen, sizeof(nlen), 0);
// 发送包数据
send(sockWaiter, szbuf, nlen, 0);
8. 关闭套接字
closesocket(sockWaiter);
closesocket(socklisten);
9. 终止Socket库的使用
WSACleanup();
客户端
1. 加载Socket库
WSAStartup()
: 通过进程启动 Winsock DLL 的使用
WORD wVersionRequested;
WSADATA wsaData;
int err;
//版本2.2(lowbyte,highbyte)
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
//找不到 winsock.dll
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
//找不到2.2版本的Winsock.dll
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
else
printf("The Winsock 2.2 dll was found okay\n");
启动Socket库的固定模板,直接使用即可。
2. 创建socket
套接字 socket(协议族,套接字类型。协议号)
- 这里使用IPv4协议族,所以选择
AF_INET
- 套接字类型选择
SOCK_STREAM
,对应的是TCP协议 - 协议号默认为0
SOCKET sockclient = socket(AF_INET, SOCK_STREAM, 0);
if(INVALID_SOCKET == sockclient){
printf("socket err\n");
WSACleanup();
return 1;
}
3. 连接connect
客户端不需要绑定地址和端口,绑定由操作系统来完成
connect(客户端套接字, (const sockaddr*)&addr, sizeof(addr))
addr
:存放着客户端要连接的服务器的地址信息
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //客户端要连接到的服务器ip地址
addr.sin_port = htons(8080);
if(SOCKET_ERROR == connect(sockclient, (const sockaddr*)&addr, sizeof(addr))){
printf("connect error\n");
closesocket(sockclient);
WSACleanup();
return 1;
}
4. 接收数据
接收到的数据大小 recv(客户端套接字, buf, sizeof(buf), 0)
考虑到TCP协议是面向字节流的,所以进程在接收缓冲区读取数据时会出现粘包问题
所以,我们采用规定传输格式的方法(接收发送双方共同遵守):
- 先发送包大小:告知服务器要接收的包大小,好让服务器动态分配出相应的空间
- 再发送包数据:服务器按照字节顺序进行接收
//接收包大小
int nPackSize;
int nRecvNum = recv(sockclient, (char*)&nPackSize, sizeof(int), 0);
if(nRecvNum <= 0){
closesocket(sockclient);
}
//接收包数据(申请空间)
char *pszbuf = new char[nPackSize];
int offset = 0;
while(nPackSize){
int nRecvNum = recv(sockclient, pszbuf+offset, nPackSize, 0); // 由于每次接收的数据大小不一定为整个数据包的大小,所以需要设置偏移量
offset += nRecvNum;
nPackSize -= nRecvNum;
}
5. 发送数据
send(客户端套接字, buf, sizeof(buf), 0)
char szbuf[1024] = {0};
int nlen = sizeof(szbuf);
printf("Send:");
cin.getline(szbuf, nlen);
// 发送包大小
send(sockclient, (char*)&nlen, sizeof(nlen), 0);
// 发送包数据
send(sockclient, szbuf, nlen, 0);
6. 关闭套接字
closesocket(sockclient);
7. 终止Socket库的使用
WSACleanup();