Libevent有一些整个进程共享的全局设置。这些设置会影响到整个的库。因此必须在调用Libevent其他函数之前进行设置,否则,LIbevent就会陷入不一致的状态。

一:Libevent中的日志信息

Libevent可以记录内部的error和warning信息,而且如果在编译时设置的话,它还可以记录debug消息。默认情况下,这些信息都会写到stderr中。可以通过提供自己的日志函数来改变该行为。

#define  EVENT_LOG_DEBUG  0

#define  EVENT_LOG_MSG   1

#define  EVENT_LOG_WARN  2

#define  EVENT_LOG_ERR   3

/* Deprecated; see note at theend of this section */

#define _EVENT_LOG_DEBUG  EVENT_LOG_DEBUG

#define _EVENT_LOG_MSG   EVENT_LOG_MSG

#define _EVENT_LOG_WARN  EVENT_LOG_WARN

#define _EVENT_LOG_ERR   EVENT_LOG_ERR

typedef void(*event_log_cb)(int severity, const char *msg);

void event_set_log_callback(event_log_cb cb);

如果想要改变Libevent默认的日志行为,需要编写自己的日志函数,该日志函数要符合event_log_cb原型,并且将该函数作为参数传入到event_set_log_callback()。当Libevent需要记录日志的时候,会将日志信息传入到你提供的函数中。如果想恢复到Libevent的默认日志行为,则只需要以NULL为参数调用event_set_log_callback()即可。

实例:

#include <event2/event.h>

#include <stdio.h>

static void discard_cb(int severity, const char *msg)

{

/* This callback does nothing. */

}

static FILE *logfile = NULL;

static void write_to_file_cb(int severity, const char *msg)

{

const char *s;

if (!logfile)

return;

switch (severity) {

case _EVENT_LOG_DEBUG: s ="debug"; break;

case _EVENT_LOG_MSG:   s = "msg";   break;

case _EVENT_LOG_WARN:  s = "warn";  break;

case _EVENT_LOG_ERR:   s = "error"; break;

default:               s = "?";     break; /* never reached */

}

fprintf(logfile, "[%s] %s\n", s,msg);

}

/* Turn off all logging from Libevent. */

void suppress_logging(void)

{

event_set_log_callback(discard_cb);

}

/* Redirect all Libevent log messages to the C stdio file 'f'. */

void set_logfile(FILE *f)

{

logfile = f;

event_set_log_callback(write_to_file_cb);

}

注意:在用户提供的event_log_cb回调函数中调用Libevent 函数是不安全的。比如,在自己写的日志回调函数中,使用bufferevents将warning信息发送到网络上,那么很可能会遇见奇怪且难以调试的bug。未来Libevent版本中,针对某些函数有可能会去除该限制。

通常,debug级别的日志是禁用的,并且不会发送到日志回调函数中。如果在构建Libevent时支持的话,可以手动启用。

#define EVENT_DBG_NONE  0

#define EVENT_DBG_ALL      0xffffffffu

void  event_enable_debug_logging(ev_uint32_t which);

在多数环境下,debug信息是冗长且无用的。使用EVENT_DBG_NONE参数调用event_enable_debug_logging() 可以得到默认行为;使用EVENT_DBG_ALL调用该函数,可以打开所有支持的debug日志。未来版本中可能会支持更加精细的选项。这些函数在<event2/event.h>声明。

兼容性:在Libevent2.0.19-stable版本之前,EVENT_LOG_*这样的宏名称是以下划线开头的,比如_EVENT_LOG_DEBUG,_EVENT_LOG_MSG, _EVENT_LOG_WARN和_EVENT_LOG_ERR。这些古老的名字已经废弃,而且只能在Libevent 2.0.18-stable之前的版本中使用。未来版本中,他们可能会被移除。

二:处理致命错误

当Libevent遇到一个无法恢复的内部错误的时候,它的默认行为是调用exit()或abort()退出当前运行的进程。这些错误意味着程序存在bug,要么在程序的代码中,要么在Libevent本身。

如果希望应用进程更加优雅的处理致命错误的话,可以改变Libevent的这种默认行为。通过提供另一个函数以供Libevent调用,替代退出这种默认行为。

typedef  void (*event_fatal_cb)(int 
err);

void  event_set_fatal_callback(event_fatal_cb  cb);

首先定义一个新的函数,该函数在Libevent遇到致命错误的时候会被调用,然后将新定义的函数作为参数传给event_set_fatal_callback()函数。之后,当Libevent遇到致命错误时,将会调用你提供的新函数。

注意:替代函数不要将控制返回到Libevent,否则会遇到非定义的行为,而且Libevent会退出。所以一旦你的函数被调用,就不要再次调用任何其他的Libevent函数。

三:内存管理

默认情况下,Libevent使用C库提供的内存管理函数从堆上分配内存。你可以提供自己的内存管理函数以供Libevent使用,从而替代malloc, realloc和free。如果你有更有效的内存分配器,或者有可以检测内存泄露的内存分配器,你就可以这样做。

void  event_set_mem_functions(void *(*malloc_fn)(size_t sz),

void*(*realloc_fn)(void *ptr, size_t sz),

void(*free_fn)(void *ptr));

下面是一个简单替换Libevent分配函数的例子。在实际环境中,需要加上锁机制,以防止在多线程环境中遇到错误。

#include <event2/event.h>

#include <sys/types.h>

#include <stdlib.h>

/* This union's purpose is tobe as big as the largest of all the

* types it contains. */

union alignment {

size_t sz;

void *ptr;

double dbl;

};

/* We need to make sure that  everything we return is on the right

alignment to hold anything, including adouble. */

#define ALIGNMENT sizeof(union  alignment)

/* We need to do this  cast-to-char*  trick on our pointers to adjust

them; doing arithmetic on a void* is notstandard. */

#define OUTPTR(ptr)  (((char*)ptr)+ALIGNMENT)

#define INPTR(ptr)      (((char*)ptr)-ALIGNMENT)

static  size_t  total_allocated = 0;

static  void*  replacement_malloc(size_t  sz)

{

void *chunk = malloc(sz + ALIGNMENT);

if (!chunk) return chunk;

total_allocated += sz;

*(size_t*)chunk = sz;

return OUTPTR(chunk);

}

static void*  replacement_realloc(void *ptr, size_t sz)

{

size_t old_size = 0;

if (ptr) {

ptr = INPTR(ptr);

old_size = *(size_t*)ptr;

}

ptr = realloc(ptr,  sz + ALIGNMENT);

if (!ptr)

return NULL;

*(size_t*)ptr = sz;

total_allocated = total_allocated - old_size + sz;

return OUTPTR(ptr);

}

static void replacement_free(void*ptr)

{

ptr = INPTR(ptr);

total_allocated  -= *(size_t*)ptr;

free(ptr);

}

void start_counting_bytes(void)

{

event_set_mem_functions(replacement_malloc,

replacement_realloc,

replacement_free);

}

注意:

1:替换内存管理函数,将会影响到Libevent中所有内存分配,调整大小和释放内存操作。因此,需要在调用任何其他Libevent函数之前,进行这种替换。否则的话,Libevent将会使用你提供的free函数,来释放由C库的malloc函数申请的空间。

2:你的malloc和realloc函数应该同C库返回的内存块一样,具有同样的内存地址对齐特性。

3:你的realloc函数需要正确的处理realloc(NULL,sz)(也就是将其当做malloc(sz)处理)。

4:你的realloc函数需要正确的处理realloc(ptr,0)(也就是将其当做free(ptr)处理)。

5:你的free函数无需处理free(Null)。

6:你的malloc函数,无需处理malloc(0)。

7:如果在多线程环境中使用Libevent的话,需要保证你的内存管理函数是线程安全的。

8:如果替代了Libevent内存管理函数,那么Libevent将会使用替代函数来分配内存,所以,应该使用free的替代版本来释放由Libevent返回的内存。

event_set_mem_functions()函数在<event2/event.h>声明。

Libevent可以在构建时禁用event_set_mem_functions()函数。如果禁用的话,那么使用event_set_mem_functions的代码将不会编译或链接。在Libevent2.0.2-alpha以及之后的版本中,可以通过检查宏定义EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED,来判断函数event_set_mem_functions是否被禁用。

四:线程和锁

如你所知,同一时刻,多个线程访问同一个数据是不安全的。多线程环境下,Libevent的结构体有三种工作方式:

一些结构体是单线程使用的:多线程同时使用是不安全的;

一些结构体带有可选的锁:针对这种结构体,你可以告诉Libevent,是否会需要多个线程同时访问它

一些结构体是带有强制锁:如果Libevent支持锁机制的话,那么这些结构体永远是线程安全的。

为了获得Libevent中的锁机制,必须在调用任何分配多线程共享的结构体的函数之前,告诉Libevent使用哪些锁函数。

如果使用pthreads库,或者使用原有的Windows多线程代码,那么已经有设置好的libevent预定义函数,能够正确的使用pthreads或者Windows函数。

#ifdef  WIN32

int  evthread_use_windows_threads(void);

#define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED

#endif

#ifdef _EVENT_HAVE_PTHREADS

int  evthread_use_pthreads(void);

#define EVTHREAD_USE_PTHREADS_IMPLEMENTED

#endif

以上函数在成功时返回0,失败是返回-1。

如果需要使用其他的多线程库,那么需要做一些额外的工作,你需要定义函数来实现下列机制:

锁、加锁、解锁、分配锁、销毁锁、条件变量、创建条件变量、销毁条件变量,等待条件变量、单播/广播条件变量、线程、线程ID监测。

然后,使用接口evthread_set_lock_callbacks和 evthread_set_id_callback 接口,告诉Libevent你要使用的函数:

#define EVTHREAD_WRITE  0x04

#define EVTHREAD_READ   0x08

#define EVTHREAD_TRY    0x10

#define  EVTHREAD_LOCKTYPE_RECURSIVE   1

#define  EVTHREAD_LOCKTYPE_READWRITE   2

#define  EVTHREAD_LOCK_API_VERSION    1

struct evthread_lock_callbacks{

int  lock_api_version;

unsigned  supported_locktypes;

void  *(*alloc)(unsigned locktype);

void  (*free)(void *lock, unsignedlocktype);

int  (*lock)(unsigned mode, void *lock);

int  (*unlock)(unsigned mode, void*lock);

};

int  evthread_set_lock_callbacks(const struct evthread_lock_callbacks *);

void  evthread_set_id_callback(unsigned long (*id_fn)(void));

struct  evthread_condition_callbacks {

int condition_api_version;

void *(*alloc_condition)(unsigned  condtype);

void (*free_condition)(void * cond);

int (*signal_condition)(void *cond, int  broadcast);

int (*wait_condition)(void * cond, void*  lock,

const struct timeval * timeout);

};

int  evthread_set_condition_callbacks(const  struct  evthread_condition_callbacks *);

evthread_lock_callbacks结构体描述了锁的性质。其中,lock_api_version必须置为EVTHREAD_LOCK_API_VERSION,supported_locktypes必须置为 EVTHREAD_LOCKTYPE_*常量的掩码来描述锁类型(在2.0.4-alpha版本中,EVTHREAD_LOCK_RECURSIVE是强制的,而EVTHREAD_LOCK_READWRITE是不能用的)。alloc函数必须能返回一个新的特定类型的锁对象。free函数必须能够释放特定类型的锁的所有资源。lock函数用来以特定的模式获得锁,返回0表示成功,非0表示失败。unlock函数用来解锁,返回0表示成功,非0表示失败。

锁类型包括:

0:常规的、非递归锁

EVTHREAD_LOCKTYPE_RECURSIVE:递归锁,允许同一个线程对其多次加锁,只有当前加锁的线程经过同样次数的解锁之后,其他线程才能够获得锁。

EVTHREAD_LOCKTYPE_READWRITE:读写锁,允许多个线程同时占有读模式的读写锁,但是同一时刻,只能有一个线程占有写模式的读写锁。一个写线程会阻塞所有读线程。

锁机制包括:

EVTHREAD_READ:读写锁特有,以读模式获得或释放读写锁

EVTHREAD_WRITE:读写锁特有,以写模式获得或释放读写锁

EVTHREAD_TRY:只有该锁可以立即获得时,才可以加锁。

id_fn参数是一个函数,该函数返回一个无符号长整型,该长整型用来区分调用该函数的线程。同一个线程必须返回同样的整数、同一时刻执行的不同线程必须返回不同的整数。

evthread_condition_callbacks结构体描述了条件变量的特性。lock_api_version必须置为EVTHREAD_CONDITION_API_VERSION。alloc_condition函数必须返回一个指向新的条件变量的指针。它接收0作为参数。free_condition函数释放条件变量所持有的资源。wait_condition函数有三个参数:由alloc_condition分配的条件变量、由evthread_lock_callbacks.alloc函数分配的锁,以及一个可选的超时参数。在该函数调用时,该锁必须已经加锁,该函数会释放该锁,然后等待条件变量的信号,或者超时时间到。wait_condition在出错时返回-1,返回0表示收到了条件变量的信号,返回1表示超时。在该函数返回时,该函数会重新加锁。

最后,如果signal_condition的broadcast 参数是false,该函数会唤醒一个等待条件变量的线程,如果broadcast 参数为True的话,所有等待条件变量的线程都会唤醒。只有在对与该条件变量相关的锁进行加锁之后,才能进行这些操作。

实例:参见evthread_pthread.c文件和evthread_win32.c文件

这些函数在<event2/thread.h>中声明,其中大多数在2.0.4-alpha版本中首次出现。2.0.1-alpha到2.0.3-alpha使用较老版本的锁函数。event_use_pthreads函数要求程序链接到event_pthreads库。

条件变量函数是2.0.7-rc版本新引入的,用于解决某些棘手的死锁问题。

构建libevent时,可以禁止锁支持。这时候已创建的使用上述线程相关函数的程序将不能运行。

五:锁调试的使用

为了能够对锁的使用进行调试,Libevent提供了“锁调试”的特性,它对锁调用进行了封装,从而可以捕捉到典型的锁错误,包括:解锁一个并没有加锁的锁;对一个非递归锁重新加锁。

如果发生了锁错误,Libevent将会以一个断言失败而退出。

void  evthread_enable_lock_debugging(void);

#define  evthread_enable_lock_debuging()  evthread_enable_lock_debugging()

该函数必须在任何锁创建和使用之前进行调用。安全起见,在设置线程函数之后调用。

六:事件调试的使用

在使用events时,Libevent可以检测并报告一些一般性的错误,包括:将一个未初始化的event当做已经初始化的event使用;对处于pending状态的event重新初始化。

因为跟踪哪个event被初始化需要额外的内存和CPU,所以应该仅调试程序时才使能调试模式。

void  event_enable_debug_mode(void);

该函数必须在任何event_base创建之前调用。

在debug模式下,如果程序用event_assign(不是event_new)创建了大量的events,那么有可能会将内存耗尽。这是因为Libevent没有办法知道由event_assign创建的event何时不再使用(对于event_new创建的event,当你调用event_free时,该event就会变为无效的了)。如果想要避免在调试模式下耗尽内存,可以明确地告诉Libevent,该event已经无效了:

void  event_debug_unassign(structevent *ev);

注意:如果没有使能调试模式,那么调用event_debug_unassign无效。

实例:

#include  <event2/event.h>

#include <event2/event_struct.h>

#include <stdlib.h>

void cb(evutil_socket_t fd, short what, void *ptr)

{

/* We pass 'NULL' as the callback pointer  for the heap allocated

* event, and we pass the event itself as  the callback pointer

* for the stack-allocated event. */

struct event *ev = ptr;

if (ev)

event_debug_unassign(ev);

}

/* Here's a simple mainloop  that waits until fd1 and fd2 are both

* ready to read. */

void mainloop(evutil_socket_t  fd1, evutil_socket_t fd2, int debug_mode)

{

struct event_base *base;

struct event event_on_stack,*event_on_heap;

if (debug_mode)

event_enable_debug_mode();

base = event_base_new();

event_on_heap = event_new(base, fd1, EV_READ, cb, NULL);

event_assign(&event_on_stack, base, fd2, EV_READ, cb, &event_on_stack);

event_add(event_on_heap, NULL);

event_add(&event_on_stack, NULL);

event_base_dispatch(base);

event_free(event_on_heap);

event_base_free(base);

}

事件调试这种特性,只有在编译时使用"-DUSE_DEBUG"的CFLAGS环境变量才能进行使能。使用该标识后,任何编译连接Libevent的程序将会输出非常详尽的日志信息。这些日志包括但不限于:添加event,删除event,特定平台的事件通知信息。

这种特性不能通过API的调用进行开启或禁用。这些调试功能在Libevent2.0.4-alpha之后加入。

七:检测Libevent版本

如果希望检测当前使用的Libevent版本,可以调试时打印Libevent的版本信息;

#define  LIBEVENT_VERSION_NUMBER  0x02000300

#define  LIBEVENT_VERSION  "2.0.3-alpha"

const char*  event_get_version(void);

ev_uint32_t  event_get_version_number(void);

这些宏提供了Libevent库的编译时版本;而函数返回运行版本。注意,如果你是动态链接到Libevent的话,这些版本有可能是不同的。

可以以两种格式得到Libevent的版本:字节整型形式。

整型形式中,使用高字节代表主版本,第二个字节代表次版本,第三个字节代表补丁版本,最后一个字节表示发布状态,0表示发布版本,非0表示给定发布版本之后的开发系列版本。因此,对于2.0.1-alpha发布版本的Libevent,它的版本号是[02 00 01 00],或者0x02000100。介于2.0.1-alpha 和 2.0.2-alpha之间的开发版本可能的版本号是[02 00 01 08],或是 0x02000108.

#include  <event2/event.h>

#if  !defined(LIBEVENT_VERSION_NUMBER) || LIBEVENT_VERSION_NUMBER < 0x02000100

#error "This version ofLibevent is not supported; Get 2.0.1-alpha or later."

#endif

int

make_sandwich(void)

{

/* Let's suppose that Libevent 6.0.5introduces a make-me-a

sandwich function. */

#if  LIBEVENT_VERSION_NUMBER>= 0x06000500

evutil_make_me_a_sandwich();

return 0;

#else

return -1;

#endif

}

Example: Run-time checks

#include <event2/event.h>

#include <string.h>

int

check_for_old_version(void)

{

const char *v = event_get_version();

/* This is a dumb way to do it, but it isthe only thing that works

before Libevent 2.0. */

if (!strncmp(v, "0.", 2) ||

!strncmp(v, "1.1", 3) ||

!strncmp(v, "1.2", 3) ||

!strncmp(v, "1.3", 3)) {

printf("Your version of Libeventis very old.  If you run into bugs,"

" consider  upgrading.\n");

return -1;

} else {

printf("Running with Libevent  version %s\n", v);

return 0;

}

}

int

check_version_match(void)

{

ev_uint32_t  v_compile, v_run;

v_compile = LIBEVENT_VERSION_NUMBER;

v_run = event_get_version_number();

if ((v_compile & 0xffff0000) != (v_run& 0xffff0000)) {

printf("Running with a Libevent  version (%s) very different from the "

"one we were built with(%s).\n", event_get_version(),

LIBEVENT_VERSION);

return -1;

}

return 0;

}

这些宏和函数定义在<event2/event.h>文件中。

八:释放Libevent全局结构

即使你已经释放了所有Libevent分配的对象,依然会残留一些全局分配的结构。一般来说这不会有问题:一旦程序退出了,所有资源都会被清理。但是,保留这些全局结构可能会使一些调试工具认为Libevent有内存泄露。如果希望Libevent释放所有内部“库全局”数据结构的话,需要调用:

void  libevent_global_shutdown(void);

注意,该函数不会释放任何Libevent返回给你的结构体。你若希望退出之前释放所有内存,则必须亲自释放所有的events,
event_bases,bufferevents等。

调用libevent_global_shutdown()会使其他的Libevent函数变得不可预知。所以该函数应该作为最后一个调用的Libevent函数。不过该函数具有幂等性,可以多次调用。(幂等性是说一个操作不管是执行一次还是多次,产生的副作用是一样的。幂等性是系统的接口对外一种承诺(而不是实现),
承诺只要调用接口成功, 外部多次调用对系统的影响是一致的。)

该函数在<event2/event.h>中声明。

原文:http://www.wangafu.net/~nickm/libevent-book/Ref1_libsetup.html

Libevent:2设置的更多相关文章

  1. 项目中的Libevent(多线程)

    多线程版Libevent //保存线程的结构体 struct LibeventThread { LibEvtServer* that; //用作传参 std::shared_ptr<std::t ...

  2. 使用libevent进行多线程socket编程demo

    最近要对一个用libevent写的C/C++项目进行修改,要改成多线程的,故做了一些学习和研究. libevent是一个用C语言写的开源的一个库.它对socket编程里的epoll/select等功能 ...

  3. Libevent核心原理

    Libevent 是一个事件驱动框架, 不能仅说他是一个网络库. notejs就是采用与libevent类似的libev来做核心驱动的.   Libevent支持三种事件:io事件.信号事件.时间事件 ...

  4. Sqlite,libevent,openssl,mosquito交叉编译

    一.设置交叉编译环境 在makefile所在目录(或源代码根目录)打开终端. 在终端中设置交叉编译所需的临时环境变量(也可写到配置文件中设置为全局环境变量),其中交叉编译工具链的名称和目录需要根据实际 ...

  5. libevent学习文档(三)working with event

    Events have similar lifecycles. Once you call a Libevent function to set up an event and associate i ...

  6. libevent库介绍--事件和数据缓冲

    首先在学习libevent库的使用前,我们还要从基本的了解开始,已经熟悉了epoll以及reactor,然后从event_base学习,依次学习事件event.数据缓冲Bufferevent和数据封装 ...

  7. windows 平台使用 VS2017 编译 libevent 源码

    一 依赖库编译 先要将其依赖的库编译好,其中openssl需要编译到libevent中,编译成libevent_openssl.lib库,zlib在新版本中只有示例用到. 1)windows 平台使用 ...

  8. 【传智播客】Libevent学习笔记(四):事件event

    目录 00. 目录 01. 事件概述 02. 创建事件 03. 事件的标志 04. 事件持久性 05. 超时事件 06. 信号事件 07. 设置不使用堆分配的事件 08. 事件的未决和非未决 09. ...

  9. libevent源码学习(17):缓冲管理框架

    目录Libevent缓冲区类型Libevent缓冲区结构缓冲区的读出与写入缓冲区的读入与写出缓冲区水位机制缓冲区回调机制延迟回调机制Libevent缓冲区类型       Libevent中提供了多种 ...

  10. libevent源码学习(9):事件event

    目录在event之前需要知道的event_baseevent结构体创建/注册一个event向event_base中添加一个event设置event的优先级激活一个event删除一个event获取指定e ...

随机推荐

  1. 5+App 基于HTML、JS、CSS编写的运行于手机端的App(DCloud开发)

    HTML5 Plus移动App(5+App) 工具: 开发工具HBuilder下载下载地址 开发工具HBuilderX下载下载地址(替代HBuilder) 框架: uni-app :是一个使用 Vue ...

  2. HYSBZ 1015/BZOJ1015 星球大战starwar

    Description 很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系.某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球.这些星球通过 ...

  3. mysqldump与mydumper

    mydumper -u root -S /srv/my3308/run/mysql.sock -B trade_platform -o /data/trade_platform

  4. 20190927 - 28 后觉 「雅礼Day3 - 4」

    我再不开$C++11$编译我就从三楼跳下去$$\text{%%%lsc}$$ Day3 -lm -O2 -std=c++ Before $Day3$? 全是$Subtask$? $\frac{1}{4 ...

  5. HDU3078 Network [2016年6月计划 树上问题05]

    Network Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Sub ...

  6. ubuntu设置终端命令历史记录

    ----------------------------------------------- HISTTIMEFORMAT='%F %T ' # 使用HISTTIMEFORMAT在历史中显示TIME ...

  7. idea添加jar包

    之前一直使用eclipse,现在使用idea,发现两者引用外部jar的时候不太一样,分享一下. 使用eclipse引用外部jar的时候,我们可以在工程下新建一个lib包来存放,然后add to bui ...

  8. 阿里云MaxCompute 2019-7月刊

    您好,MaxCompute 2019.7月刊为您带来7月产品.技术最新动态,欢迎阅读. 导读 [发布]7月产品重要发布 [资讯]7月重要资讯 [文档]7月重要文档更新推荐 [干货]7月精选技术文章推荐 ...

  9. HDU 1536 求解SG函数

    #include<stdio.h> #include<string.h> #include<algorithm> #include<set> using ...

  10. select @@identity的用法 转

    用select @@identity得到上一次插入记录时自动产生的ID 如果你使用存储过程的话,将非常简单,代码如下:SET @NewID=@@IDENTITY 说明: 在一条 INSERT.SELE ...