1.windows10环境下进行socket编程
通过socket编程的代码,分析用户空间、内核空间和硬件在网络编程中的特点,引入 RDMA编程。
2.代码
2.1 服务端
// #include "pch.h"
#include<iostream>
#include<winsock.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
void initialization();
int main()
{
//定义长度变量
int send_len = 0;
int recv_len = 0;
int len = 0;
//定义发送缓冲区和接受缓冲区
char send_buf[100];
char recv_buf[100];
//定义服务端套接字,接受请求套接字
SOCKET s_server;
SOCKET s_accept;
//服务端地址客户端地址
SOCKADDR_IN server_addr;
SOCKADDR_IN accept_addr;
initialization();
//填充服务端信息
server_addr.sin_family = AF_INET;
//server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
//server_addr.sin_port = htons(5010);
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(1234);
//创建套接字
s_server = socket(AF_INET, SOCK_STREAM, 0);
if (bind(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "套接字绑定失败!" << endl;
WSACleanup();
}
else {
cout << "套接字绑定成功!" << endl;
}
//设置套接字为监听状态
if (listen(s_server, SOMAXCONN) < 0) {
cout << "设置监听状态失败!" << endl;
WSACleanup();
}
else {
cout << "设置监听状态成功!" << endl;
}
cout << "服务端正在监听连接,请稍候...." << endl;
//接受连接请求
len = sizeof(SOCKADDR);
s_accept = accept(s_server, (SOCKADDR *)&accept_addr, &len);
if (s_accept == SOCKET_ERROR) {
cout << "连接失败!" << endl;
WSACleanup();
return 0;
}
cout << "连接建立,准备接受数据" << endl;
//接收数据
while (1) {
recv_len = recv(s_accept, recv_buf, 100, 0);
if (recv_len < 0) {
cout << "接受失败!" << endl;
break;
}
else {
cout << "客户端信息:" << recv_buf << endl;
}
cout << "请输入回复信息:";
cin >> send_buf;
send_len = send(s_accept, send_buf, 100, 0);
if (send_len < 0) {
cout << "发送失败!" << endl;
break;
}
}
//关闭套接字
closesocket(s_server);
closesocket(s_accept);
//释放DLL资源
WSACleanup();
return 0;
}
void initialization()
{
//初始化套接字库
WORD w_req = MAKEWORD(2, 2);//版本号
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0) {
cout << "初始化套接字库失败!" << endl;
}
else {
cout << "初始化套接字库成功!" << endl;
}
//检测版本号
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
cout << "套接字库版本号不符!" << endl;
WSACleanup();
}
else {
cout << "套接字库版本正确!" << endl;
}
}
//填充服务端地址信息
2.2 客户端
// #include "pch.h"
#include<iostream>
#include<winsock.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
void initialization();
int main()
{
//定义长度变量
int send_len = 0;
int recv_len = 0;
//定义发送缓冲区和接受缓冲区
char send_buf[100];
char recv_buf[100];
//定义客户端套接字,接受请求套接字
SOCKET s_client;
//客户端地址客户端地址
SOCKADDR_IN server_addr;
initialization();
//填充客户端信息
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(1234);
//创建套接字
s_client = socket(AF_INET, SOCK_STREAM, 0);
if (connect(s_client, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "服务器连接失败!" << endl;
WSACleanup();
}
else {
cout << "服务器连接成功!" << endl;
}
//发送数据
while (1) {
cout << "请输入发送信息:";
cin >> send_buf;
send_len = send(s_client, send_buf, 100, 0);
if (send_len < 0) {
cout << "发送失败!" << endl;
break;
}
recv_len = recv(s_server, recv_buf, 100, 0);
if (recv_len < 0) {
cout << "接受失败!" << endl;
break;
}
else {
cout << "服务端信息:" << recv_buf << endl;
}
}
//关闭套接字
closesocket(s_client);
//释放DLL资源
WSACleanup();
return 0;
}
void initialization() {
//初始化套接字库
WORD w_req = MAKEWORD(2, 2);//版本号
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0) {
cout << "初始化套接字库失败!" << endl;
}
else {
cout << "初始化套接字库成功!" << endl;
}
//检测版本号
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
cout << "套接字库版本号不符!" << endl;
WSACleanup();
}
else {
cout << "套接字库版本正确!" << endl;
}
}
//填充客户端地址信息
2.3 代码说明
不管是客户端还是接收端,基本的过程是打开网络编程,创建socket,建立连接,发送(客户端)/接受(服务端)数据,关闭socket,清理资源。
3. 整个流程
整个过程中,用户空间app只需要负责调用内核空间的接口,不需要关心内核层、网卡等其他信息。
分析下整个底层逻辑:
上图,两台服务器上的应用之间传输数据,过程是这样的:
首先要把数据从应用缓存拷贝到Kernel中的TCP协议栈缓存;然后再拷贝到驱动层;最后拷贝到网卡缓存。
数据发送方需要讲数据从用户空间 Buffer 复制到内核空间的 Socket Buffer。
数据发送方要在内核空间中添加数据包头,进行数据封装
数据从内核空间的 Socket Buffer 复制到 NIC Buffer 进行网络传输
数据接受方接收到从远程机器发送的数据包后,要将数据包从 NIC Buffer 中复制到内核空间的 Socket Buffer
经过一系列的多层网络协议进行数据包的解析工作,解析后的数据从内核空间的 Socket Buffer 被复制到用户空间 Buffer
3.1 特点一
需要在用户空间和内核层中来回复制数据
发送时,用户空间数据拷贝到内核空间;接收时,内核空间数据拷贝到用户空间。
例如:上述客户端中的代码send()接口中,功能就是将数据发送出去,涉及到用户空间到内核层的数据拷贝,底层的实现就是memcpy();
3.2 特点二
cpu参与大量数据的封装和解析,影响cpu资源的调度。
根据内核中的各种协议,cpu会对socket中的数据进行重新包装,方便数据能正确到达客户端,如封装(发送)和解析(接收)报文头,校验信息等,cpu不能处理其他的事情。
3.3 特点3
内核频繁操作,会影响系统调用和上下文切换的开销。