第5章 Linux网络编程基础API
请结合《Linux高性能服务器编程 by 游双》进行阅读。
1.判断机器字节序
#include <stdio.h>
void byteorder()
{
union
{
short value; // short占 2 字节
char union_bytes[ sizeof( short ) ]; // sizeof返回一个对象或者类型所占的内存字节数。char占一个字节
} test;
test.value = 0x0102; // 十六进制,每两位占一个字节
if ( ( test.union_bytes[ 0 ] == 1 ) && ( test.union_bytes[ 1 ] == 2 ) ) // union_bytes[ 0 ]为低地址,01为高位,故为大端模式
{
printf( "big endian\n" );
}
else if ( ( test.union_bytes[ 0 ] == 2 ) && ( test.union_bytes[ 1 ] == 1 ) )
{
printf( "little endian\n" );
}
else
{
printf( "unknown...\n" );
}
}
共同体Union和结构体类似。结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员共用同一段内存,修改一个成员会影响其余所有成员。
由于value和union_bytes共用一块内存,所以对value的赋值后,union_bytes访问的就是赋值后的那一部分内存
2.点分十进制字符串与网络字节序整数的互相转换
#include<iostream>
#include<arpa/inet.h>
using namespace std;
int main(){
cout<<inet_addr("192.168.2.125")<<endl; // 输出2097326272
struct in_addr network;
network.s_addr=2097326272;
cout<<inet_ntoa(network)<<endl; // 输出192.168.2.125
return 0;
}
inet_addr函数将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。它失败时返回INADDR_NONE。
inet_ntoa函数将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址
3.服务器开启监听
任务:backlog参数对listen系统调用的影响
#include<sys/socket.h>
int listen(int sockfd,int backlog);
监听队列的长度如果超过backlog,服务器将不受理新的客户连接,客户端也将收到ECONNREFUSED错误信息
#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 bool stop = false;
/*SIGTERM信号的处理函数,触发时结束主程序中的循环*/
static void handle_term( int sig )
{
stop = true;
}
int main( int argc, char* argv[] )
{
signal( SIGTERM, handle_term ); // 如果收到ctrl+c的种子信号,就会终止程序
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
int backlog = atoi( argv[3] );
int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );
/*创建一个IPv4 socket地址*/
struct sockaddr_in address;
bzero( &address, sizeof( address ) ); // 将内存前sizeof(address)个字节清零
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr ); // 需要将IP转化成网络字节序
address.sin_port = htons( port );
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );
/*循环等待连接,直到有SIGTERM信号将它中断*/
ret = listen( sock, backlog );
assert( ret != -1 );
while ( ! stop )
{
sleep( 1 );
}
close( sock );
return 0;
}
argc代表数组argv的元素个数,为int类型。argv的第一个元素argv[0]是可执行程序名称,并且包含程序所在的完整路径。argv的除第一个元素外的其他元素为传入的参数。
argc至少为1,即argv数组至少包含程序名。
netstat -nt|grep 12345 :netstat -nt可查看所有tcp连接信息,包括端口接收和发送的信息的数量、连接双方的地址以及连接所处的状态。grep 12345代表查阅有包含12345的行。
4.接收连接
#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 );
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;
}
5.发送和接收带外数据
发送带外数据
#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 ); //如果表达式为假,那么就打印错误并终止程序。assert一般用于调试,可以添加#define NDEBUG 来禁用assert调用
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;
}
6.TIME_WAIT状态立即被重用
使sock处于TIME_WAIT状态,与之绑定的socket地址也可以立即被重用
#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] );
int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );
int reuse = 1;
setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ) );
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, 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 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;
}
7.tcpdump
tcpdump是命令行形式的抓包工具。可以用wireshark 读取tcpdump 生成的pcap文件,用wireshark的图形化界面分析tcpdump 结果数据。
可参考:tcpdump详解
8.设置发送和接收缓冲区大小
设置发送缓冲区大小
#include <sys/socket.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define BUFFER_SIZE 512
int main( int argc, char* argv[] )
{
if( argc <= 3 )
{
printf( "usage: %s ip_address port_number send_bufer_size\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 sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );
int sendbuf = atoi( argv[3] );
int len = sizeof( sendbuf );
/*先设置TCP发送缓冲区的大小,然后立即读取之*/
setsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof( sendbuf ) );
getsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, ( socklen_t* )&len );
printf( "the tcp send buffer size after setting is %d\n", sendbuf );
if ( connect( sock, ( struct sockaddr* )&server_address, sizeof( server_address ) ) != -1 )
{
char buffer[ BUFFER_SIZE ];
memset( buffer, 'a', BUFFER_SIZE );
send( sock, buffer, BUFFER_SIZE, 0 );
}
close( sock );
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 BUFFER_SIZE 1024
int main( int argc, char* argv[] )
{
if( argc <= 3 )
{
printf( "usage: %s ip_address port_number receive_buffer_size\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 recvbuf = atoi( argv[3] );
int len = sizeof( recvbuf );
setsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof( recvbuf ) );
getsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, ( socklen_t* )&len );
printf( "the receive buffer size after settting is %d\n", recvbuf );
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[ BUFFER_SIZE ];
memset( buffer, '\0', BUFFER_SIZE );
while( recv( connfd, buffer, BUFFER_SIZE-1, 0 ) > 0 ){}
close( connfd );
}
close( sock );
return 0;
}
9.通过主机名字获取IP和端口
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
int main( int argc, char *argv[] )
{
assert( argc == 2 );
char *host = argv[1];
/*获取目标主机地址信息*/
struct hostent* hostinfo = gethostbyname( host ); // 从hostinfo可获取IP
assert( hostinfo );
/*获取daytime服务信息*/
struct servent* servinfo = getservbyname( "daytime", "tcp" ); // 从servinfo可获取端口号
assert( servinfo );
printf( "daytime port is %d\n", ntohs( servinfo->s_port ) );
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = servinfo->s_port;
/*注意下面的代码,因为h_addr_list本身是使用网络字节序的地址列表,所以使用其
中的IP地址时,无须对目标IP地址转换字节序*/
address.sin_addr = *( struct in_addr* )*hostinfo->h_addr_list;
int sockfd = socket( AF_INET, SOCK_STREAM, 0 );
int result = connect( sockfd, (struct sockaddr* )&address, sizeof( address ) );
assert( result != -1 );
char buffer[128];
result = read( sockfd, buffer, sizeof( buffer ) );
assert( result > 0 );
buffer[ result ] = '\0';
printf( "the day tiem is: %s", buffer );
close( sockfd );
return 0;
}