1. 主机字节序和网络字节序
cpu的累加器(存放算术或逻辑运算操作数和结果的寄存器)一次能装载4个字节(32位系统), 那么4字节在内存的排列顺序将影响它在累加器装载成的最终值, 这就是字节序问题
大端字节序(网络字节序): 符合人们的从左向右的阅读方式, 高位字节(23-31)存储在内存的低地址, 低位字节(0-7)存储在内存的高地址.
小端字节序(主机字节序): 高位字节存储在内存的高地址, 低位字节存储在内存的低地址(和阅读顺序相反)
#include <stdio.h>
// 检查机器的字节序
void byteorder()
{
union
{
short value;
char union_bytes[ sizeof(short) ];
}test;
test.value = 0x0102;
// char数组的低位存储的是高位字节 是大端
if((test.union_bytes[0] == 1) && (test.union_bytes[1] == 2))
{
printf("big endian\n");
}
// char数组的低位存储的是低位字节 是小端
else if((test.union_bytes[0] == 2) && (test.union_bytes[1] == 1))
{
printf("little endian\n");
}
else
{
printf("unknown...\n");
}
}
2. 通用和专用socket地址
#include <bits/socket.h>
// 通用socket地址
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
常见的协议族和对应的地址族:
PF_UNIX -> AF_UNIX 本地域协议族
PF_INET -> AF_INET TCP/IPv4协议族
PF_INET6 -> AF_INET6 TCP/IPv6协议族
前后两者值相同可以混用
// 专用socket地址
// 本地域协议族省略...
// IPv4
struct sockeaddr_in
{
sa_family_t sin_family; /* 地址族 */
u_int16_t sin_port; /* 端口号, 网络字节序表示 */
struct in_addr sin_addr; /* 地址结构体 */
};
struct in_addr
{
u_int32_t s_addr; /* IP地址, 网络字节序表示 */
};
// IPv6省略...
3. IP地址转换
编程中要转化为整数, 记录日志时要使用点分十进制的字符串
转换函数这里省略...
4. 创建socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: 协议族
type: SOCK_STREAM(流服务类型, 表示传输层使用TCP协议), SOCK_UGRAM(数据报服务类型, 表示传输层使用UDP)
type参数可以与SOCK_NONBOLOCK和SOCK_CLOEXEC相与, 分别表示设置socket为非阻塞的, 以及调用fork创建子进程时在子进程关闭该socket. 这两个属性也可以调用fcntl来设置
protocol: 选择具体的协议, 前两个参数已经决定了它的值, 所以一般设置为0
5. 命名socket
bind函数, 即绑定地址, 成功时返回0, 失败时返回-1并设置errno, 常见的errno:
EACCES: 被绑定的地址是受保护的地址, 仅超级用户可以访问,比如绑定到了0-1023的知名端口
EADDRINUSE: 被绑定的地址正在使用中, 比如绑定到一个处于TIME_WAIT状态的地址
6. 监听socket
#include <sys/socket.h>
int listen(in sockfd, int backlog);
sockfd: 被监听的socket
backlog: 监听队列的最大长度, 长度若超过backlog, 服务器不接受新的连接, 客户端会收到ECONNREFUSED错误.只表示完全连接状态(ESTABLISHED)的socket上限
成功返回0, 失败返回-1并设置errno
telnet 192.168.0.1 12345 /* 建立对端口12345的连接 */
netstat -nt | grep 12345 /* 显示listen监听队列的内容 */
6. 接受连接
accept函数失败是返回-1并设置errno, accept对于客户端网络断开毫不知情, 它只是从监听队列取出连接, 而不论连接处于何种状态, 也不关心网络状况的变化
7. 发起连接
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr* serv_addr, socketlen_t addrlen);
sockfd: 客户端调用socket()创建函数返回的文件描述符
serv_addr: 服务器监听的socket地址
addrlen: 地址长度
成功返回0, 连接成功后 sockfd唯一标识了这个连接, 客户端可以通过sockfd与服务器通信. 失败返回-1并设置errno, errno常见如下:
ECONNREFUSED: 目标连接被拒绝
ETIMEDOUT: 连接超时
8. 关闭连接
#include <unistd.h>
int close(int fd);
fd是待关闭的socket, close调用并非总是立刻关闭一个连接, 而是将fd的引用计数减1, 只有fd的引用计数为0时才真正关闭连接.
fork调用会将父进程中的socket引用计数加1, 因此父子进程都应该执行close才能关闭连接
int shutdown(int sockfd, int howto);
立即终止连接, 网络编程专门设计的接口
howto决定了shutdown的行为, 如下:
SHUT_RD: 关闭读, 应用程序不再执行socket的读操作, 读缓冲区的数据被丢弃
SHUT_WR: 关闭写, 应用程序不再执行socket的写操作, 写缓冲区的内容会全部发送出去
SHUT_RDWR: 同时关闭读写
成功返回0, 失败返回-1并设置errno
9. TCP数据读写
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
ssize_t send(int sockfd, const void* buf, size_t len, int flags);
recv:
读取sockfd上的数据, buf和len指定了缓冲区的位置和大小, flags通常设置为0.
recv成功返回实际读取的字节长度, 可能小于期望长度, 因此要多次调用recv.
返回0 意味着通信对方已经关闭连接
出错返回-1并设置errno
send:
往sockfd上写入数据, buf和len指定了缓冲区的位置和大小, flag通常设置为0
成功返回实际写入的数据长度, 失败返回-1并设置errno
10. UDP数据读写
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
因为没有连接的概念, 每次调用都要指定地址 recvfrom: 发送端的地址 sendto: 接收端的地址
11. socket选项
fcntl系统调用是控制文件描述符属性的通用POSIX方法
下面两个是专门读取和设置socket文件描述符属性的方法:
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int option_name, void* option_value, socketlen_t* restrict option_len);
int setsockopt(int sockfd, int level, int option_name, const void* option_value, scokelen_t option_len);
level: SOL_SOCKET(通用socket选项, 与协议无关) 对应的option name : SO_DEBUG: 打开调试
SO_REUSEADDR : 重用本地地址
SO_TYPE: 获取socket类型
SO_ERROR: 获取并清除错误状态
SO_RECVBUF: 接收缓冲区大小
SO_SNDBUF: 发送缓冲区大小
SO_KEEPALIVE: 发送周期性报文以维持连接
SO_LINGER: 若有数据待发送,则延迟关闭
SO_RCVLOWAT/SO_SNDLOWAT: 发送和接收低水位标记
SO_RCVTIMEO/SO_SNDTIMEO: 接收发送数据超时
level: IPPROTO_IP(IPv4选项) 对应的option name:
IP_TOS: 服务类型
IP_TTL: 存活时间
level: IPPROTO_IPV6(IPv6选项) 对应的option name:
暂且忽略...
level: IPPROTO_TCP(TCP选项) 对应的option name:
TCP_MAXSEG: 最大报文段大小
TCP_NODELAY: 禁用Nagle算法, 允许小包的发送, 不用等到写缓冲区积累到一定量
成功返回0, 失败返回-1 并设置errno
对服务器而言, socket选项要在listen调用前针对监听socket设置, accept返回的连接socket会继承监听socket的这些选项
对于客户端而言, socket选项要在connect之前设置, 因为之后TCP三次握手已经完成
SO_REUSEADDR选项: 即使socket处于TIME_WAIT状态, 与之绑定的地址也可以立即被重用
12. 网络信息API
未完待续