问题聚焦:
    这节介绍的不仅是网络编程的几个API
    更重要的是,探讨了Linux网络编程基础API与内核中TCP/IP协议族之间的关系。
    这节主要介绍三个方面的内容:套接字(socket地址)API,socket基础API,和网络信息API。

套接字API
套接字socket:(ip, port),即IP地址和端口对,唯一地表示了使用该TCP通信的一端。
需要了解:主机字节序和网络字节序。
原因:考虑32位的机器,CPU的累加器一次装载4字节的内容。那么这4字节在内存中的顺序将影响它被累加器装载后的所代表的含义。
分类:
  • 大端字节序:“高低,低高”,即,一个整数的高位字节(23~31位)存储在内存的低地址处,低位字节存储在内存的高地址处。
  • 小端字节序:“高高,低低”。
主机字节序:小端字节序。
网络字节序:大端字节序。
存在的问题:并不是所有的机器都采用主机字节序。一旦收发双方的字节序不同,必然导致错误的接收。
解决方法:发送端总是把要发送的数据转化成大端字节序,再发送。
注意:即使同一台机器上的两个进程,也要考虑字节序的问题,如一个客户端使用C语言编写,而另一个采用java编写(java虚拟机使用大端字节序)。
Linux提供的转换函数:
#inlcude<netinet/in.h>
unsigned long int htonl( unsigned long int hostlong );
unsigned short int htons( unsigned short int hostshort );
unsigned long int ntohl( unsigned long int netlong );
unsigned short int ntohs( unsigned short int netshort );

通用socket地址(结构体)

#include <bits/socket.h>
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
}
sa_family_da:地址族类型,与协议族类型对应。取值和对应关系如下图所示:
sa_data(char):存放socket地址。
协议族及其地址值:
数组长度有限,所以Linux提供了下面的新的通用socket地址结构体。
#include <bits/socket.h>
struct sockaddr_storage
{
sa_family_t sa_family;
unsigned long int __ssalign;
char __ss_padding[128-sizeof(__ss__align)];
}
__ss__align成员的作用:内存对齐。
专用socket地址
既然有通用socket地址,那么自然就有专用的socket地址。
专用:针对各个协议族的。
分类:
  • UNIX本地域协议族专用socket地址
#inlcude<sys/un.h>
struct sockaddr_un
{
sa_family_t sin_family; /* 地址族:AF_UNIX */
char sun_path[108]; /* 文件路径名 */
};

  • IPv4专用socket地址
#inlcude<sys/un.h>
struct sockaddr_in
{
sa_family_t sin_family; /* 地址族:AF_INET */
u_int16_t sin_port;
struct int_addr sin_addr; /* 文件路径名 */
};
struct in_addr
{
u_int32_t s_addr;
};

  • IPv6专用socket地址
#inlcude<sys/un.h>
struct sockaddr_in6
{
sa_family_t sin6_family; /* 地址族:AF_INET6*/
u_int16_t sin_port; /* 端口号,要用网络字节序表示 */
u_int32_t sin6_flowinfo; /* 流信息,应设置为0 */
struct int6_addr sin6_addr; /* IPv6地址结构体 */
u_int32_t sin6_scope_id; / * scope Id.,尚处于试验阶段 */
};
struct in_addr
{
unsigned char sa_addr[16]; /* IPv6地址,要用网络字节序表示 */
};

       虽然Linux给我们提供了专用的socket地址。但是,在实际使用时,都要强制转换为通用socket地址类型,因为所有socket编程接口使用的地址参数的类型都是sockaddr。
IP地址转换函数
#include<arpa/inet.h>
in_addr_t inet_addr( const char* strptr ); /* 点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。失败时返回INADDR_NONE */
int inet_aton( const char* cp, struct in_addr* inp ); /* 完成和in_addr_t同样的功能,但是将转化结果存储于参数inp指向的地址结构中。 成功时返回1, 失败时返回0 */
char* inet_ntoa( struct in_addr in ); /* inet_ntoa函数将网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址 */

有一点需要注意,看下面的代码

char* szValue1 = inet_ntoa( "1.2.3.4" );
char* szValue2 = inet_ntoa( "10.194.71.60" );
printf( "address 1: %s\n", szValue1 );
printf( "address 2: %s\n", szValue2 ); //打印结果
address1: 10.194.71.60
address2: 10.194.71.60

原因:inet_ntoa是不可重入的。因为该函数内部用一个固定的静态变量存储结果,函数的返回值指向该内存。每次调用,都是写入相同的静态内存区,所以不可以多次计算。
同时适用于IPv4和IPv6地址的转换函数
#include <arpa/inet.h>
int inet_pton(int af, const char* src, void* dst);
const char* inet_ntop( int af, const void* src, char* dst, socklen, cnt);

函数说明:
inet_pton函数将用字符串表示的IP地址stc(用点分十进制表示的IPv4地址或用十六进制字符串表示的IPv6地址)转换成用网络字节序整数表示的IP地址,并把转换结果可存储与dst指向的内存中。
af: 指定地址族,可以是AF_INET或AF_INET6
inet_pton成功返回1,失败返回0并设置errno
inet_ntop函数进行相反的转换,前三个参数含义与inet_pton的参数相同,最后一个参数cnt指定目标存储单元的大小。
下面的两个宏能帮助我们指定这个大小
#include <netinet/in.h>
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46

inet_ntop成功时返回目标存储单元的地址,失败则返回NULL并设置errno。 

创建socket
UNIX/Linux的一个哲学是:所有东西都是文件。socket也不例外,它就是可读、可写、可控制、可关闭的文件描述符。
创建一个socket
#include <sys/types.h>
#include <sys/socket.h>
int scoket( int domain, int type, int protocol );

函数说明:
domain: 告诉系统使用哪个底层协议。对TCP/IP 协议族而言,该参数应该设置为PF_INET(用于IPv4)或PF_INET6(用于IPv6); 对于UNIX本地域协议族而言,该参数应该设置为PF_UNIX 。 
type: 指定服务类型。服务类型主要有SOCK_STREAM服务(流服务,使用TCP协议)和SOCK_UGRAM(数据报,使用UDP协议)服务。对于TCP/IP协议族而言,其值取SOCK_STREAM(TCP协议)、SOCK_DGRAM(UDP协议)。
         还可以接受两个标志:SOCK_NONBLOCK(将新创建的socket设为非阻塞)和SOCK_CLOEXEC(用fork调用创建子进程时在子进程中关闭该socket)
protocol:在前两个参数构成的协议集合下,再选择一个具体的协议。几乎所有情况,把它设置为0.
成功时返回socket文件描述符,失败则返回-1并设置errno。

绑定socket
绑定:将一个上面创建的socket与一个具体的socket地址绑定
只有绑定了socket之后,客户端才能知道该如何连接它。
调用:bind
#include <sys/types.h>
#include <sys/socket.h>
int bind( int sockfd, const struct sockaddr* my_addr, socklen_t addrlen );

函数说明:
将my_addr所指的socket地址分配给未命名的sockfd文件描述符
addrlen:指出该socket地址的长度
成功时返回0,失败时返回-1并设置errno
其中两种常见的errno是EACCES 和 EADDRINUSE,它们的含义分别是:
  • EACCES : 被绑定的地址是受保护的地址,仅超级用户能够访问。比如普通用户将socket绑定到知名服务器端口(端口号为 0~1023)上时,bind将返回EACCES 错误。
  • EADDRINUSE:被绑定的地址正在使用中。比如讲socket绑定到一个处于TIME_WAIT状态的socket地址。

监听socket
监听:socket绑定后依然不能马上接受客户连接,我们需要创建一个监听队列以存放待处理的客户连接。
调用:listen
#include <sys/socket.h>
int listen( int sockfd, int backlog );
函数说明:
sockfd:被监听的socket
backlog:提示内核监听队列的最大长度。监听队列的长度如果超过backlog,服务器将不受理新的客户连接,客户端也将收到ECONNREFUSED 错误信息。在内核版本2.2之前的linux中,backlog参数是指所有处于半连接状态(SYN_RCVD)和完全连接状态(ESTABLISHED)的socket的上限,在内核版本2.2之后,它表示处于完全连接状态的socket的上限,处于半连接状态的socket的上限则由/proc/sys/net/ipv4/tcp_max_syn_backlog 内核参数定义。backlog典型值是5,通常监听队列中完整连接的上限通常币backlog值略大。
成功时返回0,失败时返回-1并设置errno。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h> static int stop = 0;
static void handle_term( int sig )
{
stop = 1;
} int main( int argc, char* argv[] )
{
signal( SIGTERM, handle_term ); if( argc <= 3 )
{
printf( "usage: %s ip_address port_number backlog\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
int backlog = atoi( argv[3] ); int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 ); struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port ); int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 ); ret = listen( sock, backlog );
assert( ret != -1 ); while ( ! stop )
{
sleep( 1 );
} close( sock );
return 0;
}

运行:

#./telnetlisten 10.8.56.206 12345 5  //监听12345端口 backlog取值为5
#telnet 10.8.56.206 12345 //运行10次
#netstat -nt | grep 12345

发现处于ESTABLISHED 状态的连接有6个(backlog加1 ),其它的4个连接处于SYN_RCVD 状态。 最终发现完全连接最多有(backlog +1 )个,在不同的系统下,运行结果可能有区别,不过监听队列中完全连接的上限通常比backlog值略大。


接受连接
调用:accept
#include <sys/types.h>
#include <sys/socket.h>
int accept( int sockfd, struct sockaddr *addr, socklen_t *addrlen );

作用:从listen监听队列中取出一个连接。服务器可以通过读写该socket来与被接受连接对应的客户端通信。
函数说明:
sockfd:处于监听状态的socket。
addr:用来获取被接受连接的远端socket地址。
addrlen:该socket地址的长度由addrlen指定。
成功返回0,失败返回-1并设置errno。
现在考虑如下情况:如果监听队列中处于ESTABLISHED 状态的连接对应的客户端出现网络异常(比如掉线),或者提前退出,那么服务器对这个连接执行的accept调用是否成功?
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h> int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] ); struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port ); int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 ); int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 ); ret = listen( sock, 5 );
assert( ret != -1 );
// 暂停20s 以等待客户端连接和相关操作(掉线或者退出)完成
sleep(20);
struct sockaddr_in client;
socklen_t client_addrlength = sizeof( client );
int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
}
else
{
char remote[INET_ADDRSTRLEN ];
printf( "connected with ip: %s and port: %d\n",
inet_ntop( AF_INET, &client.sin_addr, remote, INET_ADDRSTRLEN ), ntohs( client.sin_port ) );
close( connfd );
} close( sock );
return 0;
}

具体操作如下:

#./accept 10.8.56.206 12345
#telnet 10.8.56.206 12345

在启动telnet客户端程序后,在20s内关闭telnet 客户端。结果发现accept调用能够正常返回,服务器输出如下:

[root@vm MOTO]# ./accept 10.8.56.206 12345
connected with ip: 10.8.56.201 and port: 1313

由此可见,accept 只是从监听队列中取出连接,而不论连接处于何种状态(如上图CLOSE_WAIT状态或者或者ESTABLISHED状态(断开telent客户端与服务器的网络,服务器该连接的状态)),更不关心任何网络状态的变化。


发起连接
调用:connect
#include <sys/types.h>
#include <sys/socket.h>
int connect( int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen );

函数说明:
sockfd:由socket调用返回一个socket
serv_addr:服务器监听的socket地址
addrlen:指定这个地址的长度
成功时返回0,失败则返回-并设置errno
其中两种常见的errno是ECONNREFUSED和ETIMEDOUT,它们的含义如下:
  • ECONNREFUSED:目标端口不存在,连接被拒绝。
  • ETIMEDOUT:连接超时。

关闭连接
关闭:close
#include <unist.h>
int close ( int fd );

函数说明:
fd:待关闭的socket。
注意:
close系统调用并非立即关闭一个连接,而是将fd的引用计数减1,只有当fd的引用计数为0时,才真正关闭连接。
多进程程序中,一次fork调用默认使父进程中打开的socket的引用计数加1,因此我们必须在父进程和子进程两种都对该socket执行close调用才能将该连接关闭。
为了避免上面的麻烦,对于无论如何都要立即关闭连接,可以使用下面的调用:、
#include <sys/socket.h>
int shutdown ( int sockfd, int howto );

函数说明
sockfd:等待关闭的socket
howto:决定了shutdown的行为。可选值如下表:
    

数据读写
用于TCP流数据读写的调用:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv ( int sockfd, void *buf, size_t len, int flags );
ssize_t send ( int sockfd, const void *buf, size_t len, int flags );

函数说明:
recv读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小,flags一般设置为0。返回0说明连接关闭,返回-1出错并设置errno。recv可能返回0,这意味着通信对方已经关闭连接了。
send往sockfd上写入数据,buf和len参数分别指定写缓冲区的位置和大小。成功时返回实际写入的数据的长度,失败则返回-1并设置errno。
           
MSG_OOB选项给应用程序提供了发送和接收带外数据的方法:
发送方:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] ); struct sockaddr_in server_address;
bzero( &server_address, sizeof( server_address ) );
server_address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &server_address.sin_addr );
server_address.sin_port = htons( port ); int sockfd = socket( PF_INET, SOCK_STREAM, 0 );
assert( sockfd >= 0 );
if ( connect( sockfd, ( struct sockaddr* )&server_address, sizeof( server_address ) ) < 0 )
{
printf( "connection failed\n" );
}
else
{
printf( "send oob data out\n" );
const char* oob_data = "abc";
const char* normal_data = "123";
send( sockfd, normal_data, strlen( normal_data ), 0 );
send( sockfd, oob_data, strlen( oob_data ), MSG_OOB );
send( sockfd, normal_data, strlen( normal_data ), 0 );
} close( sockfd );
return 0;
}

接收方:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h> #define BUF_SIZE 1024 int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] ); struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port ); int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 ); int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 ); ret = listen( sock, 5 );
assert( ret != -1 ); struct sockaddr_in client;
socklen_t client_addrlength = sizeof( client );
int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
}
else
{
char buffer[ BUF_SIZE ]; memset( buffer, '\0', BUF_SIZE );
ret = recv( connfd, buffer, BUF_SIZE-1, 0 );
printf( "got %d bytes of normal data '%s'\n", ret, buffer ); memset( buffer, '\0', BUF_SIZE );
ret = recv( connfd, buffer, BUF_SIZE-1, MSG_OOB );
printf( "got %d bytes of oob data '%s'\n", ret, buffer ); memset( buffer, '\0', BUF_SIZE );
ret = recv( connfd, buffer, BUF_SIZE-1, 0 );
printf( "got %d bytes of normal data '%s'\n", ret, buffer ); close( connfd );
} close( sock );
return 0;
}

接收方两种结果:

[root@vm MOTO]# ./recv 10.8.56.206 12345
got 5 bytes of normal data '123ab'
got 1 bytes of oob data 'c'
got 3 bytes of normal data '123'
由此可见客户端给服务器的3字节的带外数据“abc"中,仅有最后一个字符"c"被服务器当成了真正的带外数据接收,并且,服务器对正常数据的接收将被带外数据截断,即前一部分正常数据"123ab"和后续的正常数据"123"是不能被一个recv调用全部读取的。

或者:

[root@vm MOTO]# ./recv 10.8.56.206 12345
got 3 bytes of normal data '123'
got -1 bytes of oob data ''
got 2 bytes of normal data 'ab'


客户端发送的"123"到服务器的接收缓冲区,立马被服务器读走,然后客户端发送的带外数据"abc"到服务器的接收缓冲区,当服务器调用recv( connfd, buffer, BUF_SIZE-1, MSG_OOB );时失败,因为第一个字符不是带外数据,而是字符"a",真正的带外数据是"c"。服务器接着读走了"ab"字符后关闭了连接,但是发送的不是结束报文段,发送的是复位报文段,即如果recv( connfd, buffer, BUF_SIZE-1, MSG_OOB );失败时,最后发送的不是结束报文段,是复位报文段。

接收方结果:
[root@vm MOTO]# ./send 10.8.56.206 12345
send oob data out
在实际应用中,我们通常无法预期带外数据何时到来。好在Linux内核检测到TCP紧急标志时,将通知应用程序有带外数据需要接收。内核通知应用程序带外数据到达的两种方式是:
  • I/O复用产生的异常事件。
  • SIGURG信号。
但是,即使应用程序得到了有带外数据需要接收的通知,还需要知道带外数据在数据流中的具体位置,才能准确接收带外数据。这点可通过如下系统调用实现:
       #include <sys/socket.h>

       int sockatmark(int fd);

sockatmark判断sockfd是否处于带外标记,即下一个被读到的数据是否是带外数据,如果是,sockatmark返回1,此时我们就是利用带MSG_OOB标志的recv调用来接收带外数据。如果不是,则seckatmark返回0。

用于UDP数据报的读写:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom ( int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen );
ssize_t sendto ( int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen );

函数说明:
recvfrom读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小。因为UDP通信没有连接的概念,所以我们每次读取数据都需要获取发送端的socket地址,即参数src_addr所指的内容,addrlen参数则指定该地址的长度。
sendto往sockfd上写入数据,buf和len分别指定写缓冲区的位置和大小,dest_addr指定接收端的socket地址,addrlen指定该地址的长度。
通用数据读写函数:
#include <sys/socket.h>
ssize_t recvmsg( int sockfd, struct msghdr* msg, int flags );
ssize_t sendmsg( int sockfd, struct msghdr* msg, int flags );

函数说明:
sockfd:指定被操作的目标sockfd
msg:msghdr结构体类型的指针,定义如下:
 struct msghdr
{
void* msg_name; /* socket地址 */
socklen_t msg_namelen; /* socktet地址的长度 */
struct iovec* msg_iov; /* 分散的内存块 */
int msg_iovlen; /* 分散的内存块数量 */
void msg_control; /* 指向辅助数据的起始位置 */
socklen_t msg_controllen; /* 辅助数据的大小 */
int msg_flags; /* 复制函数中的flags参数,并在调用过程中更新 */
}; struct iovec
{
void *iov_base; /* 内存起始地址 */
size_t iov_len; /* 这块内存的长度 */
};

由上可见,iovec结构体封装了一块内存的起始位置和长度。msg_iovlen指定这样的iovec结构对象有多少个。对于recvmsg而言,数据将被读取并存放在msg_iovlen块分散的内存中,这些内存的位置和长度则由msg_iov指向的数组指定,这称为分散读;对于sendmsg而言,msg_iovlen块分散内存中的数据将被一并发送,这称为集中写

       recvmsg/sendmsg 的flags参数以及返回值的含义均与send/recv的flags参数及返回值相同。 

地址信息函数
功能:获取连接的本端socket地址,以及远端的socket地址。
函数:
#include <sys/socket.h>
int getsockname ( int sockfd, struct sockaddr* address, socklen_t* address_len ); /* 获取sockfd本端socket地址,并将其存储于address参数指定的内存中 */
int getpeername ( int sockfd, struct sockaddr* address, socklen_t* address_len ); /* 获取sockfd对应的远端socket地址,其参数及返回值的含义与getsockname的参数及返回值相同 */
getsockname 获取sockfd对应的本端socket地址。
getpeername 获取sockfd对应的远端socket地址。

参考资料:
《Linux高性能服务器编程》

Linux 高性能服务器编程——Linux网络编程基础API的更多相关文章

  1. Linux 高性能服务器编程——TCP/IP协议族

    1 TCP/IP协议族体系结构 数据链路层:     职责:实现网卡接口的网络驱动程序,一处理数据在物理媒介(如以太网.令牌环等)上的传输.     常用协议:ARP协议(地址解析协议),RARP协议 ...

  2. linux高性能服务器编程

    <Linux高性能服务器编程>:当当网.亚马逊 目录: 第一章:tcp/ip协议族 第二章:ip协议族 第三章:tcp协议详解 第四章:tcp/ip通信案例:访问Internet 第五章: ...

  3. linux高性能服务器编程 (一) --Tcp/Ip协议族

    前言: 在学习swoole入门基础的过程中,遇到了很多知识瓶颈,比方说多进程.多线程.以及进程池和线程池等都有诸多的疑惑.之前也有学习相关知识,但只是单纯的知识面了解.而没有真正的学习他们的来龙去脉. ...

  4. Linux 高性能服务器编程——多线程编程

    问题聚焦:     在简单地介绍线程的基本知识之后,主要讨论三个方面的内容:    1 创建线程和结束线程:    2 读取和设置线程属性:    3 线程同步方式:POSIX信号量,互斥锁和条件变量 ...

  5. Linux 高性能服务器编程——多进程编程

    问题聚焦:     进程是Linux操作系统环境的基础.     本篇讨论以下几个内容,同时也是面试经常被问到的一些问题:     1 复制进程映像的fork系统调用和替换进程映像的exec系列系统调 ...

  6. Linux 高性能服务器编程——I/O复用

    问题聚焦:     前篇提到了I/O处理单元的四种I/O模型.     本篇详细介绍实现这些I/O模型所用到的相关技术.     核心思想:I/O复用 使用情景: 客户端程序要同时处理多个socket ...

  7. Linux 高性能服务器编程——高性能服务器程序框架

    问题聚焦:     核心章节.     服务器一般分为如下三个主要模块:I/O处理单元(四种I/O模型,两种高效事件处理模块),逻辑单元(两种高效并发模式,有效状态机)和存储单元(不讨论). 服务器模 ...

  8. Linux 高性能服务器编程——Linux服务器程序规范

    问题聚焦:     除了网络通信外,服务器程序通常还必须考虑许多其他细节问题,这些细节问题涉及面逛且零碎,而且基本上是模板式的,所以称之为服务器程序规范.     工欲善其事,必先利其器,这篇主要来探 ...

  9. Linux 高性能服务器编程——TCP协议详解

    问题聚焦:     本节从如下四个方面讨论TCP协议:     TCP头部信息:指定通信的源端端口号.目的端端口号.管理TCP连接,控制两个方向的数据流     TCP状态转移过程:TCP连接的任意一 ...

随机推荐

  1. [LeetCode] Add One Row to Tree 二叉树中增加一行

    Given the root of a binary tree, then value v and depth d, you need to add a row of nodes with value ...

  2. Who do you want to be bad? (谁会是坏人?)人工智能机器小爱的问话

    人工智能的语言理解一直是一个千古谜团. 正如人工智能机器小爱(A.L.I.C.E)的问话:“Who do you want to be bad ?(谁会是坏人?)” 纵观世界上的140多种语言,汉语是 ...

  3. 十大面试难题解惑,看完秒杀一切 HR 面。程序员必读!

    最能体现求职者能力的就是面试,能不能拿到Offer,取决于你面试时的表现,只有有准备才能在面试过程中游刃有余. 小编收集了10个面试官最爱提的问题,虽然题目千变万化,但是万变不离其宗,只要掌握了答题的 ...

  4. NOIP 2012

    Prob.1 vigenere密码 模拟代码: #include<cstdio> #include<cstring> #include<iostream> usin ...

  5. [Educational Codeforces Round 7]F. The Sum of the k-th Powers

    FallDream dalao找的插值练习题 题目大意:给定n,k,求Σi^k (i=1~n),对1e9+7取模.(n<=10^9,k<=10^6) 思路:令f(n)=Σi^k (i=1~ ...

  6. 本地缓存,Redis缓存,数据库DB查询(结合代码分析)

    问题背景 为什么要使用缓存?本地缓存/Redis缓存/数据库查询优先级? 一.为什么要使用缓存 原因:CPU的速度远远高于磁盘IO的速度问题:很多信息存在数据库当中的,每次查询数据库就是一次IO操作所 ...

  7. C语言程序设计第一次实验

    一. 1.输入圆的半径,计算圆的面积周长问题 2流程图 3测试数据及运算结果 4实验分析 不会输入r,后来问了同学得到解决. 二. 1.输入一个四位年份,判断其是否是闰年.闰年的判别条件是概念年份能被 ...

  8. Debugging TensorFlow models 调试 TensorFlow 模型

    Debugging TensorFlow models Symbolic nature of TensorFlow makes it relatively more difficult to debu ...

  9. linux下磁盘分区详解 图文(fdisk;mkfs)

    linux分区不同于windows,linux下硬盘设备名为(IDE硬盘为hdx(x为从a-d)因为IDE硬盘最多四个,SCSI,SATA,USB硬盘为sdx(x为a-z)),硬盘主分区最多为4个,不 ...

  10. @Transient 理解

    transient使用小结 1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问. 2)transient关键字只能修饰变量,而不能修饰方法和类.注意 ...