《unix网络编程》第四章笔记
1、网络编程首先就是调用socket函数,创建一个socket套接字描述符,类似于文件描述符
#include <sys/socket.h>
int socket(int family, int type, int protocol);
family 指定协议域,如:
family | 说明 |
AF_UNIX、AF_LOCAL | unix域协议 |
AF_INET | IPV4 协议 |
AF_INET6 | IPV6 协议 |
AF_PACKET | 底层数据包接口 |
有时会看到使用PF_前缀的值,如PF_INET,PF_UNIX。AF_前缀表示地址族,PF_前缀表示协议族,man手册里有如下说明:
“The manifest constants used under 4.x BSD for protocol families are PF_UNIX, PF_INET, etc.,while AF_UNIX etc. are used for address families. However, already the BSD man page promises:"The protocol family generally is the same as the address family", and subsequent standards use AF_* everywhere.”
type 指明套接字类型,如
type | 说明 |
SOCK_STREAM | 字节流套接字 |
SOCK_DGRAM | 数据报套接字 |
SOCK_RAW | 原始套接字 |
protocol指定某个协议类型常值,或设为0,由所给定的family和type组合的系统默认值。
protocol | 说明 |
IPPROTO_CP | TCP传输协议 |
IPPROTO_UDP | UDP传输协议 |
IPPROTO_SCTP | SCTP传输协议 |
2、客户通过connect函数与服务器建立连接
#include <sys/tocket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
sockfd是由socket函数返回的套接字描述符。servaddr含有服务器的IP地址和端口号。
客户在调用connect前不必非调用bind函数,因为内核会确定源IP地址,并选择一个临时端口作为源端口。
该函数返回错误可能有以下几种情况:
(1)没有收到SYN分节的相应,返回ETIMEDOUT错误
(2)对客户的SYN相应RST,返回ECONNREFUSED错误
RST是TCP在发生错误时发送的一种TCP分节,产生RST的三个条件是:目的地为某端口的SYN到达,然后该端口上没有正在监听的服务程序;TCP想取消一个已有连接;TCP接收到一个根本不存在的连接上的分节。(TCPV1第246~250有更详细的信息)
(3)客户发出的SYN在中间的某个路由器上引发“destination unreachable”(目的地不可达)ICMP错误,返回EHOSTUNREACH或ENETUNREACH错误。可能是在规定时间内没收到响应,或者根本没有到达远程系统,或者connect调用根本不等待就返回。
connect失败后,都必须close当前的套接字描述符并重新调用socket。
3、bind函数把一个本地协议地址赋予一个套接字。
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
socket是所要绑定的函数套接字;myaddr是一个指向特定于协议的地址结构的指针;addrlen是该地址结构的长度。
对于TCP,调用bind函数可以指定一个端口,或指定一个IP地址,或两者都指定,或都不指定,由内核选择。
一般服务器会绑定众所周知的端口号,而客户端则多为内核选择一个临时端口。
客户端通常也不绑定IP地址,当连接套接字时,由内核根据外出网络接口来选择源IP地址,而所用外出接口则取决与达到服务器所需的路径。TCP服务器如果没有绑定IP地址,内核则把客户发送的SYN的目的IP地址作为服务器的源IP地址。
对于IPV4,如要让内核选择IP地址,需将套接字地址设为通配地址INADDR_ANY,其值一般为0
struct sockaddr_in servaddr;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
对于IPV6
#include <netinet/in.h>
struct sockaddr_in6 serv;
serv.sin6_addr = in6addr_any;
如果让内核来选择临时端口号,必须注意,bind并不返回其值,因第二个参数有const限定词。为了得到内核所选选择的临时端口号,需调用函数getsockname来返回协议地址。
我们必须仔细区别一个分组的到达接口和该分组的目的IP地址 (8.8节)
bind返回的常见错误是EADDRINUSE (详见7.5节)
=======================
如需要绑定指定地址,则需要用到地址转换函数,用于字符串格式的地址形式和网络字节序的二进制值之间转换,如常见的下面几个函数:
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr);
in_addr_t inet_addr(const char *strptr); // 废弃,失败返回值与编译器有关
char *inet_ntoa(struct in_addr inaddr); // 返回的字符串保存在静态内存中,不可重入
上述转换函数除已写明的存在问题,还只适用于IPV4地址,更好的办法是使用两个全新的函数inet_pton和inet_ntop。p和n分别代表表达(presentation)和数值(numeric)。
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr void *addrptr);
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
family可以是AF_INET,也可以是AF_INET6。
len参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区,为有助于指定这个大小,在<netinet/in.h>头文件中有如下定义:
#define INET_ADDRSTRLEN 16// for IPV4 dotted-decimal
#define INET6_ADDRSTRLEN 64 // for IPV6 hex string
4、服务端创建socket套接字后,并绑定本地地址后,调用listen函数,指示内核应接受该套接字的连接请求。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
第二个参数backlog指定某个给定套接字上内核为之排队的最大已完成连接数。
这里需要了解下,内核为任何一个给定监听的套接字维护两个队列,如下图所示:
5、accept函数由TCP服务器调用,用于从已完成连接队列头返回下一个已完成连接。如已完成连接队列为空,则进程投入睡眠(假定套接字为默认的阻塞方式)
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
参数sockfd是前面listen监听的套接字描述符,如accept成功,则函数返回值则是内核生成的一个全新描述符,代表与所返回客户的TCP连接。参数cliaddr和addrlen可用来获取客户端的协议地址,如没有必要,则可都置为NULL。
6、最后用close函数来关闭调节自,并终止TCP连接
#include <unistd.h>
int close(int sockfd);
这里有一个重要的概念,即每个文件或套接字都有一个引用计数。如下代码:
pid_t pid;
int listenfd, connfdl
listenfd = socket(...);
bind(listenfd, ...);
listen(listenfd, LISTENQ);
for ( ; ;)
{
connfd = accept(listenfd, ...);
if (pid = fork() == 0)
{
close(listenfd);
doit(connfd);
close(connfd);
exit(0);
}
close(connfd);
}
在fork之前,listenfd和connfd描述符的引用计数值都为1;在fork之后,这两个描述符在子进程中被复制,引用计数都变为2。只有引用计数值变为0时,相应套接字才会真正的清理和资源释放。
如果不想等引用技术变为0才断开连接,可直接使用shutdown函数。
推荐阅读链接:
http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html