前言
面经中提到的epoll,涉及到了socket编程。为了深入了解epoll原理,需要首先了解socket编程。socket是进程间通信IPC,就算在网络中也是如此,所以说网络中通信的主体是进程,而不是计算机。socket学习内容一个是如何建立服务器和客户端,一个是如何使用socket API。
fd=socket(domain, type, protocol);
socket调用可以用来创建一个socket,例如
domain可以用来指定ipv4,type可以用来指定tcp,protocol一般是0。
domain
domain是通信范围与通信地址的类型。有下面几个经典类型:
UNIX IPV4 IPV6,分别对应的参数是AF_UNIX AF_INET AF_INET6。
domain 的参数都是以AF开头的代表地址簇。PF开头的代表协议簇。本来设计是地址簇和协议簇是多对多的,但是后来实现过程中,一个协议簇和地址簇是一一对应的。所以基本上domain就是指定了协议簇,地址簇也被指定了。type
socket表示是流还是数据包,其实就是TCP还是UDP。如果是TCP就是SOCK_STREAM,如果是UDP就是SOCK_DGRAM
bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
该调用用于将socket绑定到一个地址上。之后可以发送TCP报文,在一些场合下也可以通过write发送UDP报文,但是只能在该socket上读取对等socket数据。
- sockaddr
该结构体有一个整形表示地址类型,然后后面跟着一个char数组。后面可以看到具体传进来的是根据使用场合的其他数据结构,但是是通用的
listen(int sockfd, int backlog)
将一个socket描述符标记为被动。可以被主动socket连接。backlog是用于限制等待连接的数量。
accept(int sockfd, struct sockaddr *addr, socklen_t * addrlen);
accept调用会阻塞并等待在文件描述符sockfd上的接入请求。一旦请求成功,会创建一个新的socket,这个新的socket与对方进行连接。
- addr
返回对方的地址 - addrlen
传入addr的长度,用于告知能够写入输入的最大长度。
connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);
将sockfd连接到addr所述的地址上。
close(int fd)
用于关闭连接
read write
用于对sockfd进行读入或读出
recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
sendto(int sockfd, const void *buffer, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
用于发送和接受udp报文。服务器端不能使用listen函数和accept函数,客户端不能使用connect函数。
unix domain
使用上面的API就可以实现本机上通过文件的通信。
unix domain所使用的sockaddr是sockaddr_run,如下表示:
struct sockaddr_un{
sa_family_t sun_family;
char sun_path[108];
}
网络字节序
网络字节顺序是按照大端来的,x86是小端结构。转换使用的是如下的函数进行的
- htons
- htonl
- ntohs
- ntohl
h是host,n是net,s是16位,l是32位。s和l是short和long,虽然现在已经不再使用这样的标准了。
Internet socket 地址结构
网络下使用的socket地址是sockaddr_in,定义如下
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char __pad[X];
}
可以看出不一样的地方是后面的char数组变成了一个端口和地址。sin是socket Internet的简写,简写的和sun一样很差。
Internet socket 地址转换
字符串式的地址格式和二进制地址格式转换API:
inet_pton(int domain, const char *src, void *addrptr);
该函数用于将src中包含的字符串转换为网络字节的二进制地址,存入addrptr。p是presentation的意思,就是人类方便的地址。
const char * inet_ntop(int domain, const void *addrptr, char *dst_str, size_t len);
该函数执行网络字节的二进制地址转换为人类可读的地址,写入到dst_str中,缓冲区的大小有len传入。
getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct **result);
该函数给定一个主机名和服务器名,返回socket地址和端口号。
getaddrinfo以host、service、hints参数作为输入,其中host参数包括一个主机名或一个以IPV4字符串。service是服务名或者是端口号。该函数的调用之后需要使用freeaddrinfo来释放空间。
getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, size_t hostlen, char *service, size_t servlen, int flags);
给定一个socket地址结构,返回一个主机和服务器名的字符串。
setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd是代表指向套接字的文件描述符。参数level制定了套接字选项所适用的协议。例如TCP或者是IP,这表示选项作用的套接字API层。一般来说,该选项会设置为SOL_SOCKET,表示作用于套接字API层。参数optname表示了我们期待设置的选项,optvalue是用来设置刚刚的选项的值,可以是整数或者是结构体的指针,指向了一个缓冲区,而参数optlen是刚刚那个指针所指向区域的大小。
例如,要设置sockfd为reuseaddr属性时,可以如下调用:
int reuse = 1;
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
err_exit("setsockopt");
}
getsockopt(int sockfd, int level, int optname, void *optval, socklen_t optlen);
用法和上面一样,只是获取而已。