网络字节顺序与系统字节顺序不一定相同
网络字节顺序(大端顺序)是指一个数在内存中存储的时候“高对低,低对高”(即一个数的高位字节存放于低地址单元,低位字节存放在高地址单元中)。但是计算机的内存存储数据时有可能是大端顺序或者小端顺序
而上面的函数就是用来进行这方面转化工作的
- h:host 本地主机端
- to:就是to,转化为
- n:net 网络端
- l:是 unsigned long (32bit)
- s:是 unsigned short (16bit)
ntohl
无符号长整型,从网络到本机
ntohs
无符号短整型,从网络到本机
htonl
无符号长整型,从本机到网络
htons
无符号短整型,从本机到网络
INADDR_ANY 宏定义
netinet/in.h
中有关于 INADDR_ANY 的定义
/* Address to accept any incoming messages. */
#define INADDR_ANY ((in_addr_t) 0x00000000)
/* Address to send to all hosts. */
#define INADDR_BROADCAST ((in_addr_t) 0xffffffff)
/* Address indicating an error return. */
#define INADDR_NONE ((in_addr_t) 0xffffffff)
INADDR_ANY 代表通配 0.0.0.0
setsockopt
sys/socket.h
中有关于 setsockopt 的定义
/* Set socket FD's option OPTNAME at protocol level LEVEL
to *OPTVAL (which is OPTLEN bytes long).
Returns 0 on success, -1 for errors. */
extern int setsockopt (int __fd, int __level, int __optname,
__const void *__optval, socklen_t __optlen) __THROW;
__fd
指向一个打开的套接口描述字
__level
指定选项代码的类型
__optname
选项名称
__optval
是一个指向变量的指针,类型为整形
__optlen
optval 的size大小
标志打开或关闭某个特征的二进制选项
closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket
代码语言:javascript复制BOOL bReuseaddr=TRUE;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
bind
sys/socket.h
中有关于 bind 的定义
/* 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)
__THROW;
__fd
指定地址与哪个套接字绑定,这是一个由之前的socket函数调用返回的套接字。调用bind的函数之后,该套接字与一个相应的地址关联,发送到这个地址的数据可以通过这个套接字来读取与使用
__addr
指定地址。这是一个地址结构,并且是一个已经经过填写的有效的地址结构。调用bind之后这个地址与参数sockfd指定的套接字关联,从而实现上面所说的效果
__len
正如大多数socket接口一样,内核不关心地址结构,当它复制或传递地址给驱动的时候,它依据这个值来确定需要复制多少数据。这已经成为socket接口中最常见的参数之一了
成功,返回0;出错,返回-1,相应地设定全局变量errno
代码语言:javascript复制EACCESS:地址空间受保护,用户不具有超级用户的权限
EADDRINUSE:指定的地址已经在使用
EBADF:sockfd参数为非法的文件描述符
EINVAL:socket已经和地址绑定
ENOTSOCK:参数sockfd为文件描述符
Tip: bind函数并不是总是需要调用的,只有用户进程想与一个具体的地址或端口相关联的时候才需要调用这个函数。如果用户进程没有这个需要,那么程序可以依赖内核的自动的选址机制来完成自动地址选择,而不需要调用bind的函数,同时也避免不必要的复杂度。在一般情况下,对于服务器进程问题需要调用bind函数,对于客户进程则不需要调用bind函数
listen
sys/socket.h
中有关于 listen 的定义
/* Prepare to accept connections on socket FD.
N connection requests will be queued before further requests are refused.
Returns 0 on success, -1 for errors. */
extern int listen (int __fd, int __n) __THROW;
listen函数在一般在调用bind之后-调用accept之前调用。用户在调用socket函数之后,返回一个套接字sockfd. sockfd默认一个主动连接的套接字,也就是此时系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接,然后在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认时认为一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事
listen函数可使得流套接字sockfd处于监听状态,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接
处于监听状态的套接字sockfd将维护一个客户连接请求队列,该队列最多容纳backlog个用户请求
__fd
套接字
__n
队列最多同时容纳用户请求的个数
返回:0 成功, -1 失败
accept
sys/socket.h
中有关于 accept 的定义
/* Await a connection on socket FD.
When a connection arrives, open a new socket to communicate with it,
set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting
peer and *ADDR_LEN to the address's actual length, and return the
new socket's descriptor, or -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int accept (int __fd, __SOCKADDR_ARG __addr,
socklen_t *__restrict __addr_len);
服务器编程中最重要的一步是等待并接受客户的连接,那么这一步在编程中如何完成,accept函数就是完成这一步的。它从内核中取出已经建立的客户连接,然后把这个已经建立的连接返回给用户程序,此时用户程序就可以与自己的客户进行点到点的通信了
__fd
指定处于监听状态的流套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号
__addr
返回新创建的套接字的地址结构,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL
__addr_len
新创建的套接字的地址结构的长度,用来接受上述addr的结构的大小,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL
如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信
返回:非负描述字成功, -1失败
有人从很远的地方通过一个在侦听 (listen()) 的端口连接 (connect()) 到机器。它的连接将加入到等待接受 (accept()) 的队列中
recv
sys/socket.h
中有关于 recv 的声明
/* Read N bytes into BUF from socket FD.
Returns the number read or -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t recv (int __fd, void *__buf, size_t __n, int __flags);
从TCP连接的另一端接收数据
__fd
指定接收端套接字描述符
__buf
指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据
__n
指明buf的长度
__flags
参数一般置0
返回值: <0 出错 ;==0 对方调用了close API来关闭连接 ;>0 接收到的数据大小
阻塞模式下recv会一直阻塞直到接收到数据,非阻塞模式下如果没有数据就会返回,不会阻塞着读,因此需要循环读取)
可能错误
代码语言:javascript复制EAGAIN:套接字已标记为非阻塞,而接收操作被阻塞或者接收超时
EBADF:sock不是有效的描述词
ECONNREFUSE:远程主机阻绝网络连接
EFAULT:内存空间访问出错
EINTR:操作被信号中断
EINVAL:参数无效
ENOMEM:内存不足
ENOTCONN:与面向连接关联的套接字尚未被连接上
ENOTSOCK:sock索引的不是套接字
send
sys/socket.h
中有关于 send 的声明
/* Send N bytes of BUF to socket FD. Returns the number sent or -1.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t send (int __fd, __const void *__buf, size_t __n, int __flags);
向TCP连接的另一端发送数据
__fd
指定发送端套接字描述符
__buf
指明一个存放应用程序要发送数据的缓冲区
__n
指明实际要发送的数据的字节数
__flags
参数一般置0
flags | 说明 | recv | send |
---|---|---|---|
MSG_DONTROUTE | 绕过路由表查找 | • | |
MSG_DONTWAIT | 仅本操作非阻塞 | • | • |
MSG_OOB | 发送或接收带外数据 | • | • |
MSG_PEEK | 窥看外来消息 | • | |
MSG_WAITALL | 等待所有数据 | • |
返回值 :>0 表示发送的字节数(实际上是拷贝到发送缓冲中的字节数);==0 对方调用了close API来关闭连接 ;<0 发送失败,错误原因存于全局变量errno中
代码语言:javascript复制EBADF 参数s 非合法的socket处理代码
EFAULT 参数中有一指针指向无法存取的内存空间
ENOTSOCK 参数s为一文件描述词,非socket
EINTR 被信号所中断
EAGAIN 此操作会令进程阻断,但参数s的socket为不可阻断
ENOBUFS 系统的缓冲内存不足
ENOMEM 核心内存不足
EINVAL 传给系统调用的参数不正确
inet_addr
arpa/inet.h
中有关于 inet_addr 的声明
/* Convert Internet host address from numbers-and-dots notation in CP
into binary data in network byte order. */
extern in_addr_t inet_addr (__const char *__cp) __THROW;
将一个ip地址字符串转换成一个整数值,一般的IP地址串格式为:’a.b.c.d’ 分成四段
__cp
字符串指针
connect
sys/socket.h
中有关于 connect 的声明
/* Open a connection on socket FD to peer at ADDR (which LEN bytes long).
For connectionless socket types, just set the default address to send to
and the only address from which to accept transmissions.
Return 0 on success, -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);
用于建立与指定socket的连接
__fd
标识一个未连接的socket
__addr
指向要连接套接字的sockaddr结构体的指针
__len
sockaddr结构体的字节长度
返回值 : 成功则返回0,失败则返回非0,错误码GetLastError()
代码语言:javascript复制EBADF 参数sockfd 非合法socket处理代码
EFAULT 参数serv_addr指针指向无法存取的内存空间
ENOTSOCK 参数sockfd为一文件描述词,非socket
EISCONN 参数sockfd的socket已是连线状态
ECONNREFUSED 连线要求被server端拒绝
ETIMEDOUT 企图连线的操作超过限定时间仍未有响应
ENETUNREACH 无法传送数据包至指定的主机
EAFNOSUPPORT sockaddr结构的sa_family不正确
EALREADY socket为不可阻断且先前的连线操作还未完成
总结
以下函数可以进行socket的创建与控制,是TCP网络编程的基础
- socket
- htons
- setsockopt
- bind
- listen
- accept
- recv
- send
- inet_addr
- connect
通过各方面资料弄懂其参数的意义和返回值的类型,是熟练掌握的基础
原文地址