Linux网络编程基础API---Linux高性能服务器编程学习笔记

Socket API

字节序

字节序包括大端序和小端序。

小端字节序:也叫host主机字节序,PC采用。指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。

大端字节序:也叫network网络字节序,网络通讯时使用,PC发送数据前需要转换为大端。是指一个整数的高位字节(23~31 bit)存储在内存的低地址处,低位字节(0~7 bit)存储在内存的高地址处。

内存一般由低开始存放,小端字节序为最低有效字节在最前面的方式,大端字节序为最高有效字节在最前面的方式
判断机器字节序代码

//强制类型转换
#include<iostream>
using namespace std;
int main() {
    int x=0x01020304;
    char a=(char)(x);
    if(a==0x01){
        cout<<"big"<<endl;
    }
    else if(a=0x04){
        cout<<"small"<<endl;
    }
    else{
        cout<<"unknown"<<endl;
    }

}
//union联合体
#include <iostream>
using namespace std;
union endian
{
    int a;
    char ch;
};
int main()
{
    endian value;
    value.a = 0x1234;
    if (value.ch == 0x12)
        cout << "big"<<endl;
    else if (value.ch == 0x34)
        cout << "small"<<endl;
}

linux中的地址转换API

#include<netinet/in.h>
//long指32bit short 16bit 目前大部分机器long已经8byte也就是64bit
//host to network long
unsigned long int htonl(unsigned long int hostlong);
//host to network short
unsigned short int htons(unsigned short int hostshort);
//network to host long
unsigned long int ntohl(unsigned long int netlong);
//network to host short
unsigned short int ntohs(unsigned short int netshort);

内存对齐

https://www.zhihu.com/question/27862634
字节对齐主要是为了提高内存的访问效率,比如intel 32位cpu,每个总线周期都是从偶地址开始读取32位的内存数据,如果数据存放地址不是从偶数开始,则可能出现需要两个总线周期才能读取到想要的数据,因此需要在内存中存放数据时进行对齐。

内存对齐主要遵循下面三个原则:

结构体变量的起始地址能够被其最宽的成员大小整除
结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节
结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节

  1. 结构体变量的起始地址能够被其最宽的成员大小整除
  2. 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节
  3. 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节
//一般编译器默认对齐大小4,同时考虑结构体中最大的数据进行对齐
#include <iostream>
using namespace std;
struct A{
    char a;//0开始存储,没毛病
    int b;//要从地址1开始存储,但起始地址1不满足条件2,因此补3位,地址4开始存储
    short c;//地址9开始存储,占9-10,但总的大小不满足3,所以补2位
    //共计12Byte
};
int main()
{
    cout<<sizeof(A);
}

socket地址

TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构
体,它们分别用于IPv4和IPv6:

struct sockaddr_in
{
    sa_family_t sin_family;/*地址族:AF_INET*/
    u_int16_t sin_port;/*端口号,要用网络字节序表示*/
    struct in_addr sin_addr;/*IPv4地址结构体,见下面*/
};
struct in_addr
{
    u_int32_t s_addr;/*IPv4地址,要用网络字节序表示*/
};
struct sockaddr_in6
{
    sa_family_t sin6_family;/*地址族:AF_INET6*/
    u_int16_t sin6_port;/*端口号,要用网络字节序表示*/
    u_int32_t sin6_flowinfo;/*流信息,应设置为0*/
    struct in6_addr sin6_addr;/*IPv6地址结构体,见下面*/
    u_int32_t sin6_scope_id;/*scope ID,尚处于实验阶段*/
};
struct in6_addr
{
    unsigned char sa_addr[16];/*IPv6地址,要用网络字节序表示*/
};

sa_family成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型Protocol 对应。常见的协议族和对应的地址族如表所示。
地址族
关于u_int16_t 等自定义类型:

typedef signed char int8_t;   
typedef short int int16_t;  
typedef int int32_t;  
typedef long int int64_t;  
typedef long long int int64_t;  
typedef unsigned char uint8_t;  
typedef unsigned short int uint16_t;  
typedef unsigned int uint32_t;  
typedef unsigned long int uint64_t;   
typedef unsigned long long int  uint64_t;  

字符串与ip地址转换

通常,人们习惯用可读性好的字符串来表示IP地址,比如用点分
十进制字符串表示IPv4地址,以及用十六进制字符串表示IPv6地址。但
编程中我们需要先把它们转化为整数(二进制数)方能使用。而记录
日志时则相反,我们要把整数表示的IP地址转化为可读的字符串。下
面3个函数可用于用点分十进制字符串表示的IPv4地址和用网络字节序
整数表示的IPv4地址之间的转换:

#include<arpa/inet.h>
//inet_addr函数将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。它失败时返回INADDR_NONE。
in_addr_t inet_addr(const char*strptr);
//inet_aton函数完成和inet_addr同样的功能,但是将转化结果存储于参数inp指向的地址结构中。它成功时返回1,失败则返回0。
int inet_aton(const char*cp,struct in_addr*inp);
//inet_ntoa函数将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。但需要注意的是,该函数内部用一个静态变量存储转化结果,函数的返回值指向该静态内存,因此inet_ntoa是不可重入的。
char*inet_ntoa(struct in_addr in);

//下面这对更新的函数也能完成和前面3个函数同样的功能,并且它们同时适用于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_t
cnt);

inet_pton函数将用字符串表示的IP地址src(用点分十进制字符串表示的IPv4地址或用十六进制字符串表示的IPv6地址)转换成用网络字节序整数表示的IP地址,并把转换结果存储于dst指向的内存中。其中,af参数指定地址族,可以是AF_INET或者AF_INET6。inet_pton成功时返回1,失败则返回0并设置errno.
inet_ntop函数进行相反的转换,前三个参数的含义与inet_pton的参数相同,最后一个参数cnt指定目标存储单元的大小。下面的两个宏能帮助我们指定这个大小(分别用于IPv4和IPv6)

创建socket

#include<sys/types.h>#
include<sys/socket.h>
int socket(int domain,int type,int protocol);
//domain:指定协议族,网络编程可选PF_INET(Protocol Family of Internet,用于IPv4)PF_INET6(用于IPv6)
//type 用于区分tcp与udp,指定type参数指定服务类型。服务类型主要有SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据报)服务。对TCP/IP协议族而言,其值取SOCK_STREAM表示传输层使用TCP协议,取SOCK_DGRAM表示传输层使用UDP协议。
//protocol参数是在前两个参数构成的协议集合下,再选择一个具体的协议。不过这个值通常都是唯一的(前两个参数已经完全决定了它的值)。几乎在所有情况下,我们都应该把它设置为0,表示使用默认协议
//socket系统调用成功时返回一个int型socket文件描述符,失败则返回-1并设置errno。

命名socket—bind函数

创建socket时,我们给它指定了地址族,但是并未指定使用该地址族中的哪个具体socket地址。将一个socket与socket地址绑定称为给socket命名。在服务器程序中,我们通常要命名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);
//bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数指出该socket地址的长度。

bind成功时返回0,失败则返回-1并设置errno。其中两种常见的errno是EACCES和EADDRINUSE,它们的含义分别是:

  1. EACCES:被绑定的地址是受保护的地址,仅超级用户能够访问。比如普通用户将socket绑定到知名服务端口(端口号为0~1023)上时,bind将返回EACCES错误。
  2. EADDRINUSE,被绑定的地址正在使用中。比如将socket绑定到一个处于TIME_WAIT状态的socket地址。

监听socket—listen函数

socket被命名之后,还不能马上接受客户连接,我们需要使用如下系统调用来创建一个监听队列以存放待处理的客户连接:

#include<sys/socket.h>
int listen(int sockfd,int backlog);

sockfd参数指定被监听的socket。backlog参数提示内核监听队列的最大长度。最大长度为backlog+1.

接受连接–accept函数

#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen);

sockfd参数是执行过listen系统调用的监听socket。addr参数用来获取被接受连接的远端socket地址,该socket地址的长度由addrlen参数指出。accept成功时返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通信。accept失败时返回-1并设置errno。
accept只是从监听队列中取出连接,而不论连接处于何种状态(如ESTABLISHED状态和CLOSE_WAIT状态),更不关心任何网络状况的变化,把执行过listen调用、处于LISTEN状态的socket称为监听socket,而所有处于ESTABLISHED状态的socket则称为连接socket

发起连接–connet函数

如果说服务器通过listen调用来被动接受连接,那么客户端需要通过如下系统调用来主动与服务器建立连接:

#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参数则指定这个地址的长度。
connect成功时返回0。一旦成功建立连接,sockfd就唯一地标识了这个连接,客户端就可以通过读写sockfd来与服务器通信。connect失败则返回-1并设置errno。其中两种常见的errno是ECONNREFUSED和ETIMEDOUT,它们的含义如下:

  1. ECONNREFUSED,目标端口不存在,连接被拒绝。
  2. ETIMEDOUT,连接超时

关闭连接—close和shutdown

#include<unistd.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的行为,它可取表5-3中的某个值。
howto

发送和接收数据-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即可。recv成功时返回实际读取到的数据的长度,它可能小于我们期望的长度len。因此我们可能要多次调用recv,才能读取到完整的数据。
recv可能返回0,这意味着通信对方已经关闭连接了。recv出错时返回-1并设置errno。

send往sockfd上写入数据,buf和len参数分别指定写缓冲区的位置和大小。send成功时返回实际写入的数据的长度,失败则返回-1并设置errno。
flag可选参数:
flag
一个连接实例

//客户端 send
#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;
}

//服务端 recv
#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;
}


发送和接收数据-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,intflags,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参数则指定该地址的长度。
这两个系统调用的flags参数以及返回值的含义均与send/recv系统调用的flags参数及返回值相同。

recvfrom/sendto系统调用也可以用于面向连接(STREAM)的socket的数据读写,只需要把最后两个参数都设置为NULL以忽略发送端/接收端的socket地址(因为我们已经和对方建立了连接,所以已经知道其socket地址了)。

通用数据读写函数

#include<sys/socket.h>
ssize_t recvmsg(int sockfd,struct msghdr*msg,int flags);
ssize_t sendmsg(int sockfd,struct msghdr*msg,int flags);

struct msghdr
{
    void*msg_name;/*socket地址*/
    socklen_t msg_namelen;/*socket地址的长度*/
    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;/*这块内存的长度*/
};

地址信息函数

https://blog.csdn.net/workformywork/article/details/24554813

#include<sys/socket.h>
int getsockname(int sockfd,struct sockaddr *address,socklen_t address_len);
int getpeername(int sockfd,struct sockaddr*address,socklen_t*address_len);

getsockname获取sockfd对应的本端socket地址,并将其存储于address参数指定的内存中,该socket地址的长度则存储于address_len参数指向的变量中。
getpeername获取sockfd对应的远端socket地址,其参数及返回值的含义与getsockname的参数及返回值相同。

#include<netdb.h>
struct hostent*gethostbyname(const char*name);
struct hostent*gethostbyaddr(const void*addr,size_t len,inttype);

#include<netdb.h>
struct hostent
{
	char*h_name;/*主机名*/
	char**h_aliases;/*主机别名列表,可能有多个*/
	int h_addrtype;/*地址类型(地址族)*/
	int h_length;/*地址长度*/
	char**h_addr_list/*按网络字节序列出的主机IP地址列表*/
};

gethostbyname函数根据主机名称获取主机的完整信息,gethostbyaddr函数根据IP地址获取主机的完整信息。gethostbyname函数通常先在本地的/etc/hosts配置文件中查找主机,如果没有找到,再去访问DNS服务器。
name参数指定目标主机的主机名,addr参数指定目标主机的IP地址,len参数指定addr所指IP地址的长度,type参数指定addr所指IP地址的类型,其合法取值包括AF_INET(用于IPv4地址)和AF_INET6(用于IPv6地址)。

socket选项

#include<sys/socket.h>
int getsockopt(int sockfd,int level,intoption_name,void*option_value,socklen_t*restrict option_len);
int setsockopt(int sockfd,int level,int option_name,constvoid*option_value,socklen_t option_len);

sockfd参数指定被操作的目标socket。level参数指定要操作哪个协议的选项(即属性),比如IPv4、IPv6、TCP等,一般设成SOL_SOCKET 以存取socket 层。option_name参数则指定选项的名字。
socket选项

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值