• Linux网络编程之套接字基础


    1.套接字的基本结构

    struct sockaddr

    这个结构用来存储套接字地址。

    数据定义:

    struct sockaddr {

    unsigned short sa_family; /* address族, AF_xxx */

    char sa_data[14]; /* 14 bytes的协议地址 */

    };

    sa_family 一般来说,都是“AFINET”。

    sa_data 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一

    切的。

    为了处理struct sockaddr, 程序员建立了另外一个相似的结构 struct sockaddr_in:

    struct sockaddr_in (“in” 代表 “Internet”)

    struct sockaddr_in {

    short int sin_family; /* Internet地址族 */

    unsigned short int sin_port; /* 端口号 */

    struct in_addr sin_addr; /* Internet地址 */

    unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/

    };

    这个结构提供了方便的手段来访问socket address(struct sockaddr)结构中的每一个元

    2.套接字字节转换程序的列表:

    l htons()——“Host to Network Short”主机字节顺序转换为网络字节顺序(对无符号

    短型进行操作4 bytes)

    l htonl()——“Host to Network Long” 主机字节顺序转换为网络字节顺序(对无符

    号长型进行操作8 bytes)

    l ntohs()——“Network to Host Short “ 网络字节顺序转换为主机字节顺序(对无符

    号短型进行操作4 bytes)

    l ntohl()——“Network to Host Long “ 网络字节顺序转换为主机字节顺序(对无符

    号长型进行操作8 bytes)

    3. IP 地址转换

    Linux 系统提供和很多用于转换IP 地址的函数.首先,假设你有一个struct sockaddr_in ina,并且你的IP 是166.111.69.52 ,你想把你的IP 存储到ina 中。你可以使用的函数: inet_addr() ,它能够把一个用数字和点表

    示IP 地址的字符串转换成一个无符号长整型。你可以像下面这样使用它:

    ina.sin_addr.s_addr = inet_addr(“166.111.69.52”);

    注意:

    l inet_addr() 返回的地址已经是网络字节顺序了,你没有必要再去调用htonl() 函数

    反过来,如果你有一个struct in_addr 并且你想把它代表的IP 地址打印出来(按照数字.数字.数字.数字的格式),那么你可以使用函数inet_ntoa()(“ntoa”代表“Network to ASCII”),它会把struct in_addr 里面存储的网络地址以数字.数字.数字.数字的格式。

    l inet_ntoa() 使用struct in_addr 作为一个参数,不是一个长整型值。

    4.基本套接字调用

    socket() 函数

    取得套接字描述符

     socket 函数的定义是下面这样子的:

    #include <sys/types.h>

    #include <sys/socket.h>

    int socket(int domain , int type , int protocol);

    bind() 函数

    bind()函数可以帮助你指定一个套接字使用的端口。

    当你使用socket() 函数得到一个套接字描述符,你也许需要将socket 绑定上一个你的

    机器上的端口。

    l 当你需要进行端口监听 listen()操作,等待接受一个连入请求的时候,一般都需要

    经过这一步。比如网络泥巴(MUD),Telnet a.b.c.d 4000。

    l 如果你只是想进行连接一台服务器,也就是进行 connect() 操作的时候,这一步

    并不是必须的。

    bind()的系统调用声明如下:

    #include <sys/types.h>

    #include <sys/socket.h>

    int bind (int sockfd , struct sockaddr *my_addr , int addrlen) ;

    参数说明:

    l sockfd 是由socket()函数返回的套接字描述符。

    l my_addr 是一个指向struct sockaddr 的指针,包含有关你的地址的信息:名称、

    端口和IP 地址。

    l addrlen 可以设置为sizeof(struct sockaddr)。

    connect()函数

    让我们花一点时间来假设你是一个Telnet 应用程序。你的使用者命令你建立一个套接

    字描述符。你遵从命令,调用了socket()。然后,使用者告诉你连接到“166.111.69.52”

    的23 端口(标准的Telnet 端口)⋯⋯你应该怎么做呢?

    你很幸运:Telnet 应用程序,你现在正在阅读的就是套接字的进行网络连接部分:

    connect()。

    connect() 函数的定义是这样的:

    #include <sys/types.h>

    #include <sys/socket.h>

    int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);

    connect()的三个参数意义如下:

    l sockfd :套接字文件描述符,由socket()函数返回的。

    l serv_addr 是一个存储远程计算机的IP 地址和端口信息的结构。

    l addrlen 应该是sizeof(struct sockaddr)。

    listen() 函数

    listen()函数是等待别人连接,进行系统侦听请求的函数。当有人连接你的时候,你有

    两步需要做:通过listen()函数等待连接请求,然后使用accept()函数来处理。(accept()函数

    在下面介绍)。

    listen()函数调用是非常简单的。函数声明如下:

    #include <sys/socket.h>

    int listen(int sockfd, int backlog);

    listen()函数的参数意义如下:

    l sockfd 是一个套接字描述符,由socket()系统调用获得。

    l backlog 是未经过处理的连接请求队列可以容纳的最大数目。

    backlog 具体一些是什么意思呢?每一个连入请求都要进入一个连入请求队列,等待

    listen 的程序调用accept()(accept()函数下面有介绍)函数来接受这个连接。当系统还没有

    调用accept()函数的时候,如果有很多连接,那么本地能够等待的最大数目就是backlog 的

    数值。你可以将其设成5 到10 之间的数值

    accept()函数

    函数accept()有一些难懂。当调用它的时候,大致过程是下面这样的:

    l 有人从很远很远的地方尝试调用 connect()来连接你的机器上的某个端口(当然是

    你已经在listen()的)。

    l 他的连接将被 listen 加入等待队列等待accept()函数的调用(加入等待队列的最多

    数目由调用listen()函数的第二个参数backlog 来决定)。

    l 你调用 accept()函数,告诉他你准备连接。

    l accept()函数将回返回一个新的套接字描述符,这个描述符就代表了这个连接!

    好,这时候你有了两个套接字描述符,返回给你的那个就是和远程计算机的连接,而

    第一个套接字描述符仍然在你的机器上原来的那个端口上listen()。

    这时候你所得到的那个新的套接字描述符就可以进行send()操作和recv()操作了。

    下面是accept()函数的声明:

    #include <sys/socket.h>

    int accept(int sockfd, void *addr, int *addrlen);

    accept()函数的参数意义如下:

    l sockfd 是正在listen() 的一个套接字描述符。

    l addr 一般是一个指向struct sockaddr_in 结构的指针;里面存储着远程连接过来的

    计算机的信息(比如远程计算机的IP 地址和端口)

    send()、recv()函数

    这两个函数是最基本的,通过有连接的套接字流进行通讯的函数。

    send() 函数的声明:

    #include <sys/types.h>

    #include <sys/socket.h>

    int send(int sockfd, const void *msg, int len, int flags);

    send 的参数含义如下:

    l sockfd 是代表你与远程程序连接的套接字描述符。

    l msg 是一个指针,指向你想发送的信息的地址。

    l len 是你想发送信息的长度。

    l flags 发送标记。一般都设为0

    函数recv()调用在许多方面都和send()很相似,下面是recv()函数的声明:

    #include <sys/types.h>

    #include <sys/socket.h>

    int recv(int sockfd, void *buf, int len, unsigned int flags);

    recv()的参数含义如下:

    l sockfd 是你要读取数据的套接字描述符。

    l buf 是一个指针,指向你能存储数据的内存缓存区域。

    l len 是缓存区的最大尺寸。

    l flags 是recv() 函数的一个标志,一般都为0 (具体的其他数值和含义请参考recv()

    的man pages)。

    recv() 返回它所真正收到的数据的长度

    sendto() 和recvfrom() 函数

    这两个函数是进行无连接的UDP 通讯时使用的。使用这两个函数,则数据会在没有

    建立过任何连接的网络上传输。因为数据报套接字无法对远程主机进行连接,想想我们在

    发送数据前需要知道些什么呢?

    对了!是远程主机的IP 地址和端口!

    下面是sendto()函数和recvfrom()函数的声明:

    #include <sys/types.h>

    #include <sys/socket.h>

    int sendto(int sockfd, const void *msg, int len, unsigned int flags,

    const struct sockaddr *to, int tolen);

    和你所看到的一样,这个函数和send()函数基本一致。

    l sockfd 是代表你与远程程序连接的套接字描述符。

    l msg 是一个指针,指向你想发送的信息的地址。

    l len 是你想发送信息的长度。

    l flags 发送标记。一般都设为0。(你可以查看send 的man pages 来获得其他的参

    数值并且明白各个参数所代表的含义)

    l to 是一个指向struct sockaddr 结构的指针,里面包含了远程主机的IP 地址和端口

    数据。

    l tolen 只是指出了struct sockaddr 在内存中的大小sizeof(struct sockaddr)。

    和send()一样,sendto()返回它所真正发送的字节数(当然也和send()一样,它所真正

    发送的字节数可能小于你所给它的数据的字节数)。当它发生错误的时候,也是返回 –1 ,

    同时全局变量errno 存储了错误代码。

    同样的,recv()函数和recvfrom()函数也基本一致。

    recvfrom()的声明为:

    #include <sys/types.h>

    - 156 - Linux网络编程

    #include <sys/socket.h>

    int recvfrom(int sockfd, void *buf, int len, unsigned int flags

    struct sockaddr *from, int *fromlen);

    其参数含义如下:

    l sockfd 是你要读取数据的套接字描述符。

    l buf 是一个指针,指向你能存储数据的内存缓存区域。

    l len 是缓存区的最大尺寸。

    l flags 是recv() 函数的一个标志,一般都为0 (具体的其他数值和含义请参考recv()

    的man pages)。

    l from 是一个本地指针,指向一个struct sockaddr 的结构(里面存有源IP 地址和端

    口数).

    l fromlen 是一个指向一个int 型数据的指针,它的大小应该是sizeof ( struct

    sockaddr).当函数返回的时候,formlen 指向的数据是form 指向的struct sockaddr 的实际

    大小.

    recvfrom() 返回它接收到的字节数,如果发生了错误,它就返回-1

    close()和shutdown()函数

    程序进行网络传输完毕后,你需要关闭这个套接字描述符所表示的连接。实现这个非

    常简单,只需要使用标准的关闭文件的函数:close()。

    使用方法:

    close(sockfd);

    执行close()之后,套接字将不会在允许进行读操作和写操作。任何有关对套接字描述

    符进行读和写的操作都会接收到一个错误。

    如果你想对网络套接字的关闭进行进一步的操作的话,你可以使用函数shutdown()。

    它允许你进行单向的关闭操作,或是全部禁止掉。

    shutdown()的声明为:

    #include <sys/socket.h>

    int shutdown(int sockfd, int how);

    它的参数含义如下:

    l sockfd 是一个你所想关闭的套接字描述符.

    l how 可以取下面的值。0 表示不允许以后数据的接收操;1 表示不允许以后数据

    的发送操作;2 表示和close()一样,不允许以后的任何操作(包括接收,发送数据)

    shutdown() 如果执行成功将返回0,如果在调用过程中发生了错误,它将返回–1,全

    局变量errno 中存储了错误代码.

    如果你在一个未连接的数据报套接字上使用shutdown() 函数(还记得可以对数据报套

    接字UDP 进行connect()操作吗?),它将什么也不做.

    setsockopt() 和getsockopt() 函数

    Linux 所提供的socket 库含有一个错误(bug)。此错误表现为你不能为一个套接字重

    新启用同一个端口号,即使在你正常关闭该套接字以后。例如,比方说,你编写一个服务

    器在一个套接字上等待的程序.服务器打开套接字并在其上侦听是没有问题的。无论如何,

    总有一些原因(不管是正常还是非正常的结束程序)使你的程序需要重新启动。然而重启

    动后你就不能把它绑定在原来那个端口上了。从bind()系统调用返回的错误代码总是报告

    说你试图连接的端口已经被别的进程所绑定。

    问题就是Linux 内核在一个绑定套接字的进程结束后从不把端口标记为未用。在大多

    数Linux/UNIX 系统中,端口可以被一个进程重复使用,甚至可以被其它进程使用。

    在Linux 中绕开这个问题的办法是,当套接字已经打开但尚未有连接的时候用

    setsockopt()系统调用在其上设定选项(options)。setsockopt() 调用设置选项而getsockopt()

    从给定的套接字取得选项。

    这里是这些调用的语法:

    #include<sys/types.h>

    #include<sys/socket.h>

    int getsockopt(int sockfd, int level, int name, char *value, int *optlen);

    int setsockopt(int sockfd, int level, int name, char *value, int *optlen);

    下面是两个调用的参数说明:

    l sockfd 必须是一个已打开的套接字。

    l level 是函数所使用的协议标准(protocol level)(TCP/IP 协议使用IPPROTO_TCP,

    套接字标准的选项实用SOL_SOCKET)。

    l name 选项在套接字说明书中(man page)有详细说明。

    l value 指向为getsockopt()函数所获取的值,setsockopt()函数所设置的值的地址。

    l optlen 指针指向一个整数,该整数包含参数以字节计算的长度。

    现在我们再回到Linux 的错误上来.当你打开一个套接字时必须同时用下面的代码段

    来调用setsockopt()函数:

    /* 设定参数数值 */

    opt = 1; len = sizeof(opt);

    /* 设置套接字属性 */

    setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);

    getpeername()函数

    这个函数可以取得一个已经连接上的套接字的远程信息(比如IP 地址和端口),告诉

    你在远程和你连接的究竟是谁.

    它的声明为:

    #include <sys/socket.h>

    int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

    下面是参数说明:

    l sockfd 是你想取得远程信息的那个套接字描述符。

    l addr 是一个指向struct sockaddr (或是struct sockaddr_in)的指针。

    l addrlen 是一个指向int 的指针,应该赋于sizeof(struct sockaddr)的大小。

    如果在函数执行过程中出现了错误,函数将返回 –1 ,并且错误代码储存在全局变量

    errno 中。

    当你拥有了远程连接用户的IP 地址,你就可以使用inet_ntoa() 或gethostbyaddr()来输

    出信息或是做进一步的处理。

    gethostname()函数

    gethostname()函数可以取得本地主机的信息.它比getpeername()要容易使用一些。

    它返回正在执行它的计算机的名字。返回的这个名字可以被gethostbyname()函数使用,

    由此可以得到本地主机的IP 地址。

    下面是它的声明:

    #include <unistd.h>

    int gethostname(char *hostname, size_t size);

    参数说明如下:

    l hostname 是一个指向字符数组的指针,当函数返回的时候,它里面的数据就是本

    地的主机的名字.

    l size 是hostname 指向的数组的长度.

    函数如果成功执行,它返回0,如果出现错误,则返回–1,全局变量errno 中存储着错

    误代码。

     

  • 相关阅读:
    【Vue】 修饰符sync
    【VUE】vue路由跳转的方式
    【Element】elementui的Cascader 级联选择器,在懒加载的时候数据无法回显的解决方案
    【ES6】利用ES6 Set 将数组去重
    【.NETCORE】Refit 框架
    【.NETCORE】ASP.NET Core SignalR
    【Visual Studio Code】驼峰翻译助手
    VueX(Vue状态管理模式)
    hdmi 随笔
    ad 差分布线 等长布线
  • 原文地址:https://www.cnblogs.com/kunhu/p/3594744.html
Copyright © 2020-2023  润新知