网络编程教程(四)Linux网络编程基础API

        首先介绍Linux下整个的网络编程流程:

一、socket地址API

1.主机字节序和网络字节序

        字节序分为大端字节序(big endian)和小端字节序(little endian)。大端字节序是指一个整数的搞我字节存储在内存的低地址处,低位字节存储在内存的高地址处。小端字节序则是整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。

        一般,大端字节序也称为网络字节序,小端字节序也称为主机字节序。

#include <netinet/in.h>
unsigned long int htonl(unsigned long int hostlog);//将长整型的主机字节序数据转为网络字节序数据
unsigned short int htons(unsigned short int hostshort);  //
unsigned long  int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);

2.通用socket地址

#include <bits/socket.h>
struct sockaddr
{
        sa_family_t sa_family;    //地址族
        char sa_data[14];         
};

        

3.专用socket地址

       上面这个通用socket地址结构体显然很不好用,比如设置与获取IP地址和端口号就需要执行繁琐的位操作。TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,他们分别用于IPv4和IPv6,这里只介绍常用的IPv4socket地址:

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地址,要用网络字节序表示
};

        所有专用socket地址类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制类型转换),因为所有socket编成接口使用的地址参数类型都是sockaddr。

4.IP地址转换函数

#include <arpa/inet.h>

//将用点分十进制字符串表示的IPv4地址转化为网络字节序整数表示的IPv4地址,失败时返回INADDR_NONE.
in_addr_t inet_addr(const char* strptr);

//将点分十进制字符串表示的IPv4地址转化为网络字节序表示的IPv4地址,存放于inp中
int inet_aton(const char *cp, struct in_addr *inp);

//将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址
char* inet_ntoa(struct in_addr in);

//将字符串表示的IP地址src转为网络字节序整数表示的IP地址,并存放在dst中,其中af指定地址族
int inet_pton(int af, const char* src, void *dst);

//与上面相反
const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt);


二、socket基础API

1.创建socket

        socket是一个可读、可写、可控制和可关闭的文件描述符。下面的socket系统调用创建一个socket:

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

参数:
    domain  :指定协议族,一般用PF_INET
    type    :指定服务类型,主要有SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据报)服务
    protocol:一般设置为0,表示使用默认协议。因为前两个参数已经唯一确定了是使用TCP还是UDP协议

返回值:
    socket系统调用成功时返回一个socket文件描述符,失败则返回-1并设置errno.

2.命名socket

         创建socket时,指定了地址族,但是没有指定具体使用哪个socket地址,将一个socket与socket地址绑定称为给socket命名。在服务器程序中要命名socket,因为只有命名后客户端才能知道该如何连接它。客户端通常不需要命名socket,而是采用匿名方式,即使用操作系统自动分配的socket地址。

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

功能:
    将my_addr所指的socket地址分配给未命名的sockfd
参数:
    sockfd :调用socket()创建的socket文件描述符
    my_addr:socket地址
    addrlen:my_addr的长度
返回值:
    bind成功时返回0,失败则返回-1,并设置errno.其中两种常见的errno是EACCES和EADDRINUSE,EACCES是指被绑定的地址是受保护的
地址,而EADDRINUSE是指被绑定的地址正在使用中。

3.监听socket

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

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

功能:
    创建一个监听队列用于存放待处理的连接
参数:
    sockfd :指定被监听的socket
    backlog:指示内核监听队列的最大长度
返回值:
    listen成功时返回0,失败则返回-1并设置errno。

4.接收连接

        每当连接到来时就会被放入listen()系统调用创建的监听队列中,这时需要调用accept()从监听队列中接受一个连接:

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

功能:
    从listen监听队列中接受一个连接
参数:
    sockfd :执行过listen系统调用的监听socket。
    addr   :用于获取被接受连接的源端socket地址
    addrlen:指定addr的长度
返回值:
    成功时返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可以通过读写该socket来与被接受连接的客户端通信。失败时返回-1并设置errno.

5.发起连接

        服务器通过listen调用来被动接受连接,那么客户端需要通过connect()系统调用来与服务器建立连接:

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

功能:
    与服务器建立连接
参数:
    sockfd  :由socket()系统调用返回
    servAddr:服务器监听的socket地址
    addrlen :指定servAddr的长度
返回值:
    connect成功时返回0.一旦成功建立连接,sockfd就唯一标识了这个连接,客户端就可以通过读写该sockfd来与服务器通信。connect失败时返回-1并设置errno,两种常见的errno是ECONNREFUSED和ETIMEOUT,ECONNREFUSED表示目标端口不存在,ETIMEOUT表示连接超时。

6.关闭连接

       关闭一个连接实际上是关闭该连接对应的socket。

#include <unistd.h>
int close(int fd);

功能:
    关闭文件描述符。close系统调用并非总是关闭一个连接,而是将fd引用计数减1,只有当fd引用计数为0时,才真正关闭连接。
参数:
    fd:指定要关闭的文件描述符
返回值:
    成功时返回0,失败时返回-1并设置errno.

三、数据读写

1.TCP数据读写

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:
    从sockfd中读取len字节的数据到buf中
参数:
    sockfd:建立连接后的文件描述符
    buf   :读缓冲区的位置
    len   :读缓冲区的大小
    flags :一般设置为0
返回值:
    recv成功时返回实际读取到的数据的长度,它可能返回0,这意味着通信对方已经关闭连接,出错时返回-1并设置errno.

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:
    将buf中len字节数据写入sockfd中
参数:
    sockfd:建立连接后的文件描述符
    buf   :写缓冲区的位置
    len   :写缓冲区的大小
    flags :一般设置为0
返回值:
    send成功时返回实际写入数据的长度,失败时则返回-1并设置errno.

2.UDP数据读写

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, 
                 struct sockaddr *srcaddr, socklen_t *addrlen);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               struct sockaddr *destaddr, socklen_t addrlen);

3.通用数据读写函数

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

4.socket选项

socket选项作用
SO_REUSEADDR强制使用被处于TIME_WAIT状态的连接占用的socket地址
SO_RCVBUF设置接收缓冲区大小
SO_SNDBUF设置发送缓冲区大小
SO_RCVLOWAT接收缓冲区的低水位标记
SO_SNDLOWAT发送缓冲区的低水位标记
SO_LINGER控制close()系统调用在关闭TCP连接时的行为

四、实例代码分析

客户端代码:

#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 );   //创建socket,使用TCP协议
    assert( sockfd >= 0 );

    //发起连接
    int ret = connect( sockfd, (struct sockaddr*)&server_address, sizeof(server_address));
    if(ECONNREFUSED == ret)
    {
        printf("目标端口不存在,连接被拒绝.\n");
    }
    else if(ETIMEOUT == ret)
    {
        printf("连接超时.\n");
    }
    else if(ret < 0)
    {
        printf("connetion failed.\n");
    }
    else
    {
        printf( "send oob data out\n" );
        const char* normal_data = "123";
        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>

const int 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 );   //创建socket,使用TCP协议
    assert( sock >= 0 );

    //命名socket
    int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    //监听socket
    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 );

        close( connfd );   //关闭连接socket
    }

    close( sock );   //关闭监听socket
    return 0;
}

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值