socket基础学习

1.创建socket对象

        在Linux操作系统中,要实现socket通信,通信双方都需要建立各自的socket对象,在应用层,socket对象是一种特殊的文件描述符,可以使用I/O系统调用(read/write)来读写。socket()函数用于创建socket,其函数声明如下:

    //come from /user/include/sys/socket.h
    /* Create a new socket of type TYPE in domain DOMAIN, using protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically. Returns a file descriptor for the new socket, or -1 for errors.*/
    extern int socket (int __domain, int __type, int __protocol)

        此函数如果执行成功,将返回一个打开的socket文件描述符,此时,该socket对象没有绑定任何IP信息,还不能进行通信,如果执行失败,将返回-1。

        第1个参数用来指明此socket对象所使用的地址簇或协议簇,即此对象所使用的通信协议类型。(TCP/IP协议栈是当前网络应用的主流,但BSD socket仍然支持其他类型的协议栈,目前经常使用的有PF_LOCAL(本机协议)、PF_INET(IPv4协议簇)和PF_INET6(IPv6)等。)

        第2个参数为socket的类型。在usr/include/bits/socket.h文件描述了可选用类型,具体如下所示:

//come from /usr/include/bits/socket.h
/* Types of sockets.  */
enum __socket_type
{
 SOCK_STREAM = 1,         /* Sequenced, reliable, connection-based byte streams. */
#define SOCK_STREAM SOCK_STREAM        //可靠的,面向连接的流socket,默认为TCP
 SOCK_DGRAM = 2,/* Connectionless, unreliable datagrams of fixed maximum length. */
#define SOCK_DGRAM SOCK_DGRAM          /不可靠的,面向无连接的数据报socket,默认为UDP
 SOCK_RAM = 3,            /* Raw protocol interface.  */  //原始套接口
.......
};

       Linux描述了6种socket类型,最基本的socket类型为面向连接的数据流方式面向无连接的数据报方式
        面向连接的数据流方式:此类型的套接字是可靠的,在这种套接字中,数据传送和发送顺序一致。 在传输数据之前,通信双方需要建立可靠的链路,在传送过程中,数据作为字节流传输。采用这种方式传输数据的可靠性高,如TCP。

        面向无连接的数据报方式:此类型的套接字是不可靠的,在这种套接字中,数据传送和发送顺序可能不- -致。在发送和接收进程之间没有逻辑连接,每个数据报的发送和处理都是独立的,不同的数据报可以采用不同的路由路径到达目的地,如UDP。

        第3个参数标识采用协议簇中的哪一种协议。 如果将其设置为0,让系统自动选择默认协议,但原始套接口需要指定具体的协议。

2.绑定本地IP地址与端口

        使用 socket() 创建的socket 没有任何约束的,它没有与具体的端口号相关联,在服务器端,需要使用bind函数绑定该套接字。bind 函数声明如下:

//come from /usr/include/bits/socket.h
/* Give the socket FD the local address ADDR (which is LEN bytes long). */
extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)

        此函数将指定socket与对应网络地址(含有IP和箱口信息)绑定,如果执行成功,将返回0,如果执行失败,将返回-1。
        第1个参数是用于绑定本地IP信息的文件描述符
        第2个参数是一个指向sockaddr结构的指针,标识绑定的本地地址信息,如果是IP信息,则要求IP地址必须为本机IP地址,端口必须为一个未占用的本地端口。sockaddr 数据结构定义如下:

//come from /usr/include/linux/socket.h
# define __CONST_SOCKADDR_ARG __const struct sockaddr *
struct sockaddr {
    sa_family_t   sa_family;        /* address family, AF_xxx*/           //协议簇
    char     sa_data[14];           /* 14 bytes of  protocol address*/    //协议地址
};

        struct sockaddr只是提供地址类型规范,根据不同的应用,sockaddr 需要选用不同的类型,如下所述。
        如果是UNIX域套接字,即本机通信的套接字,socket 需要与一个本地socket文件进行绑定。因此,sockaddr 结构体应该选用以下定义:

/ /come from /usr/include/sys/un.h

/* Structure describing the address of an AF_LOCAL (aka AF_UNIX) socket. */
#define __SOCKADDR_COMMON(sa_prefix)   sa_family_t sa_prefix##family //##为宏联接struct sockaddr_un
  {
    _SOCKADDR_ COMMON (sun_);                        //协议 AF_UNIX
    char sun_path[108];        /* Path name. */      //文件路径名
};

        因为是本机通信,因此所选用的协议为AF_UNIX (或AF_LOCAL),其通信需要依靠本地socket类型的文件,因此,第2个成员为socket所使用临时文件路径,此文件名不能与系统文件名冲突,即使用前该文件不能存在, 使用完后最好将该文件删除。

       如果是IPV4网络通信socket需要与本机可用的IP地址和端口绑定,因此,sockaddr结构体应该选用以下定义:

//come from /usr/include/netinet/in.h
/* Structure describing an Internet socket address. */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);                            //协议AF__INET
    in_port_t sin_port;        /* Port number. */        //端口号
    struct in_addr sin_addr;   /* Internet address. */   //IP 地址
    /* Pad to size of 'struct sockaddr'. */
    unsigned char sin_zero[sizeof (struct sockaddr) -
                   _SOCKADDR_COMMON_SIZE -
                   sizeof (in_port_t) -
                   sizeof (struct in_ addr) ];        //预留位,以适应struct sockaddr位
};

        第3个参数是绑定的地址长度,一般使用sizeof求得。因为有多种地址类型,所以需要提示地址大小。

3.监听网络

        绑定了IP及端口信息的socket对象还不能进行TCP方式的通信,因为当前还没有能力监听网络请求。因此,对于面向连接的应用来说,服务器端需要调用listen()函数使该socket对象监听网络。listen函数声明如下:

//come from /usr/include/sys/socket.h
extern int listen (int _fd, int __n)

        如果执行成功,此函数将返回0,如果执行失败,将返回-1。
        第1个参数是绑定了IP及端口信息的socket文件描述符。
        第2个参数为请求排队的最大长度。当有多个客户端程序和服务器端相连时,此值表示可以使用的处于等待的队列长度。
        listen函数将绑定的socket文件描述符变为监听套接字,此时,服务器已经准备接收客户端连接请求了。

4.客户端发起连接

        如果服务器已经监听网络,且客户端创建了socket对象,则客户端可以使用connet函数与服务器端建立连接了。conneet 函数声明如下:

//come from /usr/include/sys/socket.h
extern int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);

        其第1个参数为socket返回的文件描述符。第2个参数储存连接的目的主机地址(包括IP地址和端口),第3个参数为该地址的长度。
        若执行成功,此函数将与地址为addr的服务器建立连接,并返回0,如若失败则返回-1。

5.服务器接收连接

        如果服务器端监听到客户端的连接请求,则需要调用accept函数接受请求,如果没有监听到客户端的连接请求,此函数将处于阻塞状态。accept 函数声明如下:

//come from /usr/include/sys/socket.h
extern int accept (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __addr_len);

        第1个参数是监听网络后的socket文件描述符。
        第2参数为struct sockaddr 类型的地址空间首地址。
        第3个参数为该段地址空间长度,用来存储客户端的IP地址和端口信息,以便为客户端返回数据。
        需要注意的是,如果执行成功,此函数将返回一个新的文件描述符以标识该连接,从而使原来的文件描述符可以继续监听网络等待新的连接,这样便可以实现多客户端。如果执行失败,将返回-1.

6.读/写socket对象

        socket对象是一类特殊的文件, 因此可以使用Linux 系统  I/O系统调用 read 函数来读 socket 对象数据write 函数向 socket 对象写入数据。这两个函数声明如下:

//come from /usr/include/unistd.h
extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur;      //读文件内容
extern ssize_t write (int __fd,  __const void *__buf, size_t __n) __wuZ; //写文件内容

        这两个函数对socket的读写操作默认以阻塞的方式进行。

7.TCP发送接收数据

        此外,Linux 还提供了send()recv()函数来专门实现面向连接的socket对象读写操作
send()函数用来发送数据,具体声明如下:

//come from /usr/include/sys/socket.h
extern ssize_t send (int __fd, __const void *__buf, size_t __n, int __flags);

        第1个参数为发送的目标socket对象。第2个参数为欲发送的数据位置
        第3个参数为数据的大小。第4个参数操作flags,支持的值为0或MSG_OOB (发送带外数据)等。在使用send()函数时将flags设置为0的与调用write()的行为完全相同。
        如果执行成功,此函数将返回发送数据的大小,如果失败,将返回-1。

//come from /usr/include/sys/socket.h
extern ssize_t recv (int __fd, void *__buf, size_t __ n, int __flags);

        此函数的参数含义类似于send()函数各参数含义,其将从fd所指的socket中读取n字节数据到buf中。如果执行成功,此函数将返回接收数据的大小,如果失败,将返回-1。

8.关闭socket对象

        在通信结束后,需要关闭socket对象,一种方法是直接使用 close 函数,此函数声明如下:

//come from /usr/include/unistd.h
/*Close the file descriptor FD. */
extern int close (int __fd);

        另一种方法是调用shutdown()函数来关闭,其有更大的灵活性,shutdomn()函数可以关闭全部或者socket的一端, 其函数声明如下:

//come from /usr/include/sys/socket. h
/* Shut down all or part of the connection open on socket FD.
   HOW determines what to shut down :
    SHUT_RD   = No more receptions;                   //不再接收
    SHUT_WR   = No more transmissions;                //不再发送
    SHUT_RDWR = No more receptions or transmissions.  //接收和发送都不再执行
   Returns 0 on success, -1 for errors. */
extern int shutdown (int __fd, int __how)

        TCP连接是双向的(是可读写的),当使用close()时, 会把读写通道都关闭,有时希望只关闭一个方向,这时需要使用shutdown。系统提供了以下3种关闭方式。

  •  howto=0 : 仅关闭读通道,但是可以继续往socket描述符中写。
  •  howto=1 : 仅关闭写通道,和上面相反,此时只可以读。
  •  howto=2 : 关闭读写通道,和close一样, 完全关闭。

9.获取socket本地及对端信息

        使用getsockmame()函数将获得一个套接字 (这个套接口至少完成了绑定本地IP地址)的本地地址。如果成功则返回0,如果发生错误则返回-1:

extern int getsockname (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __len)

        第1个参数为欲读取信息的socket文件描述符,第2、3个参数分别为存储地址的内存空间的地址和大小
        使用getpeername()函数将取得一个已经连接上的套接字的远程信息,比如IP地址和端口:

extern int getpeername (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __len)

你可能感兴趣的:(网络,linux,服务器)