09---socket编程









2017-05-07  未完待续

=========
套接字地址
linux/socket.h下定义了一种通用套接字地址
struct sockaddr{
    unsigned short    sa_family;    //AF_xxx,即address family
    char                sa_data[14];    //14byte 协议地址
};
AF_INET    TCP/IP协议(IPv4)
AF_INET6  TCP/IP协议 (IPv6)
其中 da_data[] 这个字符型数组保存 AF_xxx 这个协议族使用的地址,对于 TCP/IP 协议族,当我们定义一个套接字地址的时候,可以使用
struct sockadd_in,然后强制转换为  struct sockaddr 类型。

netinet/in.h 中定义了 struct sockaddr_in
struct sockaddr_in{
    unsigned short   sin_family;    // 对TCP/IP协议族,该值就是 AF_INET
    in_port_t           sin_port;      // 2 Byte
    struct in_addr    sin_addr;      // 4 Byte
    unsigned char    sin_zero[8];    // 一般填充0
};

sruct in_addr{
    in_addr_t s_addr;  //4 Byte
};

举例:
struct sockaddr_in mysock;
mysock.sin_family=AF_INET;
mysock.sin_port=0x5000      //采用网络字节序的16进制数,其实就是80 (00000000 01010000 ---> 01010000 00000000),可用
htons() 函数得到
mysock.sin_addr=0x220ca8c0  //采用网络字节序的16进制数,其实就是 192.168.12.34,可通过 inet_addr("192.168.12.34")得到
memset(mysock.sin_zero,0,sizeof(mysock.sin_zero));

函数介绍
#include<string.h>
void *memset(void *s, int c, size_t n);
对于指针 s 指向的内存空间填充 n 个 c 值,并返回 s

#include<arpa/inet.h>
uint16_t htons(uint16_t hostshort);
将主机字节序(host byte  order)转换为网络字节序(network byte order)

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
将点分十进制 IPv4 地址转换为网络字节序的二进制,点分十进制如果非法,则返回-1

===========
创建 socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
成功则返回新创建的 socket 的 file descriptor
失败则返回-1,同时错误代码记入 errno

参数 domain 指定通信使用的协议族  ---> 能否理解为指定3层使用什么协议?这里的协议族和 struct sockaddr 的协议族应该一致?
常见的如下:
AF_INET        IPv4协议
AF_INET6    IPv6协议
AF_UNIX        Local Communication
AF_IPX        IPX协议
AF_X25
AF_ATMPVC   
参数 type 指定了communication semantics   ---> 能否理解为指定4层使用什么协议?
常见的如下:
SOCK_STREAM    有序,可靠,双向,面向连接,配合 AF_INET 就是TCP
SOCK_DGRAM    无连接,不可靠,固定长度,配合 AF_INET 就是UDP   
SOCK_RAW    原始 socket
SOCK_SEQPACKET  固定长度,有序,可靠,面向连接,配合 AF_INET 就是SCTP
参数protocol
一般来讲,domain和type就已经可以确认protocol,此种场景下 protocol 可以设置为0 
当创建 raw socket 的时候,需要指定该参数

===================
建立 socket 连接   --->  指定对端的地址、端口
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
执行成功返回0,错误返回-1,错误代码记入 errno 中

参数 sockfd 是 socket 被创建时候返回的文件描述符
参数 *addr  是 struct sockaddr 类型的目的地址
    基于连接的套接字(如 TCP)只能调用一次,基于非连接的套接字可多次调用,以改变socket与目的地址的绑定,将 *addr 指向的结
构中的 sa_family 置为 AF_UNSPEC 即可解绑
参数 addrlen 是 *addr 指向的结构体的长度

===================
绑定 socket   --->  指定本端的地址及端口( bind 函数一般只有服务器端的程序调用)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
执行成功返回0,错误返回-1,错误代码记入 errno 中

参数意义类似 connect 函数,需要说明的是,如果 *addr 指定的结构体中的 sin_addr 设置为 INADDR_ANY 而非某个指定的 IP,则本
socket 会处理来自所有网络接口上相应端口的(即到达本机所有IP指定端口的)连接请求。

===================
作为客户端,socket() + connect() = 创建 socket 并将其连接到远端 server
作为服务端,socket() + bind() + listen() = 创建 socket 并指定IP及端口,同时启动监听,等待来自于客户端的连接请求

即 listen() 函数将一个 socket 标记为被动 socket 以监听连接请求,下面看下这个函数:
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
执行成功返回0,错误返回-1,错误代码记入 errno 中

参数 sockfd 应该是 type 为 SOCK_STREAM 或 SOCK_SEQPACKET 的 sock 文件描述符,参数 backlog 指定了连接队列(the queue of
pending connections)的最大长度,超过该值后的连接请求将被服务器拒绝。

然而 listen 函数只是监听,真正处理连接请求,需要使用 accept 函数,下面看下这个函数
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);  //只能对面向连接的套接字使用 accept 函数
成功返回一个非负整数,该非负整数可以认为是该 socket 的一个新的描述符(a descriptor for the accepted socket),后续利用这
个新的描述符与客户端通信(如调用 send 向客户端发送信息),而原来的 sockfd 继续监听连接请求。  <--- 对比fork()、dup(),都
是产生一个新的东西。

参数 sockfd 必须是经过 socket/bind/listen 层层处理过的 socket file descriptor
参数 *addr 用来保存发起连接请求的client的地址和端口
参数 *addrlen 是 *addr 所指向的结构体的大小

Linux下 sockfd 指定的套接字被默认设置为阻塞方式,即连接请求队列为空时,accept 函数将被阻塞直到有连接请求到达为止。若
sockfd 指定的套接字被设置为非阻塞方式,则如果连接请求队列长度为 0 时,accept 函数会返回-1,错误代码记入 errno 中。

===============
TCP 套接字的数据传输
send
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
send 函数用于在 TCP 套接字上发送数据,但只能对处于连接状态的套接字使用。
执行成功返回实际发送数据的字节数,错误返回-1,错误代码记入 errno 中

参数 sockfd 为已经建立好连接的套接字(若在服务端调用,则为 accept 返回的新的 socket 值),*buf 指向了待发送数据所在的缓冲区,len 代表要发送的长度(以字节计),参数 flags 一般设置为 0

recv
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
recv 函数用于在 TCP 套接字上接收数据,但只能对处于连接状态的套接字使用。
执行成功返回实际发送数据的字节数,错误返回-1,错误代码记入 errno 中
对于指定的套接字,如果无数据到达( <--- 这是对client说的吧?),则默认 recv 函数将被阻塞,如果设置为非阻塞方式,无数据到达则返回 -1 并将错误码记入 errno

参数 sockfd 为已经建立好连接的套接字(若在服务端调用,则为 accept 返回的新的 socket 值),*buf 指向了待发送数据所在的缓冲区
,len 代表缓冲区的长度(以字节计),参数 flags 一般设置为 0


关闭套接字
#include <unistd.h>
int close(int fd);
关闭套接字,成功返回0,出错返回-1,错误代码记入 errno
参数为 socket file descriptor

#include <sys/socket.h>
int shutdown(int sockfd, int how);
以更小的颗粒度控制套接字的关闭,成功返回0,出错返回-1,错误代码记入 errno
how 指定关闭方式
SHUT_RD  只关闭读
SHUT_WR  只关闭写
SHUT_RDWR  关闭读写,相当于 close

========================
字节序(大小端模式)及其转换函数
大端模式:数据的高字节数据存放在低地址处,数据的低字节数据存放在高地址处
小端模式:数据的低字节数据存放在低地址处,数据的高字节数据存放在高地址处
上图为 0x04030201 的大小端模式存放模式示意图

主机字节序:与硬件平台相关
网络字节序:大端模式
正因为主机字节序随硬件平台不同而不同,所以在规定传输中采用固定统一的字节序,即大端模式。
经常使用的字节序转换函数有host to network long/short,network to host long/short
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

根据上面讲的,我们应该知道 192.168.12.1 这个 IP 地址的网络字节序为:0x01 0C A8 C0 由于我们平时习惯使用点分十进制表示 IP 地址,所以 linux 提供了一系列转换函数供我们使用。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);  // *cp 指向的点分十进制地址转换为网络字节序并存储在 *inp 指向的空间,失败返回 0 
in_addr_t inet_addr(const char *cp);  //*cp 指向的点分十进制地址转换为网络字节序并 return 该值,参数无效则 return INADDR_NONE
in_addr_t inet_network(const char *cp);  //*cp 指向的点分十进制地址转换为主机字节序并 return 该值,参数无效则 return -1
char *inet_ntoa(struct in_addr in);  //将网络字节序的 IP 地址转换为点分十进制的字符串
-------------------------------------
struct in_addr inet_makeaddr(int net, int host);  //参数分别为主机字节序的网络号和主机号,该函数将其合并为一个网络字节序的 IP 地址
in_addr_t inet_lnaof(struct in_addr in);  //提取结构体重网络字节序形式的 IP 地址后获得主机号(主机字节序)
in_addr_t inet_netof(struct in_addr in);  //提取结构体重网络字节序形式的 IP 地址后获得网络号(主机字节序)

==============
获得/设置套接字属性
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,  const void *optval, socklen_t optlen);
成功返回0,出错返回-1,错误代码记入 errno

参数 sockfd 为要操作的套接字 file descriptor
参数 level 为进行套接字选项套作的层次,可选值为 SOL_SOCKET(通用socket),IPPROTO_IP(IP层socket),IPPROTO_TCP(TCP层socket) 等 
参数 optname 为套接字选项的名称
        SO_KEEPALIVE
        SO_RCVLOWAT
        SO_SNDLOWAT
        SO_RCVTIMEO
        SO_SNDTIMEO
        SO_RCVBUF
        SO_SNDBUF
        SO_BINDTODEVICE
        SO_DEBUG
        SO_REUSEADDR
        SO_TYPE
        SO_ACCEPTCONN
        SO_DONTROUTE
        SO_BROADCAST
        SO_ERROR     
参数 *optval 指向套接字选项的值
参数 optlen 为套接字选项的长度

============
多路复用 select()
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,  fd_set *exceptfds, struct timeval *timeout);
若被监控的描述符集合中有描述符有事件发生,则 select 将返回发生事件的描述符的个数

参数 nfds 为要监控的套接字描述符(或文件描述符)的个数(编号为 0~n-1)
参数 *readfds 为需要监控的可读套接字描述符(或文件描述符)集合
参数 *writefds 为需要监控的可写套接字描述符(或文件描述符)集合
参数 *exceptfds 为需要监控的异常套接字描述符(或文件描述符)集合
参数 *timeout 为指定的阻塞时间,指向如下结构
struct timeval {
     long tv_sec;    // 秒
     long tv_usec;  // 微秒
};

sys/select.h 中定义了如下宏来操作文件描述符集合
#define FD_SET(fd, fdsetp)      __FD_SET (fd, fdsetp)    // 将文件描述符加入集合
#define FD_CLR(fd, fdsetp)     __FD_CLR (fd, fdsetp)    // 将文件描述符从集合中删除
#define FD_ISSET(fd, fdsetp)   __FD_ISSET (fd, fdsetp)    // 检测文件描述符是否在集合中
#define FD_ZERO(fdsetp)        __FD_ZERO (fdsetp)    // 将文件描述符集合清空


结构体数组如何初始化???















00000000 01010000 80
01010000 00000000 20480




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值