Linux网络编程基础API

学习资料

program with libevent译文
Linux socket编程

网络字节序network byte order

网络字节序为大端模式Big-endian:高位字节在小地址处;
Little-endian:低位字节在小地址处。

INADDR_LOOPBACK和INADDR_ANY是主机字节序( host byte order )

#include<arpa/inet.h>

uint16_t htons(uint16_t host_uint16);
uint32_t htonl(uint32_t host_uint32);

uint16_t ntohs(uint16_t net_uinet16);
uint32_t ntohs(uint32_t net_uinet32);

Internet socket 地址

通用的socket 地址:struct sockaddr

头文件<sys/socket.h>

struct sockaddr
{
sa_family_t sa_family; // domain常量
char sa_data[14]; // socket address,大小随domain的变化而变化
}

sa_family是地址族类型的变量(取值:AF_UNIX、AF_INET或AF_INET6),地址族类型通常与协议族类型相对应,一般可以混用。
所有专用socket地址类型的变量在实际使用时都必须强制转换成通用socket地址类型(sockaddr),因为所有socket编程接口函数使用的地址参数的类型都是sockaddr。

专用socket地址

IPv4 socket地址:struct sockaddr_in

在使用socket 地址结构体之前。必须先用memset()函数清零。

#include <memory.h> or <string.h>
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(struct sockaddr_in));
#include<netinet/in.h>
struct in_addr{
	in_addr_t s_addr;       // unsigned 32-bit integer
};
struct sockaddr_in			//IPv4 socket address
{
	sa_family_t sin_family;		//Address family :AF_INET
	in_port_t sin_port;			//网络字节序,端口号
	struct in_addr sin_addr;  //网络字节序,IP地址
	unsigned char _pad[X];
};
IPv6 socket 地址
#include<netinet/in.h>

struct in6_addr{
	uint8_t s6_addr;
};

/* sockaddr_in6结构中的所有字段都是以网络字节序存储的 */
struct sockaddr_in6{
	sa_family_t sin6_family;   //AF_INET6
	in_port_t sin6_port;
	uint32_t sin6_flowinfo;		// IPv6 flow information
	struct in6_addr sin6_addr;
	uint32_t sin6_scope_id;		//Scope ID
};

特殊的IP

127.0.0.1

回环地址—INADDR_LOOPBACK,发送给这个地址的数据实际不会到达网络,它会自动变成发送主机的输入。

0.0.0.0

通配地址—INADDR_ANY,如果socket只绑定到多宿主机的某一个IP上,则它只能接收发送到该IP上的数据,而不能接受发送到该主机任意IP上的数据。为了使socket能够接受发送到该主机的所有数据,我们应该把socket绑定到INADDR_ANY。

IPv4映射成IPv6

为允许IPv6的应用程序与只支持IPv4的主机通信,IPv6通过了所谓的IPv4映射的IPv6地址。
前80位为0,中间16位为1,剩余的32位为IPv4:
::FFFF:192.168.1.2

IPv6也有通配地址和回环地址,这两个地址是网络字节序。

  • IN6ADDR_ANY_INIT
  • IN6ADDR_LOOPBACK_INIT

IPv4和IPv6共有相同的端口号地址空间。

IP地址转换函数

将字符串转换成网络字节序。

#include<arpa/inet.h>

//将字符串表示IP中的src_str转换成网络字节序,并存储在addrptr指向的空间中
int inet_pton(int domain, const char *src_str, void *addrptr)
//返回1表示成功,返回0表示presentation format 错误,-1表示其他错误

const char *inet_ntop(int domain, const void *addrptr, char *dst_str, size_t len)
//返回NULL表示出错,否则返回存储单元的地址

#include<netinet/in.h>

#define INET_ADDRSTRLEN 16		//maximum IPv4 dotted-decimal string

//当使用隧道等技术时,IP地址可能是ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255???
//IPv4映射的IPv6地址可能出现这种格式“ffff:ffff:ffff:ffff:ffff:ffff:255:255:255:255”,也就是45字节,加上结束符就是46字节了
#define INET6_ADDRSTRLEN 46		//maximum IPv6 hexadecimal string

过时的地址转换函数:
头文件<arpa/inet>
下面的这三个函数不能转换IPv6 的地址。

  • in_addr_t inet_addr(const char *strptr); //将点分十进制字符串表示的IPv4地址转换成网络字节序整数表示的IPv4地址。失败时返回INADDR_NONE。
  • int inet_aton(const char *cp, struct inet_addr_t *inp); //作用与inet_addr相同,只是将转换结果存储在inp指向的结构体中。成功时返回1,失败时返回0。
  • char* inet_ntoa(struct inet_addr_t in);//与inet_addr作用相反。该函数用内部静态空间存储返回值,因此是不可重入的。
#include<netinet/in.h>
#include<arpa/inet.h>

//将点分十进制字符串表示的IPv4地址转换成网络字节序整数表示的IPv4地址。
//失败时返回INADDR_NONE。
in_addr_t inet_addr(const char *strptr);  

//返回1表示成功,返回0表示失败
int inet_aton(const char *str, struct in_addr *addr)

//该函数返回的指针指向静态分配的内存空间
char* inet_ntoa(structin_addr addr)

创建socket:socket()

使用socket()系统调用创建一个socket,该系统调用返回一个在后续系统调用中引用该socket的文件描述符

#include<sys/socket.h>
int socket(int domain,int type, int protocol);
// 失败则返回-1
// 参数protocol一般设为0。

参数domain:

  • 确定socket地址的格式,IPv4、IPv6或UNIX本地域套接字。
  • 通信范围(是位于同一主机中的进程之间的通信,还是位于网络中不同主机上的进程之间的通信)

domain的取值:

  • AF_UNIX:同一主机上的进程间的通信
  • AF_INET:IPv4
  • AF_INET6:IPv6
DomainCommunication performedCommunication between applicationsAddress formatAddress structure
AF_UNIX在内核中同一主机路径名sockaddr_un
AF_INET通过IPv432位的IPv4地址+16位的端口号sockaddr_in
AF_INETv6通过IPv6128位的IPv6地址+16位的端口号sockaddr_in6

socket types:
每个socket实现至少提供两种socket:流和数据报。流以字节流的形式交换数据;数据报以数据报的形式交换数据

属性socket类型
流SOCK_STREAM数据报SOCK_DGRAM
可靠传输
消息边界
面向连接

对于2.6.17以上的Linux内核版本,type参数接受SOCK_NONBLOCK和SOCK_CLOEXEC两个参数。这两个参数也可以通过fcntl设置。

将socket绑定到指定地址:bind()

#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 如果成功则返回0,失败则返回-1

由于不同socket domain的地址结构各不相同,而bind()之类的系统调用适用于所有的socket domain。所以它们必须接受任意类型的地址结构。因此定义了一个通用的地址结构struct sockaddr。可以将特定的各种地址结构转换成单一的类型来共其他系统调用使用。客户端一般不需要绑定,但是可以绑定。

流socket

服务端socket建立步骤:

  • 新建socket
  • 调用bind()绑定
  • listen()
  • accept()等待连接
  • 通过read()和write()读写;(send()和recv())

主动和被动socket

  • 默认情况下,建立的socket是主动的。一个主动的socket可以通过connect()系统调用建立一个与被动socket的连接。
  • 一个被动的socket通过调用listen()来将自己标记成被动的。

listen()

listen()系统调用将文件描述符fdsock引用的流socket标记为被动的。

#include<sys/socket.h>
int listen(int fdsocket, int backlog);
// 如果成功返回0,失败返回-1

下面两种socket无法执行listen调用:

  • 已经成功执行connect()的socket
  • accept()返回的socket

参数backlog:

  • To understand the purpose of the backlog argument, we first observe that the client may call connect() before the server calls accept(). This could happen, for example, because the server is busy handling some other client(s). This results in a pending connection, as illustrated in Figure 56-2.
  • 内核会记录所有未决的连接请求的相关信息,以便后续的accept()能够处理这些请求。
    The kernel must record some information about each pending connection request so that a subsequent accept() can be processed. The backlog argument allows us to limit the number of such pending connections. Connection requests up to this limit succeed immediately. Further connection requests block until a pending connection is accepted (via accept()), and thus removed from the queue of pending connections.
  • backlog一般设为5,表示处于ESTABLISHED状态的socket连接的上限,当队列中socket的个数超过backlog(可能比backlog大一点,不一定是backlog)时,客户端会收到ECONNREFUSED错误消息

accept()

表示文件描述符fdsock引用的socket正在等待来自客户端的连接请求。如果存在未决的连接,则立即返回,如果不存在,则阻塞。如果backlog队列中的已经处于ESTABLISHED状态的socket连接出现问题:

  • 问题一、客户端断开连接(例如断网,重启动等),服务端仍能正常accept(),不会报错。只有在写的时候才会发现,断网是写超时,重启是收到reset消息(相当于半打开连接)。
  • 问题二、对方正常发送FIN,也能正常accept,不会报错服务端读取时会读到EOF。

#include<sys/socket.h>
int accept(int fdsock, struct sockaddr *addr, socketlen_t *addrlen);
// 如果成功则返回已链接的socket的文件描述符,如果失败,则返回-1
// 参数addr返回客户端socket的地址
// 在调用之前应该将参数addrlen初始化为addr所指缓冲区的大小。
// 如果不关心对方的地址,则可以指定为NULL和0

connect()

int connect(int fdsock, const struct sockaddr *addr, socklen_t addrlen);
// 成功返回0;失败返回-1,并设置errno,
errno的取值:1、ECONNREFUSED,目标端口不存在。2、ETIMEOUT,连接超时(可能是backlog满了)

如果连接失败,则正确的做法是先关闭这个socket,再创建一个新的socket,然后在这个新的socket上重新进行连接。

close()与shutdown()

头文件<sys/socket.h>

可以通过close()来关闭socket连接,只有当所有引用该socket的文件描述符都被关闭之后,连接才会关闭。否则只会是套接字的连接数减一,并不会发送FIN,只有连接数为1时才会发送FIN,此时也不能再读取了。
一般使用shutdown()来关闭流socket。不管里连接数是多少,都直接发送FIN。

#include<sys/socket.h>
int shutdown(int fdsock, int how)

参数howto的取值:

  • SHUT_RD:关闭读,丢弃读缓存区中的数据,不能再读了。
  • SHUT_WR:关闭写,先将写缓存中的数据发送完毕,然后发送FIN,不可以再写。但可以读取,直到读到EOF,对方也不再写。对端会读到EOF。
  • SHUT_RDWR:同时关闭读写,先关RD,再关WR。。

shutdown()不会关闭文件描述符,就算how参数为SHUT_RDWR也不会。要关闭文件描述符,则必须调用close()。

如果对方调用close()来终止连接,我们会收到FIN信号,但时,与shutdown不同的是,对方也不再接受数据,如果我们write数据,第一次收到reset,第二次会收到SIGPIPE信号。
signal(SIGPIPE, SIG_IGN)详解

write()和read()

write()和read()也可以用于TCPsocket读写。
write()函数返回值一般不会是0,只有当如下情况发生时才会返回0:write(fp, p1+len, (strlen(p1)-len)中第三参数为0,此时write()什么也不做,只返回0。

专用于套接字的I/O:recv()和send()

#include<sys/socket.h>

ssize_t recv(int sockfd, void *buffer, size_t length, int flags); //出错返回-1
ssize_t send(int sockfd, void *buffer, size_t length, int flags); //失败返回-1

参数length是期望读/写的字节个数。

flags参数只对当前的调用起作用,对下一次没有作用。
recv()的flags参数的可能取值:

  • MSG_DONTWAIT,让recv()以非阻塞的方式运行
  • MSG_OOB,接收带外数据
  • MSG_PEEK,获取缓存区数据的副本,但不会删除缓存区的数据
  • MSG_WAITALL,仅在读取到指定字节的数据后才返回

send()

  • MSG_DONTWAIT,以非阻塞的方式执行
  • MSG_MORE,告诉内核多等一会,对将要发送的数据进行积累。与套接字选项TCP_CORK类似
  • MSG_OOB,发送带外数据

sendfile()

传输速度快

#include<sys/sendfile.h>

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
//out_fd 必须是socket;in_fd必须是普通文件,可以进行mmap()

套接字的部分写和部分读问题

部分读:

  • 当缓存区中的套接字上的数据少于请求的数据时,read()调用会发生部分读;
    The value 0 may also be returned if the requested number of bytes to receive from a stream socket was 0.

部分写:

  • write()系统调用被信号中断;
  • 套接字工作在非阻塞模式下O_NONBLOCK,没有足够的空间传输;
  • 套接字API出现异常,比如连接中断。

数据报socket

建立数据报socket的步骤:
server:

  • socket()
  • bind()
  • recvfrom()
  • sendto()
  • close()

client:

  • socket()
  • sendto()
  • revvfrom()
  • close()

交换数据报:recvfrom()和sendto()

linux允许sendto()发生长度为0的数据报,但不是所有UNIX都允许。

#include<sys/socket.h>
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *sockaddr, socklen_t addrlen);
// 返回读取的字节数,读到EOF,返回0或者在失败时返回-1。
ssize_t sendto(int sockfd, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
// 发送字节数或-1
// 参数flags控制I/O特性,可以为0

If we are not interested in the address of the sender, then we specify both src_addr and addrlen as NULL . In this case, recvfrom() is equivalent to using recv() to receive a datagram. We can also use read() to read a datagram, which is equivalent to using recv() with a flags argument of 0.

在数据包socket上使用connect()也可以

Calling connect() on a datagram socket causes the kernel to record a particular address as this socket’s peer.
After a datagram socket has been connected:

  • Datagrams can be sent through the socket using write() (or send()) and are automatically sent to the same peer socket. As with sendto(), each write() call results in a separate datagram.
  • Only datagrams sent by the peer socket may be read on the socket.只能从在socket上读取对等socket发送的数据报
  • 通过再发起一个connect可以修改对等的socket。

带外数据

当收到带外数据时,I/O复用会产生异常事件,且内核会产生SIGURG信号。

int sockatmark(int fdsock)的作用?

获取socket连接的本端和远端地址

#include<sys/socket.h>

/* 
 *得到自己的地址
 *参数addrlen代表addr空间的大小
 */
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
//返回0表示成功,返回-1表示失败

/* 得到对端的地址 */
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
//返回0表示成功,返回-1表示失败

套接字选项

#include<sys/socket.h>

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen)

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t *optlen)

域名系统DNS

先搜索/etc/hosts文件,再调用DNS服务。
/etc/services

  • getaddrinfo()通过主机名得到对应的IP地址;
  • getnameinfo()通过IP地址得到对于的主机名;
#include<sys/socket.h>
#include<netdb.h>

int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **result);
//成功时返回0

int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, size_t hostlen, char *serice, size_t servlen, int flags)
//成功则返回0

过时的函数:

#include<netdb.h>

extern int h_errno

struct hostent *gethostbyname(const char *name)
struct hostent *gethostbyadder(const char *addr, socklen_t len, int tye)

服务器集群

负载分发(load distribution),将同一个域名映射到多个IP地址上,几台服务器共享同一个域名。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值