pag437
套接字描述符在unix系统是用
文件描述符实现的。
int
socket( int domain, int type, intprotocol );//创建套接字
数据报(SOCK_DGRAM)接口,与对方通信时是不需要逻辑连接的(UDP)。只需要送出一个报文,其地址是一个对方进程所使用的套接字。(无连接)
字节流(SOCK_STREAM)要求在交换数据之前(TCP),在本地套接字和与之通信的远程套接字之间建立一个逻辑连接。(面向连接)
数据报是一种自包含报文。发送数据报近似于
给某人邮寄信件。
每封信件包含接收者地址,使这封信件独立于所有其他信件。
面向连接的协议通信就像
与对方打电话。
会话中不包含地址信息。就像呼叫两端存在一个点对点虚拟连接,并且
连接本身暗含特定的源和地址。
套接字通信是双向的。
int
shutdown( int sockfd, int how);//禁止套接字上的输入、输出,只操作本端套接字
close和shutdown:
1、close只有在最后一个活动引用被关闭时才释放网络端点,套接字直到关闭了最后一个引用他的文件描述符之后才会被释放。
2、shutdown允许使一个套接字处于不活动状态,无论引用他的文件描述符数目多少。
进程的标识有两个部分:计算机的网络地址可以帮助标识网络上想与之通信的
计算机,而服务可以帮助标识计算机上特定的
进程。
大端字节序:最大字节地址对应于数字最低有效字节上;
-----------------------
| n |n+1|n+2|n+3|
------------------------
MSB LSB
小端字节序:数字最低字节对应于最小字节地址。
------------------------
|n+3|n+2|n+1| n |
-----------------------
MSB LSB
不管字节如何排序,数字最高位总是在左边,最低位总是在右边。
TCP/IP协议采用大端字节序。
套接字实现可以
自由地添加额外的成员,并且定义sa_data成员的大小。
通用地址结构:
struct sockaddr {
sa_family_t sa_family; //sa取自(s)ock(a)ddr
char sa_data[]
...
}
IPV4地址结构:
struct sockaddr_in { //in取自(in)et
sa_family_t sin_family; //sin取自(s)ockaddr_(in)
in_port_t sin_port;
struct in_addr sin_addr;
};
struct in_addr {
in_addr_t s_addr;
};
IPV6地址结构:
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
struct in6_addr {
uint8_t s6_addr[16];
};
转换端口时常用:
uint32_t
htonl( uint32_t hostint32);//以网络字节序表示32位整形数
uint16_t
htons( uint16_t hostint16);//以网络字节序表示16位整形数
uint32_t
ntohl( uint32_t netint32);//以主机字节序表示32位整形数
uint16_t
ntohs( uint16_t netint16);//以主机字节序表示16位整形数
h(host)主机字节序
n(network)网络字节序
l(long)长整数
s(short)短整数
转换地址常用:
const char *
inet_ntop( int domain, const void *restrict addr, char*restrict str, socklen_t size );//将网络字节序的二进制地址转换成文本字符串格式
int
inet_pton( int domain, const char *restrict str, void *restrictaddr );//将文本字符串格式转换成网络字节序的二进制地址
p:presentation
n:net
struct hostent
*gethostent ( void);//打开主机数据文件返回下一条目
void
sethostent( int stayopen);//打开文件,如已打开,将其回绕
void
endhostent( void );//关闭文件
获取网络名字和网络号:
struct netent *
getnetbyaddr( uint32_tnet, int type );
struct netent *
getnetbyname( constchar *name );
struct netent *
getnetent( void);
void
setnetent( int stayopen );
void
endnetent( void );
映射协议名字和协议号:
struct protoent *
getprotobyname( constchar *name );
struct protoent *
getprotobynumber( intproto );
struct protoent *
getprotoent( void);
void
setprotoent( int stayopen);
void
endprotoent( void );
服务是由地址的端口号部分表示的。每个服务由一个唯一的、熟知的端口号来提供。
struct servent *
getservbyname( constchar *name, const char *proto );
struct servent *
getservbyport( intport, const char *proto );
struct servent *
getservent( void);
void
setservent( int stayopen );
void
endservent( void );
将主机名字和服务名字映射到一个地址:
int
getaddrinfo( const char *restricthost, const char *restrict service,
const struct addrinfo *restrict hint, struct addrinfo **restrict res );
需提供主机名字、服务名字,或者两者都提供。如果仅仅提供一个名字,另外一个必须是一个空指针。
addrinfo结构:
struct addrinfo {
int ai_flag; //ai取自(a)ddr(i)nfo
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname; //canonname :标准名
struct addrinfo *ai_next;
...
};
hint是一个用于
过滤地址的模板,仅使用ai_family、ai_flags、ai_protocol和ai_socktype字段。剩余字段必须为0或NULL。
void
freeaddrinfo( struct addrinfo *ai);
const char *
gai_strerror( int error);//将返回错误码转换成消息
将地址转换成主机名或服务名:
int
getnameinfo( const struct sockaddr*restrict addr, socklen_t alen,
char *restrict host, socklen_thostlen,
char *restrict service, socklen_t servlen,
unsigned intflags );
pag449
socket: 手机
bind: 手机+SIM卡
listen: 将卡的号码告诉其他人
accept: 拿着手机等对方电话
connect:主动打电话给对方
客户端应有一种方法来发现用以连接服务器的地址,最简单的方法就是
为服务器保留一个地址并且在/etc/services或某个名字服务中
注册
int
bind( int sockfd, const structsockaddr *addr, socklen_t len );//将地址绑定到一个套接字
限制:
1、在进程所运行的机器上,指定的地址必须有效,不能指定一个其他机器的地址。
2、地址必须和创建套接字时的地址族所支持的格式相匹配
3、端口号必须不小于1024,除非该进程具有超级用户特权
4、一般只有套接字端点能够与地址绑定,尽管有些协议允许多重绑定
int
getsockname( int sockfd, structsockaddr *restrict addr, socklen_t *restrict alenp);//发现绑定到一个套接字的地址
int
getpeername( int sockfd, structsockaddr *restrict addr, socklen_t *restrict alenp);//套接字已连接,可用此函数找到对方地址
面向连接的服务网络
(SOCK_STREAM、SOCK_SEQPACKET),在开始交换数据以前,需要在请求服务的进程套接字(客户端)和提供服务的进程套接字(服务器)之间建立一个连接。(
三路握手)
在
SOCK_DGRAM套接字上调用connect,所有发送报文的目标地址设为connect调用中所指定的地址,这样每次传送报文时就不需要再提供地址。仅能接收来自指定地址的报文。(
没有三路握手,内核只是检查是否有立即可知的错误,
记录对端的IP地址和端口号,然后立即返回到调用进程)
int
connect( int sockfd, const structsockaddr *addr, socklen_t len );//主动发起三路握手
int
listen( int sockfd, int backlog);//宣告可以调用的连接请求,backlog表示该进程所要入队(已完成连接队列+未完成连接队列)的连接请求数量。
//一旦服务器调用了listen,套接字就能接收连接请求。
int
accept( int sockfd, structsockaddr *restrict addr, socklen_t *restrict len);//获得连接请求并建立连接,一个监听套接字,多个已连接套接字
六个数据传送函数:
ssize_t
send( int sockfd, const void*buf, size_t nbytes, int flags );//使用时套接字必须已建立连接
ssize_t
sendto( int sockfd, const void*buf, size_t nbytes, int flags,
const struct sockaddr *destaddr,socklen_t destlen );//允许在无连接的套接字上指定一个目标地址
ssize_t
sendmsg( int sockfd, conststruct msghdr *msg, int flags );指定多重缓冲区传输数据(类似writev)
msghdr结构:
struct msghdr {
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
int *msg_iovlen;
void *msg_control; //辅助数据(传送描述符时可用)
socklen_t msg_controllen;
int msg_flags;
...
};
ssize_t
recv( int sockfd, void *buf,size_t nbytes, int flags );//接收数据
ssize_t
recvfrom( int sockfd, void*restrict buf, size_t len, int flags,
struct sockaddr *restrict addr, socklen_t *restrict addrlen );//得到数据发送者的源地址(通常用于无连接套接字)
ssize_t
recvmsg( int sockfd, structmsghdr *msg, int flags );//将接收到的数据送入多个缓冲区(类似readv)
pag464
int
setsockopt( int sockfd, int level,int option, const void *val, socklen_t len );//设置套接字选项(SO_REUSERADDR最好在每个bind前指定,重用bind中的地址)
- 通用选项,工作在所有套接字类型上
- 在套接字层次管理的选项,但是依赖于下层协议的支持
- 特定与某协议的选项,为每个协议所独有
如果选项是通用的套接字选项,level设置成SOL_SOCKET。否则,level设置成控制这个选项的协议号。例如,TCP(IPPROTO_TCP),IP(IPPROTO_IP)。
int
getsockopt( int sockfd, int level,int option, void *restrict val, socklen_t *restrict lenp);//获取当前设置选项
带外数据:
TCP支持,UDP不支持。TCP仅支持一个字节的紧急数据(带外数据),多个取最后一个。
fcntl(sockfd, F_SETOWN, pid);//安排进程接收一个套接字信号。
通过两个步骤使用异步IO:
- 建立套接字拥有者关系,信号可以被传送到合适的进程。(fcntl,F_SETOWN)
- 通知套接字当IO操作不会阻塞时发信号告知。(fcntl,F_SETFL,O_ASYNC)
int
sockatmark( int sockfd);//判断是否接收到紧急标记
客户端--------------------------------------------------服务器
gethostname-----------------------------------------gethostname
getaddeinfo-------------------------------------------getaddrinfo
socket--------------------------------------------------socket
--------------------------------------------------bind
--------------------------------------------------listen
connect------------------------------------------------accept
recv-----------------------------------------------------send
close----------------------------------------------------close
getaddrinfo出现错误:
http://blog.csdn.net/andyxie407/article/details/1672325
pag476
unix域套接字:(仅仅复制数据)
非命名unix域套接字:(没有名字意味着无关进程不能使用他们)
int
socketpair (int domain, int type, int protocol, int sockfd[2]);//创建非命名互联的unix域套接字,进程端:0输入,1输出
命名unix域套接字:
struct sockaddr_un { //sun取自(s)ockaddr_(un)
sa_family_t sun_family; //AF_UNIX(AF_LOCAL)
char sun_path[108];//pathname
}
系统用sun_path中的路径名创建一类型为S_IFSOCK的文件。
该文件仅用于向
客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通信。
关闭套接字时并不自动删除文件,所以要由我们来删除。
(一般在程序结束前调用unlink,如果有open的话在open后就可以调用unlink,不然会导致文件已存在无法bind)
确定绑定地址长度的方法,先确定sun_path成员在sockaddr_un结构中的
偏移量(offsetof),然后将此与
路径名长度(不包括终止null字符)相加。
unix域套接字传送文件描述符
struct msghdr {
void *msg_name; //发送数据报文目的地址
socklen_t msg_namelen;
struct iovec *msg_iov;
int *msg_iovlen;
void *msg_control; //辅助数据
socklen_t msg_controllen;
int msg_flags;
}
struct cmsghdr { //控制信息首部
socklen_t cmsg_len; //辅助数据长度
int cmsg_level; //SOL_SOCKET
int cmsg_type; //SCM_RIGHT
/* data */ //这里为(int)描述符
}
unsigned char *CMSG_DATA(struct cmsghdr *cp); //获取cmsghdr结构数据部分指针
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mp); //获取与msghdr结构相关联的第一个cmshdr结构指针
struct cmsghdr *CMSG_NXTHDR(struct msghdr *mp, struct cmsghdr *cp);//获取与msghdr结构关联的下一个cmsghdr结构指针
unsigned int CMSG_LEN(unsigned int nbytes); //不计填充,返回为cmsg_len中的值。nbytes为上述结构中data的大小
unsigned int CMSG_SPACE(unsigned int length); //计填充,返回为辅助对象动态分配空间的大小值。length同上