apue读书笔记之socket

来源:互联网 发布:北京unity3d培训 编辑:程序博客网 时间:2024/05/19 03:28

 

创建 Socket:

 

#include<sys/socket.h>

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

 

domain: AF_INET AF_UNIX(AF_LOCAL 在某些系统)规定通信的本质,比如地址的格式

type: SOCK_STREAM SOCK_DGRAM SOCK_RAW SOCK_SEQPACKET 规定数据的传输方式

protocol: 通常为0,使用默认值。

 

四种 type 区别:

感觉听上去很简单,其实还是需要理解的

  • SOCK_DGRAM: 数据报方式。无连接,发出去的数据不保证能收到,但是收到的字节数和发出去的肯定相等。
  • SOCK_STREAM: 面向连接的流。注意是流,所以一次收到的字节数和一次发出的字节数不一定相等。有可能一次发出的数据,要分几次收到;有可能几次发出的数据,一次都能收到。但是肯定是面向连接的。消息之间没有界限。
  • SOCK_SEQPACKET: 面向连接的基于消息的传输。消息之间是有界限的。协议: SCTP
  • SOCK_RAW: 数据报接口,面向IP层,需要自己添加协议头什么的。需要权限

 

Socket 常用的普通IO函数:

close fcntl ioctl select poll dup/dup2 read/readvwrite/writev

 

关闭 Socket:

close 直接关闭

shutdown 可以关闭socket某个方向的传输

 

#include <sys/socket.h>

int shutdown (int sockfd, int how);

Returns: 0 if OK, 1 on error

how: SHUT_RD/SHUT_WR/SHUT_RDWR

 

为什么需要shutdown?

  • close 需要等待所有引用的描述符都关闭才最终关闭,shutdown不会
  • shutdown可以关闭指定方向的传输

 

设置socket选项

#include<sys/socket.h>

intsetsockopt(int sockfd, int level, int option, const void *val,socklen_t len);

Returns: 0 if OK, 1 on error

intgetsockopt(int sockfd, int level, int option, void *restrict val, socklen_t*restrict lenp);

 

常用的选项:

SO_REUSEADDR 防止服务器突然重启时重新绑定失败。

 

地址格式

structsockaddr {

     sa_family_t   sa_family;  /* address family */

     char          sa_data[];   /* variable-length address */

     .

     .

     .

   };

 

这个是POSIX规定的格式,系统调用用的都是这个接口,所以需要强制转换。不同的平台会有不同的实现方式。

 

AF_INET地址格式:

#include<netinet/in.h>

structin_addr {
     in_addr_t       s_addr;       /* IPv4 address */
   };

structsockaddr_in {
     sa_family_t    sin_family;   /* address family */
     in_port_t      sin_port;     /* port number */
     struct in_addr sin_addr;     /* IPv4 address */
   };

 

上面是 Single UNIXSpecification 规定的标准,每个平台有自己的实现。Linux平台的实现如下:

structsockaddr_in {
     sa_family_t     sin_family;     /* address family */
     in_port_t       sin_port;       /* port number */
     struct in_addr  sin_addr;       /* IPv4 address */
     unsigned char   sin_zero[8];    /* filler */
   };

其中 sin_zero 必须全部设为0.

 

地址和可读字符串之间的转换:

#include<arpa/inet.h>

constchar *inet_ntop(int domain, const void *restrict addr, char *restrict str,socklen_t size);

Returns: pointer to address string on success, NULL onerror

intinet_pton(int domain, const char *restrict str, void *restrict addr);

Returns: 1 on success, 0 if the format is invalid, or -1on error

 

size 表示能容纳输出字符的数组大小(INET_ADDRSTRLEN  或者 INET6_ADDRSTRLEN)

domain 只能是 AF_INET 或者AF_INET6

 

地址查找:

最常用的函数:

 

#include<sys/socket.h>

#include<netdb.h>

intgetaddrinfo(const char *restrict host, const char *restrict service, conststruct addrinfo *restrict hint, struct addrinfo **restrict res);

Returns: 0 if OK, nonzero error code on error

voidfreeaddrinfo(struct addrinfo *ai);

 

返回错误的时候错误信息由下面的函数取得:

 

#include<netdb.h>

constchar *gai_strerror(int error);

error 函数的返回值

 

getnameinfo 通过地址来查找主机名和服务名:

#include<sys/socket.h>

#include<netdb.h>

intgetnameinfo(const struct sockaddr *restrict addr, socklen_t alen, char*restrict host, socklen_t hostlen, char *restrict service, socklen_t servlen,unsigned int flags);

Returns: 0 if OK, nonzero on error

 

注意:

host 指向 主机名称或者 ip地址

service 指向服务名称或者端口号 (/etc/drivers 里面有服务名称到端口号的映射)

填充 hint 会缩小查询结构的范围

注意 flag 参数的选取。

 

绑定地址

系统调用:

#include<sys/socket.h>

intbind(int sockfd, const struct sockaddr *addr, socklen_t len);

Returns: 0 if OK

 

: addr 参数一般是有 sockaddr_in 强制转换来的, len参数一般是sizeof 来获取的。

 

AF_INET 中,地址被指定 INADDR_ANY 将绑定本机所有的IP地址的对应端口。

 

获取socket绑定的地址:

#include<sys/unistd.h>

 

intgetsockname (int sockfd, struct sockaddr *localaddr, int *addrlen);

获取本机

intgetpeername(int sockfd, struct sockaddr *peeraddr, int *addrlen);

获取对方

注意: getpeername 只能在面向连接的连接中使用。在UDP中调用recvfrom函数会返回对方的地址。

 

建立连接:

 

#include<sys/socket.h>

intconnect(int sockfd, const struct sockaddr *addr, socklen_t len);

Returns: 0 if OK

 

:

在非阻塞模式中,如果不能马上建立连接,将返回1errno设置为EINPROGRESS.程序可以使用select或者poll来确定socket什么时候是可写的。

 

connect可以用在UDP中,这样以后发数据的时候就不用每次都指定发送地址。

 

监听:

 

#include<sys/socket.h>

intlisten(int sockfd, int backlog);

Returns: 0 if OK, 1 on error

 

backlog 指定最大允许接入的连接数量。

 

接入连接:

 

#include<sys/socket.h>

intaccept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);

Returns: file (socket) descriptor if OK, 1 on error

 

返回接入的socket 描述符。addr 返回接入的地址和端口号,注意接入的客户地址中的端口号是对方主机随即分配的哦。

 

数据传输

发送: write / send/ sendto / sendmsg

接收: read / recv/ recvfrom / recvmsg

 

注意:

sendto recvfrom 一般用在 UDP

一些常用的flags:MSG_OOB MSG_PEEK MSG_NOBLOCK

 

带外数据(Out of band data)

TCP支持,紧急数据,高优先级发送。

带外数据只支持一个字节,发送的时候指定 MSG_OOB

 

接收到数据的时候,产生SIGURG 信号,但是必须先进行设置才能接收到信号:

  • fcntl(sockfd, F_SETOWN, pid); 设置socket所属的进程。F_GETOWN可以得到socket所属的进程或进程组

 

关于 urgent mark:

TCP支持在普通数据流中接受紧急数据。必须设置socket的选项为SO_OOBINLINE.

可以使用sockatmark来判断当前是否在读urgentmark

 

#include<sys/socket.h>

intsockatmark(int sockfd);

Returns: 1 if at mark, 0 if not at mark, -1 on error

 

最后

  • TCP可以在正常数据中接收OOB,也可以单独recv(指定MSG_OOB).
  • TCP 只支持一个字节的OOB,后面的会冲掉前面未接收的

 

关于非阻塞和异步

设置费阻塞模式:

fcntl 打开O_NOBLOCK 开关,这样sendrecv 暂时不能执行的时候返回错误,errno设置EWOULDBLOCK或者 EAGAIN. 然后可以用poll/select/epoll来查询什么时候可以写或读。

 

异步socket:

socket可读或者可写的时候会发信号SIGIO,估计也要先设置socket的所属进程F_SETOWN

启用异步的步骤:

  • 设置socket所属进程
    • fcntl(fd, F_SETOWN, pid)
    • ioctl(fd, FIOSETOWN, pid)
    • ioctl(fd, SIOCSPGRP, pid)
  • socket启用异步
    • fcntl(fd, F_SETFL, flags|O_ASYNC)
    • ioctl(fd, FIOASYNC, &n)

推荐使用fcntl,貌似所有平台都支持。