socket网络编程(2):socket操作相关函数
来源:互联网 发布:软件设计师 好考吗 编辑:程序博客网 时间:2024/05/16 01:44
使用socket函数应先包含winsock2.h头文件和链接Ws2_32.lib文件:
#include <winsock2.h>
#pragma comment(lib, "Ws2_32.lib")
1、WSAStartup()和WSACleanup()
WSAStartup()用来加载套接字库,函数执行成功返回0,否则返回错误码。
int WSAStartup( _In_ WORD wVersionRequested, _Out_ LPWSADATA lpWSAData);
wVersionRequested:要加载的Winsock库版本。其低位字节为主版本号,高位字节为副版本号,最新版本号为2.2(XP下可用),可以用MAKEWORD(x, y)宏来构成这个WORD(unsigned short)类型的值,其中x为低位字节,y为高位字节。lpWSAData:返回值参数,指向WSAData结构的指针。
可以通过LOBYTE(WORD wValue)和HIBYTE(WORD wValue)分别获得WORD类型值的低位字节值和高位字节值。
WSACleanup()用来结束对Winsock库的使用,释放资源。函数原型:
int WSACleanup(void);
eg:
WORD wVersionRequested = MAKEWORD(1, 1);//指定要加载的Winsock库主版本号和副版本号WSADATA wsaData;int err;err = WSAStartup(wVersionRequested, &wsaData);if(err != 0)return 1;if(LOBYTE(wsaData.wVersion)!=1 || HIBYTE(wsaData.wVersion)!=1){WSACleanup();return 1;} ...... WSACleanup(); return 0;
2、socket()/closesocket()
socket()用来创建套接字,函数调用成功会返回创建的套接字,失败返回INVALID_SOCKET;
SOCKET WSAAPI socket( _In_ int af, _In_ int type, _In_ int protocol);af:地址族。对于TCP/IP协议套接字来说应为AF_INET(也可写成PF_INET)。
type:socket类型。对于TCP协议,应为SOCK_STREAM,表示面向流的传输协议;对于UDP协议,应为SOCK_DGRAM,表示面向数据报的传输协议。
protocol:通常指定为0,让系统自动设置协议。
SOCKET ListenSocket = INVALID_SOCKET; ListenSocket = socket(AF_INET, SOCK_STREAM, 0); if (ListenSocket == INVALID_SOCKET) { wprintf(L"socket function failed with error: %u\n", WSAGetLastError()); WSACleanup(); return 1; }
closesocket()用来关闭套接字,从而关闭连接。closesocket()的一些行为受setsockopt()影响。
3、bind()
bind()用来将套接字绑定到本机的某个地址和端口上,函数执行成功返回0,否则返回SOCKET_ERROR。
int bind( _In_ SOCKET s, _In_ const struct sockaddr *name, _In_ int namelen);s:要绑定的套接字。
name:sockaddr地址结构指针,其包含了要绑定过的本机地址信息,在TCP/IP socket编程中,可以用sockaddr_in地址结构来替代此结构,以方便我们填写地址信息。
struct sockaddr_in { short sin_family; //地址族,对于TCP/IP来说应为AF_INET u_short sin_port; //端口号 struct in_addr sin_addr; //IP地址 char sin_zero[8]; //为与sockaddr结构长度相同增加的填充值,无意义 };其中,sin_addr成员实际上是一个联合,故直接向成员s_addr赋值即可。
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr
.......
}
inet_addr()可以将一个点分十进制格式的IPV4地址转换为一个32位网络字节序IP地址。
inet_ntoa()可以将一个in_addr结构体类型转换为点分十进制格式IP地址。
htons()可以将端口号转换为网络字节序。
ntohs()可以将网络字节序的端口号转化为主机字节序。
namelen:为地址结构长度。
eg:
sockaddr_in service; service.sin_family = AF_INET; service.sin_addr.s_addr = inet_addr("127.0.0.1"); service.sin_port = htons(27015); //---------------------- // Bind the socket. iResult = bind(ListenSocket, (SOCKADDR *) &service, sizeof (service)); if (iResult == SOCKET_ERROR) { wprintf(L"bind failed with error %u\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; }
4、setsockopt()/getsockopt()
setsockopt()/getsockopt()用来设置/获得socket套接字的选项值,函数原型:
int setsockopt( _In_ SOCKET s, _In_ int level, _In_ int optname, _In_ const char *optval, _In_ int optlen);s:要设置的套接口描述字。
level:选项所定义的层次协议,SOL_SOCKET: socket层
IPPROTO_TCP: TCP层
IPPROTO_IP: IP层
.......
optname:要设置的选项名。
optval:要设置的选项值。
optlen:optval缓冲区的长度。
有两种类型的套接口选项:一种是布尔型选项,允许或禁止一种特性,允许一个布尔型选项,则将optval指向非零整形数,禁止一个选项将optval指向一个等于零的整形数。另一种是整形或结构选项,optval指向包含所需选项的整形数或结构,而optlen则为整形数或结构的长度。
socket层选项:
SO_BROADCAST BOOL 发送广播信息。
SO_DEBUG BOOL 启用调试输出。
SO_DONTLINER BOOL 允许该项则closesocket()调用立即返回,但是,如果可能,排队的数据将在套接口关闭前发送
(相当于使用SO_LINGER的时候结构体成员l_onoff设为0(l_linger不关心))。
SO_DONTROUTE BOOL 禁止路由选径;直接传送。
SO_KEEPALIVE BOOL 发送“保持活动”包;保持连接。
SO_LINGER struct linger 如关闭socket时有未发送数据,则等待或不等待。
SO_OOBINLINE BOOL 在常规数据流中接收带外数据。
SO_RCVBUF int socket接收缓冲区大小。
SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑(参见bind());地址重用。
SO_RCVTIMEO int 接收超时时间。
SO_SNDBUF int socket发送缓冲区大小
SO_SNDTIMEO int 发送超时时间。
SO_MAX_MSG_SIZE unsigned int 发送的数据包的最大长度,仅对应面向数据报UDP的情况下。
SO_CONNECT_TIME DWORD 连接已经建立了多长时间(检查是否接受了连接)
SO_ERROR int 获得错误码
........
其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM类型的套接口不支持SO_DONTLINGER 、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE选项。
setsockopt()使用举例:
(以下转自http://bbs.csdn.net/topics/60430131)
int nNetTimeout=1000;//1秒
//发送时限
setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
//接收时限
setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
SO_SNDTIMEO和SO_RCVTIMEO仅支持阻塞socket。
3.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节
(异步);系统默认的状态发送和接收缓冲区为8K;在实际的过程中发送数据
和接收数据量比较大,可以设置socket缓冲区:
// 接收缓冲区
int nRecvBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
4. 如果在发送数据的时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响
程序的性能:
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_SNDBUF,(char *)&nZero,sizeof(nZero));
5.同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero=0;
setsockopt(socket,SOL_S0CKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
6.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
BOOL bBroadcast=TRUE;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&bBroadcast,sizeof(BOOL));
7.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可
以设置connect()延时,直到accpet()被呼叫(本函数设置只有在非阻塞的过程中有显著的
作用,在阻塞的函数调用中作用不大)
BOOL bConditionalAccept=TRUE;
setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char*)&bConditionalAccept,sizeof(BOOL));
8.SO_LINGER选项可以设置closesocket()时的动作:
struct linger {
u_short l_onoff;
u_short l_linger;
};
eg:
linger m_sLinger;
m_sLinger.l_onoff=1;
m_sLinger.l_linger=5;
setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));
SO_LINGER选项:
数据将在关闭前发送Nononzerozero强制关闭:closesocket()调用立即返回,不论是否有排队
数据未发送或未被确认Nononzerononzero
closesocket()调用直到所剩数据发送完毕或超时才返回。
对于非阻塞socket,closesocket()将立即返回而不论
数据是否发送完毕或时间是否到达。
优雅关闭:如果排队数据在l_linger指定的时间内被全部
发送
强制关闭:如果排队数据在l_linger指定的时间内没有被
全部发送,closesocket()返回WSAEWOULDBLOCK.
5、listen()
listen()设置socket为监听模式,并为socket建立一个未决连接的队列。listen()会将到来的但还未来得及accept()的连接请求放到此队列中,一旦连接被accept()那么就从这个队列中删除这个未决连接。函数执行成功返回0,否则返回SOCKET_ERROR。int listen( _In_ SOCKET s, _In_ int backlog);s:套接字。
backlog:未决连接队列的最大长度,如果连接数超过这个最大长度,则新的连接请求会被拒绝,客户调用WSAGetLastError()会得到一个WSAECONNREFUSED错误,所以客户此时应该稍后重试。backlog的最大值是由下层协议提供程序决定的,还不存在合理最大值的标准,SOMAXCONN为下层服务自动设置此值为合理的一个最大值,通常为几百或更大。
linux下默认支持的最大长度为128(cat /proc/sys/net/core/somaxconn),如果想要支持更大的长度,可以这样设置:echo 2048 > /proc/sys/net/core/somaxconn,不过系统重启后somaxconn还是会变回原来的128,另一个方法就是在/etc/sysctl.conf中添加如下: net.core.somaxconn = 2048,然后在终端中执行:sysctl -p,这样就永久保存了somaxconn。
eg:
if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) wprintf(L"listen function failed with error: %d\n", WSAGetLastError());
6、accept()
accept()接收客户端的连接请求,函数执行成功返回一个新的已连接的socket,使用这个socket同客户端进行通信,而原来的监听socket可以接收其它客户的连接请求。运行失败返回INVALID_SOCKET。
SOCKET accept( _In_ SOCKET s, _Out_ struct sockaddr *addr, _Inout_ int *addrlen);s:已设置为监听状态的套接字。
addr:返回值参数,包含客户端的IP地址和端口。
addrlen:返回值参数,地址信息的长度。
eg:
SOCKET AcceptSocket; sockaddr_in addrClient; int len = sizeof(SOCKADDR); AcceptSocket = accept(ListenSocket, (SOCKADDR*)&addrClient, &len); if (AcceptSocket == INVALID_SOCKET) { wprintf(L"accept failed with error: %ld\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; }else{ char sendBuf[100]; sprintf(sendBuf, "Welcome %s:%d!", inet_ntoa(addrClient.sin_addr), ntohs(addrClient.sin_port)); }
7、send()和recv()
send和recv一般用于TCP连接。
//send()向一个已经连接的套接字发送数据,如果没有错误发生,则返回已经发送的数据的大小,其值可能小于len,
//否则返回SOCKET_ERROR。实际上send()成功返回后数据只是发送到了socket发送缓冲区中,比如在对方关闭socket后,
//调用send()还可能会成功一次或几次。
//当socket发送缓冲区已满的话send在阻塞模式下会阻塞,在非阻塞模式返回WSAEWOULDBLOCK错误。
int send( _In_ SOCKET s, _In_ const char *buf, _In_ int len, _In_ int flags);s:一个已经连接的套接字。
buf:要发送的数据缓冲区。
len:buf的大小。
flags:0或以下值及其组合
MSG_DONTROUTE 指明数据不选径,WINDOWS套接字供应商可以忽略此标志。
MSG_OOB 发送带外数据,仅适用于SOCK_STREAM。
eg:
int send_bytes; char buf[] = "welcome to TCP test server(version 0.1)."; int send_total = 0; while(send_total < strlen(buf)) { send_bytes = send(conect_socket, buf, strlen(buf)-send_total, 0); if(send_bytes == SOCKET_ERROR) { wprintf(L"shutdown failed with error: %d\n", WSAGetLastError()); closesocket(ConnectSocket); break; } else { send_total += send_bytes; } }recv()从一个已经连接的套接字接收数据。如果没有错误发生,函数返回接收到数据的大小,如果对方已经将连接优雅的关闭则返回0,超时或者发生错误返回SOCKET_ERROR。
int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);s:一个已经连接的套接字。
buf:接收数据的缓冲区。
len:buf的大小
flags:可以为0或以下值,
MSG_PEEK:读取当前系统缓冲区中数据,但系统缓冲区中数据不会被删除,读取的数据大小可能与缓冲区中数据大小不同。
MSG_OOB:处理带外数据。
MSG_WAITALL:直到buf已满或者连接关闭或者出现错误函数才返回。如果底层传输不支持该选项或socket已经被设置成非阻塞的则函数会失败,错误为WSAEOPNOTSUPP。如果同时指定了 MSG_OOB或MSG_PEEK函数也会出错。
当socket接收缓冲区为空的话recv在阻塞模式下会阻塞,在非阻塞模式返回WSAEWOULDBLOCK错误。
eg:
#define DEFAULT_BUFLEN 512 char recvbuf[DEFAULT_BUFLEN] = ""; int iResult; //接收数据直到对方关闭连接 do { iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0); if ( iResult > 0 ) wprintf(L"Bytes received: %d\n", iResult); else if ( iResult == 0 ) wprintf(L"Connection closed\n"); else wprintf(L"recv failed with error: %d\n", WSAGetLastError()); } while( iResult > 0 );8、connect()
connect()将套接字与远端地址进行连接。如果没有错误发生函数返回0,否则返回SOCKET_ERROR。
int connect( _In_ SOCKET s, _In_ const struct sockaddr *name, _In_ int namelen);
s:本地套接字,是即将在其上建立连接的套接字。
name:对方地址。
namelen:地址信息长度。
eg:
int iResult; sockaddr_in clientService; clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr("127.0.0.1"); clientService.sin_port = htons(27015); iResult = connect(ConnectSocket, (SOCKADDR *) & clientService, sizeof (clientService)); if (iResult == SOCKET_ERROR) { wprintf(L"connect function failed with error: %ld\n", WSAGetLastError()); iResult = closesocket(ConnectSocket); if (iResult == SOCKET_ERROR) wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError()); }9、sendto()和recvfrom()
sendto和recvfrom一般用于UDP连接,但也能用于TCP连接,其比send和recv多了对方地址信息和长度,剩余参数和返回值意义同send和recv。
需要注意的地方是对于面向数据报的socket,比如面向数据报的UDP传输,在发送数据的时候必需注意发送的数据包大小不应超过底层数据包的最大长度,底层数据包的最大长度可以通过getsockopt()函数获得(参数 optname传入SO_MAX_MSG_SIZE)。
int sendto( _In_ SOCKET s, _In_ const char *buf, _In_ int len, _In_ int flags, _In_ const struct sockaddr *to,//对方地址信息 _In_ int tolen//对方地址信息长度);
eg:
//UDP下发送数据 int iResult; sockaddr_in RecvAddr; RecvAddr.sin_family = AF_INET; RecvAddr.sin_port = htons(Port); RecvAddr.sin_addr.s_addr = inet_addr("192.168.1.1"); wprintf(L"Sending a datagram to the receiver...\n"); iResult = sendto(SendSocket, SendBuf, BufLen, 0, (SOCKADDR *) & RecvAddr, sizeof (RecvAddr)); if (iResult == SOCKET_ERROR) { wprintf(L"sendto failed with error: %d\n", WSAGetLastError()); closesocket(SendSocket); }
int recvfrom( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags, _Out_ struct sockaddr *from,//对方地址信息 _Inout_opt_ int *fromlen//对方地址信息长度);
eg:
//UDP下接受数据 int iResult = 0; sockaddr_in SenderAddr; int SenderAddrSize = sizeof (SenderAddr); wprintf(L"Receiving datagrams...\n"); iResult = recvfrom(RecvSocket, RecvBuf, BufLen, 0, (SOCKADDR *) & SenderAddr, &SenderAddrSize); if (iResult == SOCKET_ERROR) { wprintf(L"recvfrom failed with error %d\n", WSAGetLastError()); }10、WSASend()/WSARecv()、WSASendto()/WSARecvfrom()
WSASend和WSARecv(WSASendto和WSARecvfrom)用于异步IO。
typedef struct _WSABUF {CHAR *buf; /* the pointer to the buffer */ ULONG len; /* the length of the buffer */} WSABUF, * LPWSABUF;int WSASend( _In_ SOCKET s, //套接字句柄 _In_ LPWSABUF lpBuffers, //WSABUF结构的数组,WSABUF结构包含一个缓冲区指针和对应缓冲区的长度 _In_ DWORD dwBufferCount, //上面WSABUF数组的大小 _Out_ LPDWORD lpNumberOfBytesSent,//如果I/O操作立即完成的话,此参数取得实际传输数据的字节数 _In_ DWORD dwFlags, //标志,与send()中flags意义相同,一般设为0 _In_ LPWSAOVERLAPPED lpOverlapped, //与此I/O操作相关的WSAOVERLAPPED结构 _In_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine //指定一个I/O操作完成后执行的例程,一般设为NULL);int WSARecv( _In_ SOCKET s, _Inout_ LPWSABUF lpBuffers, _In_ DWORD dwBufferCount, _Out_ LPDWORD lpNumberOfBytesRecvd, _Inout_ LPDWORD lpFlags, _In_ LPWSAOVERLAPPED lpOverlapped, _In_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);lpBuffers这个参数是一个指针,它指向一个WSABUF结构的数组。这个数组可以是瞬态的(transient),就是说你可以定义一个局部变量的WSABUF传入,当异步I/O返回后系统会保存这个数组的副本。
异步I/O操作都接收一个WSAOVERLAPPED结构的数据参数,这些I/O操作调用之后会立即返回,它们依靠传递的WSAOVERLAPPED结构来管理I/O请求的完成。
调用WSASend可以完成一次发送多个缓冲区中的数据来进行集中写入,提高了性能,应该相当于unix上的writev,好处看来是避免Nagle算法。
11、WSASocket()
WSASocket()主要用于完成端口中,使对于socket的发送操作和接收操作都可以被重叠使用,即投递多个重叠操作。
SOCKET WSASocket( _In_ int af, //地址协议族,一般设为AF_INET或AF_INET6 _In_ int type, //套接字类型,一般设为SOCK_DREAM或SOCK_DGRAM _In_ int protocol, //使用协议,一般设为0让系统自动设置协议 _In_ LPWSAPROTOCOL_INFO lpProtocolInfo, //套接口的特性,一般我们设为NULL _In_ GROUP g, //套接字组的描述字,0为不执行组操作 _In_ DWORD dwFlags //套接字属性,在完成端口中我们一般将其设为WSA_FLAG_OVERLAPPED以支持重叠I/O操作);
12、shutdown()
closesocket()会把描述符的引用计数减1,只有在该计数为0时才会关闭套接字,在多进程并发服务器中,比如父子进程共享着套接字,当只是父
进程或某一子进程close掉套接字时套接字引用计数减1但不为0,所以连接不会被关闭。调用close()时,默认情况下TCP将尝试发送已排队等待发
送到对端的任何数据,发送完毕后再关闭连接,可以调用setsockopt()来设置closesocket()时对于排队数据的处理方式。
shutdown()不会去关心引用计数而直接关闭套接字。shutdown()还可以仅关闭读方向或写方向。
一般情况下我们发送完数据再调用closesocket()关闭socket后数据最多是能保证已经写入到TCP缓冲区中,怎么能确定对端已经读取数据后才返
回呢?一个办法是发送完数据后使用shutdown来先关闭socket的写端,然后再阻塞的调用一个recv(),此时对端会在收到数据后收到一个FIN,当对端关闭socket后recv()会返回0。
13、ioctlsocket()
ioctlsocket()用来设置套接字的IO模式。
int ioctlsocket( _In_ SOCKET s, _In_ long cmd, _Inout_ u_long *argp);s:一个标识套接口的描述字。
cmd:对套接口s的操作命令。
FIONBIO:允许或禁止套接字非阻塞。默认情况下套接字为阻塞模式,若无数据,send/recv会发生阻塞。而WSAAsyncSelect和WSAEventSelect会自动将套接字设为非阻塞模式。如果已对一个套接口进行了WSAAsynSelect() 操作,则任何用ioctlsocket()来把套接口重新设置成阻塞模式的试图将以WSAEINVAL失败,为了把套接口重新设置成阻塞模式,必须首先用WSAAsynSelect()调用(IEvent参数置为0)来禁止WSAAsynSelect()。
eg:将套接字设为非阻塞模式: u_long Len=1; ioctlsocket(SocketServer,FIONBIO,&Len);
将套接字设为阻塞模式: u_long Len=0; ioctlsocket(SocketServer,FIONBIO,&Len);
FIONREAD:使用该参数的意义是获得系统读缓冲区中数据大小。
SIOCATMARK:确实是否所有的带外数据都已被读入。这个命令仅适用于SOCK_STREAM类型的套接口,且该套接口已被设置为可以在线接收带外数据(SO_OOBINLINE)。
argp:指向cmd命令所带参数的指针。
参考:《孙鑫VC++深入详解》
<<Windows网络与通信程序设计>>王艳平
http://baike.baidu.com/view/569202.htm?fr=aladdin
- socket网络编程(2):socket操作相关函数
- 网络socket编程相关函数
- socket网络编程的相关函数
- socket网络编程函数
- 网络(2):网络配置&socket编程相关的函数选项及异常处理
- Socket网络编程常用函数
- socket网络编程2
- 网络编程(2)-socket
- 1.socket编程:socket编程,网络字节序,函数介绍,IP地址转换函数,sockaddr数据结构,网络套接字函数,socket相关函数,TCP server和client
- 1.socket编程:socket编程,网络字节序,函数介绍,IP地址转换函数,sockaddr数据结构,网络套接字函数,socket相关函数,TCP server和client
- socket网络编程--初等网络函数介绍
- socket编程之三:socket网络编程中的常用函数
- socket网络编程的集中操作
- C#网络编程(二、Socket基本操作)
- 网络socket编程指南 3 socket bind connect 函数
- Beej网络socket编程指南 3 socket bind connect 函数
- linux网络编程--socket(2)
- socket 网络编程recv()函数错误
- jquery json
- 电源引起的内存报警故障
- 水火旣濟 (易經大意 韓長庚)
- 第四章 网络层 4.1网络层提供的两种服务
- hadoop1.0.4在windows上的cygwin中安装出现的错误
- socket网络编程(2):socket操作相关函数
- 已知畸变图像中某一点,计算该点对应的无畸变图像点
- 五招防止QQ密码被盗
- 解决IOS无法播放3gp视频, 出现无法编码问题
- 从50个数中取出若干个
- 进程与线程的一个简单解释
- 在LNMP或Nginx上配置NameCheap免费SSL
- Android LinearLayout 设置 OnClickListener 无效时解决方法
- python联接hive的模板