1、写在开始
这一年以来,在服务器后台做了大量工作。到11月中旬,我们用python重写的c++服务器终于正式发版了,也算是对这一年的交代。从以后的工作规划来看,几无接触到c++/python服务器后台的可能了。我们的代码主要采用的框架是c++/boost vs python/Django/REST。c++的代码应该是10年前的,能在那个时候使用boost库来写生产环境的服务器代码,实在令人敬佩。不久前无意中了解到,boost网络编程库还是没有被纳入c++标准库,这不得不说是一种遗憾。而反观Django社区,则出现了蓬勃发展的态势,知名网站如国外的Instagram、Mozilla,国内的诸如搜狐、豆瓣、头条等。Django下REST Framework是一个用于构建Web API的功能强大且灵活的工具包,在Django下使用REST能极大的方便web开发。
2、轮子
开发服务器后台有许多框架层的工作,诸如:日志模块、json序列化模块、国际化模块等等。以json序列化模块为例,c++就不得不自己造轮子。
2.1 c++的json序列化
在我们的c++代码中,使用一个ruby脚本来做json数据的序列化和反序列化。需要遵循项目自定义的一些规则及步骤:
2.1.1 事先定义好数据及格式
struct MasterSrvUserInfoAndPhone {
// @json_serialization_object;
std::string id; // @json_serialization_field ;
std::string user_name; // @json_serialization_field ;
std::string email; // @json_serialization_field ;
std::string phone_number; // @json_serialization_field ;
int device_number; // @json_serialization_field ;
time_t last_invite_time; // @json_serialization_field ;
};
bool Serialize(const tmms_json::MasterSrvUserInfoAndPhone& obj_val, Json::Value& json_val);
bool Deserialize(const Json::Value& json_val, tmms_json::MasterSrvUserInfoAndPhone& obj_val);
2.1.2 调用ruby脚本生成具体序列化及反序列化的代码
bool Serialize(const MasterSrvUserInfoAndPhone& obj_val, Json::Value& json_val) {
std::vector<bool> ret_vector;
ret_vector.push_back(Serialize(obj_val.id, "id", json_val));
ret_vector.push_back(Serialize(obj_val.user_name, "user_name", json_val));
ret_vector.push_back(Serialize(obj_val.email, "email", json_val));
ret_vector.push_back(Serialize(obj_val.phone_number, "phone_number", json_val));
ret_vector.push_back(Serialize(obj_val.device_number, "device_number", json_val));
ret_vector.push_back(Serialize(obj_val.last_invite_time, "last_invite_time", json_val));
return tmms_json::Passed("tmms_json::Serialize", typeid(obj_val).name(), ret_vector);
}
bool Deserialize(const Json::Value& json_val, MasterSrvUserInfoAndPhone& obj_val) {
std::vector<bool> ret_vector;
ret_vector.push_back(Deserialize(json_val, "id", obj_val.id));
ret_vector.push_back(Deserialize(json_val, "user_name", obj_val.user_name));
ret_vector.push_back(Deserialize(json_val, "email", obj_val.email));
ret_vector.push_back(Deserialize(json_val, "phone_number", obj_val.phone_number));
ret_vector.push_back(Deserialize(json_val, "device_number", obj_val.device_number));
ret_vector.push_back(Deserialize(json_val, "last_invite_time", obj_val.last_invite_time));
return tmms_json::Passed("tmms_json::Deserialize", typeid(obj_val).name(), ret_vector);
}
这样,json数据序列化及反序列化就做好了,使用的时候如下:
std::vector<tmms_json::MasterSrvUserInfoAndPhone> users_vec;
tmms_json::MasterSrvUserInfoAndPhone user;
……
2.2 Django的json序列化
Django下采用REST Framework的话,json序列化就变得极其简单起来。
class DeviceLocationSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ['Latitude', 'Longitude', 'LocationUpdateTime']']
按照REST文档,model对应于数据库表名,fields 表明了需要进行序列化的字段,使用的时候如下:
……
serialized_device = DeviceLocationSerializer(device)
……
这样就得到了经过序列化之后的数据,通常是list类型的数据。
2.3 c++的请求接收
c++是如何来接收请求的呢,还是得自己造轮子。先给出一张时序图:
这个轮子造起来就显得比较费时费力了,需要对boost库及常用Windows库函数比较熟悉才行。重要过程在时序图中已经作了说明。
2.4 Django的请求接收
根据文档,Django框架层已经做了请求接收,只需直接处理请求即可。具体来说,在url.py文件中定义正则表达式,请求就会被转发到相应接口。
urlpatterns = [
url(r'^login/$', LoginView.as_view(), name='user-login'),
url(r'^logout/$', LogoutView.as_view(), name='user-logout'),
……
]
相应的类方法LoginView就可以处理类似http://****/login这样的请求。
3、业务处理
列举几个接口来比较两种语言下web后台是如何处理请求的。
3.1 一个删除接口
3.1.1 c++的处理
int my_universal_mdm::DeleteDevices(void *ctx, std::string& is, std::string& os)
{
std::vector<my_dal::Device> in_data;
my_json::DeleteDevicesResponse out_data;
try
{
//转换为json字符串
FROM_JSON_STRING(is, in_data);
//遍历数据
BOOST_FOREACH(my_dal::Device& device, in_data)
{
//查询数据库得到指定device
if (CODE_SUCCESS != device_manager.GetDevice(ctx, device.id, device))
{
RETURN(out_data, os, CODE_ERR_ACCESS_DB_FAIL);
}
//删除指定device
if (CODE_SUCCESS != device_manager.DeleteDevice(ctx, device))
{
RETURN(out_data, os, CODE_ERR_ACCESS_DB_FAIL);
}
}
TO_JSON_STRING(out_data, os);
}
catch(...)
{
RETURN(out_data, os, CODE_ERR_FAIL);
}
return out_data.error_code;
}
3.1.2 python的处理
class DeleteDevices(APIView):
def post(self, request):
device_ids = self.request.data['data']
#直接获取数据库对象删除数据
Device.objects.filter(Id__in=device_ids).delete()
……
return general_response(status.HTTP_200_OK, ErrorCode.SUCCESS)
c++代码还有一层一层的封装,固然和架构有些关系,这当然不能说明全部问题;不可否认,python代码比c++代码简洁了很多数量级,区区几行代码就搞定了c++代码上百行代码才能完成的功能。
3.2 一个搜索接口
3.2.1 C++的实现
首先给出主体代码,搜索符合条件的设备,序列化之后返回结果。
//返回搜索到符合条件的设备
int DeviceManager::SearchDevices(void *ctx,my_dal::SearchDevicesCondition& search_condition,
int& total_count,
std::vector<my_json::MasterSrvDeviceInfo>& devices)
{
……
std::vector<my_dal::Device> db_devices;
if (!device_service->SearchDevices(search_condition, total_count, db_devices))
{
return ERR_ACCESS_DB_FAIL;
}
BOOST_FOREACH(my_dal::Device& db_device, db_devices)
{
my_json::MasterSrvDeviceInfo device;
device.id = to_utf8(db_device.id);
device.device_name = to_utf8(db_device.name);
device.phone_number = to_utf8(db_device.phone_number);
device.email = to_utf8(db_device.user.email);
device.description = to_utf8(db_device.description);
devices.push_back(device);
}
return MDM_SUCCESS;
}
搜索的详细过程,
//序列化搜索到的设备
bool DeviceRepository::SearchDevices(my_dal::SearchDevicesCondition& search_condition,
int& total_count,
std::vector<my_dal::Device>& devices)
{
try
{
total_count = 0;
std::wostringstream sql = GetSearchDevicesSqlString(search_condition);
CADORecordSet recordset;
recordset.Open(connection_, sql.str(), adOpenForwardOnly, adLockReadOnly);
CADORecordMemo record;
devices.clear();
while (!recordset.IsEOF())
{
recordset.GetCurrent(record);
my_dal::Device device;
MappingDevice::Deserialize(record, device, L"Device_Description");
MappingDevice::Deserialize(record, device.agent, L"Device_Name");
MappingDevice::Deserialize(record, device.user, L"Device_Email");
MappingDevice::Deserialize(record, device, L"Device_PhoneNumber");
if (IsValidAgent(search_condition, device.agent))
{
devices.push_back(device);
}
recordset.MoveNext();
}
……
return true;
}
catch (const sql_exception& ex) {
MY_ERROR(L"DeviceRepository::SearchDevices -> illegal parameter: " << *boost::get_error_info<err_str>(ex));
}
catch (exception& e) {
MY_INFO(L"DeviceRepository::SearchDevices -> get exception:"<<e.what());
}
return false;
}
搜索主语句,主分支进到if语句,这里搜索的结果是返回符合查询条件的结果,并将搜索结果分页,返回指定页码的数据。这里的分页技巧有赖于SQL实现,如果对SQL高级语句不太熟悉的话,其实理解这段搜索语句还是存在一定困难。
//搜索主语句
std::wostringstream DeviceRepository::GetSearchDevicesSqlString(my_dal::SearchDevicesCondition& search_condition)
{
size_t start_pos = search_condition.paging_info.page_index * search_condition.paging_info.page_size;
size_t end_pos = start_pos + search_condition.paging_info.page_size;
wstring order_str = L"ORDER by Device_DeviceName";
std::wostringstream sql;
if (!search_condition.paging_info.Empty())
{
sql << L"SELECT * from ("<<endl
<< L" SELECT TOP "<<end_pos<< L" *, "<<endl
<< L" ROW_NUMBER() OVER( "<<order_str<<" ) as 'RowNo' "<<endl
<< L" FROM View_Device "<<endl
<< GetSearchDevicesWhereString(search_condition).str() << endl
<<L") as A"<<endl
<<L"where A.RowNo BETWEEN "<<start_pos + 1<<" AND "<<end_pos<<endl
<<order_str<<endl;
}
else
{
sql << L"SELECT * "<<endl
<< L"FROM View_Device "<<endl
<< GetSearchDevicesWhereString(search_condition).str()<<endl
<<order_str<<endl;
}
return sql;
}
搜索场景分为简单搜索和高级搜索。简单搜索只支持名字及电话号码,高级搜索支持各个维度的搜索。给出了代码示例,其实这部分代码有600多行。
//拼接搜索条件
std::wostringstream DeviceRepository::GetSearchDevicesWhereString(my_dal::SearchDevicesCondition& search_condition)
{
std::wostringstream where_str;
where_str << L"WHERE 1=1 "<<endl;
if (!search_condition.device_name.empty())
{
where_str<< L" AND Device_DeviceName LIKE N'%"<< SQLParameter::escapeSQL(search_condition.device_name, true) <<"%' "<<endl;
}
if (!search_condition.phone_number.empty())
{
where_str<< L" AND Device_DevicePhoneNumber LIKE '%"<< _GUARD(search_condition.phone_number) <<"%' "<<endl;
}
if (!search_condition.description.empty())
{
where_str<< L" AND Device_DeviceDescription LIKE '%"<< SQLParameter::escapeSQL(search_condition.description, true) <<"%' "<<endl;
}
if (!search_condition.user_name.empty())
{
where_str<< L" AND Device_UserLDAPAccount LIKE N'%"<< SQLParameter::escapeSQL(search_condition.user_name, true) <<"%' "<<endl;
}
if (!search_condition.device_name_or_phone_number.empty())
{
std::wstring local_device_name_or_phone_number = SQLParameter::escapeSQL(search_condition.device_name_or_phone_number, true);
where_str<< L" AND (Device_DevicePhoneNumber LIKE '%"<< local_device_name_or_phone_number <<"%' "<<endl
<< L" OR Device_DeviceName LIKE N'%"<< local_device_name_or_phone_number <<"%') "<<endl;
}
……
return where_str;
}
这样,就通过c++实现了搜索接口,这个接口整合了简单及高级搜索。
3.2.2 python实现
笔者将简单搜索和高级搜索拆分成了两个接口。这样代码的可读性会强一些。
#分页的实现类,有赖于Django框架的Paginator
class CustomSearchDevicePagination(pagination.PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'results': data
})
#简单搜索的实现
class SearchNameOrPhone(generics.ListAPIView):
serializer_class = PagedDeviceSerializer
def get_queryset(self):
page_index = self.request.query_params.get('page', None)
page_size = self.request.query_params.get('page_size', None)
name_or_phone = self.request.query_params.get('device_name_or_phone_number', None)
search_result = Device.objects.filter(Q(Name=name_or_phone) | Q(PhoneNumber=name_or_phone)).order_by('Name')
out_put_devices = []
for db_device in search_result:
out_put_device = {
'description': db_device.Description,
'device_name': db_device.Name,
'email': User.objects.get(Id=db_device.UserId).Email,
……
}
out_put_devices.append(out_put_device)
return out_put_devices
可以看到,虽然只是简单搜索的实现,python代码也简洁了很多。这里,email结果的返回直接查询了数据库。如果设置了主外键关系,在rest Framework下还有更简单的实现。
#高级搜索的实现
class AdvancedSearch(generics.ListAPIView):
serializer_class = PagedDeviceSerializer
def get_queryset(self):
condition = self.request.query_params.get('data', None)
page_index = condition.get('paging_info').get('page_index')
page_size = condition.get('paging_info').get('page_size')
sql = ''
#拼接查询条件
if 'device_name' in condition:
sql += "AND Name LIKE " + "'" + condition.get('device_name') + "'"
if 'phone_number' in condition:
sql += " AND PhoneNumber LIKE " + "'" + condition.get('phone_number') + "'"
if 'description' in condition:
sql += " AND Description LIKE " + "'" + condition.get('description') + "'"
……
devices_set = DeviceManager().advanced_search(sql)
out_put_devices = []
for db_device in devices_set:
out_put_device = {
'description': db_device['Device_DeviceDescription'],
'device_name': db_device['Device_DeviceName'],
'phone_number': db_device['Device_DevicePhoneNumber'],
}
out_put_devices.append(out_put_device)
return out_put_devices
Django不支持数据库view,这里强行“构造”了view:
def advanced_search(self, condition):
sql = view_device_sql()
sql += " WHERE 1=1 " + condition
with connection.cursor() as cursor:
cursor.execute(sql)
rows = cursor.fetchall()
col_names = [desc[0] for desc in cursor.description]
result = []
# dump raw data to dict
for row in rows:
objDict = {}
for index, value in enumerate(row):
objDict[col_names[index]] = value
result.append(objDict)
return result
def view_device_sql():
sql = "SELECT device_device.Id AS Device_DeviceId, \
device_device.Name AS Device_DeviceName,\
device_device.PhoneNumber AS Device_DevicePhoneNumber, \
device_device.Description AS Device_DeviceDescription,\
……
FROM device_deviceandroidextension RIGHT OUTER JOIN \
device_device LEFT OUTER JOIN \
device_user ON device_device.UserId = device_user.Id LEFT OUTER JOIN \
……"
return sql
4、总结
用c++来做服务器后台确实有诸多不便,可移植性较差。且为了实现灵活部署,原有的代码还做了好多模块化开发,安装路径下有许多.dll文件,追踪这些.dll文件间传递的数据对没有Windows开发的程序员来说也不容易。python先天就具有跨平台的特性,语言本身还有许多令人振奋的特性,诸如list切片等,Django社区发展也很蓬勃,对服务器初级开发人员来说非常友好。