socket

套接字类型与协议设置

创建套接字

#include <sys/socket.h>

/* 成功时返回文件描述符,失败时返回-1 */
int socket(int domain, int type, int protocol);

协议族(Protocol Family)

PF_INET:IPV4

PF_INET6:IPV6

套接字类型(Type)

面向连接的套接字(SOCK_STREAM)

特点

  • 传输过程中数据不会消失
  • 按序传输数据
  • 一一对应
  • 传输的数据不存在数据边界(TCP粘包问题)
面向消息的套接字(SOCK_DGRAM)

特点

  • 快速传输,不按顺序
  • 数据可能丢失(UDP不可靠)
  • 有数据边界(UDP无粘包问题)
  • 限制每次传输的数据大小

协议类型(Protocol)

SOCK_STREAM面向连接,在IPV4协议族中,只有IPPROTO_TCP面向连接。

SOCK_DGRAM面向消息,在IPV4协议族中,只有IPPORT_UDP面向消息。

所以第三个参数通常指定为0,系统会自动推导出协议类型。

网络地址的初始化与分配

端口号

IP地址用于定位计算机,端口号用于定位计算机中的进程。

TCP和UDP不共享端口号,不会互相占用。

地址信息的表示

sockaddr_in和in_addr
struct sockaddr_in
{
    sa_family_t 	sin_family;  	//地址族
    uint16_t 		sin_port;	//16位TCP/UDP端口号
    struct in_addr      sin_addr;	//32位IP地址
    char 		sin_zero[8];	//填充为0,使sockaddr_in与sockaddr保持一致
};

struct in_addr
{
    In_addr_t		s_addr;		//32位IPV4地址
};

sockaddr和sockaddr_in的区别:后者保存IPV4地址信息,前者不只为IPV4设计。

网络字节序与地址变换

CPU字节序
  • 大端法
  • 小端法

进行网络传输时,统一转换为大端序。

字节序转换
  • h(host):主机
  • n(network):网络
  • l(long):long型数据,通常用于IP地址转换
  • s(short):short型数据,通常用于端口号转换

组合出四种转换字节序的函数

htons:把short型数据,从主机字节序转换到网络字节序

htonl:把long型数据,从网络字节序转换到主机字节序

ntohs:把short型数据,从主机字节序转换到网络字节序

ntohl:把long型数据,从网络字节序转换到主机字节序

数据传输无需考虑字节序问题。

网络地址的初始化与分配

IP地址格式的转换

存储在sockaddr_in中的IP地址是32位整型,而人类熟悉的IP地址表示方法是点分十进制表示法

#include <arpa/inet.h>

/**
 * 将点分十进制表示的IP地址转换为32位整型的IP地址
 * 成功时返回32位整数,失败时返回INADDR_NONE
 * 1)检测无效IP地址 2)验证是否转换为了网络字节序
 */
in_addr_t inet_addr(const char * string);

/**
 * 功能与inet_addr相同,将转换后的IP地址直接赋给sockaddr_in结构体中的in_addr结构体变量
 * 成功返回1,失败返回0
 */
int inet_aton(const char * string, struct in_addr * addr);

/**
 * 32位整数转点分十进制IP地址
 * 成功返回字符串地址,失败返回-1
 * 调用该函数后,应立即将字符串值复制到其他内存空间,下一次调用该函数时,上一次的值将被覆盖
 */
char * inet_ntoa(struct in_addr adr);
网络地址初始化
memset(&serv_addr, 0, sizeof(serv_addr));		        //清零
serv_addr.sin_family = AF_INET;					//地址族
serv_addr.sin_addr.s_addr = htonl(inet_addr(serv_ip));	        //IP地址
serv_addr.sin_port = htons(atoi(serv_port));			//端口号

INADDR_ANY:利用常数分配服务器端的IP地址,自动获取服务器IP。若服务器有多个IP,只要端口号一致,可以从多个IP地址接收数据。

serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

向套接字分配网络地址

#include <sys/socket.h>

/**
 * 传入3个参数,依次为socket文件描述符,存储地址信息的结构体变量,结构体变量的长度
 * 成功返回0,失败返回-1
 */
int bind(int sockfd, struct sockaddr * myaddr, socklen_t addrlen);

进入等待连接请求状态

#include <sys/socket.h>

/**
 * sock:希望进入等待状态的socket文件描述符
 * backlog:连接请求等待队列的长度
 */
int listen(int sock, int backlog);

受理客户端请求

#include <sys/socket.h>

/**
 * sock:服务器socket文件描述符
 * addr:保存客户端地址信息的结构体变量
 * addrlen:第二个参数的长度
 * 成功返回客户端socket文件描述符,失败返回-1
 */
int accept(int sock, struct sockaddr * addr, socket_t * addrlen);

UDP套接字

基于UDP的I/O函数

#include <sys/socket.h>

/**
 * 成功时返回传输的字节数,失败是返回 -1
 * sock: 用于传输数据的 UDP 套接字
 * buff: 保存待传输数据的缓冲地址值
 * nbytes: 待传输的数据长度,以字节为单位
 * flags: 可选项参数,若没有则传递 0
 * to: 存有目标地址的 sockaddr 结构体变量的地址值
 * addrlen: 传递给参数 to 的地址值结构体变量长度
 */
ssize_t sendto(int sock, void *buff, size_t nbytes, int flags,
               struct sockaddr *to, socklen_t addrlen);

/**
 * 成功时返回传输的字节数,失败是返回 -1
 * sock: 用于传输数据的 UDP 套接字
 * buff: 保存待传输数据的缓冲地址值
 * nbytes: 待传输的数据长度,以字节为单位
 * flags: 可选项参数,若没有则传递 0
 * from: 存有发送端地址信息的 sockaddr 结构体变量的地址值
 * addrlen: 保存参数 from 的结构体变量长度的变量地址值。
 */
ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags,
                 struct sockaddr *from, socklen_t *addrlen);

UDP的IO次数

由于UDP套接字存在数据边界,输入次数必须和输出次数相等。

断开套接字连接

TCP半关闭

close

close函数的断开是完全断开,即同时关闭接收和发送。

试想一种场景,A、B主机进行双向通信,A发送完所有数据后close,同时失去接收数据的能力,此时B尚未发送给A的数据将无法被A接收到。

shutdown
#include <sys/socket.h>

/**
 * sock:文件描述符
 * howto:SHUT_RD(断开输入流),SHUT_WR(断开输出流),SHUTRDWR(同时断开)
 * 成功返回0,失败返回-1
 */
int shutdown(int sock, int howto);

SHUTRDWR相当于调用两次shutdown,两次参数分别为SHUT_RD,SHUT_WR。

DNS

使用域名的必要性

域名不常变而IP常变,在代码中使用IP地址不是一个好办法。

IP地址和域名的转换

域名->IP
#include <netdb.h>

/**
 * 域名->IP
 * 成功返回hostent结构体地址,失败返回NULL
 */
struct hostent * gethostbyname(const char * hostname);

struct hostent
{
    char * h_name;			//官方域名
    char ** h_aliases;		        //其他域名
    int h_addrtype;			//IP地址类型(IPV4,IPV6)
    int h_length;			//IP地址长度
    char ** h_addr_list;	        //IP地址
}    
IP->域名
#include <netdb.h>

/**
 * IP->域名
 * 传入三个参数,依次为IP地址,长度(IPV4时为4,IPV6时为16),地址族信息(AF_INET,AF_INET6)
 * 成功返回hostent结构体变量,失败返回NULL
 */
struct hostent * gethostbyaddr(const char * addr, socklen_t len, int family);

套接字的多种可选项

套接字选项的分层

  • IPPROTO_IP:IP协议相关
  • IPPROTO_TCP:TCP协议相关
  • SOL_SOCKET:套接字通用

getsocket & setsocket

#include <sys/socket.h>

/**
 * getsocket():读取可选项
 * sock:套接字文件描述符
 * level:可选的协议层
 * optname:可选项名
 * optval:保存结果的缓冲地址
 * optlen:optval的缓冲大小,调用后该变量中保存通过第四个参数返回的信息的字节数
 * 成功时返回0,失败时返回-1
 */
int getsockopt(int sock, int level, int optname, void *optval, socket_t *optlen);

/**
 * setsocket():修改可选项
 * 成功时返回0,失败时返回-1
 */
int setsockopt(int sock, int level, int optname, const void *optval, socket_t *optlen);
原文地址:https://www.cnblogs.com/CofJus/p/14468445.html