python 服务器
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_devicesDjango不支持数据库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 resultdef 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 sql4、总结
用c++来做服务器后台确实有诸多不便,可移植性较差。且为了实现灵活部署,原有的代码还做了好多模块化开发,安装路径下有许多.dll文件,追踪这些.dll文件间传递的数据对没有Windows开发的程序员来说也不容易。python先天就具有跨平台的特性,语言本身还有许多令人振奋的特性,诸如list切片等,Django社区发展也很蓬勃,对服务器初级开发人员来说非常友好。
Windows 10 搭建Python开发环境(PyCharm )pycharm是python的集成开发环境
FTP pyqt5 python 服务器 python ftp客户端
Python 服务器UDP Python 服务器和c#服务器
python代码用服务器IP部署到服务器上 python架设服务器
Windows10,python3.8安装scrapypython3.8安装
windows10 配置python windowspycharm配置python
python 判断 Thread 是否已启动 python判断linux进程状态