目录
1.主机字节序列和网络字节序列
主机字节序列分为大端字节序和小端字节序,不同的主机采用的字节序列可能不同。
- 大端字节序是指一个整数的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。
- 小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。
在两台使用不同字节序的主机之间传递数据时,可能会出现冲突。所以,在将数据发送到网络时规定整形数据使用大端字节序,所以也把大端字节序成为网络字节序列。对方接收到数据后,可以根据自己的字节序进行转换。
Linux 系统提供如下 4 个函数来完成主机字节序和网络字节序之间的转换
SYNOPSIS
#include <arpa/inet.h>
// 长整型的主机字节序转网络字节序
uint32_t htonl(uint32_t hostlong);
//短整型的主机字节序转网络字节序
uint16_t htons(uint16_t hostshort);
// 长整型的网络字节序转主机字节序
uint32_t ntohl(uint32_t netlong);
// 短整型的网络字节序转主机字节序
uint16_t ntohs(uint16_t netshort);
2. 套接字地址结构
2.1 通用socket地址结构
ocket 网络编程接口中表示 socket 地址的是结构体 sockaddr
#include<bits/socket.h>
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
sa_family 成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。
2.2专用socket地址结构
struct in_addr
{
//IPv4地址,要用网络字节序表示
u_int32_t s_addr;
};
struct sockaddr
{
sa_family_t sin_family; //地址族:AF_INET
u_int16_t sin_port; //端口号,用网络字节序表示
struct in_addr sin_addr; //IPv4地址结构体
};
2.3 IP地址转换函数
#include <arpa/inet.h>
//字符串表示的 IPV4 地址转化为网络字节序
in_addr_t inet_addr(const char *cp);
// IPV4 地址的网络字节序转化为字符串表示
char* inet_ntoa(struct in_addr in);
3.网络编程接口
3.1创建socket
Linux的一个哲学是:所有的东西都是文件,socket也不例外。它就是个 可读,可写,可关闭,可控制的文件描述符。
头文件:
#include<sys/types.h>
#include<sys/socket.h>
系统调用:
int socket(int domain, int tupe, int protocol);
参数介绍:
- socket系统调用成功时返回一个socket文件描述符,失败返回-1,并置为errno。
- domain: 设置套接字的协议簇, AF_UNIX AF_INET AF_INET6
- type: 设置套接字的服务类型 SOCK_STREAM SOCK_DGRAM
- protocol: 一般设置为 0,表示使用默认协议
3.2 命名socket(bind)
创建socket时,我们给它指定了地址族,但是并未指定使用该地址族中的哪个具体socket地址。将一个socket与 socket地址绑定称为给socket命名。
在服务器程序中,我们通常要命名socket,因为只有命名后客户端才能知道该如何连接它。客户端则通常不需要命名socket,而是采用匿名方式,即使用操作系统自动分配的socket地址。命名socket 的系统调用是bind,其定义如下:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind()将 sockfd 与一个 socket 地址绑定,成功返回 0,失败返回-1
参数介绍:
- sockfd 是网络套接字描述符
- addr 是地址结构
- addrlen 是 socket 地址的长度
3.3 监听socket(listen)
socket被命名之后,还不能马上接受客户连接,我们需要使用如下系统调用来创建一个监听队列以存放待处理的客户连接:
int listen(int sockfd, int backlog);
listen()创建一个监听队列以存储待处理的客户连接,成功返回 0,失败返回-1并设置errno。
参数介绍:
- sockfd 是被监听的 socket 套接字
- backlog 表示处于完全连接状态的 socket 的上限
3.4 接受连接(accept)
accept()从 listen 监听队列中接收一个连接,成功返回一个新的连接 socket,该 socket 唯一地标识了被接收的这个连接,失败返回-1。
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
参数介绍:
- sockfd 是执行过 listen 系统调用的监听 socket
- addr 参数用来获取被接受连接的远端 socket 地址
- addrlen 指定该 socket 地址的长度
3.5 发起连接(connect)
如果说服务器通过listen()调用 来被动接受连接, 那么客户端需要发起连接与服务器建立连接:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
connect()客户端需要通过此系统调用来主动与服务器建立连接,成功返回 0,失败返回-1。
参数介绍:
- sockfd 参数是由 socket()返回的一个 socket。
- serv_addr 是服务器监听的 socket 地址。
- addrlen 则指定这个地址的长度
3.6 关闭连接(close)
#include<unistd.h>
int close(int sockfd);
sockfd参数是待关闭的socket。
不过,close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1。只有当fd的引用计数为0时,才真正关闭连接。 多进程程序中,一次fork系统调用默认将使父进程中打开的socket的引用计数加1,因此我们必须在父进程和子进程中都对该socket执行closc调用才能将连接关闭。
如果无论如何都要立即终止连接(而不是将socket的引用计数减1),可以使用如下的shutdown系统调用(相对于close来说,它是专门为网络编程设计的):
#include<sys/socket.h>
int shutdown(int sockfd, int howto);
shutdown 成功时返回0,失败返回-1并置为 errno.
- sockfd是待关闭的 socket
- howto决定了 shutdown 的行为。下面为可选值。