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++是如何来接收请求的呢,还是得自己造轮子。先给出一张时序图:

python 服务器_NeurDICOM Python 服务器和c#服务器_django

这个轮子造起来就显得比较费时费力了,需要对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社区发展也很蓬勃,对服务器初级开发人员来说非常友好。