大家知道一个数据库都会有服务端和客户端。他们之间的通信就需要socket编程来实现,所以socket编程是完成我们EmeraldDB的第一步。
先给出一个socket编程的例子:(该示例网上找的,版权归原作者所有):
服务端:
#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
//初始化WSA
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
return 0;
}
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//开始监听
if(listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("等待连接...\n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
//接收数据
int ret = recv(sClient, revData, 255, 0);
if(ret > 0)
{
revData[ret] = 0x00;
printf(revData);
}
//发送数据
char * sendData = "你好,TCP客户端!\n";
send(sClient, sendData, strlen(sendData), 0);
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}
客户端:
#include "stdafx.h"
#include <WINSOCK2.H>
#include <STDIO.H>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return 0;
}
char * sendData = "你好,TCP服务端,我是客户端!\n";
send(sclient, sendData, strlen(sendData), 0);
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if(ret > 0)
{
recData[ret] = 0x00;
printf(recData);
}
closesocket(sclient);
WSACleanup();
return 0;
}
这是windows下面的C++代码,到linux下可能略有不同。我们先到windows vitual studio下进行测试:
新建两个项目,分别是服务端和客户端,将代码分别复制上去
右键项目,将服务端设置为启动项目,启动服务端,会跳出如下界面,表示服务端已启动
右键客户端项目-->调试-->单步执行,按F10执行到最后,可以看到服务端和客户端之间如下消息的传递
程序比较简单,但是我们需要弄懂背后的原理。
首先看服务端,它首先创建了一个套接字,用于客户端的连接:
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
socket函数有三个参数,都是socket默认定义的宏,第一个参数AF_INET基本是固定的值,意思是使用INTERNET address family,就是我们平常使用的标准的IPV4格式的IP地址,比如192.168.0.40.第二个参数是选择socket类型,常用的就两种参数,Socket_stream对应TCP socket_dgram对应UDP ,本例中,自然指的是TCP了。第三个也是指TCP,如果你使用前两个参数中的一个,也可以把它置为0,只有第二个参数为原始socket才需要指出具体类型。
然后是定义了一个sockt地址:socketaddr_in sin。这也基本上是标准写法了,它是一个internet下的结构体,定义如下:
struct sockaddr_in
{
short int sin_family; //一般就是AF_INET
unsigned short int sin_port; //指定端口
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
结构体最后两个成员我们先不用管。
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
有了这个结构体,上面的代码就好理解了。INADDR_ANY指的是任何ip都可以连入。htons则是将本机的字符流转换成网络字符流,这里涉及到一些大小端的问题,扯开来就比较长了。有兴趣可以自己百度下。
<pre name="code" class="cpp">bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR
接着开始绑定socket的端口和ip。bind函数也是标准的socket函数,如果发起失败,则返回socket_error值。
listen(slisten, 5)
接着监听端口,此时服务端正式工作
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("等待连接...\n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
//接收数据
int ret = recv(sClient, revData, 255, 0);
if(ret > 0)
{
revData[ret] = 0x00;
printf(revData);
}
//发送数据
char * sendData = "你好,TCP客户端!\n";
send(sClient, sendData, strlen(sendData), 0);
closesocket(sClient);
}
在这个死循环里,服务端不断的监听是否有客户端发来的链接请求,accept和recv都是标准的socket函数,当accept接收到连接请求时,就建立连接,recv则接受客户端发来的请求,把请求内容放到revData数组里面。本例中客户端发来的就是一串字符串,服务端把它打印到屏幕上。最后服务端调用closesocket函数关闭连接。
服务端内容结束,我们看客户端的代码:
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
开始的代码都差不多,先创建socket,再给出连接参数
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
连接服务端,这是标准的socket函数。最后一个参数给出了serAddr的长度
send(sclient, sendData, strlen(sendData), 0);
发送sendData信息。
其他的基本上和服务端一样。
这就是socket编程的一个例子,可以看到它的标准函数其实并不多,掌握上述的几个函数基本上就掌握了socket编程了,所以完全不用担心其难度。在edb中,我们用到的也就这几个函数,但因为是在Linux下,极少数函数名和参数会略有不同(其实linux才是标准的,windows喜欢自搞一家),但不影响我们使用。
下一节我们会用之前搭好的环境,写一个简单的socket封装,并用封装的函数建立scoket连接